Enhance LimboDB.java open logic

This commit is contained in:
김선우
2025-01-08 22:50:48 +09:00
parent 281ba8d552
commit 29e434754b
10 changed files with 220 additions and 64 deletions

View File

@@ -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 + '\'' +
'}';
}
}

View File

@@ -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());
}
}

View File

@@ -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 {
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}