Apply lints

This commit is contained in:
김선우 2025-01-26 20:04:57 +09:00
parent 8b6e761496
commit 2212cc2a09
30 changed files with 2841 additions and 2838 deletions

View file

@ -1,79 +1,79 @@
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.*;
import java.util.Locale;
import java.util.Properties;
import java.util.logging.Logger;
import org.github.tursodatabase.annotations.Nullable;
import org.github.tursodatabase.annotations.SkipNullableCheck;
import org.github.tursodatabase.core.LimboConnection;
import org.github.tursodatabase.jdbc4.JDBC4Connection;
public class JDBC implements Driver {
private static final String VALID_URL_PREFIX = "jdbc:sqlite:";
private static final String VALID_URL_PREFIX = "jdbc:sqlite:";
static {
try {
DriverManager.registerDriver(new JDBC());
} catch (Exception e) {
// TODO: log
}
static {
try {
DriverManager.registerDriver(new JDBC());
} catch (Exception e) {
// TODO: log
}
}
@Nullable
public static LimboConnection createConnection(String url, Properties properties) throws SQLException {
if (!isValidURL(url)) return null;
@Nullable
public static LimboConnection createConnection(String url, Properties properties)
throws SQLException {
if (!isValidURL(url)) return null;
url = url.trim();
return new JDBC4Connection(url, extractAddress(url), properties);
}
url = url.trim();
return new JDBC4Connection(url, extractAddress(url), properties);
}
private static boolean isValidURL(String url) {
return url != null && url.toLowerCase(Locale.ROOT).startsWith(VALID_URL_PREFIX);
}
private static boolean isValidURL(String url) {
return url != null && url.toLowerCase(Locale.ROOT).startsWith(VALID_URL_PREFIX);
}
private static String extractAddress(String url) {
return url.substring(VALID_URL_PREFIX.length());
}
private static String extractAddress(String url) {
return url.substring(VALID_URL_PREFIX.length());
}
@Nullable
@Override
public Connection connect(String url, Properties info) throws SQLException {
return createConnection(url, info);
}
@Nullable
@Override
public Connection connect(String url, Properties info) throws SQLException {
return createConnection(url, info);
}
@Override
public boolean acceptsURL(String url) throws SQLException {
return isValidURL(url);
}
@Override
public boolean acceptsURL(String url) throws SQLException {
return isValidURL(url);
}
@Override
public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException {
return LimboConfig.getDriverPropertyInfo();
}
@Override
public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException {
return LimboConfig.getDriverPropertyInfo();
}
@Override
public int getMajorVersion() {
// TODO
return 0;
}
@Override
public int getMajorVersion() {
// TODO
return 0;
}
@Override
public int getMinorVersion() {
// TODO
return 0;
}
@Override
public int getMinorVersion() {
// TODO
return 0;
}
@Override
public boolean jdbcCompliant() {
return false;
}
@Override
public boolean jdbcCompliant() {
return false;
}
@Override
@SkipNullableCheck
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
// TODO
return null;
}
@Override
@SkipNullableCheck
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
// TODO
return null;
}
}

View file

@ -4,48 +4,47 @@ import java.sql.DriverPropertyInfo;
import java.util.Arrays;
import java.util.Properties;
/**
* Limbo Configuration.
*/
/** Limbo Configuration. */
public class LimboConfig {
private final Properties pragma;
private final Properties pragma;
public LimboConfig(Properties properties) {
this.pragma = properties;
public LimboConfig(Properties properties) {
this.pragma = properties;
}
public static DriverPropertyInfo[] getDriverPropertyInfo() {
return Arrays.stream(Pragma.values())
.map(
p -> {
DriverPropertyInfo info = new DriverPropertyInfo(p.pragmaName, null);
info.description = p.description;
info.choices = p.choices;
info.required = false;
return info;
})
.toArray(DriverPropertyInfo[]::new);
}
public Properties toProperties() {
Properties copy = new Properties();
copy.putAll(pragma);
return copy;
}
public enum Pragma {
;
private final String pragmaName;
private final String description;
private final String[] choices;
Pragma(String pragmaName, String description, String[] choices) {
this.pragmaName = pragmaName;
this.description = description;
this.choices = choices;
}
public static DriverPropertyInfo[] getDriverPropertyInfo() {
return Arrays.stream(Pragma.values())
.map(p -> {
DriverPropertyInfo info = new DriverPropertyInfo(p.pragmaName, null);
info.description = p.description;
info.choices = p.choices;
info.required = false;
return info;
})
.toArray(DriverPropertyInfo[]::new);
}
public Properties toProperties() {
Properties copy = new Properties();
copy.putAll(pragma);
return copy;
}
public enum Pragma {
;
private final String pragmaName;
private final String description;
private final String[] choices;
Pragma(String pragmaName, String description, String[] choices) {
this.pragmaName = pragmaName;
this.description = description;
this.choices = choices;
}
public String getPragmaName() {
return pragmaName;
}
public String getPragmaName() {
return pragmaName;
}
}
}

View file

@ -1,88 +1,87 @@
package org.github.tursodatabase;
import org.github.tursodatabase.annotations.Nullable;
import org.github.tursodatabase.annotations.SkipNullableCheck;
import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.Properties;
import java.util.logging.Logger;
import javax.sql.DataSource;
import org.github.tursodatabase.annotations.Nullable;
import org.github.tursodatabase.annotations.SkipNullableCheck;
/**
* Provides {@link DataSource} API for configuring Limbo database connection.
*/
/** Provides {@link DataSource} API for configuring Limbo database connection. */
public class LimboDataSource implements DataSource {
private final LimboConfig limboConfig;
private final String url;
private final LimboConfig limboConfig;
private final String url;
/**
* Creates a datasource based on the provided configuration.
*
* @param limboConfig The configuration for the datasource.
*/
public LimboDataSource(LimboConfig limboConfig, String url) {
this.limboConfig = limboConfig;
this.url = url;
}
/**
* Creates a datasource based on the provided configuration.
*
* @param limboConfig The configuration for the datasource.
*/
public LimboDataSource(LimboConfig limboConfig, String url) {
this.limboConfig = limboConfig;
this.url = url;
}
@Override
@Nullable
public Connection getConnection() throws SQLException {
return getConnection(null, null);
}
@Override
@Nullable
public Connection getConnection() throws SQLException {
return getConnection(null, null);
}
@Override
@Nullable
public Connection getConnection(@Nullable String username, @Nullable String password) throws SQLException {
Properties properties = limboConfig.toProperties();
if (username != null) properties.put("user", username);
if (password != null) properties.put("pass", password);
return JDBC.createConnection(url, properties);
}
@Override
@SkipNullableCheck
public PrintWriter getLogWriter() throws SQLException {
// TODO
return null;
}
@Override
@Nullable
public Connection getConnection(@Nullable String username, @Nullable String password)
throws SQLException {
Properties properties = limboConfig.toProperties();
if (username != null) properties.put("user", username);
if (password != null) properties.put("pass", password);
return JDBC.createConnection(url, properties);
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
// TODO
}
@Override
@SkipNullableCheck
public PrintWriter getLogWriter() throws SQLException {
// TODO
return null;
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
// TODO
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
// TODO
}
@Override
public int getLoginTimeout() throws SQLException {
// TODO
return 0;
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
// TODO
}
@Override
@SkipNullableCheck
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
// TODO
return null;
}
@Override
public int getLoginTimeout() throws SQLException {
// TODO
return 0;
}
@Override
@SkipNullableCheck
public <T> T unwrap(Class<T> iface) throws SQLException {
// TODO
return null;
}
@Override
@SkipNullableCheck
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
// TODO
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
// TODO
return false;
}
@Override
@SkipNullableCheck
public <T> T unwrap(Class<T> iface) throws SQLException {
// TODO
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
// TODO
return false;
}
}

View file

@ -2,72 +2,67 @@ package org.github.tursodatabase;
import org.github.tursodatabase.core.SqliteCode;
/**
* Limbo error code. Superset of SQLite3 error code.
*/
/** Limbo error code. Superset of SQLite3 error code. */
public enum LimboErrorCode {
SQLITE_OK(SqliteCode.SQLITE_OK, "Successful result"),
SQLITE_ERROR(SqliteCode.SQLITE_ERROR, "SQL error or missing database"),
SQLITE_INTERNAL(SqliteCode.SQLITE_INTERNAL, "An internal logic error in SQLite"),
SQLITE_PERM(SqliteCode.SQLITE_PERM, "Access permission denied"),
SQLITE_ABORT(SqliteCode.SQLITE_ABORT, "Callback routine requested an abort"),
SQLITE_BUSY(SqliteCode.SQLITE_BUSY, "The database file is locked"),
SQLITE_LOCKED(SqliteCode.SQLITE_LOCKED, "A table in the database is locked"),
SQLITE_NOMEM(SqliteCode.SQLITE_NOMEM, "A malloc() failed"),
SQLITE_READONLY(SqliteCode.SQLITE_READONLY, "Attempt to write a readonly database"),
SQLITE_INTERRUPT(SqliteCode.SQLITE_INTERRUPT, "Operation terminated by sqlite_interrupt()"),
SQLITE_IOERR(SqliteCode.SQLITE_IOERR, "Some kind of disk I/O error occurred"),
SQLITE_CORRUPT(SqliteCode.SQLITE_CORRUPT, "The database disk image is malformed"),
SQLITE_NOTFOUND(SqliteCode.SQLITE_NOTFOUND, "(Internal Only) Table or record not found"),
SQLITE_FULL(SqliteCode.SQLITE_FULL, "Insertion failed because database is full"),
SQLITE_CANTOPEN(SqliteCode.SQLITE_CANTOPEN, "Unable to open the database file"),
SQLITE_PROTOCOL(SqliteCode.SQLITE_PROTOCOL, "Database lock protocol error"),
SQLITE_EMPTY(SqliteCode.SQLITE_EMPTY, "(Internal Only) Database table is empty"),
SQLITE_SCHEMA(SqliteCode.SQLITE_SCHEMA, "The database schema changed"),
SQLITE_TOOBIG(SqliteCode.SQLITE_TOOBIG, "Too much data for one row of a table"),
SQLITE_CONSTRAINT(SqliteCode.SQLITE_CONSTRAINT, "Abort due to constraint violation"),
SQLITE_MISMATCH(SqliteCode.SQLITE_MISMATCH, "Data type mismatch"),
SQLITE_MISUSE(SqliteCode.SQLITE_MISUSE, "Library used incorrectly"),
SQLITE_NOLFS(SqliteCode.SQLITE_NOLFS, "Uses OS features not supported on host"),
SQLITE_AUTH(SqliteCode.SQLITE_AUTH, "Authorization denied"),
SQLITE_ROW(SqliteCode.SQLITE_ROW, "sqlite_step() has another row ready"),
SQLITE_DONE(SqliteCode.SQLITE_DONE, "sqlite_step() has finished executing"),
SQLITE_INTEGER(SqliteCode.SQLITE_INTEGER, "Integer type"),
SQLITE_FLOAT(SqliteCode.SQLITE_FLOAT, "Float type"),
SQLITE_TEXT(SqliteCode.SQLITE_TEXT, "Text type"),
SQLITE_BLOB(SqliteCode.SQLITE_BLOB, "Blob type"),
SQLITE_NULL(SqliteCode.SQLITE_NULL, "Null type"),
SQLITE_OK(SqliteCode.SQLITE_OK, "Successful result"),
SQLITE_ERROR(SqliteCode.SQLITE_ERROR, "SQL error or missing database"),
SQLITE_INTERNAL(SqliteCode.SQLITE_INTERNAL, "An internal logic error in SQLite"),
SQLITE_PERM(SqliteCode.SQLITE_PERM, "Access permission denied"),
SQLITE_ABORT(SqliteCode.SQLITE_ABORT, "Callback routine requested an abort"),
SQLITE_BUSY(SqliteCode.SQLITE_BUSY, "The database file is locked"),
SQLITE_LOCKED(SqliteCode.SQLITE_LOCKED, "A table in the database is locked"),
SQLITE_NOMEM(SqliteCode.SQLITE_NOMEM, "A malloc() failed"),
SQLITE_READONLY(SqliteCode.SQLITE_READONLY, "Attempt to write a readonly database"),
SQLITE_INTERRUPT(SqliteCode.SQLITE_INTERRUPT, "Operation terminated by sqlite_interrupt()"),
SQLITE_IOERR(SqliteCode.SQLITE_IOERR, "Some kind of disk I/O error occurred"),
SQLITE_CORRUPT(SqliteCode.SQLITE_CORRUPT, "The database disk image is malformed"),
SQLITE_NOTFOUND(SqliteCode.SQLITE_NOTFOUND, "(Internal Only) Table or record not found"),
SQLITE_FULL(SqliteCode.SQLITE_FULL, "Insertion failed because database is full"),
SQLITE_CANTOPEN(SqliteCode.SQLITE_CANTOPEN, "Unable to open the database file"),
SQLITE_PROTOCOL(SqliteCode.SQLITE_PROTOCOL, "Database lock protocol error"),
SQLITE_EMPTY(SqliteCode.SQLITE_EMPTY, "(Internal Only) Database table is empty"),
SQLITE_SCHEMA(SqliteCode.SQLITE_SCHEMA, "The database schema changed"),
SQLITE_TOOBIG(SqliteCode.SQLITE_TOOBIG, "Too much data for one row of a table"),
SQLITE_CONSTRAINT(SqliteCode.SQLITE_CONSTRAINT, "Abort due to constraint violation"),
SQLITE_MISMATCH(SqliteCode.SQLITE_MISMATCH, "Data type mismatch"),
SQLITE_MISUSE(SqliteCode.SQLITE_MISUSE, "Library used incorrectly"),
SQLITE_NOLFS(SqliteCode.SQLITE_NOLFS, "Uses OS features not supported on host"),
SQLITE_AUTH(SqliteCode.SQLITE_AUTH, "Authorization denied"),
SQLITE_ROW(SqliteCode.SQLITE_ROW, "sqlite_step() has another row ready"),
SQLITE_DONE(SqliteCode.SQLITE_DONE, "sqlite_step() has finished executing"),
SQLITE_INTEGER(SqliteCode.SQLITE_INTEGER, "Integer type"),
SQLITE_FLOAT(SqliteCode.SQLITE_FLOAT, "Float type"),
SQLITE_TEXT(SqliteCode.SQLITE_TEXT, "Text type"),
SQLITE_BLOB(SqliteCode.SQLITE_BLOB, "Blob type"),
SQLITE_NULL(SqliteCode.SQLITE_NULL, "Null type"),
UNKNOWN_ERROR(-1, "Unknown error"),
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");
UNKNOWN_ERROR(-1, "Unknown error"),
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;
public final String message;
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;
/**
* @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;
}
public static LimboErrorCode getErrorCode(int errorCode) {
for (LimboErrorCode limboErrorCode: LimboErrorCode.values()) {
if (errorCode == limboErrorCode.code) return limboErrorCode;
}
return UNKNOWN_ERROR;
}
return UNKNOWN_ERROR;
}
@Override
public String toString() {
return "LimboErrorCode{" +
"code=" + code +
", message='" + message + '\'' +
'}';
}
@Override
public String toString() {
return "LimboErrorCode{" + "code=" + code + ", message='" + message + '\'' + '}';
}
}

View file

@ -1,17 +1,16 @@
package org.github.tursodatabase.annotations;
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.
* For example, throwing exceptions or creating java objects.
* Annotation to mark methods that are called by native functions. For example, throwing exceptions
* or creating java objects.
*/
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
public @interface NativeInvocation {
String invokedFrom() default "";
String invokedFrom() default "";
}

View file

@ -1,6 +1,5 @@
package org.github.tursodatabase.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@ -8,11 +7,10 @@ import java.lang.annotation.Target;
/**
* Annotation to mark nullable types.
* <p>
* This annotation is used to indicate that a method, field, or parameter can be null.
* It helps in identifying potential nullability issues and improving code quality.
*
* <p>This annotation is used to indicate that a method, field, or parameter can be null. It helps
* in identifying potential nullability issues and improving code quality.
*/
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
public @interface Nullable {
}
public @interface Nullable {}

View file

@ -1,6 +1,5 @@
package org.github.tursodatabase.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@ -8,11 +7,11 @@ import java.lang.annotation.Target;
/**
* Marker annotation to skip nullable checks.
* <p>
* This annotation is used to mark methods, fields, or parameters that should be excluded from nullable checks.
* It is typically applied to code that is still under development or requires special handling.
*
* <p>This annotation is used to mark methods, fields, or parameters that should be excluded from
* nullable checks. It is typically applied to code that is still under development or requires
* special handling.
*/
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
public @interface SkipNullableCheck {
}
public @interface SkipNullableCheck {}

View file

@ -5,10 +5,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation to mark methods that use larger visibility for testing purposes.
*/
/** Annotation to mark methods that use larger visibility for testing purposes. */
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)
public @interface VisibleForTesting {
}
public @interface VisibleForTesting {}

