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;")); + } }