From df41994eccaa4c525506e6b3bb2ee7b19f9fd474 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=84=A0=EC=9A=B0?= Date: Sun, 24 Aug 2025 09:15:07 +0900 Subject: [PATCH 1/6] Implement execute batch --- .../java/tech/turso/jdbc4/JDBC4Statement.java | 79 ++++++- .../tech/turso/jdbc4/JDBC4StatementTest.java | 215 ++++++++++++++++++ 2 files changed, 290 insertions(+), 4 deletions(-) diff --git a/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4Statement.java b/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4Statement.java index b86b838f5..67225aac5 100644 --- a/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4Statement.java +++ b/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4Statement.java @@ -2,11 +2,16 @@ package tech.turso.jdbc4; import static java.util.Objects.requireNonNull; +import java.sql.BatchUpdateException; import java.sql.Connection; +import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.SQLTimeoutException; import java.sql.SQLWarning; import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.locks.ReentrantLock; import tech.turso.annotations.Nullable; import tech.turso.annotations.SkipNullableCheck; @@ -33,6 +38,12 @@ public class JDBC4Statement implements Statement { private ReentrantLock connectionLock = new ReentrantLock(); + /** + * List of SQL statements to be executed as a batch. Used for batch processing as per JDBC + * specification. + */ + private List batchCommands = new ArrayList<>(); + public JDBC4Statement(JDBC4Connection connection) { this( connection, @@ -232,18 +243,78 @@ public class JDBC4Statement implements Statement { @Override public void addBatch(String sql) throws SQLException { - // TODO + ensureOpen(); + if (sql == null) { + throw new SQLException("SQL command cannot be null"); + } + batchCommands.add(sql); } @Override public void clearBatch() throws SQLException { - // TODO + ensureOpen(); + batchCommands.clear(); } @Override public int[] executeBatch() throws SQLException { - // TODO - return new int[0]; + ensureOpen(); + + int[] updateCounts = new int[batchCommands.size()]; + List failedCommands = new ArrayList<>(); + int[] successfulCounts = new int[batchCommands.size()]; + + // Execute each command in the batch + for (int i = 0; i < batchCommands.size(); i++) { + String sql = batchCommands.get(i); + try { + // Check if the statement returns a ResultSet (SELECT statements) + // In batch processing, SELECT statements should throw an exception + if (execute(sql)) { + // This means the statement returned a ResultSet, which is not allowed in batch + failedCommands.add(sql); + updateCounts[i] = EXECUTE_FAILED; + // Create a BatchUpdateException for the failed command + BatchUpdateException bue = + new BatchUpdateException( + "Batch entry " + + i + + " (" + + sql + + ") was aborted. " + + "Batch commands cannot return result sets.", + "HY000", // General error SQL state + 0, + updateCounts); + // Clear the batch after failure + clearBatch(); + throw bue; + } else { + // For DML statements, get the update count + updateCounts[i] = getUpdateCount(); + } + } catch (SQLException e) { + // Handle SQL exceptions during batch execution + failedCommands.add(sql); + updateCounts[i] = EXECUTE_FAILED; + + // Create a BatchUpdateException with the partial results + BatchUpdateException bue = + new BatchUpdateException( + "Batch entry " + i + " (" + sql + ") failed: " + e.getMessage(), + e.getSQLState(), + e.getErrorCode(), + updateCounts, + e.getCause()); + // Clear the batch after failure + clearBatch(); + throw bue; + } + } + + // Clear the batch after successful execution + clearBatch(); + return updateCounts; } @Override diff --git a/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4StatementTest.java b/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4StatementTest.java index e8266c76a..53572d3be 100644 --- a/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4StatementTest.java +++ b/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4StatementTest.java @@ -7,6 +7,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.sql.BatchUpdateException; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; @@ -120,4 +121,218 @@ class JDBC4StatementTest { assertThat(stmt.executeUpdate("DELETE FROM s1")).isEqualTo(3); } + + /** Tests for batch processing functionality */ + @Test + void testAddBatch_single_statement() throws SQLException { + stmt.execute("CREATE TABLE batch_test (id INTEGER PRIMARY KEY, value TEXT);"); + + // Add a single batch command + stmt.addBatch("INSERT INTO batch_test VALUES (1, 'test1');"); + + // Execute batch + int[] updateCounts = stmt.executeBatch(); + + // Verify results + assertThat(updateCounts).hasSize(1); + assertThat(updateCounts[0]).isEqualTo(1); + + // Verify data was inserted + ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM batch_test;"); + assertTrue(rs.next()); + assertThat(rs.getInt(1)).isEqualTo(1); + } + + @Test + void testAddBatch_multiple_statements() throws SQLException { + stmt.execute("CREATE TABLE batch_test (id INTEGER PRIMARY KEY, value TEXT);"); + + // Add multiple batch commands + stmt.addBatch("INSERT INTO batch_test VALUES (1, 'test1');"); + stmt.addBatch("INSERT INTO batch_test VALUES (2, 'test2');"); + stmt.addBatch("INSERT INTO batch_test VALUES (3, 'test3');"); + + // Execute batch + int[] updateCounts = stmt.executeBatch(); + + // Verify results + assertThat(updateCounts).hasSize(3); + assertThat(updateCounts[0]).isEqualTo(1); + assertThat(updateCounts[1]).isEqualTo(1); + assertThat(updateCounts[2]).isEqualTo(1); + + // Verify all data was inserted + ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM batch_test;"); + assertTrue(rs.next()); + assertThat(rs.getInt(1)).isEqualTo(3); + } + + @Test + void testAddBatch_with_updates_and_deletes() throws SQLException { + stmt.execute("CREATE TABLE batch_test (id INTEGER PRIMARY KEY, value TEXT);"); + + // Insert initial data + stmt.execute( + "INSERT INTO batch_test VALUES (1, 'initial1'), (2, 'initial2'), (3, 'initial3');"); + + // Add batch commands with different operations + stmt.addBatch("UPDATE batch_test SET value = 'updated' WHERE id = 1;"); + stmt.addBatch("DELETE FROM batch_test WHERE id = 2;"); + stmt.addBatch("INSERT INTO batch_test VALUES (4, 'new');"); + + // Execute batch + int[] updateCounts = stmt.executeBatch(); + + // Verify update counts + assertThat(updateCounts).hasSize(3); + assertThat(updateCounts[0]).isEqualTo(1); // UPDATE affected 1 row + assertThat(updateCounts[1]).isEqualTo(1); // DELETE affected 1 row + assertThat(updateCounts[2]).isEqualTo(1); // INSERT affected 1 row + + // Verify final state + ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM batch_test;"); + assertTrue(rs.next()); + assertThat(rs.getInt(1)).isEqualTo(3); // 3 initial - 1 deleted + 1 inserted = 3 + } + + @Test + void testClearBatch() throws SQLException { + stmt.execute("CREATE TABLE batch_test (id INTEGER PRIMARY KEY, value TEXT);"); + + // Add batch commands + stmt.addBatch("INSERT INTO batch_test VALUES (1, 'test1');"); + stmt.addBatch("INSERT INTO batch_test VALUES (2, 'test2');"); + + // Clear the batch + stmt.clearBatch(); + + // Execute batch should return empty array + int[] updateCounts = stmt.executeBatch(); + assertThat(updateCounts).isEmpty(); + + // Verify no data was inserted + ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM batch_test;"); + assertTrue(rs.next()); + assertThat(rs.getInt(1)).isEqualTo(0); + } + + @Test + void testBatch_with_DDL_statements() throws SQLException { + // DDL statements should work in batch + stmt.addBatch("CREATE TABLE batch_test1 (id INTEGER);"); + stmt.addBatch("CREATE TABLE batch_test2 (id INTEGER);"); + stmt.addBatch("CREATE TABLE batch_test3 (id INTEGER);"); + + // Execute batch + int[] updateCounts = stmt.executeBatch(); + + // DDL statements typically return 0 for update count + assertThat(updateCounts).hasSize(3); + assertThat(updateCounts[0]).isEqualTo(0); + assertThat(updateCounts[1]).isEqualTo(0); + assertThat(updateCounts[2]).isEqualTo(0); + + // Verify tables were created by inserting data + assertDoesNotThrow(() -> stmt.execute("INSERT INTO batch_test1 VALUES (1);")); + assertDoesNotThrow(() -> stmt.execute("INSERT INTO batch_test2 VALUES (1);")); + assertDoesNotThrow(() -> stmt.execute("INSERT INTO batch_test3 VALUES (1);")); + } + + @Test + void testBatch_with_SELECT_should_throw_exception() throws SQLException { + stmt.execute("CREATE TABLE batch_test (id INTEGER PRIMARY KEY, value TEXT);"); + stmt.execute("INSERT INTO batch_test VALUES (1, 'test1');"); + + // Add a SELECT statement to batch (not allowed) + stmt.addBatch("INSERT INTO batch_test VALUES (2, 'test2');"); + stmt.addBatch("SELECT * FROM batch_test;"); // This should cause an exception + stmt.addBatch("INSERT INTO batch_test VALUES (3, 'test3');"); + + // Execute batch should throw BatchUpdateException + BatchUpdateException exception = + assertThrows(BatchUpdateException.class, () -> stmt.executeBatch()); + + // Verify exception message + assertTrue(exception.getMessage().contains("Batch commands cannot return result sets")); + + // Verify update counts for executed statements before the failure + int[] updateCounts = exception.getUpdateCounts(); + assertThat(updateCounts).hasSize(3); + assertThat(updateCounts[0]).isEqualTo(1); // First INSERT succeeded + assertThat(updateCounts[1]).isEqualTo(Statement.EXECUTE_FAILED); // SELECT failed + // The third statement may not have been executed depending on implementation + } + + @Test + void testBatch_with_null_command_should_throw_exception() throws SQLException { + // Adding null command should throw SQLException + assertThrows(SQLException.class, () -> stmt.addBatch(null)); + } + + @Test + void testBatch_operations_on_closed_statement_should_throw_exception() throws SQLException { + stmt.close(); + + // All batch operations should throw SQLException on closed statement + assertThrows(SQLException.class, () -> stmt.addBatch("INSERT INTO test VALUES (1);")); + assertThrows(SQLException.class, () -> stmt.clearBatch()); + assertThrows(SQLException.class, () -> stmt.executeBatch()); + } + + @Test + void testBatch_with_syntax_error_should_throw_exception() throws SQLException { + stmt.execute("CREATE TABLE batch_test (id INTEGER PRIMARY KEY, value TEXT);"); + + // Add batch commands with a syntax error + stmt.addBatch("INSERT INTO batch_test VALUES (1, 'test1');"); + stmt.addBatch("INVALID SQL SYNTAX;"); // This should cause an exception + stmt.addBatch("INSERT INTO batch_test VALUES (3, 'test3');"); + + // Execute batch should throw BatchUpdateException + BatchUpdateException exception = + assertThrows(BatchUpdateException.class, () -> stmt.executeBatch()); + + // Verify update counts show partial execution + int[] updateCounts = exception.getUpdateCounts(); + assertThat(updateCounts).hasSize(3); + assertThat(updateCounts[0]).isEqualTo(1); // First INSERT succeeded + assertThat(updateCounts[1]).isEqualTo(Statement.EXECUTE_FAILED); // Invalid SQL failed + } + + @Test + void testBatch_empty_batch_returns_empty_array() throws SQLException { + // Execute empty batch + int[] updateCounts = stmt.executeBatch(); + + // Should return empty array + assertThat(updateCounts).isEmpty(); + } + + @Test + void testBatch_clears_after_successful_execution() throws SQLException { + stmt.execute("CREATE TABLE batch_test (id INTEGER PRIMARY KEY, value TEXT);"); + + // Add and execute batch + stmt.addBatch("INSERT INTO batch_test VALUES (1, 'test1');"); + stmt.executeBatch(); + + // Execute batch again should return empty array (batch was cleared) + int[] updateCounts = stmt.executeBatch(); + assertThat(updateCounts).isEmpty(); + } + + @Test + void testBatch_clears_after_failed_execution() throws SQLException { + stmt.execute("CREATE TABLE batch_test (id INTEGER PRIMARY KEY, value TEXT);"); + + // Add batch with SELECT statement that will fail + stmt.addBatch("SELECT * FROM batch_test;"); + + // Execute batch should fail + assertThrows(BatchUpdateException.class, () -> stmt.executeBatch()); + + // Execute batch again should return empty array (batch was cleared after failure) + int[] updateCounts = stmt.executeBatch(); + assertThat(updateCounts).isEmpty(); + } } From 346525e5f0c8877dd5d42438b23a7936f18de013 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=84=A0=EC=9A=B0?= Date: Sun, 24 Aug 2025 09:25:59 +0900 Subject: [PATCH 2/6] Update test --- .../java/tech/turso/jdbc4/JDBC4Statement.java | 44 ++++++++++++++----- .../tech/turso/jdbc4/JDBC4StatementTest.java | 4 +- 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4Statement.java b/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4Statement.java index 67225aac5..2230b1f96 100644 --- a/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4Statement.java +++ b/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4Statement.java @@ -4,15 +4,14 @@ import static java.util.Objects.requireNonNull; import java.sql.BatchUpdateException; import java.sql.Connection; -import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.SQLException; -import java.sql.SQLTimeoutException; import java.sql.SQLWarning; import java.sql.Statement; import java.util.ArrayList; import java.util.List; import java.util.concurrent.locks.ReentrantLock; + import tech.turso.annotations.Nullable; import tech.turso.annotations.SkipNullableCheck; import tech.turso.core.TursoResultSet; @@ -268,13 +267,9 @@ public class JDBC4Statement implements Statement { for (int i = 0; i < batchCommands.size(); i++) { String sql = batchCommands.get(i); try { - // Check if the statement returns a ResultSet (SELECT statements) - // In batch processing, SELECT statements should throw an exception - if (execute(sql)) { - // This means the statement returned a ResultSet, which is not allowed in batch + if (!isBatchCompatibleStatement(sql)) { failedCommands.add(sql); updateCounts[i] = EXECUTE_FAILED; - // Create a BatchUpdateException for the failed command BatchUpdateException bue = new BatchUpdateException( "Batch entry " @@ -289,12 +284,12 @@ public class JDBC4Statement implements Statement { // Clear the batch after failure clearBatch(); throw bue; - } else { - // For DML statements, get the update count - updateCounts[i] = getUpdateCount(); } + + execute(sql); + // For DML statements, get the update count + updateCounts[i] = getUpdateCount(); } catch (SQLException e) { - // Handle SQL exceptions during batch execution failedCommands.add(sql); updateCounts[i] = EXECUTE_FAILED; @@ -317,6 +312,33 @@ public class JDBC4Statement implements Statement { return updateCounts; } + /** + * Checks if a SQL statement is compatible with batch execution. Only INSERT, UPDATE, DELETE, and + * DDL statements are allowed in batch. SELECT and other query statements are not allowed. + * + * @param sql The SQL statement to check + * @return true if the statement is batch-compatible, false otherwise + */ + private boolean isBatchCompatibleStatement(String sql) { + if (sql == null || sql.trim().isEmpty()) { + return false; + } + + // Trim and convert to uppercase for case-insensitive comparison + String trimmedSql = sql.trim().toUpperCase(); + + // Check if it starts with batch-compatible keywords + return trimmedSql.startsWith("INSERT") + || trimmedSql.startsWith("UPDATE") + || trimmedSql.startsWith("DELETE") + || trimmedSql.startsWith("CREATE") + || trimmedSql.startsWith("DROP") + || trimmedSql.startsWith("ALTER") + || trimmedSql.startsWith("TRUNCATE") + || trimmedSql.startsWith("REPLACE") + || trimmedSql.startsWith("MERGE"); + } + @Override public Connection getConnection() { return connection; diff --git a/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4StatementTest.java b/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4StatementTest.java index 53572d3be..4fe1d32ed 100644 --- a/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4StatementTest.java +++ b/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4StatementTest.java @@ -176,7 +176,7 @@ class JDBC4StatementTest { "INSERT INTO batch_test VALUES (1, 'initial1'), (2, 'initial2'), (3, 'initial3');"); // Add batch commands with different operations - stmt.addBatch("UPDATE batch_test SET value = 'updated' WHERE id = 1;"); + stmt.addBatch("UPDATE batch_test SET value = 'updated';"); stmt.addBatch("DELETE FROM batch_test WHERE id = 2;"); stmt.addBatch("INSERT INTO batch_test VALUES (4, 'new');"); @@ -185,7 +185,7 @@ class JDBC4StatementTest { // Verify update counts assertThat(updateCounts).hasSize(3); - assertThat(updateCounts[0]).isEqualTo(1); // UPDATE affected 1 row + assertThat(updateCounts[0]).isEqualTo(3); // UPDATE affected 3 row assertThat(updateCounts[1]).isEqualTo(1); // DELETE affected 1 row assertThat(updateCounts[2]).isEqualTo(1); // INSERT affected 1 row From bf1473dc08aa72dbe008cac1ac209edd9742075a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=84=A0=EC=9A=B0?= Date: Sun, 24 Aug 2025 09:35:29 +0900 Subject: [PATCH 3/6] Override JDBC4PreparedStatement to throw exception when calling addBatch method --- .../main/java/tech/turso/jdbc4/JDBC4PreparedStatement.java | 5 +++++ .../java/src/main/java/tech/turso/jdbc4/JDBC4Statement.java | 2 -- bindings/java/src/test/resources/turso/.rustc_info.json | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4PreparedStatement.java b/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4PreparedStatement.java index a3f8b3d4d..60b5316fe 100644 --- a/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4PreparedStatement.java +++ b/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4PreparedStatement.java @@ -176,6 +176,11 @@ public final class JDBC4PreparedStatement extends JDBC4Statement implements Prep // TODO } + @Override + public void addBatch(String sql) throws SQLException { + throw new SQLException("addBatch(String) cannot be called on a PreparedStatement"); + } + @Override public void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException {} diff --git a/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4Statement.java b/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4Statement.java index 2230b1f96..3e722f8cf 100644 --- a/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4Statement.java +++ b/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4Statement.java @@ -11,7 +11,6 @@ import java.sql.Statement; import java.util.ArrayList; import java.util.List; import java.util.concurrent.locks.ReentrantLock; - import tech.turso.annotations.Nullable; import tech.turso.annotations.SkipNullableCheck; import tech.turso.core.TursoResultSet; @@ -261,7 +260,6 @@ public class JDBC4Statement implements Statement { int[] updateCounts = new int[batchCommands.size()]; List failedCommands = new ArrayList<>(); - int[] successfulCounts = new int[batchCommands.size()]; // Execute each command in the batch for (int i = 0; i < batchCommands.size(); i++) { diff --git a/bindings/java/src/test/resources/turso/.rustc_info.json b/bindings/java/src/test/resources/turso/.rustc_info.json index b01291daa..68aeec704 100644 --- a/bindings/java/src/test/resources/turso/.rustc_info.json +++ b/bindings/java/src/test/resources/turso/.rustc_info.json @@ -1 +1 @@ -{"rustc_fingerprint":11551670960185020797,"outputs":{"14427667104029986310":{"success":true,"status":"","code":0,"stdout":"rustc 1.83.0 (90b35a623 2024-11-26)\nbinary: rustc\ncommit-hash: 90b35a6239c3d8bdabc530a6a0816f7ff89a0aaf\ncommit-date: 2024-11-26\nhost: x86_64-unknown-linux-gnu\nrelease: 1.83.0\nLLVM version: 19.1.1\n","stderr":""},"11399821309745579047":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/home/merlin/.rustup/toolchains/1.83.0-x86_64-unknown-linux-gnu\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"gnu\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"linux\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"unknown\"\nunix\n","stderr":""}},"successes":{}} \ No newline at end of file +{"rustc_fingerprint":4908805493570777128,"outputs":{"11769396151817893336":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.dylib\nlib___.dylib\nlib___.a\nlib___.dylib\n/Users/seonwoo960000/.rustup/toolchains/1.88.0-aarch64-apple-darwin\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"aarch64\"\ntarget_endian=\"little\"\ntarget_env=\"\"\ntarget_family=\"unix\"\ntarget_feature=\"aes\"\ntarget_feature=\"crc\"\ntarget_feature=\"dit\"\ntarget_feature=\"dotprod\"\ntarget_feature=\"dpb\"\ntarget_feature=\"dpb2\"\ntarget_feature=\"fcma\"\ntarget_feature=\"fhm\"\ntarget_feature=\"flagm\"\ntarget_feature=\"fp16\"\ntarget_feature=\"frintts\"\ntarget_feature=\"jsconv\"\ntarget_feature=\"lor\"\ntarget_feature=\"lse\"\ntarget_feature=\"neon\"\ntarget_feature=\"paca\"\ntarget_feature=\"pacg\"\ntarget_feature=\"pan\"\ntarget_feature=\"pmuv3\"\ntarget_feature=\"ras\"\ntarget_feature=\"rcpc\"\ntarget_feature=\"rcpc2\"\ntarget_feature=\"rdm\"\ntarget_feature=\"sb\"\ntarget_feature=\"sha2\"\ntarget_feature=\"sha3\"\ntarget_feature=\"ssbs\"\ntarget_feature=\"vh\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"macos\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"apple\"\ntokio_unstable\nunix\n","stderr":""},"10375286590057847751":{"success":true,"status":"","code":0,"stdout":"rustc 1.88.0 (6b00bc388 2025-06-23)\nbinary: rustc\ncommit-hash: 6b00bc3880198600130e1cf62b8f8a93494488cc\ncommit-date: 2025-06-23\nhost: aarch64-apple-darwin\nrelease: 1.88.0\nLLVM version: 20.1.5\n","stderr":""},"10459017571985955891":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.dylib\nlib___.dylib\nlib___.a\nlib___.dylib\n/Users/seonwoo960000/.rustup/toolchains/1.88.0-aarch64-apple-darwin\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"aarch64\"\ntarget_endian=\"little\"\ntarget_env=\"\"\ntarget_family=\"unix\"\ntarget_feature=\"aes\"\ntarget_feature=\"crc\"\ntarget_feature=\"dit\"\ntarget_feature=\"dotprod\"\ntarget_feature=\"dpb\"\ntarget_feature=\"dpb2\"\ntarget_feature=\"fcma\"\ntarget_feature=\"fhm\"\ntarget_feature=\"flagm\"\ntarget_feature=\"fp16\"\ntarget_feature=\"frintts\"\ntarget_feature=\"jsconv\"\ntarget_feature=\"lor\"\ntarget_feature=\"lse\"\ntarget_feature=\"neon\"\ntarget_feature=\"paca\"\ntarget_feature=\"pacg\"\ntarget_feature=\"pan\"\ntarget_feature=\"pmuv3\"\ntarget_feature=\"ras\"\ntarget_feature=\"rcpc\"\ntarget_feature=\"rcpc2\"\ntarget_feature=\"rdm\"\ntarget_feature=\"sb\"\ntarget_feature=\"sha2\"\ntarget_feature=\"sha3\"\ntarget_feature=\"ssbs\"\ntarget_feature=\"vh\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"macos\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"apple\"\ntokio_unstable\nunix\n","stderr":""}},"successes":{}} \ No newline at end of file From 9f6eb8bc923619ddf3f982e6cf61d82434a9d8f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=84=A0=EC=9A=B0?= Date: Sun, 24 Aug 2025 10:13:04 +0900 Subject: [PATCH 4/6] Update verification of batch compatible statements using regex --- .../java/tech/turso/jdbc4/JDBC4Statement.java | 41 +++-- .../tech/turso/jdbc4/JDBC4StatementTest.java | 159 +++++++++++++++--- 2 files changed, 157 insertions(+), 43 deletions(-) diff --git a/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4Statement.java b/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4Statement.java index 3e722f8cf..97bf65af9 100644 --- a/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4Statement.java +++ b/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4Statement.java @@ -11,6 +11,7 @@ import java.sql.Statement; import java.util.ArrayList; import java.util.List; import java.util.concurrent.locks.ReentrantLock; +import java.util.regex.Pattern; import tech.turso.annotations.Nullable; import tech.turso.annotations.SkipNullableCheck; import tech.turso.core.TursoResultSet; @@ -18,6 +19,20 @@ import tech.turso.core.TursoStatement; public class JDBC4Statement implements Statement { + private static final Pattern BATCH_COMPATIBLE_PATTERN = + Pattern.compile( + "^\\s*" + + // Leading whitespace + "(?:/\\*.*?\\*/\\s*)*" + + // Optional C-style comments + "(?:--[^\\n]*\\n\\s*)*" + + // Optional SQL line comments + "(?:" + + // Start of keywords group + "INSERT|UPDATE|DELETE" + + ")\\b", + Pattern.CASE_INSENSITIVE | Pattern.DOTALL); + private final JDBC4Connection connection; @Nullable protected TursoStatement statement = null; @@ -254,6 +269,7 @@ public class JDBC4Statement implements Statement { batchCommands.clear(); } + // TODO: let's make this batch operation atomic @Override public int[] executeBatch() throws SQLException { ensureOpen(); @@ -310,31 +326,14 @@ public class JDBC4Statement implements Statement { return updateCounts; } - /** - * Checks if a SQL statement is compatible with batch execution. Only INSERT, UPDATE, DELETE, and - * DDL statements are allowed in batch. SELECT and other query statements are not allowed. - * - * @param sql The SQL statement to check - * @return true if the statement is batch-compatible, false otherwise - */ - private boolean isBatchCompatibleStatement(String sql) { + boolean isBatchCompatibleStatement(String sql) { if (sql == null || sql.trim().isEmpty()) { return false; } - // Trim and convert to uppercase for case-insensitive comparison - String trimmedSql = sql.trim().toUpperCase(); - - // Check if it starts with batch-compatible keywords - return trimmedSql.startsWith("INSERT") - || trimmedSql.startsWith("UPDATE") - || trimmedSql.startsWith("DELETE") - || trimmedSql.startsWith("CREATE") - || trimmedSql.startsWith("DROP") - || trimmedSql.startsWith("ALTER") - || trimmedSql.startsWith("TRUNCATE") - || trimmedSql.startsWith("REPLACE") - || trimmedSql.startsWith("MERGE"); + // Check if the SQL matches batch-compatible patterns (DML and DDL) + // This will return false for SELECT, EXPLAIN, PRAGMA, SHOW, etc. + return BATCH_COMPATIBLE_PATTERN.matcher(sql).find(); } @Override diff --git a/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4StatementTest.java b/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4StatementTest.java index 4fe1d32ed..0e073549d 100644 --- a/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4StatementTest.java +++ b/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4StatementTest.java @@ -216,28 +216,6 @@ class JDBC4StatementTest { assertThat(rs.getInt(1)).isEqualTo(0); } - @Test - void testBatch_with_DDL_statements() throws SQLException { - // DDL statements should work in batch - stmt.addBatch("CREATE TABLE batch_test1 (id INTEGER);"); - stmt.addBatch("CREATE TABLE batch_test2 (id INTEGER);"); - stmt.addBatch("CREATE TABLE batch_test3 (id INTEGER);"); - - // Execute batch - int[] updateCounts = stmt.executeBatch(); - - // DDL statements typically return 0 for update count - assertThat(updateCounts).hasSize(3); - assertThat(updateCounts[0]).isEqualTo(0); - assertThat(updateCounts[1]).isEqualTo(0); - assertThat(updateCounts[2]).isEqualTo(0); - - // Verify tables were created by inserting data - assertDoesNotThrow(() -> stmt.execute("INSERT INTO batch_test1 VALUES (1);")); - assertDoesNotThrow(() -> stmt.execute("INSERT INTO batch_test2 VALUES (1);")); - assertDoesNotThrow(() -> stmt.execute("INSERT INTO batch_test3 VALUES (1);")); - } - @Test void testBatch_with_SELECT_should_throw_exception() throws SQLException { stmt.execute("CREATE TABLE batch_test (id INTEGER PRIMARY KEY, value TEXT);"); @@ -335,4 +313,141 @@ class JDBC4StatementTest { int[] updateCounts = stmt.executeBatch(); assertThat(updateCounts).isEmpty(); } + + /** Tests for isBatchCompatibleStatement method */ + @Test + void testIsBatchCompatibleStatement_compatible_statements() { + JDBC4Statement jdbc4Stmt = (JDBC4Statement) stmt; + + // Basic INSERT statements + assertTrue(jdbc4Stmt.isBatchCompatibleStatement("INSERT INTO table VALUES (1, 2);")); + assertTrue(jdbc4Stmt.isBatchCompatibleStatement("insert into table values (1, 2);")); + assertTrue( + jdbc4Stmt.isBatchCompatibleStatement("INSERT INTO table (col1, col2) VALUES (1, 2);")); + assertTrue(jdbc4Stmt.isBatchCompatibleStatement("INSERT OR REPLACE INTO table VALUES (1);")); + assertTrue(jdbc4Stmt.isBatchCompatibleStatement("INSERT OR IGNORE INTO table VALUES (1);")); + + // INSERT with whitespace + assertTrue(jdbc4Stmt.isBatchCompatibleStatement(" INSERT INTO table VALUES (1);")); + assertTrue(jdbc4Stmt.isBatchCompatibleStatement("\t\nINSERT INTO table VALUES (1);")); + assertTrue(jdbc4Stmt.isBatchCompatibleStatement(" \n\t INSERT INTO table VALUES (1);")); + + // INSERT with comments + assertTrue(jdbc4Stmt.isBatchCompatibleStatement("/* comment */ INSERT INTO table VALUES (1);")); + assertTrue( + jdbc4Stmt.isBatchCompatibleStatement( + "/* multi\nline\ncomment */ INSERT INTO table VALUES (1);")); + assertTrue( + jdbc4Stmt.isBatchCompatibleStatement("-- line comment\nINSERT INTO table VALUES (1);")); + assertTrue( + jdbc4Stmt.isBatchCompatibleStatement( + "-- comment 1\n-- comment 2\nINSERT INTO table VALUES (1);")); + + // Complex cases with multiple comments + assertTrue( + jdbc4Stmt.isBatchCompatibleStatement( + " /* comment */ -- another\n INSERT INTO table VALUES (1);")); + + // Basic UPDATE statements + assertTrue(jdbc4Stmt.isBatchCompatibleStatement("UPDATE table SET col = 1;")); + assertTrue(jdbc4Stmt.isBatchCompatibleStatement("update table set col = 1;")); + assertTrue( + jdbc4Stmt.isBatchCompatibleStatement("UPDATE table SET col1 = 1, col2 = 2 WHERE id = 3;")); + assertTrue(jdbc4Stmt.isBatchCompatibleStatement("UPDATE OR REPLACE table SET col = 1;")); + + // UPDATE with whitespace + assertTrue(jdbc4Stmt.isBatchCompatibleStatement(" UPDATE table SET col = 1;")); + assertTrue(jdbc4Stmt.isBatchCompatibleStatement("\t\nUPDATE table SET col = 1;")); + + // UPDATE with comments + assertTrue(jdbc4Stmt.isBatchCompatibleStatement("/* comment */ UPDATE table SET col = 1;")); + assertTrue(jdbc4Stmt.isBatchCompatibleStatement("-- comment\nUPDATE table SET col = 1;")); + + // Basic DELETE statements + assertTrue(jdbc4Stmt.isBatchCompatibleStatement("DELETE FROM table;")); + assertTrue(jdbc4Stmt.isBatchCompatibleStatement("delete from table;")); + assertTrue(jdbc4Stmt.isBatchCompatibleStatement("DELETE FROM table WHERE id = 1;")); + + // DELETE with whitespace + assertTrue(jdbc4Stmt.isBatchCompatibleStatement(" DELETE FROM table;")); + assertTrue(jdbc4Stmt.isBatchCompatibleStatement("\t\nDELETE FROM table;")); + + // DELETE with comments + assertTrue(jdbc4Stmt.isBatchCompatibleStatement("/* comment */ DELETE FROM table;")); + assertTrue(jdbc4Stmt.isBatchCompatibleStatement("-- comment\nDELETE FROM table;")); + } + + @Test + void testIsBatchCompatibleStatement_non_compatible_statements() { + JDBC4Statement jdbc4Stmt = (JDBC4Statement) stmt; + + // SELECT statements should not be compatible + assertFalse(jdbc4Stmt.isBatchCompatibleStatement("SELECT * FROM table;")); + assertFalse(jdbc4Stmt.isBatchCompatibleStatement("select * from table;")); + assertFalse(jdbc4Stmt.isBatchCompatibleStatement(" SELECT * FROM table;")); + assertFalse(jdbc4Stmt.isBatchCompatibleStatement("/* comment */ SELECT * FROM table;")); + assertFalse(jdbc4Stmt.isBatchCompatibleStatement("-- comment\nSELECT * FROM table;")); + + // EXPLAIN statements + assertFalse(jdbc4Stmt.isBatchCompatibleStatement("EXPLAIN SELECT * FROM table;")); + assertFalse(jdbc4Stmt.isBatchCompatibleStatement("EXPLAIN QUERY PLAN SELECT * FROM table;")); + + // PRAGMA statements + assertFalse(jdbc4Stmt.isBatchCompatibleStatement("PRAGMA table_info(table);")); + assertFalse(jdbc4Stmt.isBatchCompatibleStatement("PRAGMA foreign_keys = ON;")); + + // ANALYZE statements + assertFalse(jdbc4Stmt.isBatchCompatibleStatement("ANALYZE;")); + assertFalse(jdbc4Stmt.isBatchCompatibleStatement("ANALYZE table;")); + + // WITH statements (CTEs) + assertFalse( + jdbc4Stmt.isBatchCompatibleStatement( + "WITH cte AS (SELECT * FROM table) SELECT * FROM cte;")); + + // VACUUM + assertFalse(jdbc4Stmt.isBatchCompatibleStatement("VACUUM;")); + + // VALUES + assertFalse(jdbc4Stmt.isBatchCompatibleStatement("VALUES (1, 2), (3, 4);")); + } + + @Test + void testIsBatchCompatibleStatement_edge_cases() { + JDBC4Statement jdbc4Stmt = (JDBC4Statement) stmt; + + // Null and empty cases + assertFalse(jdbc4Stmt.isBatchCompatibleStatement(null)); + assertFalse(jdbc4Stmt.isBatchCompatibleStatement("")); + assertFalse(jdbc4Stmt.isBatchCompatibleStatement(" ")); + assertFalse(jdbc4Stmt.isBatchCompatibleStatement("\t\n")); + + // Comments only + assertFalse(jdbc4Stmt.isBatchCompatibleStatement("/* comment only */")); + assertFalse(jdbc4Stmt.isBatchCompatibleStatement("-- comment only")); + assertFalse(jdbc4Stmt.isBatchCompatibleStatement("/* comment */ -- another comment")); + + // Keywords in wrong context (should not match if not at statement start) + assertFalse(jdbc4Stmt.isBatchCompatibleStatement("SELECT * FROM table WHERE name = 'INSERT';")); + assertFalse( + jdbc4Stmt.isBatchCompatibleStatement("SELECT * FROM table WHERE action = 'DELETE';")); + + // Partial keywords (should not match) + assertFalse(jdbc4Stmt.isBatchCompatibleStatement("INSER INTO table VALUES (1);")); + assertFalse(jdbc4Stmt.isBatchCompatibleStatement("UPDAT table SET col = 1;")); + assertFalse(jdbc4Stmt.isBatchCompatibleStatement("DELET FROM table;")); + } + + @Test + void testIsBatchCompatibleStatement_case_insensitive() { + JDBC4Statement jdbc4Stmt = (JDBC4Statement) stmt; + + // Mixed case should work + assertTrue(jdbc4Stmt.isBatchCompatibleStatement("Insert INTO table VALUES (1);")); + assertTrue(jdbc4Stmt.isBatchCompatibleStatement("InSeRt INTO table VALUES (1);")); + assertTrue(jdbc4Stmt.isBatchCompatibleStatement("UPDATE table SET col = 1;")); + assertTrue(jdbc4Stmt.isBatchCompatibleStatement("UpDaTe table SET col = 1;")); + assertTrue(jdbc4Stmt.isBatchCompatibleStatement("Delete FROM table;")); + assertTrue(jdbc4Stmt.isBatchCompatibleStatement("DeLeTe FROM table;")); + } } From fa8896d9ee75d152f4f905e6472dfe642251531d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=84=A0=EC=9A=B0?= Date: Sun, 24 Aug 2025 10:20:39 +0900 Subject: [PATCH 5/6] Nit --- .../tech/turso/jdbc4/JDBC4StatementTest.java | 58 +------------------ 1 file changed, 1 insertion(+), 57 deletions(-) diff --git a/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4StatementTest.java b/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4StatementTest.java index 0e073549d..f12fa07a7 100644 --- a/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4StatementTest.java +++ b/bindings/java/src/test/java/tech/turso/jdbc4/JDBC4StatementTest.java @@ -127,17 +127,13 @@ class JDBC4StatementTest { void testAddBatch_single_statement() throws SQLException { stmt.execute("CREATE TABLE batch_test (id INTEGER PRIMARY KEY, value TEXT);"); - // Add a single batch command stmt.addBatch("INSERT INTO batch_test VALUES (1, 'test1');"); - // Execute batch int[] updateCounts = stmt.executeBatch(); - // Verify results assertThat(updateCounts).hasSize(1); assertThat(updateCounts[0]).isEqualTo(1); - // Verify data was inserted ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM batch_test;"); assertTrue(rs.next()); assertThat(rs.getInt(1)).isEqualTo(1); @@ -147,21 +143,17 @@ class JDBC4StatementTest { void testAddBatch_multiple_statements() throws SQLException { stmt.execute("CREATE TABLE batch_test (id INTEGER PRIMARY KEY, value TEXT);"); - // Add multiple batch commands stmt.addBatch("INSERT INTO batch_test VALUES (1, 'test1');"); stmt.addBatch("INSERT INTO batch_test VALUES (2, 'test2');"); stmt.addBatch("INSERT INTO batch_test VALUES (3, 'test3');"); - // Execute batch int[] updateCounts = stmt.executeBatch(); - // Verify results assertThat(updateCounts).hasSize(3); assertThat(updateCounts[0]).isEqualTo(1); assertThat(updateCounts[1]).isEqualTo(1); assertThat(updateCounts[2]).isEqualTo(1); - // Verify all data was inserted ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM batch_test;"); assertTrue(rs.next()); assertThat(rs.getInt(1)).isEqualTo(3); @@ -171,19 +163,15 @@ class JDBC4StatementTest { void testAddBatch_with_updates_and_deletes() throws SQLException { stmt.execute("CREATE TABLE batch_test (id INTEGER PRIMARY KEY, value TEXT);"); - // Insert initial data stmt.execute( "INSERT INTO batch_test VALUES (1, 'initial1'), (2, 'initial2'), (3, 'initial3');"); - // Add batch commands with different operations stmt.addBatch("UPDATE batch_test SET value = 'updated';"); stmt.addBatch("DELETE FROM batch_test WHERE id = 2;"); stmt.addBatch("INSERT INTO batch_test VALUES (4, 'new');"); - // Execute batch int[] updateCounts = stmt.executeBatch(); - // Verify update counts assertThat(updateCounts).hasSize(3); assertThat(updateCounts[0]).isEqualTo(3); // UPDATE affected 3 row assertThat(updateCounts[1]).isEqualTo(1); // DELETE affected 1 row @@ -199,18 +187,14 @@ class JDBC4StatementTest { void testClearBatch() throws SQLException { stmt.execute("CREATE TABLE batch_test (id INTEGER PRIMARY KEY, value TEXT);"); - // Add batch commands stmt.addBatch("INSERT INTO batch_test VALUES (1, 'test1');"); stmt.addBatch("INSERT INTO batch_test VALUES (2, 'test2');"); - // Clear the batch stmt.clearBatch(); - // Execute batch should return empty array int[] updateCounts = stmt.executeBatch(); assertThat(updateCounts).isEmpty(); - // Verify no data was inserted ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM batch_test;"); assertTrue(rs.next()); assertThat(rs.getInt(1)).isEqualTo(0); @@ -221,29 +205,23 @@ class JDBC4StatementTest { stmt.execute("CREATE TABLE batch_test (id INTEGER PRIMARY KEY, value TEXT);"); stmt.execute("INSERT INTO batch_test VALUES (1, 'test1');"); - // Add a SELECT statement to batch (not allowed) stmt.addBatch("INSERT INTO batch_test VALUES (2, 'test2');"); stmt.addBatch("SELECT * FROM batch_test;"); // This should cause an exception stmt.addBatch("INSERT INTO batch_test VALUES (3, 'test3');"); - // Execute batch should throw BatchUpdateException BatchUpdateException exception = assertThrows(BatchUpdateException.class, () -> stmt.executeBatch()); - // Verify exception message assertTrue(exception.getMessage().contains("Batch commands cannot return result sets")); - // Verify update counts for executed statements before the failure int[] updateCounts = exception.getUpdateCounts(); assertThat(updateCounts).hasSize(3); assertThat(updateCounts[0]).isEqualTo(1); // First INSERT succeeded assertThat(updateCounts[1]).isEqualTo(Statement.EXECUTE_FAILED); // SELECT failed - // The third statement may not have been executed depending on implementation } @Test - void testBatch_with_null_command_should_throw_exception() throws SQLException { - // Adding null command should throw SQLException + void testBatch_with_null_command_should_throw_exception() { assertThrows(SQLException.class, () -> stmt.addBatch(null)); } @@ -251,7 +229,6 @@ class JDBC4StatementTest { void testBatch_operations_on_closed_statement_should_throw_exception() throws SQLException { stmt.close(); - // All batch operations should throw SQLException on closed statement assertThrows(SQLException.class, () -> stmt.addBatch("INSERT INTO test VALUES (1);")); assertThrows(SQLException.class, () -> stmt.clearBatch()); assertThrows(SQLException.class, () -> stmt.executeBatch()); @@ -261,16 +238,13 @@ class JDBC4StatementTest { void testBatch_with_syntax_error_should_throw_exception() throws SQLException { stmt.execute("CREATE TABLE batch_test (id INTEGER PRIMARY KEY, value TEXT);"); - // Add batch commands with a syntax error stmt.addBatch("INSERT INTO batch_test VALUES (1, 'test1');"); stmt.addBatch("INVALID SQL SYNTAX;"); // This should cause an exception stmt.addBatch("INSERT INTO batch_test VALUES (3, 'test3');"); - // Execute batch should throw BatchUpdateException BatchUpdateException exception = assertThrows(BatchUpdateException.class, () -> stmt.executeBatch()); - // Verify update counts show partial execution int[] updateCounts = exception.getUpdateCounts(); assertThat(updateCounts).hasSize(3); assertThat(updateCounts[0]).isEqualTo(1); // First INSERT succeeded @@ -279,10 +253,7 @@ class JDBC4StatementTest { @Test void testBatch_empty_batch_returns_empty_array() throws SQLException { - // Execute empty batch int[] updateCounts = stmt.executeBatch(); - - // Should return empty array assertThat(updateCounts).isEmpty(); } @@ -290,11 +261,9 @@ class JDBC4StatementTest { void testBatch_clears_after_successful_execution() throws SQLException { stmt.execute("CREATE TABLE batch_test (id INTEGER PRIMARY KEY, value TEXT);"); - // Add and execute batch stmt.addBatch("INSERT INTO batch_test VALUES (1, 'test1');"); stmt.executeBatch(); - // Execute batch again should return empty array (batch was cleared) int[] updateCounts = stmt.executeBatch(); assertThat(updateCounts).isEmpty(); } @@ -303,13 +272,10 @@ class JDBC4StatementTest { void testBatch_clears_after_failed_execution() throws SQLException { stmt.execute("CREATE TABLE batch_test (id INTEGER PRIMARY KEY, value TEXT);"); - // Add batch with SELECT statement that will fail stmt.addBatch("SELECT * FROM batch_test;"); - // Execute batch should fail assertThrows(BatchUpdateException.class, () -> stmt.executeBatch()); - // Execute batch again should return empty array (batch was cleared after failure) int[] updateCounts = stmt.executeBatch(); assertThat(updateCounts).isEmpty(); } @@ -319,7 +285,6 @@ class JDBC4StatementTest { void testIsBatchCompatibleStatement_compatible_statements() { JDBC4Statement jdbc4Stmt = (JDBC4Statement) stmt; - // Basic INSERT statements assertTrue(jdbc4Stmt.isBatchCompatibleStatement("INSERT INTO table VALUES (1, 2);")); assertTrue(jdbc4Stmt.isBatchCompatibleStatement("insert into table values (1, 2);")); assertTrue( @@ -327,12 +292,10 @@ class JDBC4StatementTest { assertTrue(jdbc4Stmt.isBatchCompatibleStatement("INSERT OR REPLACE INTO table VALUES (1);")); assertTrue(jdbc4Stmt.isBatchCompatibleStatement("INSERT OR IGNORE INTO table VALUES (1);")); - // INSERT with whitespace assertTrue(jdbc4Stmt.isBatchCompatibleStatement(" INSERT INTO table VALUES (1);")); assertTrue(jdbc4Stmt.isBatchCompatibleStatement("\t\nINSERT INTO table VALUES (1);")); assertTrue(jdbc4Stmt.isBatchCompatibleStatement(" \n\t INSERT INTO table VALUES (1);")); - // INSERT with comments assertTrue(jdbc4Stmt.isBatchCompatibleStatement("/* comment */ INSERT INTO table VALUES (1);")); assertTrue( jdbc4Stmt.isBatchCompatibleStatement( @@ -343,36 +306,29 @@ class JDBC4StatementTest { jdbc4Stmt.isBatchCompatibleStatement( "-- comment 1\n-- comment 2\nINSERT INTO table VALUES (1);")); - // Complex cases with multiple comments assertTrue( jdbc4Stmt.isBatchCompatibleStatement( " /* comment */ -- another\n INSERT INTO table VALUES (1);")); - // Basic UPDATE statements assertTrue(jdbc4Stmt.isBatchCompatibleStatement("UPDATE table SET col = 1;")); assertTrue(jdbc4Stmt.isBatchCompatibleStatement("update table set col = 1;")); assertTrue( jdbc4Stmt.isBatchCompatibleStatement("UPDATE table SET col1 = 1, col2 = 2 WHERE id = 3;")); assertTrue(jdbc4Stmt.isBatchCompatibleStatement("UPDATE OR REPLACE table SET col = 1;")); - // UPDATE with whitespace assertTrue(jdbc4Stmt.isBatchCompatibleStatement(" UPDATE table SET col = 1;")); assertTrue(jdbc4Stmt.isBatchCompatibleStatement("\t\nUPDATE table SET col = 1;")); - // UPDATE with comments assertTrue(jdbc4Stmt.isBatchCompatibleStatement("/* comment */ UPDATE table SET col = 1;")); assertTrue(jdbc4Stmt.isBatchCompatibleStatement("-- comment\nUPDATE table SET col = 1;")); - // Basic DELETE statements assertTrue(jdbc4Stmt.isBatchCompatibleStatement("DELETE FROM table;")); assertTrue(jdbc4Stmt.isBatchCompatibleStatement("delete from table;")); assertTrue(jdbc4Stmt.isBatchCompatibleStatement("DELETE FROM table WHERE id = 1;")); - // DELETE with whitespace assertTrue(jdbc4Stmt.isBatchCompatibleStatement(" DELETE FROM table;")); assertTrue(jdbc4Stmt.isBatchCompatibleStatement("\t\nDELETE FROM table;")); - // DELETE with comments assertTrue(jdbc4Stmt.isBatchCompatibleStatement("/* comment */ DELETE FROM table;")); assertTrue(jdbc4Stmt.isBatchCompatibleStatement("-- comment\nDELETE FROM table;")); } @@ -381,34 +337,27 @@ class JDBC4StatementTest { void testIsBatchCompatibleStatement_non_compatible_statements() { JDBC4Statement jdbc4Stmt = (JDBC4Statement) stmt; - // SELECT statements should not be compatible assertFalse(jdbc4Stmt.isBatchCompatibleStatement("SELECT * FROM table;")); assertFalse(jdbc4Stmt.isBatchCompatibleStatement("select * from table;")); assertFalse(jdbc4Stmt.isBatchCompatibleStatement(" SELECT * FROM table;")); assertFalse(jdbc4Stmt.isBatchCompatibleStatement("/* comment */ SELECT * FROM table;")); assertFalse(jdbc4Stmt.isBatchCompatibleStatement("-- comment\nSELECT * FROM table;")); - // EXPLAIN statements assertFalse(jdbc4Stmt.isBatchCompatibleStatement("EXPLAIN SELECT * FROM table;")); assertFalse(jdbc4Stmt.isBatchCompatibleStatement("EXPLAIN QUERY PLAN SELECT * FROM table;")); - // PRAGMA statements assertFalse(jdbc4Stmt.isBatchCompatibleStatement("PRAGMA table_info(table);")); assertFalse(jdbc4Stmt.isBatchCompatibleStatement("PRAGMA foreign_keys = ON;")); - // ANALYZE statements assertFalse(jdbc4Stmt.isBatchCompatibleStatement("ANALYZE;")); assertFalse(jdbc4Stmt.isBatchCompatibleStatement("ANALYZE table;")); - // WITH statements (CTEs) assertFalse( jdbc4Stmt.isBatchCompatibleStatement( "WITH cte AS (SELECT * FROM table) SELECT * FROM cte;")); - // VACUUM assertFalse(jdbc4Stmt.isBatchCompatibleStatement("VACUUM;")); - // VALUES assertFalse(jdbc4Stmt.isBatchCompatibleStatement("VALUES (1, 2), (3, 4);")); } @@ -416,23 +365,19 @@ class JDBC4StatementTest { void testIsBatchCompatibleStatement_edge_cases() { JDBC4Statement jdbc4Stmt = (JDBC4Statement) stmt; - // Null and empty cases assertFalse(jdbc4Stmt.isBatchCompatibleStatement(null)); assertFalse(jdbc4Stmt.isBatchCompatibleStatement("")); assertFalse(jdbc4Stmt.isBatchCompatibleStatement(" ")); assertFalse(jdbc4Stmt.isBatchCompatibleStatement("\t\n")); - // Comments only assertFalse(jdbc4Stmt.isBatchCompatibleStatement("/* comment only */")); assertFalse(jdbc4Stmt.isBatchCompatibleStatement("-- comment only")); assertFalse(jdbc4Stmt.isBatchCompatibleStatement("/* comment */ -- another comment")); - // Keywords in wrong context (should not match if not at statement start) assertFalse(jdbc4Stmt.isBatchCompatibleStatement("SELECT * FROM table WHERE name = 'INSERT';")); assertFalse( jdbc4Stmt.isBatchCompatibleStatement("SELECT * FROM table WHERE action = 'DELETE';")); - // Partial keywords (should not match) assertFalse(jdbc4Stmt.isBatchCompatibleStatement("INSER INTO table VALUES (1);")); assertFalse(jdbc4Stmt.isBatchCompatibleStatement("UPDAT table SET col = 1;")); assertFalse(jdbc4Stmt.isBatchCompatibleStatement("DELET FROM table;")); @@ -442,7 +387,6 @@ class JDBC4StatementTest { void testIsBatchCompatibleStatement_case_insensitive() { JDBC4Statement jdbc4Stmt = (JDBC4Statement) stmt; - // Mixed case should work assertTrue(jdbc4Stmt.isBatchCompatibleStatement("Insert INTO table VALUES (1);")); assertTrue(jdbc4Stmt.isBatchCompatibleStatement("InSeRt INTO table VALUES (1);")); assertTrue(jdbc4Stmt.isBatchCompatibleStatement("UPDATE table SET col = 1;")); From 7057c97cfe90ed7f68f95155444d3bfb6aee19fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=84=A0=EC=9A=B0?= Date: Sun, 24 Aug 2025 10:25:14 +0900 Subject: [PATCH 6/6] Remove .rustc_info.json --- .../java/src/main/java/tech/turso/jdbc4/JDBC4Statement.java | 2 -- bindings/java/src/test/resources/turso/.rustc_info.json | 1 - 2 files changed, 3 deletions(-) delete mode 100644 bindings/java/src/test/resources/turso/.rustc_info.json diff --git a/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4Statement.java b/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4Statement.java index 97bf65af9..eb31c8d0b 100644 --- a/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4Statement.java +++ b/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4Statement.java @@ -331,8 +331,6 @@ public class JDBC4Statement implements Statement { return false; } - // Check if the SQL matches batch-compatible patterns (DML and DDL) - // This will return false for SELECT, EXPLAIN, PRAGMA, SHOW, etc. return BATCH_COMPATIBLE_PATTERN.matcher(sql).find(); } diff --git a/bindings/java/src/test/resources/turso/.rustc_info.json b/bindings/java/src/test/resources/turso/.rustc_info.json deleted file mode 100644 index 68aeec704..000000000 --- a/bindings/java/src/test/resources/turso/.rustc_info.json +++ /dev/null @@ -1 +0,0 @@ -{"rustc_fingerprint":4908805493570777128,"outputs":{"11769396151817893336":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.dylib\nlib___.dylib\nlib___.a\nlib___.dylib\n/Users/seonwoo960000/.rustup/toolchains/1.88.0-aarch64-apple-darwin\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"aarch64\"\ntarget_endian=\"little\"\ntarget_env=\"\"\ntarget_family=\"unix\"\ntarget_feature=\"aes\"\ntarget_feature=\"crc\"\ntarget_feature=\"dit\"\ntarget_feature=\"dotprod\"\ntarget_feature=\"dpb\"\ntarget_feature=\"dpb2\"\ntarget_feature=\"fcma\"\ntarget_feature=\"fhm\"\ntarget_feature=\"flagm\"\ntarget_feature=\"fp16\"\ntarget_feature=\"frintts\"\ntarget_feature=\"jsconv\"\ntarget_feature=\"lor\"\ntarget_feature=\"lse\"\ntarget_feature=\"neon\"\ntarget_feature=\"paca\"\ntarget_feature=\"pacg\"\ntarget_feature=\"pan\"\ntarget_feature=\"pmuv3\"\ntarget_feature=\"ras\"\ntarget_feature=\"rcpc\"\ntarget_feature=\"rcpc2\"\ntarget_feature=\"rdm\"\ntarget_feature=\"sb\"\ntarget_feature=\"sha2\"\ntarget_feature=\"sha3\"\ntarget_feature=\"ssbs\"\ntarget_feature=\"vh\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"macos\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"apple\"\ntokio_unstable\nunix\n","stderr":""},"10375286590057847751":{"success":true,"status":"","code":0,"stdout":"rustc 1.88.0 (6b00bc388 2025-06-23)\nbinary: rustc\ncommit-hash: 6b00bc3880198600130e1cf62b8f8a93494488cc\ncommit-date: 2025-06-23\nhost: aarch64-apple-darwin\nrelease: 1.88.0\nLLVM version: 20.1.5\n","stderr":""},"10459017571985955891":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.dylib\nlib___.dylib\nlib___.a\nlib___.dylib\n/Users/seonwoo960000/.rustup/toolchains/1.88.0-aarch64-apple-darwin\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"aarch64\"\ntarget_endian=\"little\"\ntarget_env=\"\"\ntarget_family=\"unix\"\ntarget_feature=\"aes\"\ntarget_feature=\"crc\"\ntarget_feature=\"dit\"\ntarget_feature=\"dotprod\"\ntarget_feature=\"dpb\"\ntarget_feature=\"dpb2\"\ntarget_feature=\"fcma\"\ntarget_feature=\"fhm\"\ntarget_feature=\"flagm\"\ntarget_feature=\"fp16\"\ntarget_feature=\"frintts\"\ntarget_feature=\"jsconv\"\ntarget_feature=\"lor\"\ntarget_feature=\"lse\"\ntarget_feature=\"neon\"\ntarget_feature=\"paca\"\ntarget_feature=\"pacg\"\ntarget_feature=\"pan\"\ntarget_feature=\"pmuv3\"\ntarget_feature=\"ras\"\ntarget_feature=\"rcpc\"\ntarget_feature=\"rcpc2\"\ntarget_feature=\"rdm\"\ntarget_feature=\"sb\"\ntarget_feature=\"sha2\"\ntarget_feature=\"sha3\"\ntarget_feature=\"ssbs\"\ntarget_feature=\"vh\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"macos\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"apple\"\ntokio_unstable\nunix\n","stderr":""}},"successes":{}} \ No newline at end of file