View file

@ -5,74 +5,71 @@ import java.sql.SQLFeatureNotSupportedException;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Interface to Limbo. It provides some helper functions
* used by other parts of the driver. The goal of the helper functions here
* are not only to provide functionality, but to handle contractual
* Interface to Limbo. It provides some helper functions used by other parts of the driver. The goal
* of the helper functions here are not only to provide functionality, but to handle contractual
* differences between the JDBC specification and the Limbo API.
*/
public abstract class AbstractDB {
protected final String url;
protected final String filePath;
private final AtomicBoolean closed = new AtomicBoolean(true);
protected final String url;
protected final String filePath;
private final AtomicBoolean closed = new AtomicBoolean(true);
public AbstractDB(String url, String filePath) {
this.url = url;
this.filePath = filePath;
}
public AbstractDB(String url, String filePath) {
this.url = url;
this.filePath = filePath;
}
public boolean isClosed() {
return closed.get();
}
public boolean isClosed() {
return closed.get();
}
/**
* Aborts any pending operation and returns at its earliest opportunity.
*/
public abstract void interrupt() throws SQLException;
/** Aborts any pending operation and returns at its earliest opportunity. */
public abstract void interrupt() throws SQLException;
/**
* Creates an SQLite interface to a database for the given connection.
*
* @param openFlags Flags for opening the database.
* @throws SQLException if a database access error occurs.
*/
public final synchronized void open(int openFlags) throws SQLException {
open0(filePath, openFlags);
}
/**
* Creates an SQLite interface to a database for the given connection.
*
* @param openFlags Flags for opening the database.
* @throws SQLException if a database access error occurs.
*/
public final synchronized void open(int openFlags) throws SQLException {
open0(filePath, openFlags);
}
protected abstract void open0(String fileName, int openFlags) throws SQLException;
protected abstract void open0(String fileName, int openFlags) throws SQLException;
/**
* Closes a database connection and finalizes any remaining statements before the closing
* operation.
*
* @throws SQLException if a database access error occurs.
*/
public final synchronized void close() throws SQLException {
// TODO: add implementation
throw new SQLFeatureNotSupportedException();
}
/**
* Closes a database connection and finalizes any remaining statements before the closing
* operation.
*
* @throws SQLException if a database access error occurs.
*/
public final synchronized void close() throws SQLException {
// TODO: add implementation
throw new SQLFeatureNotSupportedException();
}
/**
* Connects to a database.
*
* @return Pointer to the connection.
*/
public abstract long connect() throws SQLException;
/**
* Connects to a database.
*
* @return Pointer to the connection.
*/
public abstract long connect() throws SQLException;
/**
* Creates an SQLite interface to a database with the provided open flags.
*
* @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 long openUtf8(byte[] fileName, int openFlags) throws SQLException;
/**
* Creates an SQLite interface to a database with the provided open flags.
*
* @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 long openUtf8(byte[] fileName, int openFlags) throws SQLException;
/**
* Closes the SQLite interface to a database.
*
* @throws SQLException if a database access error occurs.
*/
protected abstract void close0() throws SQLException;
/**
* Closes the SQLite interface to a database.
*
* @throws SQLException if a database access error occurs.
*/
protected abstract void close0() throws SQLException;
}

View file

