mirror of
https://github.com/aljazceru/turso.git
synced 2025-12-25 12:04:21 +01:00
Implement execute batch
This commit is contained in:
@@ -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<String> 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<String> 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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user