Implement prepare on java side

This commit is contained in:
김선우 2025-01-17 04:26:44 +09:00
parent 3e2e998060
commit b77bf879f7
7 changed files with 71 additions and 35 deletions

View file

@ -2,6 +2,7 @@ package org.github.tursodatabase;
import org.github.tursodatabase.annotations.Nullable;
import org.github.tursodatabase.annotations.SkipNullableCheck;
import org.github.tursodatabase.core.LimboConnection;
import org.github.tursodatabase.jdbc4.JDBC4Connection;
import java.sql.*;

View file

@ -39,7 +39,8 @@ public enum LimboErrorCode {
SQLITE_NULL(SqliteCode.SQLITE_NULL, "Null type"),
UNKNOWN_ERROR(-1, "Unknown error"),
LIMBO_DATABASE_ALREADY_CLOSED(1000, "Database already closed"),
LIMBO_FAILED_TO_PARSE_BYTE_ARRAY(1100, "Failed to parse ut8 byte array"),
LIMBO_FAILED_TO_PREPARE_STATEMENT(1200, "Failed to prepare statement"),
LIMBO_ETC(9999, "Unclassified error");
public final int code;

View file

@ -1,6 +1,5 @@
package org.github.tursodatabase.core;
import org.github.tursodatabase.LimboConnection;
import org.github.tursodatabase.annotations.Nullable;
import org.github.tursodatabase.jdbc4.JDBC4ResultSet;
@ -68,6 +67,6 @@ public abstract class CoreStatement {
}
}
return stmtPointer.safeRunInt(AbstractDB::columnCount) != 0;
return this.stmtPointer.columnCount() != 0;
}
}

View file

@ -72,6 +72,24 @@ public abstract class LimboConnection implements Connection {
return database;
}
/**
* Compiles an SQL statement.
*
* @param sql An SQL statement.
* @return A SafeStmtPtr object.
* @throws SQLException if a database access error occurs.
*/
public SafeStatementPointer prepare(String sql) throws SQLException {
logger.trace("DriverManager [{}] [SQLite EXEC] {}", Thread.currentThread().getName(), sql);
byte[] sqlBytes = stringToUtf8ByteArray(sql);
if (sqlBytes == null) {
throw new SQLException("Failed to convert " + sql + " into bytes");
}
return new SafeStatementPointer(this, prepareUtf8(connectionPtr, sqlBytes));
}
private native long prepareUtf8(long connectionPtr, byte[] sqlUtf8) throws SQLException;
/**
* @return busy timeout in milliseconds.
*/
@ -108,4 +126,15 @@ public abstract class LimboConnection implements Connection {
public void setBusyTimeout(int busyTimeout) {
// TODO: add support for busy timeout
}
/**
* Throws formatted SQLException with error code and message.
*
* @param errorCode Error code.
* @param errorMessageBytes Error message.
*/
@NativeInvocation
private void throwLimboException(int errorCode, byte[] errorMessageBytes) throws SQLException {
LimboExceptionUtils.throwLimboException(errorCode, errorMessageBytes);
}
}

View file

@ -10,16 +10,16 @@ import java.util.concurrent.locks.ReentrantLock;
public class SafeStatementPointer {
// Store a reference to database, so we can lock it before calling any safe functions.
private final AbstractDB database;
private final long databasePointer;
private final LimboConnection connection;
private final long statementPtr;
private volatile boolean closed = false;
private final ReentrantLock databaseLock = new ReentrantLock();
private final ReentrantLock connectionLock = new ReentrantLock();
public SafeStatementPointer(AbstractDB database, long databasePointer) {
this.database = database;
this.databasePointer = databasePointer;
public SafeStatementPointer(LimboConnection connection, long statementPtr) {
this.connection = connection;
this.statementPtr = statementPtr;
}
/**
@ -36,10 +36,10 @@ public class SafeStatementPointer {
*/
public int close() throws SQLException {
try {
databaseLock.lock();
connectionLock.lock();
return internalClose();
} finally {
databaseLock.unlock();
connectionLock.unlock();
}
}
@ -48,24 +48,8 @@ public class SafeStatementPointer {
return 0;
}
public <E extends Throwable> int safeRunInt(SafePointerIntFunction<E> function) throws SQLException, E {
try {
databaseLock.lock();
this.ensureOpen();
return function.run(database, databasePointer);
} finally {
databaseLock.unlock();
}
}
private void ensureOpen() throws SQLException {
if (this.closed) {
throw new SQLException("Pointer is closed");
}
}
@FunctionalInterface
public interface SafePointerIntFunction<E extends Throwable> {
int run(AbstractDB database, long pointer) throws E;
public long columnCount() throws SQLException {
// TODO
return 0;
}
}

View file

@ -1,8 +1,7 @@
package org.github.tursodatabase.jdbc4;
import org.github.tursodatabase.LimboConnection;
import org.github.tursodatabase.core.LimboConnection;
import org.github.tursodatabase.annotations.SkipNullableCheck;
import org.github.tursodatabase.core.AbstractDB;
import org.github.tursodatabase.core.CoreStatement;
import java.sql.*;
@ -126,13 +125,12 @@ public class JDBC4Statement extends CoreStatement implements Statement {
return this.withConnectionTimeout(
() -> {
final AbstractDB database = connection.getDatabase();
try {
connectionLock.lock();
database.prepare(this);
connection.prepare(sql);
boolean result = exec();
updateGeneratedKeys();
updateCount = database.changes();
// TODO: updateCount = connection.changes();
exhaustedResults = false;
return result;
} finally {

View file

@ -0,0 +1,24 @@
package org.github.tursodatabase.utils;
import org.github.tursodatabase.annotations.Nullable;
import java.nio.charset.StandardCharsets;
public class ByteArrayUtils {
@Nullable
public static String utf8ByteBufferToString(@Nullable byte[] buffer) {
if (buffer == null) {
return null;
}
return new String(buffer, StandardCharsets.UTF_8);
}
@Nullable
public static byte[] stringToUtf8ByteArray(@Nullable String str) {
if (str == null) {
return null;
}
return str.getBytes(StandardCharsets.UTF_8);
}
}