@ -1,140 +1,139 @@
package org.github.tursodatabase.core;
import org.github.tursodatabase.annotations.NativeInvocation;
import org.github.tursodatabase.utils.LimboExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.github.tursodatabase.utils.ByteArrayUtils.stringToUtf8ByteArray;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
import static org.github.tursodatabase.utils.ByteArrayUtils.stringToUtf8ByteArray;
import org.github.tursodatabase.annotations.NativeInvocation;
import org.github.tursodatabase.utils.LimboExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class LimboConnection implements Connection {
private static final Logger logger = LoggerFactory.getLogger(LimboConnection.class);
private static final Logger logger = LoggerFactory.getLogger(LimboConnection.class);
private final long connectionPtr;
private final AbstractDB database;
private final long connectionPtr;
private final AbstractDB database;
public LimboConnection(String url, String filePath) throws SQLException {
this(url, filePath, new Properties());
}
public LimboConnection(String url, String filePath) throws SQLException {
this(url, filePath, new Properties());
}
/**
* Creates a connection to limbo database
*
* @param url e.g. "jdbc:sqlite:fileName"
* @param filePath path to file
*/
public LimboConnection(String url, String filePath, Properties properties) throws SQLException {
AbstractDB db = null;
/**
* Creates a connection to limbo database
*
* @param url e.g. "jdbc:sqlite:fileName"
* @param filePath path to file
*/
public LimboConnection(String url, String filePath, Properties properties) throws SQLException {
AbstractDB db = null;
try {
db = open(url, filePath, properties);
} catch (Throwable t) {
try {
if (db != null) {
db.close();
}
} catch (Throwable t2) {
t.addSuppressed(t2);
}
throw t;
try {
db = open(url, filePath, properties);
} catch (Throwable t) {
try {
if (db != null) {
db.close();
}
} catch (Throwable t2) {
t.addSuppressed(t2);
}
this.database = db;
this.connectionPtr = db.connect();
throw t;
}
private static AbstractDB open(String url, String filePath, Properties properties) throws SQLException {
return LimboDBFactory.open(url, filePath, properties);
this.database = db;
this.connectionPtr = db.connect();
}
private static AbstractDB open(String url, String filePath, Properties properties)
throws SQLException {
return LimboDBFactory.open(url, filePath, properties);
}
protected void checkOpen() throws SQLException {
if (isClosed()) throw new SQLException("database connection closed");
}
@Override
public void close() throws SQLException {
if (isClosed()) return;
database.close();
}
@Override
public boolean isClosed() throws SQLException {
return database.isClosed();
}
public AbstractDB getDatabase() {
return database;
}
/**
* Compiles an SQL statement.
*
* @param sql An SQL statement.
* @return Pointer to statement.
* @throws SQLException if a database access error occurs.
*/
public LimboStatement 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 LimboStatement(sql, prepareUtf8(connectionPtr, sqlBytes));
}
protected void checkOpen() throws SQLException {
if (isClosed()) throw new SQLException("database connection closed");
}
private native long prepareUtf8(long connectionPtr, byte[] sqlUtf8) throws SQLException;
@Override
public void close() throws SQLException {
if (isClosed()) return;
database.close();
}
/** @return busy timeout in milliseconds. */
public int getBusyTimeout() {
// TODO: add support for busyTimeout
return 0;
}
@Override
public boolean isClosed() throws SQLException {
return database.isClosed();
}
// TODO: check whether this is still valid for limbo
public AbstractDB getDatabase() {
return database;
}
/**
* Checks whether the type, concurrency, and holdability settings for a {@link ResultSet} are
* supported by the SQLite interface. Supported settings are:
*
* <ul>
* <li>type: {@link ResultSet#TYPE_FORWARD_ONLY}
* <li>concurrency: {@link ResultSet#CONCUR_READ_ONLY})
* <li>holdability: {@link ResultSet#CLOSE_CURSORS_AT_COMMIT}
* </ul>
*
* @param resultSetType the type setting.
* @param resultSetConcurrency the concurrency setting.
* @param resultSetHoldability the holdability setting.
*/
protected void checkCursor(int resultSetType, int resultSetConcurrency, int resultSetHoldability)
throws SQLException {
if (resultSetType != ResultSet.TYPE_FORWARD_ONLY)
throw new SQLException("SQLite only supports TYPE_FORWARD_ONLY cursors");
if (resultSetConcurrency != ResultSet.CONCUR_READ_ONLY)
throw new SQLException("SQLite only supports CONCUR_READ_ONLY cursors");
if (resultSetHoldability != ResultSet.CLOSE_CURSORS_AT_COMMIT)
throw new SQLException("SQLite only supports closing cursors at commit");
}
/**
* Compiles an SQL statement.
*
* @param sql An SQL statement.
* @return Pointer to statement.
* @throws SQLException if a database access error occurs.
*/
public LimboStatement 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 LimboStatement(sql, prepareUtf8(connectionPtr, sqlBytes));
}
public void setBusyTimeout(int busyTimeout) {
// TODO: add support for busy timeout
}
private native long prepareUtf8(long connectionPtr, byte[] sqlUtf8) throws SQLException;
/**
* @return busy timeout in milliseconds.
*/
public int getBusyTimeout() {
// TODO: add support for busyTimeout
return 0;
}
// TODO: check whether this is still valid for limbo
/**
* Checks whether the type, concurrency, and holdability settings for a {@link ResultSet} are
* supported by the SQLite interface. Supported settings are:
*
* <ul>
* <li>type: {@link ResultSet#TYPE_FORWARD_ONLY}
* <li>concurrency: {@link ResultSet#CONCUR_READ_ONLY})
* <li>holdability: {@link ResultSet#CLOSE_CURSORS_AT_COMMIT}
* </ul>
*
* @param resultSetType the type setting.
* @param resultSetConcurrency the concurrency setting.
* @param resultSetHoldability the holdability setting.
*/
protected void checkCursor(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
if (resultSetType != ResultSet.TYPE_FORWARD_ONLY)
throw new SQLException("SQLite only supports TYPE_FORWARD_ONLY cursors");
if (resultSetConcurrency != ResultSet.CONCUR_READ_ONLY)
throw new SQLException("SQLite only supports CONCUR_READ_ONLY cursors");
if (resultSetHoldability != ResultSet.CLOSE_CURSORS_AT_COMMIT)
throw new SQLException("SQLite only supports closing cursors at commit");
}
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(invokedFrom = "limbo_connection.rs")
private void throwLimboException(int errorCode, byte[] errorMessageBytes) throws SQLException {
LimboExceptionUtils.throwLimboException(errorCode, errorMessageBytes);
}
/**
* Throws formatted SQLException with error code and message.
*
* @param errorCode Error code.
* @param errorMessageBytes Error message.
*/
@NativeInvocation(invokedFrom = "limbo_connection.rs")
private void throwLimboException(int errorCode, byte[] errorMessageBytes) throws SQLException {
LimboExceptionUtils.throwLimboException(errorCode, errorMessageBytes);
}
}

View file

@ -4,7 +4,6 @@ import static org.github.tursodatabase.utils.ByteArrayUtils.stringToUtf8ByteArra
import java.sql.SQLException;
import java.util.concurrent.locks.ReentrantLock;
import org.github.tursodatabase.LimboErrorCode;
import org.github.tursodatabase.annotations.NativeInvocation;
import org.github.tursodatabase.annotations.VisibleForTesting;
@ -12,105 +11,104 @@ import org.github.tursodatabase.utils.LimboExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class provides a thin JNI layer over the SQLite3 C API.
*/
/** This class provides a thin JNI layer over the SQLite3 C API. */
public final class LimboDB extends AbstractDB {
private static final Logger logger = LoggerFactory.getLogger(LimboDB.class);
// Pointer to database instance
private long dbPointer;
private boolean isOpen;
private static final Logger logger = LoggerFactory.getLogger(LimboDB.class);
// Pointer to database instance
private long dbPointer;
private boolean isOpen;
private static boolean isLoaded;
private ReentrantLock dbLock = new ReentrantLock();
private static boolean isLoaded;
private ReentrantLock dbLock = new ReentrantLock();
static {
if ("The Android Project".equals(System.getProperty("java.vm.vendor"))) {
// TODO
} else {
// continue with non Android execution path
isLoaded = false;
}
static {
if ("The Android Project".equals(System.getProperty("java.vm.vendor"))) {
// TODO
} else {
// continue with non Android execution path
isLoaded = false;
}
}
/** Loads the SQLite interface backend. */
public static void load() {
if (isLoaded) {
return;
}
/**
* Loads the SQLite interface backend.
*/
public static void load() {
if (isLoaded) {return;}
try {
System.loadLibrary("_limbo_java");
} finally {
isLoaded = true;
}
}
try {
System.loadLibrary("_limbo_java");
} finally {
isLoaded = true;
}
/**
* @param url e.g. "jdbc:sqlite:fileName
* @param filePath e.g. path to file
*/
public static LimboDB create(String url, String filePath) throws SQLException {
return new LimboDB(url, filePath);
}
// TODO: receive config as argument
private LimboDB(String url, String filePath) {
super(url, filePath);
}
// WRAPPER FUNCTIONS ////////////////////////////////////////////
// TODO: add support for JNI
@Override
protected native long openUtf8(byte[] file, int openFlags) throws SQLException;
// TODO: add support for JNI
@Override
protected native void close0() throws SQLException;
// TODO: add support for JNI
native int execUtf8(byte[] sqlUtf8) throws SQLException;
// TODO: add support for JNI
@Override
public native void interrupt();
@Override
protected void open0(String filePath, int openFlags) throws SQLException {
if (isOpen) {
throw LimboExceptionUtils.buildLimboException(
LimboErrorCode.LIMBO_ETC.code, "Already opened");
}
/**
* @param url e.g. "jdbc:sqlite:fileName
* @param filePath e.g. path to file
*/
public static LimboDB create(String url, String filePath) throws SQLException {
return new LimboDB(url, filePath);
byte[] filePathBytes = stringToUtf8ByteArray(filePath);
if (filePathBytes == null) {
throw LimboExceptionUtils.buildLimboException(
LimboErrorCode.LIMBO_ETC.code,
"File path cannot be converted to byteArray. File name: " + filePath);
}
// TODO: receive config as argument
private LimboDB(String url, String filePath) {
super(url, filePath);
}
dbPointer = openUtf8(filePathBytes, openFlags);
isOpen = true;
}
// WRAPPER FUNCTIONS ////////////////////////////////////////////
@Override
public long connect() throws SQLException {
return connect0(dbPointer);
}
// TODO: add support for JNI
@Override
protected native long openUtf8(byte[] file, int openFlags) throws SQLException;
private native long connect0(long databasePtr) throws SQLException;
// TODO: add support for JNI
@Override
protected native void close0() throws SQLException;
@VisibleForTesting
native void throwJavaException(int errorCode) throws SQLException;
// TODO: add support for JNI
native int execUtf8(byte[] sqlUtf8) throws SQLException;
// TODO: add support for JNI
@Override
public native void interrupt();
@Override
protected void open0(String filePath, int openFlags) throws SQLException {
if (isOpen) {
throw LimboExceptionUtils.buildLimboException(LimboErrorCode.LIMBO_ETC.code, "Already opened");
}
byte[] filePathBytes = stringToUtf8ByteArray(filePath);
if (filePathBytes == null) {
throw LimboExceptionUtils.buildLimboException(
LimboErrorCode.LIMBO_ETC.code,
"File path cannot be converted to byteArray. File name: " + filePath);
}
dbPointer = openUtf8(filePathBytes, openFlags);
isOpen = true;
}
@Override
public long connect() throws SQLException {
return connect0(dbPointer);
}
private native long connect0(long databasePtr) throws SQLException;
@VisibleForTesting
native void throwJavaException(int errorCode) throws SQLException;
/**
* Throws formatted SQLException with error code and message.
*
* @param errorCode Error code.
* @param errorMessageBytes Error message.
*/
@NativeInvocation(invokedFrom = "limbo_db.rs")
private void throwLimboException(int errorCode, byte[] errorMessageBytes) throws SQLException {
LimboExceptionUtils.throwLimboException(errorCode, errorMessageBytes);
}
/**
* Throws formatted SQLException with error code and message.
*
* @param errorCode Error code.
* @param errorMessageBytes Error message.
*/
@NativeInvocation(invokedFrom = "limbo_db.rs")
private void throwLimboException(int errorCode, byte[] errorMessageBytes) throws SQLException {
LimboExceptionUtils.throwLimboException(errorCode, errorMessageBytes);
}
}

View file

@ -5,43 +5,45 @@ import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
/**
* Factory class for managing and creating instances of {@link LimboDB}.
* This class ensures that multiple instances of {@link LimboDB} with the same URL are not created.
* Factory class for managing and creating instances of {@link LimboDB}. This class ensures that
* multiple instances of {@link LimboDB} with the same URL are not created.
*/
public class LimboDBFactory {
private static final ConcurrentHashMap<String, LimboDB> databaseHolder = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<String, LimboDB> databaseHolder =
new ConcurrentHashMap<>();
/**
* If a database with the same URL already exists, it returns the existing instance.
* Otherwise, it creates a new instance and stores it in the database holder.
*
* @param url the URL of the database
* @param filePath the path to the database file
* @param properties additional properties for the database connection
* @return an instance of {@link LimboDB}
* @throws SQLException if there is an error opening the connection
* @throws IllegalArgumentException if the fileName is empty
*/
public static LimboDB open(String url, String filePath, Properties properties) throws SQLException {
if (databaseHolder.containsKey(url)) {
return databaseHolder.get(url);
}
if (filePath.isEmpty()) {
throw new IllegalArgumentException("filePath should not be empty");
}
final LimboDB database;
try {
LimboDB.load();
database = LimboDB.create(url, filePath);
} catch (Exception e) {
throw new SQLException("Error opening connection", e);
}
database.open(0);
databaseHolder.put(url, database);
return database;
/**
* If a database with the same URL already exists, it returns the existing instance. Otherwise, it
* creates a new instance and stores it in the database holder.
*
* @param url the URL of the database
* @param filePath the path to the database file
* @param properties additional properties for the database connection
* @return an instance of {@link LimboDB}
* @throws SQLException if there is an error opening the connection
* @throws IllegalArgumentException if the fileName is empty
*/
public static LimboDB open(String url, String filePath, Properties properties)
throws SQLException {
if (databaseHolder.containsKey(url)) {
return databaseHolder.get(url);
}
if (filePath.isEmpty()) {
throw new IllegalArgumentException("filePath should not be empty");
}
final LimboDB database;
try {
LimboDB.load();
database = LimboDB.create(url, filePath);
} catch (Exception e) {
throw new SQLException("Error opening connection", e);
}
database.open(0);
databaseHolder.put(url, database);
return database;
}
}

View file

@ -1,116 +1,119 @@
package org.github.tursodatabase.core;
import java.sql.SQLException;
import org.github.tursodatabase.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A table of data representing limbo database result set, which is generated by executing a statement that queries the
* database.
* <p>
* A {@link LimboResultSet} object is automatically closed when the {@link LimboStatement} object that generated it is
* closed or re-executed.
* A table of data representing limbo database result set, which is generated by executing a
* statement that queries the database.
*
* <p>A {@link LimboResultSet} object is automatically closed when the {@link LimboStatement} object
* that generated it is closed or re-executed.
*/
public class LimboResultSet {
private static final Logger log = LoggerFactory.getLogger(LimboResultSet.class);
private static final Logger log = LoggerFactory.getLogger(LimboResultSet.class);
private final LimboStatement statement;
private final LimboStatement statement;
// Whether the result set does not have any rows.
private boolean isEmptyResultSet = false;
// If the result set is open. Doesn't mean it has results.
private boolean open;
// Maximum number of rows as set by the statement
private long maxRows;
// number of current row, starts at 1 (0 is used to represent loading data)
private int row = 0;
private boolean pastLastRow = false;
// Whether the result set does not have any rows.
private boolean isEmptyResultSet = false;
// If the result set is open. Doesn't mean it has results.
private boolean open;
// Maximum number of rows as set by the statement
private long maxRows;
// number of current row, starts at 1 (0 is used to represent loading data)
private int row = 0;
private boolean pastLastRow = false;
@Nullable
private LimboStepResult lastStepResult;
@Nullable private LimboStepResult lastStepResult;
public static LimboResultSet of(LimboStatement statement) {
return new LimboResultSet(statement);
public static LimboResultSet of(LimboStatement statement) {
return new LimboResultSet(statement);
}
private LimboResultSet(LimboStatement statement) {
this.open = true;
this.statement = statement;
}
/**
* Moves the cursor forward one row from its current position. A {@link LimboResultSet} cursor is
* initially positioned before the first fow; the first call to the method <code>next</code> makes
* the first row the current row; the second call makes the second row the current row, and so on.
* When a call to the <code>next</code> method returns <code>false</code>, the cursor is
* positioned after the last row.
*
* <p>Note that limbo only supports <code>ResultSet.TYPE_FORWARD_ONLY</code>, which means that the
* cursor can only move forward.
*/
public boolean next() throws SQLException {
if (!open || isEmptyResultSet || pastLastRow) {
return false; // completed ResultSet
}
private LimboResultSet(LimboStatement statement) {
this.open = true;
this.statement = statement;
if (maxRows != 0 && row == maxRows) {
return false;
}
/**
* Moves the cursor forward one row from its current position. A {@link LimboResultSet} cursor is initially positioned
* before the first fow; the first call to the method <code>next</code> makes the first row the current row; the second call
* makes the second row the current row, and so on.
* When a call to the <code>next</code> method returns <code>false</code>, the cursor is positioned after the last row.
* <p>
* Note that limbo only supports <code>ResultSet.TYPE_FORWARD_ONLY</code>, which means that the cursor can only move forward.
*/
public boolean next() throws SQLException {
if (!open || isEmptyResultSet || pastLastRow) {
return false; // completed ResultSet
}
if (maxRows != 0 && row == maxRows) {
return false;
}
lastStepResult = this.statement.step();
log.debug("lastStepResult: {}", lastStepResult);
if (lastStepResult.isRow()) {
row++;
}
if (lastStepResult.isInInvalidState()) {
open = false;
throw new SQLException("step() returned invalid result: " + lastStepResult);
}
pastLastRow = lastStepResult.isDone();
if (pastLastRow) {
open = false;
}
return !pastLastRow;
lastStepResult = this.statement.step();
log.debug("lastStepResult: {}", lastStepResult);
if (lastStepResult.isRow()) {
row++;
}
/**
* Checks whether the last step result has returned row result.
*/
public boolean hasLastStepReturnedRow() {
return lastStepResult != null && lastStepResult.isRow();
if (lastStepResult.isInInvalidState()) {
open = false;
throw new SQLException("step() returned invalid result: " + lastStepResult);
}
/**
* Checks the status of the result set.
*
* @return true if it's ready to iterate over the result set; false otherwise.
*/
public boolean isOpen() {
return open;
pastLastRow = lastStepResult.isDone();
if (pastLastRow) {
open = false;
}
return !pastLastRow;
}
/**
* @throws SQLException if not {@link #open}
*/
public void checkOpen() throws SQLException {
if (!open) {
throw new SQLException("ResultSet closed");
}
}
/** Checks whether the last step result has returned row result. */
public boolean hasLastStepReturnedRow() {
return lastStepResult != null && lastStepResult.isRow();
}
@Override
public String toString() {
return "LimboResultSet{" +
"statement=" + statement +
", isEmptyResultSet=" + isEmptyResultSet +
", open=" + open +
", maxRows=" + maxRows +
", row=" + row +
", pastLastRow=" + pastLastRow +
", lastResult=" + lastStepResult +
'}';
/**
* Checks the status of the result set.
*
* @return true if it's ready to iterate over the result set; false otherwise.
*/
public boolean isOpen() {
return open;
}
/** @throws SQLException if not {@link #open} */
public void checkOpen() throws SQLException {
if (!open) {
throw new SQLException("ResultSet closed");
}
}
@Override
public String toString() {
return "LimboResultSet{"
+ "statement="
+ statement
+ ", isEmptyResultSet="
+ isEmptyResultSet
+ ", open="
+ open
+ ", maxRows="
+ maxRows
+ ", row="
+ row
+ ", pastLastRow="
+ pastLastRow
+ ", lastResult="
+ lastStepResult
+ '}';
}
}

View file

@ -1,7 +1,6 @@
package org.github.tursodatabase.core;
import java.sql.SQLException;
import org.github.tursodatabase.annotations.NativeInvocation;
import org.github.tursodatabase.annotations.Nullable;
import org.github.tursodatabase.utils.LimboExceptionUtils;
@ -9,68 +8,73 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* By default, only one <code>resultSet</code> object per <code>LimboStatement</code> can be open at the same time.
* Therefore, if the reading of one <code>resultSet</code> object is interleaved with the reading of another, each must
* have been generated by different <code>LimboStatement</code> objects. All execution method in the <code>LimboStatement</code>
* implicitly close the current <code>resultSet</code> object of the statement if an open one exists.
* By default, only one <code>resultSet</code> object per <code>LimboStatement</code> can be open at
* the same time. Therefore, if the reading of one <code>resultSet</code> object is interleaved with
* the reading of another, each must have been generated by different <code>LimboStatement</code>
* objects. All execution method in the <code>LimboStatement</code> implicitly close the current
* <code>resultSet</code> object of the statement if an open one exists.
*/
public class LimboStatement {
private static final Logger log = LoggerFactory.getLogger(LimboStatement.class);
private static final Logger log = LoggerFactory.getLogger(LimboStatement.class);
private final String sql;
private final long statementPointer;
private final LimboResultSet resultSet;
private final String sql;
private final long statementPointer;
private final LimboResultSet resultSet;
// TODO: what if the statement we ran was DDL, update queries and etc. Should we still create a resultSet?
public LimboStatement(String sql, long statementPointer) {
this.sql = sql;
this.statementPointer = statementPointer;
this.resultSet = LimboResultSet.of(this);
log.debug("Creating statement with sql: {}", this.sql);
// TODO: what if the statement we ran was DDL, update queries and etc. Should we still create a
// resultSet?
public LimboStatement(String sql, long statementPointer) {
this.sql = sql;
this.statementPointer = statementPointer;
this.resultSet = LimboResultSet.of(this);
log.debug("Creating statement with sql: {}", this.sql);
}
public LimboResultSet getResultSet() {
return resultSet;
}
/**
* Expects a clean statement created right after prepare method is called.
*
* @return true if the ResultSet has at least one row; false otherwise.
*/
public boolean execute() throws SQLException {
resultSet.next();
return resultSet.hasLastStepReturnedRow();
}
LimboStepResult step() throws SQLException {
final LimboStepResult result = step(this.statementPointer);
if (result == null) {
throw new SQLException("step() returned null, which is only returned when an error occurs");
}
public LimboResultSet getResultSet() {
return resultSet;
}
return result;
}
/**
* Expects a clean statement created right after prepare method is called.
*
* @return true if the ResultSet has at least one row; false otherwise.
*/
public boolean execute() throws SQLException {
resultSet.next();
return resultSet.hasLastStepReturnedRow();
}
@Nullable
private native LimboStepResult step(long stmtPointer) throws SQLException;
LimboStepResult step() throws SQLException {
final LimboStepResult result = step(this.statementPointer);
if (result == null) {
throw new SQLException("step() returned null, which is only returned when an error occurs");
}
/**
* Throws formatted SQLException with error code and message.
*
* @param errorCode Error code.
* @param errorMessageBytes Error message.
*/
@NativeInvocation(invokedFrom = "limbo_statement.rs")
private void throwLimboException(int errorCode, byte[] errorMessageBytes) throws SQLException {
LimboExceptionUtils.throwLimboException(errorCode, errorMessageBytes);
}
return result;
}
@Nullable
private native LimboStepResult step(long stmtPointer) throws SQLException;
/**
* Throws formatted SQLException with error code and message.
*
* @param errorCode Error code.
* @param errorMessageBytes Error message.
*/
@NativeInvocation(invokedFrom = "limbo_statement.rs")
private void throwLimboException(int errorCode, byte[] errorMessageBytes) throws SQLException {
LimboExceptionUtils.throwLimboException(errorCode, errorMessageBytes);
}
@Override
public String toString() {
return "LimboStatement{" +
"statementPointer=" + statementPointer +
", sql='" + sql + '\'' +
'}';
}
@Override
public String toString() {
return "LimboStatement{"
+ "statementPointer="
+ statementPointer
+ ", sql='"
+ sql
+ '\''
+ '}';
}
}

View file

@ -1,79 +1,78 @@
package org.github.tursodatabase.core;
import java.util.Arrays;
import org.github.tursodatabase.annotations.NativeInvocation;
import org.github.tursodatabase.annotations.Nullable;
/**
* Represents the step result of limbo's statement's step function.
*/
/** Represents the step result of limbo's statement's step function. */
public class LimboStepResult {
private static final int STEP_RESULT_ID_ROW = 10;
private static final int STEP_RESULT_ID_IO = 20;
private static final int STEP_RESULT_ID_DONE = 30;
private static final int STEP_RESULT_ID_INTERRUPT = 40;
// Indicates that the database file could not be written because of concurrent activity by some other connection
private static final int STEP_RESULT_ID_BUSY = 50;
private static final int STEP_RESULT_ID_ERROR = 60;
private static final int STEP_RESULT_ID_ROW = 10;
private static final int STEP_RESULT_ID_IO = 20;
private static final int STEP_RESULT_ID_DONE = 30;
private static final int STEP_RESULT_ID_INTERRUPT = 40;
// Indicates that the database file could not be written because of concurrent activity by some
// other connection
private static final int STEP_RESULT_ID_BUSY = 50;
private static final int STEP_RESULT_ID_ERROR = 60;
// Identifier for limbo's StepResult
private final int stepResultId;
@Nullable
private final Object[] result;
// Identifier for limbo's StepResult
private final int stepResultId;
@Nullable private final Object[] result;
@NativeInvocation(invokedFrom = "limbo_statement.rs")
public LimboStepResult(int stepResultId) {
this.stepResultId = stepResultId;
this.result = null;
}
@NativeInvocation(invokedFrom = "limbo_statement.rs")
public LimboStepResult(int stepResultId, Object[] result) {
this.stepResultId = stepResultId;
this.result = result;
}
public boolean isRow() {
return stepResultId == STEP_RESULT_ID_ROW;
}
public boolean isDone() {
return stepResultId == STEP_RESULT_ID_DONE;
}
public boolean isInInvalidState() {
// current implementation doesn't allow STEP_RESULT_ID_IO to be returned
return stepResultId == STEP_RESULT_ID_IO ||
stepResultId == STEP_RESULT_ID_INTERRUPT ||
stepResultId == STEP_RESULT_ID_BUSY ||
stepResultId == STEP_RESULT_ID_ERROR;
}
@Override
public String toString() {
return "LimboStepResult{" +
"stepResultName=" + getStepResultName() +
", result=" + Arrays.toString(result) +
'}';
}
private String getStepResultName() {
switch (stepResultId) {
case STEP_RESULT_ID_ROW:
return "ROW";
case STEP_RESULT_ID_IO:
return "IO";
case STEP_RESULT_ID_DONE:
return "DONE";
case STEP_RESULT_ID_INTERRUPT:
return "INTERRUPT";
case STEP_RESULT_ID_BUSY:
return "BUSY";
case STEP_RESULT_ID_ERROR:
return "ERROR";
default:
return "UNKNOWN";
}
@NativeInvocation(invokedFrom = "limbo_statement.rs")
public LimboStepResult(int stepResultId) {
this.stepResultId = stepResultId;
this.result = null;
}
@NativeInvocation(invokedFrom = "limbo_statement.rs")
public LimboStepResult(int stepResultId, Object[] result) {
this.stepResultId = stepResultId;
this.result = result;
}
public boolean isRow() {
return stepResultId == STEP_RESULT_ID_ROW;
}
public boolean isDone() {
return stepResultId == STEP_RESULT_ID_DONE;
}
public boolean isInInvalidState() {
// current implementation doesn't allow STEP_RESULT_ID_IO to be returned
return stepResultId == STEP_RESULT_ID_IO
|| stepResultId == STEP_RESULT_ID_INTERRUPT
|| stepResultId == STEP_RESULT_ID_BUSY
|| stepResultId == STEP_RESULT_ID_ERROR;
}
@Override
public String toString() {
return "LimboStepResult{"
+ "stepResultName="
+ getStepResultName()
+ ", result="
+ Arrays.toString(result)
+ '}';
}
private String getStepResultName() {
switch (stepResultId) {
case STEP_RESULT_ID_ROW:
return "ROW";
case STEP_RESULT_ID_IO:
return "IO";
case STEP_RESULT_ID_DONE:
return "DONE";
case STEP_RESULT_ID_INTERRUPT:
return "INTERRUPT";
case STEP_RESULT_ID_BUSY:
return "BUSY";
case STEP_RESULT_ID_ERROR:
return "ERROR";
default:
return "UNKNOWN";
}
}
}

View file

@ -15,93 +15,91 @@
*/
package org.github.tursodatabase.core;
/**
* Sqlite error codes.
*/
/** Sqlite error codes. */
public class SqliteCode {
/** Successful result */
public static final int SQLITE_OK = 0;
/** Successful result */
public static final int SQLITE_OK = 0;
/** SQL error or missing database */
public static final int SQLITE_ERROR = 1;
/** SQL error or missing database */
public static final int SQLITE_ERROR = 1;
/** An internal logic error in SQLite */
public static final int SQLITE_INTERNAL = 2;
/** An internal logic error in SQLite */
public static final int SQLITE_INTERNAL = 2;
/** Access permission denied */
public static final int SQLITE_PERM = 3;
/** Access permission denied */
public static final int SQLITE_PERM = 3;
/** Callback routine requested an abort */
public static final int SQLITE_ABORT = 4;
/** Callback routine requested an abort */
public static final int SQLITE_ABORT = 4;
/** The database file is locked */
public static final int SQLITE_BUSY = 5;
/** The database file is locked */
public static final int SQLITE_BUSY = 5;
/** A table in the database is locked */
public static final int SQLITE_LOCKED = 6;
/** A table in the database is locked */
public static final int SQLITE_LOCKED = 6;
/** A malloc() failed */
public static final int SQLITE_NOMEM = 7;
/** A malloc() failed */
public static final int SQLITE_NOMEM = 7;
/** Attempt to write a readonly database */
public static final int SQLITE_READONLY = 8;
/** Attempt to write a readonly database */
public static final int SQLITE_READONLY = 8;
/** Operation terminated by sqlite_interrupt() */
public static final int SQLITE_INTERRUPT = 9;
/** Operation terminated by sqlite_interrupt() */
public static final int SQLITE_INTERRUPT = 9;
/** Some kind of disk I/O error occurred */
public static final int SQLITE_IOERR = 10;
/** Some kind of disk I/O error occurred */
public static final int SQLITE_IOERR = 10;
/** The database disk image is malformed */
public static final int SQLITE_CORRUPT = 11;
/** The database disk image is malformed */
public static final int SQLITE_CORRUPT = 11;
/** (Internal Only) Table or record not found */
public static final int SQLITE_NOTFOUND = 12;
/** (Internal Only) Table or record not found */
public static final int SQLITE_NOTFOUND = 12;
/** Insertion failed because database is full */
public static final int SQLITE_FULL = 13;
/** Insertion failed because database is full */
public static final int SQLITE_FULL = 13;
/** Unable to open the database file */
public static final int SQLITE_CANTOPEN = 14;
/** Unable to open the database file */
public static final int SQLITE_CANTOPEN = 14;
/** Database lock protocol error */
public static final int SQLITE_PROTOCOL = 15;
/** Database lock protocol error */
public static final int SQLITE_PROTOCOL = 15;
/** (Internal Only) Database table is empty */
public static final int SQLITE_EMPTY = 16;
/** (Internal Only) Database table is empty */
public static final int SQLITE_EMPTY = 16;
/** The database schema changed */
public static final int SQLITE_SCHEMA = 17;
/** The database schema changed */
public static final int SQLITE_SCHEMA = 17;
/** Too much data for one row of a table */
public static final int SQLITE_TOOBIG = 18;
/** Too much data for one row of a table */
public static final int SQLITE_TOOBIG = 18;
/** Abort due to constraint violation */
public static final int SQLITE_CONSTRAINT = 19;
/** Abort due to constraint violation */
public static final int SQLITE_CONSTRAINT = 19;
/** Data type mismatch */
public static final int SQLITE_MISMATCH = 20;
/** Data type mismatch */
public static final int SQLITE_MISMATCH = 20;
/** Library used incorrectly */
public static final int SQLITE_MISUSE = 21;
/** Library used incorrectly */
public static final int SQLITE_MISUSE = 21;
/** Uses OS features not supported on host */
public static final int SQLITE_NOLFS = 22;
/** Uses OS features not supported on host */
public static final int SQLITE_NOLFS = 22;
/** Authorization denied */
public static final int SQLITE_AUTH = 23;
/** Authorization denied */
public static final int SQLITE_AUTH = 23;
/** sqlite_step() has another row ready */
public static final int SQLITE_ROW = 100;
/** sqlite_step() has another row ready */
public static final int SQLITE_ROW = 100;
/** sqlite_step() has finished executing */
public static final int SQLITE_DONE = 101;
/** sqlite_step() has finished executing */
public static final int SQLITE_DONE = 101;
// types returned by sqlite3_column_type()
// types returned by sqlite3_column_type()
public static final int SQLITE_INTEGER = 1;
public static final int SQLITE_FLOAT = 2;
public static final int SQLITE_TEXT = 3;
public static final int SQLITE_BLOB = 4;
public static final int SQLITE_NULL = 5;
public static final int SQLITE_INTEGER = 1;
public static final int SQLITE_FLOAT = 2;
public static final int SQLITE_TEXT = 3;
public static final int SQLITE_BLOB = 4;
public static final int SQLITE_NULL = 5;
}

View file

@ -1,18 +1,17 @@
package org.github.tursodatabase.exceptions;
import java.sql.SQLException;
import org.github.tursodatabase.LimboErrorCode;
import java.sql.SQLException;
public class LimboException extends SQLException {
private final LimboErrorCode resultCode;
private final LimboErrorCode resultCode;
public LimboException(String message, LimboErrorCode resultCode) {
super(message, null, resultCode.code & 0xff);
this.resultCode = resultCode;
}
public LimboException(String message, LimboErrorCode resultCode) {
super(message, null, resultCode.code & 0xff);
this.resultCode = resultCode;
}
public LimboErrorCode getResultCode() {
return resultCode;
}
public LimboErrorCode getResultCode() {
return resultCode;
}
}

View file

@ -1,353 +1,357 @@
package org.github.tursodatabase.jdbc4;
import org.github.tursodatabase.core.LimboConnection;
import org.github.tursodatabase.annotations.SkipNullableCheck;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;
import org.github.tursodatabase.annotations.SkipNullableCheck;
import org.github.tursodatabase.core.LimboConnection;
public class JDBC4Connection extends LimboConnection {
public JDBC4Connection(String url, String filePath) throws SQLException {
super(url, filePath);
}
public JDBC4Connection(String url, String filePath) throws SQLException {
super(url, filePath);
}
public JDBC4Connection(String url, String filePath, Properties properties) throws SQLException {
super(url, filePath, properties);
}
public JDBC4Connection(String url, String filePath, Properties properties) throws SQLException {
super(url, filePath, properties);
}
@Override
public Statement createStatement() throws SQLException {
return createStatement(
ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_READ_ONLY,
ResultSet.CLOSE_CURSORS_AT_COMMIT
);
}
@Override
public Statement createStatement() throws SQLException {
return createStatement(
ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT);
}
@Override
public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
return createStatement(resultSetType, resultSetConcurrency, ResultSet.CLOSE_CURSORS_AT_COMMIT);
}
@Override
public Statement createStatement(int resultSetType, int resultSetConcurrency)
throws SQLException {
return createStatement(resultSetType, resultSetConcurrency, ResultSet.CLOSE_CURSORS_AT_COMMIT);
}
@Override
public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
checkOpen();
checkCursor(resultSetType, resultSetConcurrency, resultSetHoldability);
@Override
public Statement createStatement(
int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
checkOpen();
checkCursor(resultSetType, resultSetConcurrency, resultSetHoldability);
return new JDBC4Statement(this);
}
return new JDBC4Statement(this);
}
@Override
@SkipNullableCheck
public PreparedStatement prepareStatement(String sql) throws SQLException {
// TODO
return null;
}
@Override
@SkipNullableCheck
public PreparedStatement prepareStatement(String sql) throws SQLException {
// TODO
return null;
}
@Override
@SkipNullableCheck
public CallableStatement prepareCall(String sql) throws SQLException {
// TODO
return null;
}
@Override
@SkipNullableCheck
public CallableStatement prepareCall(String sql) throws SQLException {
// TODO
return null;
}
@Override
@SkipNullableCheck
public String nativeSQL(String sql) throws SQLException {
// TODO
return "";
}
@Override
@SkipNullableCheck
public String nativeSQL(String sql) throws SQLException {
// TODO
return "";
}
@Override
public void setAutoCommit(boolean autoCommit) throws SQLException {
// TODO
}
@Override
public void setAutoCommit(boolean autoCommit) throws SQLException {
// TODO
}
@Override
public boolean getAutoCommit() throws SQLException {
// TODO
return false;
}
@Override
public boolean getAutoCommit() throws SQLException {
// TODO
return false;
}
@Override
public void commit() throws SQLException {
// TODO
}
@Override
public void commit() throws SQLException {
// TODO
}
@Override
public void rollback() throws SQLException {
// TODO
}
@Override
public void rollback() throws SQLException {
// TODO
}
@Override
public void close() throws SQLException {
// TODO
}
@Override
public void close() throws SQLException {
// TODO
}
@Override
public boolean isClosed() throws SQLException {
// TODO
return false;
}
@Override
public boolean isClosed() throws SQLException {
// TODO
return false;
}
@Override
@SkipNullableCheck
public DatabaseMetaData getMetaData() throws SQLException {
// TODO
return null;
}
@Override
@SkipNullableCheck
public DatabaseMetaData getMetaData() throws SQLException {
// TODO
return null;
}
@Override
public void setReadOnly(boolean readOnly) throws SQLException {
// TODO
}
@Override
public void setReadOnly(boolean readOnly) throws SQLException {
// TODO
}
@Override
public boolean isReadOnly() throws SQLException {
// TODO
return false;
}
@Override
public boolean isReadOnly() throws SQLException {
// TODO
return false;
}
@Override
public void setCatalog(String catalog) throws SQLException {
// TODO
}
@Override
public void setCatalog(String catalog) throws SQLException {
// TODO
}
@Override
public String getCatalog() throws SQLException {
// TODO
return "";
}
@Override
public String getCatalog() throws SQLException {
// TODO
return "";
}
@Override
public void setTransactionIsolation(int level) throws SQLException {
// TODO
}
@Override
public void setTransactionIsolation(int level) throws SQLException {
// TODO
}
@Override
public int getTransactionIsolation() throws SQLException {
// TODO
return 0;
}
@Override
public int getTransactionIsolation() throws SQLException {
// TODO
return 0;
}
@Override
@SkipNullableCheck
public SQLWarning getWarnings() throws SQLException {
// TODO
return null;
}
@Override
@SkipNullableCheck
public SQLWarning getWarnings() throws SQLException {
// TODO
return null;
}
@Override
public void clearWarnings() throws SQLException {
// TODO
}
@Override
public void clearWarnings() throws SQLException {
// TODO
}
@Override
@SkipNullableCheck
public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
// TODO
return null;
}
@Override
@SkipNullableCheck
public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency)
throws SQLException {
// TODO
return null;
}
@Override
@SkipNullableCheck
public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
// TODO
return null;
}
@Override
@SkipNullableCheck
public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency)
throws SQLException {
// TODO
return null;
}
@Override
public Map<String, Class<?>> getTypeMap() throws SQLException {
// TODO
return new HashMap<>();
}
@Override
public Map<String, Class<?>> getTypeMap() throws SQLException {
// TODO
return new HashMap<>();
}
@Override
public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
// TODO
}
@Override
public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
// TODO
}
@Override
public void setHoldability(int holdability) throws SQLException {
// TODO
}
@Override
public void setHoldability(int holdability) throws SQLException {
// TODO
}
@Override
public int getHoldability() throws SQLException {
return 0;
}
@Override
public int getHoldability() throws SQLException {
return 0;
}
@Override
@SkipNullableCheck
public Savepoint setSavepoint() throws SQLException {
// TODO
return null;
}
@Override
@SkipNullableCheck
public Savepoint setSavepoint() throws SQLException {
// TODO
return null;
}
@Override
@SkipNullableCheck
public Savepoint setSavepoint(String name) throws SQLException {
// TODO
return null;
}
@Override
@SkipNullableCheck
public Savepoint setSavepoint(String name) throws SQLException {
// TODO
return null;
}
@Override
public void rollback(Savepoint savepoint) throws SQLException {
// TODO
}
@Override
public void rollback(Savepoint savepoint) throws SQLException {
// TODO
}
@Override
public void releaseSavepoint(Savepoint savepoint) throws SQLException {
// TODO
}
@Override
public void releaseSavepoint(Savepoint savepoint) throws SQLException {
// TODO
}
@Override
@SkipNullableCheck
public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
// TODO
return null;
}
@Override
@SkipNullableCheck
public PreparedStatement prepareStatement(
String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability)
throws SQLException {
// TODO
return null;
}
@Override
@SkipNullableCheck
public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
// TODO
return null;
}
@Override
@SkipNullableCheck
public CallableStatement prepareCall(
String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability)
throws SQLException {
// TODO
return null;
}
@Override
@SkipNullableCheck
public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
// TODO
return null;
}
@Override
@SkipNullableCheck
public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
// TODO
return null;
}
@Override
@SkipNullableCheck
public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
// TODO
return null;
}
@Override
@SkipNullableCheck
public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
// TODO
return null;
}
@Override
@SkipNullableCheck
public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
// TODO
return null;
}
@Override
@SkipNullableCheck
public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
// TODO
return null;
}
@Override
@SkipNullableCheck
public Clob createClob() throws SQLException {
// TODO
return null;
}
@Override
@SkipNullableCheck
public Clob createClob() throws SQLException {
// TODO
return null;
}
@Override
@SkipNullableCheck
public Blob createBlob() throws SQLException {
// TODO
return null;
}
@Override
@SkipNullableCheck
public Blob createBlob() throws SQLException {
// TODO
return null;
}
@Override
@SkipNullableCheck
public NClob createNClob() throws SQLException {
// TODO
return null;
}
@Override
@SkipNullableCheck
public NClob createNClob() throws SQLException {
// TODO
return null;
}
@Override
@SkipNullableCheck
public SQLXML createSQLXML() throws SQLException {
// TODO
return null;
}
@Override
@SkipNullableCheck
public SQLXML createSQLXML() throws SQLException {
// TODO
return null;
}
@Override
public boolean isValid(int timeout) throws SQLException {
// TODO
return false;
}
@Override
public boolean isValid(int timeout) throws SQLException {
// TODO
return false;
}
@Override
public void setClientInfo(String name, String value) throws SQLClientInfoException {
// TODO
}
@Override
public void setClientInfo(String name, String value) throws SQLClientInfoException {
// TODO
}
@Override
public void setClientInfo(Properties properties) throws SQLClientInfoException {
// TODO
}
@Override
public void setClientInfo(Properties properties) throws SQLClientInfoException {
// TODO
}
@Override
public String getClientInfo(String name) throws SQLException {
// TODO
return "";
}
@Override
public String getClientInfo(String name) throws SQLException {
// TODO
return "";
}
@Override
@SkipNullableCheck
public Properties getClientInfo() throws SQLException {
// TODO
return null;
}
@Override
@SkipNullableCheck
public Properties getClientInfo() throws SQLException {
// TODO
return null;
}
@Override
@SkipNullableCheck
public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
// TODO
return null;
}
@Override
@SkipNullableCheck
public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
// TODO
return null;
}
@Override
@SkipNullableCheck
public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
// TODO
return null;
}
@Override
@SkipNullableCheck
public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
// TODO
return null;
}
@Override
public void setSchema(String schema) throws SQLException {
// TODO
}
@Override
public void setSchema(String schema) throws SQLException {
// TODO
}
@Override
@SkipNullableCheck
public String getSchema() throws SQLException {
// TODO
return "";
}
@Override
@SkipNullableCheck
public String getSchema() throws SQLException {
// TODO
return "";
}
@Override
public void abort(Executor executor) throws SQLException {
// TODO
}
@Override
public void abort(Executor executor) throws SQLException {
// TODO
}
@Override
public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {
// TODO
}
@Override
public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {
// TODO
}
@Override
public int getNetworkTimeout() throws SQLException {
// TODO
return 0;
}
@Override
public int getNetworkTimeout() throws SQLException {
// TODO
return 0;
}
@Override
@SkipNullableCheck
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
@SkipNullableCheck
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
// TODO
return false;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
// TODO
return false;
}
}

