diff --git a/bindings/java/src/main/java/org/github/tursodatabase/LimboConnection.java b/bindings/java/src/main/java/org/github/tursodatabase/LimboConnection.java index 98f0ad04b..de1a5228e 100644 --- a/bindings/java/src/main/java/org/github/tursodatabase/LimboConnection.java +++ b/bindings/java/src/main/java/org/github/tursodatabase/LimboConnection.java @@ -4,6 +4,7 @@ import org.github.tursodatabase.core.AbstractDB; import org.github.tursodatabase.core.LimboDB; import java.sql.Connection; +import java.sql.ResultSet; import java.sql.SQLException; import java.util.Properties; @@ -61,4 +62,43 @@ public abstract class LimboConnection implements Connection { database.open(0); return database; } + + 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(); + } + + // 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: + * + * + * + * @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"); + } } diff --git a/bindings/java/src/main/java/org/github/tursodatabase/core/Codes.java b/bindings/java/src/main/java/org/github/tursodatabase/core/Codes.java new file mode 100644 index 000000000..0f8a3c402 --- /dev/null +++ b/bindings/java/src/main/java/org/github/tursodatabase/core/Codes.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2007 David Crawshaw + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +package org.github.tursodatabase.core; + +public class Codes { + /** Successful result */ + public static final int SQLITE_OK = 0; + + /** 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; + + /** Access permission denied */ + public static final int SQLITE_PERM = 3; + + /** Callback routine requested an abort */ + public static final int SQLITE_ABORT = 4; + + /** 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 malloc() failed */ + public static final int SQLITE_NOMEM = 7; + + /** 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; + + /** 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; + + /** (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; + + /** Unable to open the database file */ + public static final int SQLITE_CANTOPEN = 14; + + /** Database lock protocol error */ + public static final int SQLITE_PROTOCOL = 15; + + /** (Internal Only) Database table is empty */ + public static final int SQLITE_EMPTY = 16; + + /** 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; + + /** Abort due to constraint violation */ + public static final int SQLITE_CONSTRAINT = 19; + + /** Data type mismatch */ + public static final int SQLITE_MISMATCH = 20; + + /** Library used incorrectly */ + public static final int SQLITE_MISUSE = 21; + + /** Uses OS features not supported on host */ + public static final int SQLITE_NOLFS = 22; + + /** 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 finished executing */ + public static final int SQLITE_DONE = 101; + + // 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; +} diff --git a/bindings/java/src/main/java/org/github/tursodatabase/core/CoreStatement.java b/bindings/java/src/main/java/org/github/tursodatabase/core/CoreStatement.java index f71827d07..98dd89ab3 100644 --- a/bindings/java/src/main/java/org/github/tursodatabase/core/CoreStatement.java +++ b/bindings/java/src/main/java/org/github/tursodatabase/core/CoreStatement.java @@ -1,5 +1,26 @@ package org.github.tursodatabase.core; -// TODO: add fields and methods -public class CoreStatement { +import org.github.tursodatabase.LimboConnection; + +import java.sql.SQLException; + +public abstract class CoreStatement { + + private final LimboConnection connection; + + protected CoreStatement(LimboConnection connection) { + this.connection = connection; + } + + protected void internalClose() throws SQLException { + // TODO + } + + protected void clearGeneratedKeys() throws SQLException { + // TODO + } + + protected void updateGeneratedKeys() throws SQLException { + // TODO + } } 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 index 9e67ae501..5883f7487 100644 --- a/bindings/java/src/main/java/org/github/tursodatabase/jdbc4/JDBC4Connection.java +++ b/bindings/java/src/main/java/org/github/tursodatabase/jdbc4/JDBC4Connection.java @@ -16,10 +16,25 @@ public class JDBC4Connection extends LimboConnection { } @Override - @SkipNullableCheck public Statement createStatement() throws SQLException { - // TODO - return null; + 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, int resultSetHoldability) throws SQLException { + checkOpen(); + checkCursor(resultSetType, resultSetConcurrency, resultSetHoldability); + + return new JDBC4Statement(this); } @Override @@ -127,13 +142,6 @@ public class JDBC4Connection extends LimboConnection { // TODO } - @Override - @SkipNullableCheck - public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException { - // TODO - return null; - } - @Override @SkipNullableCheck public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { @@ -193,13 +201,6 @@ public class JDBC4Connection extends LimboConnection { // TODO } - @Override - @SkipNullableCheck - public Statement createStatement(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 { diff --git a/bindings/java/src/main/java/org/github/tursodatabase/jdbc4/JDBC4Statement.java b/bindings/java/src/main/java/org/github/tursodatabase/jdbc4/JDBC4Statement.java new file mode 100644 index 000000000..f1fb14221 --- /dev/null +++ b/bindings/java/src/main/java/org/github/tursodatabase/jdbc4/JDBC4Statement.java @@ -0,0 +1,290 @@ +package org.github.tursodatabase.jdbc4; + +import org.github.tursodatabase.LimboConnection; +import org.github.tursodatabase.annotations.SkipNullableCheck; +import org.github.tursodatabase.core.CoreStatement; + +import java.sql.*; + +/** + * Implementation of the {@link Statement} interface for JDBC 4. + */ +public class JDBC4Statement extends CoreStatement implements Statement { + + private boolean closed; + private boolean closeOnCompletion; + + private final int resultSetType; + private final int resultSetConcurrency; + private final int resultSetHoldability; + + 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) { + super(connection); + this.resultSetType = resultSetType; + this.resultSetConcurrency = resultSetConcurrency; + this.resultSetHoldability = resultSetHoldability; + } + + @Override + @SkipNullableCheck + public ResultSet executeQuery(String sql) throws SQLException { + // TODO + return null; + } + + @Override + public int executeUpdate(String sql) throws SQLException { + // TODO + 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 { + // TODO + } + + @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 + } + + @Override + public boolean execute(String sql) throws SQLException { + // TODO + return false; + } + + @Override + @SkipNullableCheck + public ResultSet getResultSet() throws SQLException { + // TODO + return null; + } + + @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 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/test/java/org/github/tursodatabase/jdbc4/JDBC4ConnectionTest.java b/bindings/java/src/test/java/org/github/tursodatabase/jdbc4/JDBC4ConnectionTest.java new file mode 100644 index 000000000..bf2a20b88 --- /dev/null +++ b/bindings/java/src/test/java/org/github/tursodatabase/jdbc4/JDBC4ConnectionTest.java @@ -0,0 +1,58 @@ +package org.github.tursodatabase.jdbc4; + +import org.github.tursodatabase.TestUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Properties; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class JDBC4ConnectionTest { + + private JDBC4Connection connection; + + @BeforeEach + void setUp() throws Exception { + String fileUrl = TestUtils.createTempFile(); + String url = "jdbc:sqlite:" + fileUrl; + connection = new JDBC4Connection(url, fileUrl, 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_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_invalid() { + assertThrows(SQLException.class, () -> { + connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, -1); + }); + } +}