Update verification of batch compatible statements using regex

This commit is contained in:
김선우
2025-08-24 10:13:04 +09:00
parent bf1473dc08
commit 9f6eb8bc92
2 changed files with 157 additions and 43 deletions

View File

@@ -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

View File

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