View file

@ -8,7 +8,6 @@ import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.util.concurrent.locks.ReentrantLock;
import org.github.tursodatabase.annotations.Nullable;
import org.github.tursodatabase.annotations.SkipNullableCheck;
import org.github.tursodatabase.core.LimboConnection;
@ -17,359 +16,366 @@ import org.github.tursodatabase.core.LimboStatement;
public class JDBC4Statement implements Statement {
private final LimboConnection connection;
@Nullable
private LimboStatement statement = null;
private final LimboConnection connection;
@Nullable private LimboStatement statement = null;
private boolean closed;
private boolean closeOnCompletion;
private boolean closed;
private boolean closeOnCompletion;
private final int resultSetType;
private final int resultSetConcurrency;
private final int resultSetHoldability;
private final int resultSetType;
private final int resultSetConcurrency;
private final int resultSetHoldability;
private int queryTimeoutSeconds;
private long updateCount;
private boolean exhaustedResults = false;
private int queryTimeoutSeconds;
private long updateCount;
private boolean exhaustedResults = false;
private ReentrantLock connectionLock = new ReentrantLock();
private ReentrantLock connectionLock = new ReentrantLock();
public JDBC4Statement(LimboConnection connection) {
this(connection, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY,
ResultSet.CLOSE_CURSORS_AT_COMMIT);
public JDBC4Statement(LimboConnection connection) {
this(
connection,
ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_READ_ONLY,
ResultSet.CLOSE_CURSORS_AT_COMMIT);
}
public JDBC4Statement(
LimboConnection connection,
int resultSetType,
int resultSetConcurrency,
int resultSetHoldability) {
this.connection = connection;
this.resultSetType = resultSetType;
this.resultSetConcurrency = resultSetConcurrency;
this.resultSetHoldability = resultSetHoldability;
}
@Override
public ResultSet executeQuery(String sql) throws SQLException {
execute(sql);
requireNonNull(statement, "statement should not be null after running execute method");
return new JDBC4ResultSet(statement.getResultSet());
}
@Override
public int executeUpdate(String sql) throws SQLException {
execute(sql);
requireNonNull(statement, "statement should not be null after running execute method");
final LimboResultSet resultSet = statement.getResultSet();
while (resultSet.isOpen()) {
resultSet.next();
}
public JDBC4Statement(LimboConnection connection, int resultSetType, int resultSetConcurrency,
int resultSetHoldability) {
this.connection = connection;
this.resultSetType = resultSetType;
this.resultSetConcurrency = resultSetConcurrency;
this.resultSetHoldability = resultSetHoldability;
// TODO: return update count;
return 0;
}
@Override
public void close() throws SQLException {
clearGeneratedKeys();
internalClose();
closed = true;
}
@Override
public int getMaxFieldSize() throws SQLException {
// TODO
return 0;
}
@Override
public void setMaxFieldSize(int max) throws SQLException {
// TODO
}
@Override
public int getMaxRows() throws SQLException {
// TODO
return 0;
}
@Override
public void setMaxRows(int max) throws SQLException {
// TODO
}
@Override
public void setEscapeProcessing(boolean enable) throws SQLException {
// TODO
}
@Override
public int getQueryTimeout() throws SQLException {
// TODO
return 0;
}
@Override
public void setQueryTimeout(int seconds) throws SQLException {
if (seconds < 0) {
throw new SQLException("Query timeout must be greater than 0");
}
this.queryTimeoutSeconds = seconds;
}
@Override
public void cancel() throws SQLException {
// TODO
}
@Override
@SkipNullableCheck
public SQLWarning getWarnings() throws SQLException {
// TODO
return null;
}
@Override
public void clearWarnings() throws SQLException {
// TODO
}
@Override
public void setCursorName(String name) throws SQLException {
// TODO
}
/**
* The <code>execute</code> method executes an SQL statement and indicates the form of the first
* result. You must then use the methods <code>getResultSet</code> or <code>getUpdateCount</code>
* to retrieve the result, and <code>getMoreResults</code> to move to any subsequent result(s).
*/
@Override
public boolean execute(String sql) throws SQLException {
internalClose();
return this.withConnectionTimeout(
() -> {
try {
// TODO: if sql is a readOnly query, do we still need the locks?
connectionLock.lock();
statement = connection.prepare(sql);
final boolean result = statement.execute();
updateGeneratedKeys();
exhaustedResults = false;
return result;
} finally {
connectionLock.unlock();
}
});
}
@Override
public ResultSet getResultSet() throws SQLException {
requireNonNull(statement, "statement is null");
return new JDBC4ResultSet(statement.getResultSet());
}
@Override
public int getUpdateCount() throws SQLException {
// TODO
return 0;
}
@Override
public boolean getMoreResults() throws SQLException {
// TODO
return false;
}
@Override
public void setFetchDirection(int direction) throws SQLException {
// TODO
}
@Override
public int getFetchDirection() throws SQLException {
// TODO
return 0;
}
@Override
public void setFetchSize(int rows) throws SQLException {
// TODO
}
@Override
public int getFetchSize() throws SQLException {
// TODO
return 0;
}
@Override
public int getResultSetConcurrency() {
return resultSetConcurrency;
}
@Override
public int getResultSetType() {
return resultSetType;
}
@Override
public void addBatch(String sql) throws SQLException {
// TODO
}
@Override
public void clearBatch() throws SQLException {
// TODO
}
@Override
public int[] executeBatch() throws SQLException {
// TODO
return new int[0];
}
@Override
@SkipNullableCheck
public Connection getConnection() throws SQLException {
// TODO
return null;
}
@Override
public boolean getMoreResults(int current) throws SQLException {
// TODO
return false;
}
@Override
@SkipNullableCheck
public ResultSet getGeneratedKeys() throws SQLException {
// TODO
return null;
}
@Override
public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
// TODO
return 0;
}
@Override
public int executeUpdate(String sql, int[] columnIndexes) throws SQLException {
// TODO
return 0;
}
@Override
public int executeUpdate(String sql, String[] columnNames) throws SQLException {
// TODO
return 0;
}
@Override
public boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
// TODO
return false;
}
@Override
public boolean execute(String sql, int[] columnIndexes) throws SQLException {
// TODO
return false;
}
@Override
public boolean execute(String sql, String[] columnNames) throws SQLException {
// TODO
return false;
}
@Override
public int getResultSetHoldability() {
return resultSetHoldability;
}
@Override
public boolean isClosed() throws SQLException {
// TODO
return false;
}
@Override
public void setPoolable(boolean poolable) throws SQLException {
// TODO
}
@Override
public boolean isPoolable() throws SQLException {
// TODO
return false;
}
@Override
public void closeOnCompletion() throws SQLException {
if (closed) {
throw new SQLException("statement is closed");
}
closeOnCompletion = true;
}
/**
* Indicates whether the statement should be closed automatically when all its dependent result
* sets are closed.
*/
@Override
public boolean isCloseOnCompletion() throws SQLException {
if (closed) {
throw new SQLException("statement is closed");
}
return closeOnCompletion;
}
@Override
@SkipNullableCheck
public <T> T unwrap(Class<T> iface) throws SQLException {
// TODO
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
// TODO
return false;
}
protected void internalClose() throws SQLException {
// TODO
}
protected void clearGeneratedKeys() throws SQLException {
// TODO
}
protected void updateGeneratedKeys() throws SQLException {
// TODO
}
private <T> T withConnectionTimeout(SQLCallable<T> callable) throws SQLException {
final int originalBusyTimeoutMillis = connection.getBusyTimeout();
if (queryTimeoutSeconds > 0) {
// TODO: set busy timeout
connection.setBusyTimeout(1000 * queryTimeoutSeconds);
}
@Override
public ResultSet executeQuery(String sql) throws SQLException {
execute(sql);
requireNonNull(statement, "statement should not be null after running execute method");
return new JDBC4ResultSet(statement.getResultSet());
try {
return callable.call();
} finally {
if (queryTimeoutSeconds > 0) {
connection.setBusyTimeout(originalBusyTimeoutMillis);
}
}
}
@Override
public int executeUpdate(String sql) throws SQLException {
execute(sql);
requireNonNull(statement, "statement should not be null after running execute method");
final LimboResultSet resultSet = statement.getResultSet();
while (resultSet.isOpen()) {
resultSet.next();
}
// TODO: return update count;
return 0;
}
@Override
public void close() throws SQLException {
clearGeneratedKeys();
internalClose();
closed = true;
}
@Override
public int getMaxFieldSize() throws SQLException {
// TODO
return 0;
}
@Override
public void setMaxFieldSize(int max) throws SQLException {
// TODO
}
@Override
public int getMaxRows() throws SQLException {
// TODO
return 0;
}
@Override
public void setMaxRows(int max) throws SQLException {
// TODO
}
@Override
public void setEscapeProcessing(boolean enable) throws SQLException {
// TODO
}
@Override
public int getQueryTimeout() throws SQLException {
// TODO
return 0;
}
@Override
public void setQueryTimeout(int seconds) throws SQLException {
if (seconds < 0) {
throw new SQLException("Query timeout must be greater than 0");
}
this.queryTimeoutSeconds = seconds;
}
@Override
public void cancel() throws SQLException {
// TODO
}
@Override
@SkipNullableCheck
public SQLWarning getWarnings() throws SQLException {
// TODO
return null;
}
@Override
public void clearWarnings() throws SQLException {
// TODO
}
@Override
public void setCursorName(String name) throws SQLException {
// TODO
}
/**
* The <code>execute</code> method executes an SQL statement and indicates the
* form of the first result. You must then use the methods
* <code>getResultSet</code> or <code>getUpdateCount</code>
* to retrieve the result, and <code>getMoreResults</code> to
* move to any subsequent result(s).
*/
@Override
public boolean execute(String sql) throws SQLException {
internalClose();
return this.withConnectionTimeout(
() -> {
try {
// TODO: if sql is a readOnly query, do we still need the locks?
connectionLock.lock();
statement = connection.prepare(sql);
final boolean result = statement.execute();
updateGeneratedKeys();
exhaustedResults = false;
return result;
} finally {
connectionLock.unlock();
}
}
);
}
@Override
public ResultSet getResultSet() throws SQLException {
requireNonNull(statement, "statement is null");
return new JDBC4ResultSet(statement.getResultSet());
}
@Override
public int getUpdateCount() throws SQLException {
// TODO
return 0;
}
@Override
public boolean getMoreResults() throws SQLException {
// TODO
return false;
}
@Override
public void setFetchDirection(int direction) throws SQLException {
// TODO
}
@Override
public int getFetchDirection() throws SQLException {
// TODO
return 0;
}
@Override
public void setFetchSize(int rows) throws SQLException {
// TODO
}
@Override
public int getFetchSize() throws SQLException {
// TODO
return 0;
}
@Override
public int getResultSetConcurrency() {
return resultSetConcurrency;
}
@Override
public int getResultSetType() {
return resultSetType;
}
@Override
public void addBatch(String sql) throws SQLException {
// TODO
}
@Override
public void clearBatch() throws SQLException {
// TODO
}
@Override
public int[] executeBatch() throws SQLException {
// TODO
return new int[0];
}
@Override
@SkipNullableCheck
public Connection getConnection() throws SQLException {
// TODO
return null;
}
@Override
public boolean getMoreResults(int current) throws SQLException {
// TODO
return false;
}
@Override
@SkipNullableCheck
public ResultSet getGeneratedKeys() throws SQLException {
// TODO
return null;
}
@Override
public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
// TODO
return 0;
}
@Override
public int executeUpdate(String sql, int[] columnIndexes) throws SQLException {
// TODO
return 0;
}
@Override
public int executeUpdate(String sql, String[] columnNames) throws SQLException {
// TODO
return 0;
}
@Override
public boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
// TODO
return false;
}
@Override
public boolean execute(String sql, int[] columnIndexes) throws SQLException {
// TODO
return false;
}
@Override
public boolean execute(String sql, String[] columnNames) throws SQLException {
// TODO
return false;
}
@Override
public int getResultSetHoldability() {
return resultSetHoldability;
}
@Override
public boolean isClosed() throws SQLException {
// TODO
return false;
}
@Override
public void setPoolable(boolean poolable) throws SQLException {
// TODO
}
@Override
public boolean isPoolable() throws SQLException {
// TODO
return false;
}
@Override
public void closeOnCompletion() throws SQLException {
if (closed) {throw new SQLException("statement is closed");}
closeOnCompletion = true;
}
/**
* Indicates whether the statement should be closed automatically when all its dependent result sets are closed.
*/
@Override
public boolean isCloseOnCompletion() throws SQLException {
if (closed) {throw new SQLException("statement is closed");}
return closeOnCompletion;
}
@Override
@SkipNullableCheck
public <T> T unwrap(Class<T> iface) throws SQLException {
// TODO
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
// TODO
return false;
}
protected void internalClose() throws SQLException {
// TODO
}
protected void clearGeneratedKeys() throws SQLException {
// TODO
}
protected void updateGeneratedKeys() throws SQLException {
// TODO
}
private <T> T withConnectionTimeout(SQLCallable<T> callable) throws SQLException {
final int originalBusyTimeoutMillis = connection.getBusyTimeout();
if (queryTimeoutSeconds > 0) {
// TODO: set busy timeout
connection.setBusyTimeout(1000 * queryTimeoutSeconds);
}
try {
return callable.call();
} finally {
if (queryTimeoutSeconds > 0) {
connection.setBusyTimeout(originalBusyTimeoutMillis);
}
}
}
@FunctionalInterface
protected interface SQLCallable<T> {
T call() throws SQLException;
}
@FunctionalInterface
protected interface SQLCallable<T> {
T call() throws SQLException;
}
}

