mirror of
https://github.com/aljazceru/turso.git
synced 2026-01-09 03:04:20 +01:00
Merge 'Implement open function in Java bindings' from Kim Seon Woo
## Purpose of this PR - Implement open function - Add basic structure for the following - exception handling - testing using gradle ## Changes - Java - Remove unnecessary example code(Connection.java, Cursor.java, Limbo.java) - Implement `open` - Add exception handling logic - Add junit test - Rust - Add limbo_db.rs which implements native functions defined in `Limbo.java` - Remove unnecessary example code in lib.rs ## TODOS - Implement core features for AbstractDB.java and LimboDB.java (I'm currently referencing sqlite-java, but there are some minor differences as we use rust instead of C) ## Reference - https://github.com/tursodatabase/limbo/issues/615 Closes #632
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
java_run: lib
|
||||
export LIMBO_SYSTEM_PATH=../../target/debug && ./gradlew run
|
||||
|
||||
.PHONY: lib
|
||||
|
||||
lib:
|
||||
cargo build
|
||||
run_test: build_test
|
||||
./gradlew test
|
||||
|
||||
build_test:
|
||||
CARGO_TARGET_DIR=src/test/resources/limbo cargo build
|
||||
|
||||
@@ -13,6 +13,7 @@ repositories {
|
||||
dependencies {
|
||||
testImplementation(platform("org.junit:junit-bom:5.10.0"))
|
||||
testImplementation("org.junit.jupiter:junit-jupiter")
|
||||
testImplementation("org.assertj:assertj-core:3.27.0")
|
||||
}
|
||||
|
||||
application {
|
||||
@@ -28,4 +29,6 @@ application {
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform()
|
||||
// In order to find rust built file under resources, we need to set it as system path
|
||||
systemProperty("java.library.path", "${System.getProperty("java.library.path")}:$projectDir/src/test/resources/limbo/debug")
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ pub struct CustomError {
|
||||
}
|
||||
|
||||
/// This struct defines error codes that correspond to the constants defined in the
|
||||
/// Java package `org.github.tursodatabase.exceptions.ErrorCode`.
|
||||
/// Java package `org.github.tursodatabase.LimboErrorCode`.
|
||||
///
|
||||
/// These error codes are used to handle and represent specific error conditions
|
||||
/// that may occur within the Rust code and need to be communicated to the Java side.
|
||||
@@ -14,8 +14,7 @@ pub struct CustomError {
|
||||
pub struct ErrorCode;
|
||||
|
||||
impl ErrorCode {
|
||||
pub const CONNECTION_FAILURE: i32 = -1;
|
||||
|
||||
// TODO: change CONNECTION_FAILURE_STATEMENT_IS_DML to appropriate error code number
|
||||
pub const STATEMENT_IS_DML: i32 = -1;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,66 +1,6 @@
|
||||
mod connection;
|
||||
mod cursor;
|
||||
mod errors;
|
||||
mod limbo_db;
|
||||
mod macros;
|
||||
mod utils;
|
||||
|
||||
use crate::connection::Connection;
|
||||
use crate::errors::ErrorCode;
|
||||
use jni::errors::JniError;
|
||||
use jni::objects::{JClass, JString};
|
||||
use jni::sys::jlong;
|
||||
use jni::JNIEnv;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
/// Establishes a connection to the database specified by the given path.
|
||||
///
|
||||
/// This function is called from the Java side to create a connection to the database.
|
||||
/// It returns a pointer to the `Connection` object, which can be used in subsequent
|
||||
/// native function calls.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `env` - The JNI environment pointer.
|
||||
/// * `_class` - The Java class calling this function.
|
||||
/// * `path` - A `JString` representing the path to the database file.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A `jlong` representing the pointer to the newly created `Connection` object,
|
||||
/// or [ErrorCode::CONNECTION_FAILURE] if the connection could not be established.
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_org_github_tursodatabase_limbo_Limbo_connect<'local>(
|
||||
mut env: JNIEnv<'local>,
|
||||
_class: JClass<'local>,
|
||||
path: JString<'local>,
|
||||
) -> jlong {
|
||||
connect_internal(&mut env, path).unwrap_or_else(|_| ErrorCode::CONNECTION_FAILURE as jlong)
|
||||
}
|
||||
|
||||
#[allow(improper_ctypes_definitions, clippy::arc_with_non_send_sync)] // TODO: remove
|
||||
fn connect_internal<'local>(
|
||||
env: &mut JNIEnv<'local>,
|
||||
path: JString<'local>,
|
||||
) -> Result<jlong, JniError> {
|
||||
let io = Arc::new(limbo_core::PlatformIO::new().map_err(|e| {
|
||||
println!("IO initialization failed: {:?}", e);
|
||||
JniError::Unknown
|
||||
})?);
|
||||
|
||||
let path: String = env
|
||||
.get_string(&path)
|
||||
.expect("Failed to convert JString to Rust String")
|
||||
.into();
|
||||
let db = limbo_core::Database::open_file(io.clone(), &path).map_err(|e| {
|
||||
println!("Failed to open database: {:?}", e);
|
||||
JniError::Unknown
|
||||
})?;
|
||||
|
||||
let conn = db.connect().clone();
|
||||
let connection = Connection {
|
||||
conn: Arc::new(Mutex::new(conn)),
|
||||
io,
|
||||
};
|
||||
|
||||
Ok(Box::into_raw(Box::new(connection)) as jlong)
|
||||
}
|
||||
|
||||
86
bindings/java/rs_src/limbo_db.rs
Normal file
86
bindings/java/rs_src/limbo_db.rs
Normal file
@@ -0,0 +1,86 @@
|
||||
use jni::objects::{JByteArray, JObject};
|
||||
use jni::sys::{jint, jlong};
|
||||
use jni::JNIEnv;
|
||||
use limbo_core::Database;
|
||||
use std::sync::Arc;
|
||||
|
||||
const ERROR_CODE_ETC: i32 = 9999;
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(clippy::arc_with_non_send_sync)]
|
||||
pub extern "system" fn Java_org_github_tursodatabase_core_LimboDB__1open_1utf8<'local>(
|
||||
mut env: JNIEnv<'local>,
|
||||
obj: JObject<'local>,
|
||||
file_name_byte_arr: JByteArray<'local>,
|
||||
_open_flags: jint,
|
||||
) -> jlong {
|
||||
let io = match limbo_core::PlatformIO::new() {
|
||||
Ok(io) => Arc::new(io),
|
||||
Err(e) => {
|
||||
set_err_msg_and_throw_exception(&mut env, obj, ERROR_CODE_ETC, e.to_string());
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
||||
let path = match env
|
||||
.convert_byte_array(file_name_byte_arr)
|
||||
.map_err(|e| e.to_string())
|
||||
{
|
||||
Ok(bytes) => match String::from_utf8(bytes) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
set_err_msg_and_throw_exception(&mut env, obj, ERROR_CODE_ETC, e.to_string());
|
||||
return -1;
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
set_err_msg_and_throw_exception(&mut env, obj, ERROR_CODE_ETC, e.to_string());
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
||||
let db = match Database::open_file(io.clone(), &path) {
|
||||
Ok(db) => db,
|
||||
Err(e) => {
|
||||
set_err_msg_and_throw_exception(&mut env, obj, ERROR_CODE_ETC, e.to_string());
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
||||
Box::into_raw(Box::new(db)) as jlong
|
||||
}
|
||||
|
||||
fn set_err_msg_and_throw_exception<'local>(
|
||||
env: &mut JNIEnv<'local>,
|
||||
obj: JObject<'local>,
|
||||
err_code: i32,
|
||||
err_msg: String,
|
||||
) {
|
||||
let error_message_pointer = Box::into_raw(Box::new(err_msg)) as i64;
|
||||
match env.call_method(
|
||||
obj,
|
||||
"newSQLException",
|
||||
"(IJ)Lorg/github/tursodatabase/exceptions/LimboException;",
|
||||
&[err_code.into(), error_message_pointer.into()],
|
||||
) {
|
||||
Ok(_) => {
|
||||
// do nothing because above method will always return Err
|
||||
}
|
||||
Err(_e) => {
|
||||
// do nothing because our java app will handle Err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "system" fn Java_org_github_tursodatabase_core_LimboDB_getErrorMessageUtf8<
|
||||
'local,
|
||||
>(
|
||||
env: JNIEnv<'local>,
|
||||
_obj: JObject<'local>,
|
||||
error_message_ptr: jlong,
|
||||
) -> JByteArray<'local> {
|
||||
let error_message = Box::from_raw(error_message_ptr as *mut String);
|
||||
let error_message_bytes = error_message.as_bytes();
|
||||
env.byte_array_from_slice(error_message_bytes).unwrap()
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.github.tursodatabase;
|
||||
|
||||
public enum LimboErrorCode {
|
||||
UNKNOWN_ERROR(-1, "Unknown error"),
|
||||
ETC(9999, "Unclassified error");
|
||||
|
||||
public final int code;
|
||||
public final String message;
|
||||
|
||||
/**
|
||||
* @param code Error code
|
||||
* @param message Message for the error.
|
||||
*/
|
||||
LimboErrorCode(int code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public static LimboErrorCode getErrorCode(int errorCode) {
|
||||
for (LimboErrorCode limboErrorCode: LimboErrorCode.values()) {
|
||||
if (errorCode == limboErrorCode.code) return limboErrorCode;
|
||||
}
|
||||
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LimboErrorCode{" +
|
||||
"code=" + code +
|
||||
", message='" + message + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package org.github.tursodatabase;
|
||||
|
||||
import org.github.tursodatabase.limbo.Connection;
|
||||
import org.github.tursodatabase.limbo.Cursor;
|
||||
import org.github.tursodatabase.limbo.Limbo;
|
||||
|
||||
/**
|
||||
* TODO: Remove Main class. We can use test code to verify behaviors.
|
||||
*/
|
||||
public class Main {
|
||||
public static void main(String[] args) throws Exception {
|
||||
Limbo limbo = Limbo.create();
|
||||
Connection connection = limbo.getConnection("database.db");
|
||||
|
||||
Cursor cursor = connection.cursor();
|
||||
cursor.execute("SELECT * FROM example_table;");
|
||||
System.out.println("result: " + cursor.fetchOne());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.github.tursodatabase;
|
||||
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotation to mark methods that are called by native functions.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface NativeInvocation {
|
||||
}
|
||||
@@ -1,5 +1,9 @@
|
||||
package org.github.tursodatabase.core;
|
||||
|
||||
import org.github.tursodatabase.LimboErrorCode;
|
||||
import org.github.tursodatabase.NativeInvocation;
|
||||
import org.github.tursodatabase.exceptions.LimboException;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.sql.SQLFeatureNotSupportedException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
@@ -10,18 +14,14 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
* are not only to provide functionality, but to handle contractual
|
||||
* differences between the JDBC specification and the Limbo API.
|
||||
*/
|
||||
public abstract class DB {
|
||||
public abstract class AbstractDB {
|
||||
private final String url;
|
||||
private final String fileName;
|
||||
private final AtomicBoolean closed = new AtomicBoolean(true);
|
||||
|
||||
public DB(String url, String fileName) throws SQLException {
|
||||
public AbstractDB(String url, String filaName) throws SQLException {
|
||||
this.url = url;
|
||||
this.fileName = fileName;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
this.fileName = filaName;
|
||||
}
|
||||
|
||||
public boolean isClosed() {
|
||||
@@ -36,7 +36,7 @@ public abstract class DB {
|
||||
/**
|
||||
* Executes an SQL statement.
|
||||
*
|
||||
* @param sql SQL statement to be executed.
|
||||
* @param sql SQL statement to be executed.
|
||||
* @param autoCommit Whether to auto-commit the transaction.
|
||||
* @throws SQLException if a database access error occurs.
|
||||
*/
|
||||
@@ -47,17 +47,16 @@ public abstract class DB {
|
||||
|
||||
/**
|
||||
* Creates an SQLite interface to a database for the given connection.
|
||||
* @see <a href="https://www.sqlite.org/c3ref/c_open_autoproxy.html">SQLite Open Flags</a>
|
||||
*
|
||||
* @param fileName The database.
|
||||
* @param openFlags Flags for opening the database.
|
||||
* @throws SQLException if a database access error occurs.
|
||||
*/
|
||||
public final synchronized void open(String fileName, int openFlags) throws SQLException {
|
||||
// TODO: add implementation
|
||||
throw new SQLFeatureNotSupportedException();
|
||||
public final synchronized void open(int openFlags) throws SQLException {
|
||||
_open(fileName, openFlags);
|
||||
}
|
||||
|
||||
protected abstract void _open(String fileName, int openFlags) throws SQLException;
|
||||
|
||||
/**
|
||||
* Closes a database connection and finalizes any remaining statements before the closing
|
||||
* operation.
|
||||
@@ -95,13 +94,13 @@ public abstract class DB {
|
||||
|
||||
/**
|
||||
* Creates an SQLite interface to a database with the provided open flags.
|
||||
* @see <a href="https://www.sqlite.org/c3ref/c_open_autoproxy.html">SQLite Open Flags</a>
|
||||
*
|
||||
* @param filename The database to open.
|
||||
* @param fileName The database to open.
|
||||
* @param openFlags Flags for opening the database.
|
||||
* @return pointer to database instance
|
||||
* @throws SQLException if a database access error occurs.
|
||||
*/
|
||||
protected abstract void _open(String filename, int openFlags) throws SQLException;
|
||||
protected abstract long _open_utf8(byte[] fileName, int openFlags) throws SQLException;
|
||||
|
||||
/**
|
||||
* Closes the SQLite interface to a database.
|
||||
@@ -173,4 +172,35 @@ public abstract class DB {
|
||||
// TODO: add implementation
|
||||
throw new SQLFeatureNotSupportedException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws SQL Exception with error code.
|
||||
*
|
||||
* @param errorCode Error code to be passed.
|
||||
* @throws SQLException Formatted SQLException with error code
|
||||
*/
|
||||
@NativeInvocation
|
||||
private LimboException newSQLException(int errorCode, long errorMessagePointer) throws SQLException {
|
||||
throw newSQLException(errorCode, getErrorMessage(errorMessagePointer));
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws formatted SQLException with error code and message.
|
||||
*
|
||||
* @param errorCode Error code to be passed.
|
||||
* @param errorMessage throw newSQLException(errorCode);Error message to be passed.
|
||||
* @return Formatted SQLException with error code and message.
|
||||
*/
|
||||
public static LimboException newSQLException(int errorCode, String errorMessage) {
|
||||
LimboErrorCode code = LimboErrorCode.getErrorCode(errorCode);
|
||||
String msg;
|
||||
if (code == LimboErrorCode.UNKNOWN_ERROR) {
|
||||
msg = String.format("%s:%s (%s)", code, errorCode, errorMessage);
|
||||
} else {
|
||||
msg = String.format("%s (%s)", code, errorMessage);
|
||||
}
|
||||
return new LimboException(msg, code);
|
||||
}
|
||||
|
||||
protected abstract String getErrorMessage(long errorMessagePointer);
|
||||
}
|
||||
@@ -1,65 +1,67 @@
|
||||
package org.github.tursodatabase.core;
|
||||
|
||||
|
||||
import org.github.tursodatabase.LimboErrorCode;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.SQLFeatureNotSupportedException;
|
||||
|
||||
/**
|
||||
* This class provides a thin JNI layer over the SQLite3 C API.
|
||||
*/
|
||||
public final class LimboDB extends DB {
|
||||
/**
|
||||
* SQLite connection handle.
|
||||
*/
|
||||
private long pointer = 0;
|
||||
public final class LimboDB extends AbstractDB {
|
||||
|
||||
// Pointer to database instance
|
||||
private long dbPtr;
|
||||
private boolean isOpen;
|
||||
|
||||
private static boolean isLoaded;
|
||||
private static boolean loadSucceeded;
|
||||
|
||||
static {
|
||||
if ("The Android Project".equals(System.getProperty("java.vm.vendor"))) {
|
||||
System.loadLibrary("sqlitejdbc");
|
||||
isLoaded = true;
|
||||
loadSucceeded = true;
|
||||
// TODO
|
||||
} else {
|
||||
// continue with non Android execution path
|
||||
isLoaded = false;
|
||||
loadSucceeded = false;
|
||||
}
|
||||
}
|
||||
|
||||
// url example: "jdbc:sqlite:{fileName}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param url e.g. "jdbc:sqlite:fileName
|
||||
* @param fileName e.g. path to file
|
||||
*/
|
||||
public static LimboDB create(String url, String fileName) throws SQLException {
|
||||
return new LimboDB(url, fileName);
|
||||
}
|
||||
|
||||
// TODO: receive config as argument
|
||||
public LimboDB(String url, String fileName) throws SQLException {
|
||||
private LimboDB(String url, String fileName) throws SQLException {
|
||||
super(url, fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the SQLite interface backend.
|
||||
*
|
||||
* @return True if the SQLite JDBC driver is successfully loaded; false otherwise.
|
||||
*/
|
||||
public static boolean load() throws Exception {
|
||||
if (isLoaded) return loadSucceeded;
|
||||
public void load() {
|
||||
if (isLoaded) return;
|
||||
|
||||
try {
|
||||
System.loadLibrary("_limbo_java");
|
||||
loadSucceeded = true;
|
||||
|
||||
} finally {
|
||||
isLoaded = true;
|
||||
}
|
||||
return loadSucceeded;
|
||||
}
|
||||
|
||||
// WRAPPER FUNCTIONS ////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
protected synchronized void _open(String file, int openFlags) throws SQLException {
|
||||
// TODO: add implementation
|
||||
throw new SQLFeatureNotSupportedException();
|
||||
}
|
||||
|
||||
// TODO: add support for JNI
|
||||
synchronized native void _open_utf8(byte[] fileUtf8, int openFlags) throws SQLException;
|
||||
@Override
|
||||
protected synchronized native long _open_utf8(byte[] file, int openFlags) throws SQLException;
|
||||
|
||||
// TODO: add support for JNI
|
||||
@Override
|
||||
@@ -78,6 +80,15 @@ public final class LimboDB extends DB {
|
||||
@Override
|
||||
public native void interrupt();
|
||||
|
||||
@Override
|
||||
protected void _open(String fileName, int openFlags) throws SQLException {
|
||||
if (isOpen) {
|
||||
throw newSQLException(LimboErrorCode.UNKNOWN_ERROR.code, "Already opened");
|
||||
}
|
||||
dbPtr = _open_utf8(stringToUtf8ByteArray(fileName), openFlags);
|
||||
isOpen = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized SafeStmtPtr prepare(String sql) throws SQLException {
|
||||
// TODO: add implementation
|
||||
@@ -91,4 +102,26 @@ public final class LimboDB extends DB {
|
||||
// TODO: add support for JNI
|
||||
@Override
|
||||
public synchronized native int step(long stmt);
|
||||
|
||||
@Override
|
||||
protected String getErrorMessage(long errorMessagePointer) {
|
||||
return utf8ByteBufferToString(getErrorMessageUtf8(errorMessagePointer));
|
||||
}
|
||||
|
||||
private native byte[] getErrorMessageUtf8(long errorMessagePointer);
|
||||
|
||||
private static String utf8ByteBufferToString(byte[] buffer) {
|
||||
if (buffer == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new String(buffer, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private static byte[] stringToUtf8ByteArray(String str) {
|
||||
if (str == null) {
|
||||
return null;
|
||||
}
|
||||
return str.getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.github.tursodatabase.exceptions;
|
||||
|
||||
import org.github.tursodatabase.LimboErrorCode;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class LimboException extends SQLException {
|
||||
private final LimboErrorCode resultCode;
|
||||
|
||||
public LimboException(String message, LimboErrorCode resultCode) {
|
||||
super(message, null, resultCode.code & 0xff);
|
||||
this.resultCode = resultCode;
|
||||
}
|
||||
|
||||
public LimboErrorCode getResultCode() {
|
||||
return resultCode;
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package org.github.tursodatabase.limbo;
|
||||
|
||||
import java.lang.Exception;
|
||||
|
||||
/**
|
||||
* Represents a connection to the database.
|
||||
* TODO: Deprecate classes under limbo package. We leave this source code for reference.
|
||||
*/
|
||||
public class Connection {
|
||||
|
||||
// Pointer to the connection object
|
||||
private final long connectionPtr;
|
||||
|
||||
public Connection(long connectionPtr) {
|
||||
this.connectionPtr = connectionPtr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new cursor object using this connection.
|
||||
*
|
||||
* @return A new Cursor object.
|
||||
* @throws Exception If the cursor cannot be created.
|
||||
*/
|
||||
public Cursor cursor() throws Exception {
|
||||
long cursorId = cursor(connectionPtr);
|
||||
return new Cursor(cursorId);
|
||||
}
|
||||
|
||||
private native long cursor(long connectionPtr);
|
||||
|
||||
/**
|
||||
* Closes the connection to the database.
|
||||
*
|
||||
* @throws Exception If there is an error closing the connection.
|
||||
*/
|
||||
public void close() throws Exception {
|
||||
close(connectionPtr);
|
||||
}
|
||||
|
||||
private native void close(long connectionPtr);
|
||||
|
||||
/**
|
||||
* Commits the current transaction.
|
||||
*
|
||||
* @throws Exception If there is an error during commit.
|
||||
*/
|
||||
public void commit() throws Exception {
|
||||
try {
|
||||
commit(connectionPtr);
|
||||
} catch (Exception e) {
|
||||
System.out.println("caught exception: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
private native void commit(long connectionPtr) throws Exception;
|
||||
|
||||
/**
|
||||
* Rolls back the current transaction.
|
||||
*
|
||||
* @throws Exception If there is an error during rollback.
|
||||
*/
|
||||
public void rollback() throws Exception {
|
||||
rollback(connectionPtr);
|
||||
}
|
||||
|
||||
private native void rollback(long connectionPtr) throws Exception;
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
package org.github.tursodatabase.limbo;
|
||||
|
||||
/**
|
||||
* Represents a database cursor.
|
||||
* TODO: Deprecate classes under limbo package. We leave this source code for reference.
|
||||
*/
|
||||
public class Cursor {
|
||||
private long cursorPtr;
|
||||
|
||||
public Cursor(long cursorPtr) {
|
||||
this.cursorPtr = cursorPtr;
|
||||
}
|
||||
|
||||
// TODO: support parameters
|
||||
public Cursor execute(String sql) {
|
||||
var result = execute(cursorPtr, sql);
|
||||
System.out.println("resut: " + result);
|
||||
return this;
|
||||
}
|
||||
|
||||
private static native int execute(long cursorPtr, String sql);
|
||||
|
||||
public Object fetchOne() throws Exception {
|
||||
Object result = fetchOne(cursorPtr);
|
||||
return processSingleResult(result);
|
||||
}
|
||||
|
||||
private static native Object fetchOne(long cursorPtr);
|
||||
|
||||
public Object fetchAll() throws Exception {
|
||||
Object result = fetchAll(cursorPtr);
|
||||
return processArrayResult(result);
|
||||
}
|
||||
|
||||
private static native Object fetchAll(long cursorPtr);
|
||||
|
||||
private Object processSingleResult(Object result) throws Exception {
|
||||
if (result instanceof Object[]) {
|
||||
System.out.println("The result is of type: Object[]");
|
||||
for (Object element : (Object[]) result) {
|
||||
printElementType(element);
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
printElementType(result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private Object processArrayResult(Object result) throws Exception {
|
||||
if (result instanceof Object[][]) {
|
||||
System.out.println("The result is of type: Object[][]");
|
||||
Object[][] array = (Object[][]) result;
|
||||
for (Object[] row : array) {
|
||||
for (Object element : row) {
|
||||
printElementType(element);
|
||||
}
|
||||
}
|
||||
return array;
|
||||
} else {
|
||||
throw new Exception("result should be of type Object[][]. Maybe internal logic has error.");
|
||||
}
|
||||
}
|
||||
|
||||
private void printElementType(Object element) {
|
||||
if (element instanceof String) {
|
||||
System.out.println("String: " + element);
|
||||
} else if (element instanceof Integer) {
|
||||
System.out.println("Integer: " + element);
|
||||
} else if (element instanceof Double) {
|
||||
System.out.println("Double: " + element);
|
||||
} else if (element instanceof Boolean) {
|
||||
System.out.println("Boolean: " + element);
|
||||
} else if (element instanceof Long) {
|
||||
System.out.println("Long: " + element);
|
||||
} else if (element instanceof byte[]) {
|
||||
System.out.print("byte[]: ");
|
||||
for (byte b : (byte[]) element) {
|
||||
System.out.print(b + " ");
|
||||
}
|
||||
System.out.println();
|
||||
} else {
|
||||
System.out.println("Unknown type: " + element);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package org.github.tursodatabase.limbo;
|
||||
|
||||
import org.github.tursodatabase.exceptions.ErrorCode;
|
||||
|
||||
import java.lang.Exception;
|
||||
|
||||
/**
|
||||
* TODO: Deprecate classes under limbo package. We leave this source code for reference.
|
||||
*/
|
||||
public class Limbo {
|
||||
|
||||
private static volatile boolean initialized;
|
||||
|
||||
private Limbo() {
|
||||
if (!initialized) {
|
||||
System.loadLibrary("_limbo_java");
|
||||
initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static Limbo create() {
|
||||
return new Limbo();
|
||||
}
|
||||
|
||||
public Connection getConnection(String path) throws Exception {
|
||||
long connectionId = connect(path);
|
||||
if (connectionId == ErrorCode.CONNECTION_FAILURE) {
|
||||
throw new Exception("Failed to initialize connection");
|
||||
}
|
||||
return new Connection(connectionId);
|
||||
}
|
||||
|
||||
private static native long connect(String path);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.github.tursodatabase;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
|
||||
public class TestUtils {
|
||||
/**
|
||||
* Create temporary file and returns the path.
|
||||
*/
|
||||
public static String createTempFile() throws IOException {
|
||||
return Files.createTempFile("limbo_test_db", null).toAbsolutePath().toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.github.tursodatabase.core;
|
||||
|
||||
import org.github.tursodatabase.TestUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
public class LimboDBTest {
|
||||
|
||||
@Test
|
||||
void db_should_open_normally() throws Exception {
|
||||
String dbPath = TestUtils.createTempFile();
|
||||
LimboDB db = LimboDB.create("jdbc:sqlite" + dbPath, dbPath);
|
||||
db.load();
|
||||
db.open(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_throw_exception_when_opened_twice() throws Exception {
|
||||
String dbPath = TestUtils.createTempFile();
|
||||
LimboDB db = LimboDB.create("jdbc:sqlite:" + dbPath, dbPath);
|
||||
db.load();
|
||||
db.open(0);
|
||||
|
||||
assertThatThrownBy(() -> db.open(0)).isInstanceOf(SQLException.class);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user