diff --git a/bindings/java/src/main/java/org/github/tursodatabase/JDBC.java b/bindings/java/src/main/java/org/github/tursodatabase/JDBC.java new file mode 100644 index 000000000..87200ff5c --- /dev/null +++ b/bindings/java/src/main/java/org/github/tursodatabase/JDBC.java @@ -0,0 +1,72 @@ +package org.github.tursodatabase; + +import org.github.tursodatabase.jdbc4.JDBC4Connection; + +import java.sql.*; +import java.util.Properties; +import java.util.logging.Logger; + +public class JDBC implements Driver { + private static final String VALID_URL_PREFIX = "jdbc:limbo:"; + + static { + try { + DriverManager.registerDriver(new JDBC()); + } catch (Exception e) { + // TODO: log + } + } + + 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); + } + + private static boolean isValidURL(String url) { + return url != null && url.toLowerCase().startsWith(VALID_URL_PREFIX); + } + + private static String extractAddress(String url) { + return url.substring(VALID_URL_PREFIX.length()); + } + + @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 DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException { + return LimboConfig.getDriverPropertyInfo(); + } + + @Override + public int getMajorVersion() { + // TODO + return 0; + } + + @Override + public int getMinorVersion() { + // TODO + return 0; + } + + @Override + public boolean jdbcCompliant() { + return false; + } + + @Override + public Logger getParentLogger() throws SQLFeatureNotSupportedException { + // TODO + return null; + } +} diff --git a/bindings/java/src/main/java/org/github/tursodatabase/LimboConfig.java b/bindings/java/src/main/java/org/github/tursodatabase/LimboConfig.java new file mode 100644 index 000000000..7f2a2cdf0 --- /dev/null +++ b/bindings/java/src/main/java/org/github/tursodatabase/LimboConfig.java @@ -0,0 +1,51 @@ +package org.github.tursodatabase; + +import java.sql.DriverPropertyInfo; +import java.util.Arrays; +import java.util.Properties; + +/** + * Limbo Configuration. + */ +public class LimboConfig { + private final Properties pragma; + + 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 String getPragmaName() { + return pragmaName; + } + } +} diff --git a/bindings/java/src/main/java/org/github/tursodatabase/LimboConnection.java b/bindings/java/src/main/java/org/github/tursodatabase/LimboConnection.java new file mode 100644 index 000000000..5bb5e973f --- /dev/null +++ b/bindings/java/src/main/java/org/github/tursodatabase/LimboConnection.java @@ -0,0 +1,64 @@ +package org.github.tursodatabase; + +import org.github.tursodatabase.core.AbstractDB; +import org.github.tursodatabase.core.LimboDB; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Properties; + +public abstract class LimboConnection implements Connection { + + private final AbstractDB database; + + public LimboConnection(AbstractDB database) { + this.database = database; + } + + public LimboConnection(String url, String fileName) throws SQLException { + this(url, fileName, new Properties()); + } + + /** + * Creates a connection to limbo database. + * + * @param url e.g. "jdbc:sqlite:fileName" + * @param fileName path to file + */ + public LimboConnection(String url, String fileName, Properties properties) throws SQLException { + AbstractDB db = null; + + try { + db = open(url, fileName, properties); + } catch (Throwable t) { + try { + if (db != null) { + db.close(); + } + } catch (Throwable t2) { + t.addSuppressed(t2); + } + + throw t; + } + + this.database = db; + } + + private static AbstractDB open(String url, String fileName, Properties properties) throws SQLException { + if (fileName.isBlank()) { + throw new IllegalArgumentException("fileName should not be empty"); + } + + final AbstractDB database; + try { + LimboDB.load(); + database = LimboDB.create(url, fileName); + } catch (Exception e) { + throw new SQLException("Error opening connection", e); + } + + database.open(0); + return database; + } +} diff --git a/bindings/java/src/main/java/org/github/tursodatabase/LimboDataSource.java b/bindings/java/src/main/java/org/github/tursodatabase/LimboDataSource.java new file mode 100644 index 000000000..12a53c303 --- /dev/null +++ b/bindings/java/src/main/java/org/github/tursodatabase/LimboDataSource.java @@ -0,0 +1,81 @@ +package org.github.tursodatabase; + +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; + +/** + * Provides {@link DataSource} API for configuring Limbo database connection. + */ +public class LimboDataSource implements DataSource { + + 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; + } + + @Override + public Connection getConnection() throws SQLException { + return getConnection(null, null); + } + + @Override + public Connection getConnection(String username, 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 PrintWriter getLogWriter() throws SQLException { + // TODO + return null; + } + + @Override + public void setLogWriter(PrintWriter out) throws SQLException { + // TODO + } + + @Override + public void setLoginTimeout(int seconds) throws SQLException { + // TODO + } + + @Override + public int getLoginTimeout() throws SQLException { + // TODO + return 0; + } + + @Override + public Logger getParentLogger() throws SQLFeatureNotSupportedException { + // TODO + return null; + } + + @Override + public T unwrap(Class iface) throws SQLException { + // TODO + return null; + } + + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + // TODO + return false; + } +} diff --git a/bindings/java/src/main/java/org/github/tursodatabase/core/LimboDB.java b/bindings/java/src/main/java/org/github/tursodatabase/core/LimboDB.java index f3001aead..accbad76b 100644 --- a/bindings/java/src/main/java/org/github/tursodatabase/core/LimboDB.java +++ b/bindings/java/src/main/java/org/github/tursodatabase/core/LimboDB.java @@ -30,6 +30,19 @@ public final class LimboDB extends AbstractDB { } } + /** + * Loads the SQLite interface backend. + */ + public static void load() { + if (isLoaded) return; + + try { + System.loadLibrary("_limbo_java"); + } finally { + isLoaded = true; + } + } + /** * @param url e.g. "jdbc:sqlite:fileName * @param fileName e.g. path to file @@ -43,19 +56,6 @@ public final class LimboDB extends AbstractDB { super(url, fileName); } - /** - * Loads the SQLite interface backend. - */ - public void load() { - if (isLoaded) return; - - try { - System.loadLibrary("_limbo_java"); - } finally { - isLoaded = true; - } - } - // WRAPPER FUNCTIONS //////////////////////////////////////////// // TODO: add support for JNI diff --git a/bindings/java/src/main/java/org/github/tursodatabase/jdbc4/JDBC4Connection.java b/bindings/java/src/main/java/org/github/tursodatabase/jdbc4/JDBC4Connection.java new file mode 100644 index 000000000..04c83b6b9 --- /dev/null +++ b/bindings/java/src/main/java/org/github/tursodatabase/jdbc4/JDBC4Connection.java @@ -0,0 +1,321 @@ +package org.github.tursodatabase.jdbc4; + +import org.github.tursodatabase.LimboConnection; + +import java.sql.*; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.Executor; + +public class JDBC4Connection extends LimboConnection { + + public JDBC4Connection(String url, String fileName, Properties properties) throws SQLException { + super(url, fileName, properties); + } + + @Override + public Statement createStatement() throws SQLException { + // TODO + return null; + } + + @Override + public PreparedStatement prepareStatement(String sql) throws SQLException { + // TODO + return null; + } + + @Override + public CallableStatement prepareCall(String sql) throws SQLException { + // TODO + return null; + } + + @Override + public String nativeSQL(String sql) throws SQLException { + // TODO + return ""; + } + + @Override + public void setAutoCommit(boolean autoCommit) throws SQLException { + // TODO + } + + @Override + public boolean getAutoCommit() throws SQLException { + // TODO + return false; + } + + @Override + public void commit() throws SQLException { + // TODO + } + + @Override + public void rollback() throws SQLException { + // TODO + } + + @Override + public void close() throws SQLException { + // TODO + } + + @Override + public boolean isClosed() throws SQLException { + // TODO + return false; + } + + @Override + public DatabaseMetaData getMetaData() throws SQLException { + // TODO + return null; + } + + @Override + public void setReadOnly(boolean readOnly) throws SQLException { + // TODO + } + + @Override + public boolean isReadOnly() throws SQLException { + // TODO + return false; + } + + @Override + public void setCatalog(String catalog) throws SQLException { + // TODO + } + + @Override + public String getCatalog() throws SQLException { + // TODO + return ""; + } + + @Override + public void setTransactionIsolation(int level) throws SQLException { + // TODO + } + + @Override + public int getTransactionIsolation() throws SQLException { + // TODO + return 0; + } + + @Override + public SQLWarning getWarnings() throws SQLException { + // TODO + return null; + } + + @Override + public void clearWarnings() throws SQLException { + // TODO + } + + @Override + public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException { + // TODO + return null; + } + + @Override + public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { + // TODO + return null; + } + + @Override + public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { + // TODO + return null; + } + + @Override + public Map> getTypeMap() throws SQLException { + // TODO + return Map.of(); + } + + @Override + public void setTypeMap(Map> map) throws SQLException { + // TODO + } + + @Override + public void setHoldability(int holdability) throws SQLException { + // TODO + } + + @Override + public int getHoldability() throws SQLException { + return 0; + } + + @Override + public Savepoint setSavepoint() throws SQLException { + // TODO + return null; + } + + @Override + public Savepoint setSavepoint(String name) throws SQLException { + // TODO + return null; + } + + @Override + public void rollback(Savepoint savepoint) throws SQLException { + // TODO + } + + @Override + public void releaseSavepoint(Savepoint savepoint) throws SQLException { + // TODO + } + + @Override + public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { + // TODO + return null; + } + + @Override + public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { + // TODO + return null; + } + + @Override + public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { + // TODO + return null; + } + + @Override + public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException { + // TODO + return null; + } + + @Override + public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException { + // TODO + return null; + } + + @Override + public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException { + // TODO + return null; + } + + @Override + public Clob createClob() throws SQLException { + // TODO + return null; + } + + @Override + public Blob createBlob() throws SQLException { + // TODO + return null; + } + + @Override + public NClob createNClob() throws SQLException { + // TODO + return null; + } + + @Override + public SQLXML createSQLXML() throws SQLException { + // TODO + return null; + } + + @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(Properties properties) throws SQLClientInfoException { + // TODO + } + + @Override + public String getClientInfo(String name) throws SQLException { + // TODO + return ""; + } + + @Override + public Properties getClientInfo() throws SQLException { + // TODO + return null; + } + + @Override + public Array createArrayOf(String typeName, Object[] elements) throws SQLException { + // TODO + return null; + } + + @Override + public Struct createStruct(String typeName, Object[] attributes) throws SQLException { + // TODO + return null; + } + + @Override + public void setSchema(String schema) throws SQLException { + // TODO + } + + @Override + public String getSchema() throws SQLException { + // TODO + return ""; + } + + @Override + public void abort(Executor executor) 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 T unwrap(Class iface) throws SQLException { + // TODO + return null; + } + + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + // TODO + return false; + } +} diff --git a/bindings/java/src/main/resources/META-INF/services/java.sql.Driver b/bindings/java/src/main/resources/META-INF/services/java.sql.Driver new file mode 100644 index 000000000..71922046d --- /dev/null +++ b/bindings/java/src/main/resources/META-INF/services/java.sql.Driver @@ -0,0 +1 @@ +org.github.tursodatabase.JDBC diff --git a/bindings/java/src/test/java/org/github/tursodatabase/JDBCTest.java b/bindings/java/src/test/java/org/github/tursodatabase/JDBCTest.java new file mode 100644 index 000000000..d0cdc4dc3 --- /dev/null +++ b/bindings/java/src/test/java/org/github/tursodatabase/JDBCTest.java @@ -0,0 +1,33 @@ +package org.github.tursodatabase; + +import org.junit.jupiter.api.Test; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.util.Properties; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +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 non_null_connection_is_returned_when_valid_url_is_passed() throws Exception { + String fileUrl = TestUtils.createTempFile(); + LimboConnection connection = JDBC.createConnection("jdbc:limbo:" + fileUrl, new Properties()); + assertThat(connection).isNotNull(); + } + + @Test + void connection_can_be_retrieved_from_DriverManager() throws SQLException { + try (Connection connection = DriverManager.getConnection("jdbc:limbo:sample.db")) { + assertThat(connection).isNotNull(); + } + } +} diff --git a/bindings/java/src/test/java/org/github/tursodatabase/core/LimboDBTest.java b/bindings/java/src/test/java/org/github/tursodatabase/core/LimboDBTest.java index feeeff060..66e842ea4 100644 --- a/bindings/java/src/test/java/org/github/tursodatabase/core/LimboDBTest.java +++ b/bindings/java/src/test/java/org/github/tursodatabase/core/LimboDBTest.java @@ -15,16 +15,16 @@ 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.load(); 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.load(); db.open(0); assertThatThrownBy(() -> db.open(0)).isInstanceOf(SQLException.class); @@ -33,8 +33,8 @@ public class LimboDBTest { @Test void throwJavaException_should_throw_appropriate_java_exception() throws Exception { String dbPath = TestUtils.createTempFile(); + LimboDB.load(); LimboDB db = LimboDB.create("jdbc:sqlite:" + dbPath, dbPath); - db.load(); final int limboExceptionCode = LimboErrorCode.ETC.code; try {