View file

@ -1,24 +1,23 @@
package org.github.tursodatabase.utils;
import java.nio.charset.StandardCharsets;
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 String utf8ByteBufferToString(@Nullable byte[] buffer) {
if (buffer == null) {
return null;
}
@Nullable
public static byte[] stringToUtf8ByteArray(@Nullable String str) {
if (str == null) {
return null;
}
return str.getBytes(StandardCharsets.UTF_8);
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);
}
}

View file

@ -3,39 +3,39 @@ package org.github.tursodatabase.utils;
import static org.github.tursodatabase.utils.ByteArrayUtils.utf8ByteBufferToString;
import java.sql.SQLException;
import org.github.tursodatabase.LimboErrorCode;
import org.github.tursodatabase.annotations.Nullable;
import org.github.tursodatabase.exceptions.LimboException;
public class LimboExceptionUtils {
/**
* Throws formatted SQLException with error code and message.
*
* @param errorCode Error code.
* @param errorMessageBytes Error message.
*/
public static void throwLimboException(int errorCode, byte[] errorMessageBytes) throws SQLException {
String errorMessage = utf8ByteBufferToString(errorMessageBytes);
throw buildLimboException(errorCode, errorMessage);
/**
* Throws formatted SQLException with error code and message.
*
* @param errorCode Error code.
* @param errorMessageBytes Error message.
*/
public static void throwLimboException(int errorCode, byte[] errorMessageBytes)
throws SQLException {
String errorMessage = utf8ByteBufferToString(errorMessageBytes);
throw buildLimboException(errorCode, errorMessage);
}
/**
* Throws formatted SQLException with error code and message.
*
* @param errorCode Error code.
* @param errorMessage Error message.
*/
public static LimboException buildLimboException(int errorCode, @Nullable String errorMessage)
throws SQLException {
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);
}
/**
* Throws formatted SQLException with error code and message.
*
* @param errorCode Error code.
* @param errorMessage Error message.
*/
public static LimboException buildLimboException(int errorCode, @Nullable String errorMessage)
throws SQLException {
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);
}
return new LimboException(msg, code);
}
}

View file

@ -1,37 +1,36 @@
package org.github.tursodatabase;
import org.github.tursodatabase.jdbc4.JDBC4Connection;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import org.github.tursodatabase.jdbc4.JDBC4Connection;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class IntegrationTest {
private JDBC4Connection connection;
private JDBC4Connection connection;
@BeforeEach
void setUp() throws Exception {
String filePath = TestUtils.createTempFile();
String url = "jdbc:sqlite:" + filePath;
connection = new JDBC4Connection(url, filePath, new Properties());
}
@BeforeEach
void setUp() throws Exception {
String filePath = TestUtils.createTempFile();
String url = "jdbc:sqlite:" + filePath;
connection = new JDBC4Connection(url, filePath, new Properties());
}
@Test
void create_table_multi_inserts_select() throws Exception {
Statement stmt = createDefaultStatement();
stmt.execute("CREATE TABLE users (id INT PRIMARY KEY, username TEXT);");
stmt.execute("INSERT INTO users VALUES (1, 'seonwoo');");
stmt.execute("INSERT INTO users VALUES (2, 'seonwoo');");
stmt.execute("INSERT INTO users VALUES (3, 'seonwoo');");
stmt.execute("SELECT * FROM users");
}
@Test
void create_table_multi_inserts_select() throws Exception {
Statement stmt = createDefaultStatement();
stmt.execute("CREATE TABLE users (id INT PRIMARY KEY, username TEXT);");
stmt.execute("INSERT INTO users VALUES (1, 'seonwoo');");
stmt.execute("INSERT INTO users VALUES (2, 'seonwoo');");
stmt.execute("INSERT INTO users VALUES (3, 'seonwoo');");
stmt.execute("SELECT * FROM users");
}
private Statement createDefaultStatement() throws SQLException {
return connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT);
}
private Statement createDefaultStatement() throws SQLException {
return connection.createStatement(
ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT);
}
}

View file

@ -1,34 +1,33 @@
package org.github.tursodatabase;
import org.github.tursodatabase.core.LimboConnection;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import org.github.tursodatabase.core.LimboConnection;
import org.junit.jupiter.api.Test;
class JDBCTest {
@Test
void null_is_returned_when_invalid_url_is_passed() throws Exception {
LimboConnection connection = JDBC.createConnection("jdbc:invalid:xxx", new Properties());
assertThat(connection).isNull();
}
@Test
void null_is_returned_when_invalid_url_is_passed() throws Exception {
LimboConnection connection = JDBC.createConnection("jdbc:invalid:xxx", new Properties());
assertThat(connection).isNull();
}
@Test
void non_null_connection_is_returned_when_valid_url_is_passed() throws Exception {
String fileUrl = TestUtils.createTempFile();
LimboConnection connection = JDBC.createConnection("jdbc:sqlite:" + fileUrl, new Properties());
assertThat(connection).isNotNull();
}
@Test
void non_null_connection_is_returned_when_valid_url_is_passed() throws Exception {
String fileUrl = TestUtils.createTempFile();
LimboConnection connection = JDBC.createConnection("jdbc:sqlite:" + fileUrl, new Properties());
assertThat(connection).isNotNull();
}
@Test
void connection_can_be_retrieved_from_DriverManager() throws SQLException {
try (Connection connection = DriverManager.getConnection("jdbc:sqlite:sample.db")) {
assertThat(connection).isNotNull();
}
@Test
void connection_can_be_retrieved_from_DriverManager() throws SQLException {
try (Connection connection = DriverManager.getConnection("jdbc:sqlite:sample.db")) {
assertThat(connection).isNotNull();
}
}
}

View file

@ -4,10 +4,8 @@ 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();
}
/** Create temporary file and returns the path. */
public static String createTempFile() throws IOException {
return Files.createTempFile("limbo_test_db", null).toAbsolutePath().toString();
}
}

View file

@ -1,32 +1,31 @@
package org.github.tursodatabase.core;
import org.github.tursodatabase.TestUtils;
import org.junit.jupiter.api.Test;
import java.util.Properties;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import java.util.Properties;
import org.github.tursodatabase.TestUtils;
import org.junit.jupiter.api.Test;
class LimboDBFactoryTest {
@Test
void single_database_should_be_created_when_urls_are_same() throws Exception {
String filePath = TestUtils.createTempFile();
String url = "jdbc:sqlite:" + filePath;
LimboDB db1 = LimboDBFactory.open(url, filePath, new Properties());
LimboDB db2 = LimboDBFactory.open(url, filePath, new Properties());
assertEquals(db1, db2);
}
@Test
void single_database_should_be_created_when_urls_are_same() throws Exception {
String filePath = TestUtils.createTempFile();
String url = "jdbc:sqlite:" + filePath;
LimboDB db1 = LimboDBFactory.open(url, filePath, new Properties());
LimboDB db2 = LimboDBFactory.open(url, filePath, new Properties());
assertEquals(db1, db2);
}
@Test
void multiple_databases_should_be_created_when_urls_differ() throws Exception {
String filePath1 = TestUtils.createTempFile();
String filePath2 = TestUtils.createTempFile();
String url1 = "jdbc:sqlite:" + filePath1;
String url2 = "jdbc:sqlite:" + filePath2;
LimboDB db1 = LimboDBFactory.open(url1, filePath1, new Properties());
LimboDB db2 = LimboDBFactory.open(url2, filePath2, new Properties());
assertNotEquals(db1, db2);
}
@Test
void multiple_databases_should_be_created_when_urls_differ() throws Exception {
String filePath1 = TestUtils.createTempFile();
String filePath2 = TestUtils.createTempFile();
String url1 = "jdbc:sqlite:" + filePath1;
String url2 = "jdbc:sqlite:" + filePath2;
LimboDB db1 = LimboDBFactory.open(url1, filePath1, new Properties());
LimboDB db2 = LimboDBFactory.open(url2, filePath2, new Properties());
assertNotEquals(db1, db2);
}
}

View file

@ -1,48 +1,47 @@
package org.github.tursodatabase.core;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import java.sql.SQLException;
import org.github.tursodatabase.LimboErrorCode;
import org.github.tursodatabase.TestUtils;
import org.github.tursodatabase.exceptions.LimboException;
import org.junit.jupiter.api.Test;
import java.sql.SQLException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
public class LimboDBTest {
@Test
void db_should_open_normally() throws Exception {
String dbPath = TestUtils.createTempFile();
LimboDB.load();
LimboDB db = LimboDB.create("jdbc:sqlite" + dbPath, dbPath);
db.open(0);
}
@Test
void should_throw_exception_when_opened_twice() throws Exception {
String dbPath = TestUtils.createTempFile();
LimboDB.load();
LimboDB db = LimboDB.create("jdbc:sqlite:" + dbPath, dbPath);
db.open(0);
assertThatThrownBy(() -> db.open(0)).isInstanceOf(SQLException.class);
}
@Test
void throwJavaException_should_throw_appropriate_java_exception() throws Exception {
String dbPath = TestUtils.createTempFile();
LimboDB.load();
LimboDB db = LimboDB.create("jdbc:sqlite:" + dbPath, dbPath);
final int limboExceptionCode = LimboErrorCode.LIMBO_ETC.code;
try {
db.throwJavaException(limboExceptionCode);
} catch (Exception e) {
assertThat(e).isInstanceOf(LimboException.class);
LimboException limboException = (LimboException) e;
assertThat(limboException.getResultCode().code).isEqualTo(limboExceptionCode);
}
@Test
void db_should_open_normally() throws Exception {
String dbPath = TestUtils.createTempFile();
LimboDB.load();
LimboDB db = LimboDB.create("jdbc:sqlite" + dbPath, dbPath);
db.open(0);
}
@Test
void should_throw_exception_when_opened_twice() throws Exception {
String dbPath = TestUtils.createTempFile();
LimboDB.load();
LimboDB db = LimboDB.create("jdbc:sqlite:" + dbPath, dbPath);
db.open(0);
assertThatThrownBy(() -> db.open(0)).isInstanceOf(SQLException.class);
}
@Test
void throwJavaException_should_throw_appropriate_java_exception() throws Exception {
String dbPath = TestUtils.createTempFile();
LimboDB.load();
LimboDB db = LimboDB.create("jdbc:sqlite:" + dbPath, dbPath);
final int limboExceptionCode = LimboErrorCode.LIMBO_ETC.code;
try {
db.throwJavaException(limboExceptionCode);
} catch (Exception e) {
assertThat(e).isInstanceOf(LimboException.class);
LimboException limboException = (LimboException) e;
assertThat(limboException.getResultCode().code).isEqualTo(limboExceptionCode);
}
}
}

View file

@ -1,63 +1,68 @@
package org.github.tursodatabase.jdbc4;
import org.github.tursodatabase.TestUtils;
import org.github.tursodatabase.core.LimboConnection;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import static org.junit.jupiter.api.Assertions.*;
import org.github.tursodatabase.TestUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class JDBC4ConnectionTest {
private JDBC4Connection connection;
private JDBC4Connection connection;
@BeforeEach
void setUp() throws Exception {
String filePath = TestUtils.createTempFile();
String url = "jdbc:sqlite:" + filePath;
connection = new JDBC4Connection(url, filePath, new Properties());
}
@BeforeEach
void setUp() throws Exception {
String filePath = TestUtils.createTempFile();
String url = "jdbc:sqlite:" + filePath;
connection = new JDBC4Connection(url, filePath, new Properties());
}
@Test
void test_create_statement_valid() throws SQLException {
Statement stmt = connection.createStatement();
assertNotNull(stmt);
assertEquals(ResultSet.TYPE_FORWARD_ONLY, stmt.getResultSetType());
assertEquals(ResultSet.CONCUR_READ_ONLY, stmt.getResultSetConcurrency());
assertEquals(ResultSet.CLOSE_CURSORS_AT_COMMIT, stmt.getResultSetHoldability());
}
@Test
void test_create_statement_valid() throws SQLException {
Statement stmt = connection.createStatement();
assertNotNull(stmt);
assertEquals(ResultSet.TYPE_FORWARD_ONLY, stmt.getResultSetType());
assertEquals(ResultSet.CONCUR_READ_ONLY, stmt.getResultSetConcurrency());
assertEquals(ResultSet.CLOSE_CURSORS_AT_COMMIT, stmt.getResultSetHoldability());
}
@Test
void test_create_statement_with_type_and_concurrency_valid() throws SQLException {
Statement stmt = connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
assertNotNull(stmt);
assertEquals(ResultSet.TYPE_FORWARD_ONLY, stmt.getResultSetType());
assertEquals(ResultSet.CONCUR_READ_ONLY, stmt.getResultSetConcurrency());
}
@Test
void test_create_statement_with_type_and_concurrency_valid() throws SQLException {
Statement stmt =
connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
assertNotNull(stmt);
assertEquals(ResultSet.TYPE_FORWARD_ONLY, stmt.getResultSetType());
assertEquals(ResultSet.CONCUR_READ_ONLY, stmt.getResultSetConcurrency());
}
@Test
void test_create_statement_with_all_params_valid() throws SQLException {
Statement stmt = connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT);
assertNotNull(stmt);
assertEquals(ResultSet.TYPE_FORWARD_ONLY, stmt.getResultSetType());
assertEquals(ResultSet.CONCUR_READ_ONLY, stmt.getResultSetConcurrency());
assertEquals(ResultSet.CLOSE_CURSORS_AT_COMMIT, stmt.getResultSetHoldability());
}
@Test
void test_create_statement_with_all_params_valid() throws SQLException {
Statement stmt =
connection.createStatement(
ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_READ_ONLY,
ResultSet.CLOSE_CURSORS_AT_COMMIT);
assertNotNull(stmt);
assertEquals(ResultSet.TYPE_FORWARD_ONLY, stmt.getResultSetType());
assertEquals(ResultSet.CONCUR_READ_ONLY, stmt.getResultSetConcurrency());
assertEquals(ResultSet.CLOSE_CURSORS_AT_COMMIT, stmt.getResultSetHoldability());
}
@Test
void test_create_statement_invalid() {
assertThrows(SQLException.class, () -> {
connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, -1);
@Test
void test_create_statement_invalid() {
assertThrows(
SQLException.class,
() -> {
connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, -1);
});
}
}
@Test
void prepare_simple_create_table() throws Exception {
connection.prepare("CREATE TABLE users (id INT PRIMARY KEY, username TEXT)");
}
@Test
void prepare_simple_create_table() throws Exception {
connection.prepare("CREATE TABLE users (id INT PRIMARY KEY, username TEXT)");
}
}

View file

@ -6,53 +6,55 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Properties;
import org.github.tursodatabase.TestUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class JDBC4ResultSetTest {
private Statement stmt;
private Statement stmt;
@BeforeEach
void setUp() throws Exception {
String filePath = TestUtils.createTempFile();
String url = "jdbc:sqlite:" + filePath;
final JDBC4Connection connection = new JDBC4Connection(url, filePath, new Properties());
stmt = connection.createStatement(ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_READ_ONLY,
ResultSet.CLOSE_CURSORS_AT_COMMIT);
@BeforeEach
void setUp() throws Exception {
String filePath = TestUtils.createTempFile();
String url = "jdbc:sqlite:" + filePath;
final JDBC4Connection connection = new JDBC4Connection(url, filePath, new Properties());
stmt =
connection.createStatement(
ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_READ_ONLY,
ResultSet.CLOSE_CURSORS_AT_COMMIT);
}
@Test
void invoking_next_before_the_last_row_should_return_true() throws Exception {
stmt.executeUpdate("CREATE TABLE users (id INT PRIMARY KEY, username TEXT);");
stmt.executeUpdate("INSERT INTO users VALUES (1, 'sinwoo');");
stmt.executeUpdate("INSERT INTO users VALUES (2, 'seonwoo');");
// first call to next occur internally
stmt.executeQuery("SELECT * FROM users");
ResultSet resultSet = stmt.getResultSet();
assertTrue(resultSet.next());
}
@Test
void invoking_next_after_the_last_row_should_return_false() throws Exception {
stmt.executeUpdate("CREATE TABLE users (id INT PRIMARY KEY, username TEXT);");
stmt.executeUpdate("INSERT INTO users VALUES (1, 'sinwoo');");
stmt.executeUpdate("INSERT INTO users VALUES (2, 'seonwoo');");
// first call to next occur internally
stmt.executeQuery("SELECT * FROM users");
ResultSet resultSet = stmt.getResultSet();
while (resultSet.next()) {
// run until next() returns false
}
@Test
void invoking_next_before_the_last_row_should_return_true() throws Exception {
stmt.executeUpdate("CREATE TABLE users (id INT PRIMARY KEY, username TEXT);");
stmt.executeUpdate("INSERT INTO users VALUES (1, 'sinwoo');");
stmt.executeUpdate("INSERT INTO users VALUES (2, 'seonwoo');");
// first call to next occur internally
stmt.executeQuery("SELECT * FROM users");
ResultSet resultSet = stmt.getResultSet();
assertTrue(resultSet.next());
}
@Test
void invoking_next_after_the_last_row_should_return_false() throws Exception {
stmt.executeUpdate("CREATE TABLE users (id INT PRIMARY KEY, username TEXT);");
stmt.executeUpdate("INSERT INTO users VALUES (1, 'sinwoo');");
stmt.executeUpdate("INSERT INTO users VALUES (2, 'seonwoo');");
// first call to next occur internally
stmt.executeQuery("SELECT * FROM users");
ResultSet resultSet = stmt.getResultSet();
while (resultSet.next()) {
// run until next() returns false
}
// if the previous call to next() returned false, consecutive call to next() should return false as well
assertFalse(resultSet.next());
}
// if the previous call to next() returned false, consecutive call to next() should return false
// as well
assertFalse(resultSet.next());
}
}

View file

@ -5,7 +5,6 @@ import static org.junit.jupiter.api.Assertions.*;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Properties;
import org.github.tursodatabase.TestUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
@ -13,41 +12,43 @@ import org.junit.jupiter.api.Test;
class JDBC4StatementTest {
private Statement stmt;
private Statement stmt;
@BeforeEach
void setUp() throws Exception {
String filePath = TestUtils.createTempFile();
String url = "jdbc:sqlite:" + filePath;
final JDBC4Connection connection = new JDBC4Connection(url, filePath, new Properties());
stmt = connection.createStatement(ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_READ_ONLY,
ResultSet.CLOSE_CURSORS_AT_COMMIT);
}
@BeforeEach
void setUp() throws Exception {
String filePath = TestUtils.createTempFile();
String url = "jdbc:sqlite:" + filePath;
final JDBC4Connection connection = new JDBC4Connection(url, filePath, new Properties());
stmt =
connection.createStatement(
ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_READ_ONLY,
ResultSet.CLOSE_CURSORS_AT_COMMIT);
}
@Test
void execute_ddl_should_return_false() throws Exception{
assertFalse(stmt.execute("CREATE TABLE users (id INT PRIMARY KEY, username TEXT);"));
}
@Test
void execute_ddl_should_return_false() throws Exception {
assertFalse(stmt.execute("CREATE TABLE users (id INT PRIMARY KEY, username TEXT);"));
}
@Test
void execute_insert_should_return_false() throws Exception {
stmt.execute("CREATE TABLE users (id INT PRIMARY KEY, username TEXT);");
assertFalse(stmt.execute("INSERT INTO users VALUES (1, 'limbo');"));
}
@Test
void execute_insert_should_return_false() throws Exception {
stmt.execute("CREATE TABLE users (id INT PRIMARY KEY, username TEXT);");
assertFalse(stmt.execute("INSERT INTO users VALUES (1, 'limbo');"));
}
@Test
@Disabled("UPDATE not supported yet")
void execute_update_should_return_false() throws Exception {
stmt.execute("CREATE TABLE users (id INT PRIMARY KEY, username TEXT);");
stmt.execute("INSERT INTO users VALUES (1, 'limbo');");
assertFalse(stmt.execute("UPDATE users SET username = 'seonwoo' WHERE id = 1;"));
}
@Test
@Disabled("UPDATE not supported yet")
void execute_update_should_return_false() throws Exception {
stmt.execute("CREATE TABLE users (id INT PRIMARY KEY, username TEXT);");
stmt.execute("INSERT INTO users VALUES (1, 'limbo');");
assertFalse(stmt.execute("UPDATE users SET username = 'seonwoo' WHERE id = 1;"));
}
@Test
void execute_select_should_return_true() throws Exception {
stmt.execute("CREATE TABLE users (id INT PRIMARY KEY, username TEXT);");
stmt.execute("INSERT INTO users VALUES (1, 'limbo');");
assertTrue(stmt.execute("SELECT * FROM users;"));
}
@Test
void execute_select_should_return_true() throws Exception {
stmt.execute("CREATE TABLE users (id INT PRIMARY KEY, username TEXT);");
stmt.execute("INSERT INTO users VALUES (1, 'limbo');");
assertTrue(stmt.execute("SELECT * FROM users;"));
}
}