mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-17 05:09:53 +01:00
Unit tests for transaction isolation. #272
This commit is contained in:
@@ -0,0 +1,277 @@
|
|||||||
|
package org.briarproject.db;
|
||||||
|
|
||||||
|
import org.briarproject.BriarTestCase;
|
||||||
|
import org.briarproject.TestUtils;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.DriverManager;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
public class TransactionIsolationTest extends BriarTestCase {
|
||||||
|
|
||||||
|
private static final String DROP_TABLE = "DROP TABLE foo IF EXISTS";
|
||||||
|
private static final String CREATE_TABLE = "CREATE TABLE foo"
|
||||||
|
+ " (key INT NOT NULL,"
|
||||||
|
+ " counter INT NOT NULL)";
|
||||||
|
private static final String INSERT_ROW =
|
||||||
|
"INSERT INTO foo (key, counter) VALUES (1, 123)";
|
||||||
|
private static final String GET_COUNTER =
|
||||||
|
"SELECT counter FROM foo WHERE key = 1";
|
||||||
|
private static final String SET_COUNTER =
|
||||||
|
"UPDATE foo SET counter = ? WHERE key = 1";
|
||||||
|
|
||||||
|
private final File testDir = TestUtils.getTestDirectory();
|
||||||
|
private final File db = new File(testDir, "db");
|
||||||
|
private final String withMvcc = "jdbc:h2:" + db.getAbsolutePath()
|
||||||
|
+ ";MV_STORE=TRUE;MVCC=TRUE";
|
||||||
|
private final String withoutMvcc = "jdbc:h2:" + db.getAbsolutePath()
|
||||||
|
+ ";MV_STORE=FALSE;MVCC=FALSE;LOCK_MODE=1";
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
assertTrue(testDir.mkdirs());
|
||||||
|
Class.forName("org.h2.Driver");
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() throws Exception {
|
||||||
|
TestUtils.deleteTestDirectory(testDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDoesNotReadUncommittedWritesWithMvcc() throws Exception {
|
||||||
|
Connection connection = openConnection(true);
|
||||||
|
try {
|
||||||
|
createTableAndInsertRow(connection);
|
||||||
|
} finally {
|
||||||
|
connection.close();
|
||||||
|
}
|
||||||
|
// Start the first transaction
|
||||||
|
Connection txn1 = openConnection(true);
|
||||||
|
try {
|
||||||
|
txn1.setAutoCommit(false);
|
||||||
|
// The first transaction should read the initial value
|
||||||
|
assertEquals(123, getCounter(txn1));
|
||||||
|
// The first transaction updates the value but doesn't commit it
|
||||||
|
assertEquals(1, setCounter(txn1, 234));
|
||||||
|
// Start the second transaction
|
||||||
|
Connection txn2 = openConnection(true);
|
||||||
|
try {
|
||||||
|
txn2.setAutoCommit(false);
|
||||||
|
// The second transaction should still read the initial value
|
||||||
|
assertEquals(123, getCounter(txn2));
|
||||||
|
// Commit the second transaction
|
||||||
|
txn2.commit();
|
||||||
|
} finally {
|
||||||
|
txn2.close();
|
||||||
|
}
|
||||||
|
// Commit the first transaction
|
||||||
|
txn1.commit();
|
||||||
|
} finally {
|
||||||
|
txn1.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLastWriterWinsWithMvcc() throws Exception {
|
||||||
|
Connection connection = openConnection(true);
|
||||||
|
try {
|
||||||
|
createTableAndInsertRow(connection);
|
||||||
|
} finally {
|
||||||
|
connection.close();
|
||||||
|
}
|
||||||
|
// Start the first transaction
|
||||||
|
Connection txn1 = openConnection(true);
|
||||||
|
try {
|
||||||
|
txn1.setAutoCommit(false);
|
||||||
|
// The first transaction should read the initial value
|
||||||
|
assertEquals(123, getCounter(txn1));
|
||||||
|
// The first transaction updates the value but doesn't commit it
|
||||||
|
assertEquals(1, setCounter(txn1, 234));
|
||||||
|
// Start the second transaction
|
||||||
|
Connection txn2 = openConnection(true);
|
||||||
|
try {
|
||||||
|
txn2.setAutoCommit(false);
|
||||||
|
// The second transaction should still read the initial value
|
||||||
|
assertEquals(123, getCounter(txn2));
|
||||||
|
// Commit the first transaction
|
||||||
|
txn1.commit();
|
||||||
|
// The second transaction updates the value
|
||||||
|
assertEquals(1, setCounter(txn2, 345));
|
||||||
|
// Commit the second transaction
|
||||||
|
txn2.commit();
|
||||||
|
} finally {
|
||||||
|
txn2.close();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
txn1.close();
|
||||||
|
}
|
||||||
|
// The second transaction was the last writer, so it should win
|
||||||
|
connection = openConnection(true);
|
||||||
|
try {
|
||||||
|
assertEquals(345, getCounter(connection));
|
||||||
|
} finally {
|
||||||
|
connection.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLockTimeoutOnRowWithMvcc() throws Exception {
|
||||||
|
Connection connection = openConnection(true);
|
||||||
|
try {
|
||||||
|
createTableAndInsertRow(connection);
|
||||||
|
} finally {
|
||||||
|
connection.close();
|
||||||
|
}
|
||||||
|
// Start the first transaction
|
||||||
|
Connection txn1 = openConnection(true);
|
||||||
|
try {
|
||||||
|
txn1.setAutoCommit(false);
|
||||||
|
// The first transaction should read the initial value
|
||||||
|
assertEquals(123, getCounter(txn1));
|
||||||
|
// Start the second transaction
|
||||||
|
Connection txn2 = openConnection(true);
|
||||||
|
try {
|
||||||
|
txn2.setAutoCommit(false);
|
||||||
|
// The second transaction should read the initial value
|
||||||
|
assertEquals(123, getCounter(txn2));
|
||||||
|
// The first transaction updates the value but doesn't commit it
|
||||||
|
assertEquals(1, setCounter(txn1, 234));
|
||||||
|
// The second transaction tries to update the value
|
||||||
|
try {
|
||||||
|
setCounter(txn2, 345);
|
||||||
|
fail();
|
||||||
|
} catch (SQLException expected) {
|
||||||
|
// Expected: the row is locked by the first transaction
|
||||||
|
}
|
||||||
|
// Abort the transactions
|
||||||
|
txn1.rollback();
|
||||||
|
txn2.rollback();
|
||||||
|
} finally {
|
||||||
|
txn2.close();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
txn1.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadLockTimeoutOnTableWithoutMvcc() throws Exception {
|
||||||
|
Connection connection = openConnection(false);
|
||||||
|
try {
|
||||||
|
createTableAndInsertRow(connection);
|
||||||
|
} finally {
|
||||||
|
connection.close();
|
||||||
|
}
|
||||||
|
// Start the first transaction
|
||||||
|
Connection txn1 = openConnection(false);
|
||||||
|
try {
|
||||||
|
txn1.setAutoCommit(false);
|
||||||
|
// The first transaction should read the initial value
|
||||||
|
assertEquals(123, getCounter(txn1));
|
||||||
|
// Start the second transaction
|
||||||
|
Connection txn2 = openConnection(false);
|
||||||
|
try {
|
||||||
|
txn2.setAutoCommit(false);
|
||||||
|
// The second transaction should read the initial value
|
||||||
|
assertEquals(123, getCounter(txn2));
|
||||||
|
// The first transaction tries to update the value
|
||||||
|
try {
|
||||||
|
setCounter(txn1, 345);
|
||||||
|
fail();
|
||||||
|
} catch (SQLException expected) {
|
||||||
|
// Expected: the table is locked by the second transaction
|
||||||
|
}
|
||||||
|
// Abort the transactions
|
||||||
|
txn1.rollback();
|
||||||
|
txn2.rollback();
|
||||||
|
} finally {
|
||||||
|
txn2.close();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
txn1.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWriteLockTimeoutOnTableWithoutMvcc() throws Exception {
|
||||||
|
Connection connection = openConnection(false);
|
||||||
|
try {
|
||||||
|
createTableAndInsertRow(connection);
|
||||||
|
} finally {
|
||||||
|
connection.close();
|
||||||
|
}
|
||||||
|
// Start the first transaction
|
||||||
|
Connection txn1 = openConnection(false);
|
||||||
|
try {
|
||||||
|
txn1.setAutoCommit(false);
|
||||||
|
// The first transaction should read the initial value
|
||||||
|
assertEquals(123, getCounter(txn1));
|
||||||
|
// The first transaction updates the value but doesn't commit yet
|
||||||
|
assertEquals(1, setCounter(txn1, 345));
|
||||||
|
// Start the second transaction
|
||||||
|
Connection txn2 = openConnection(false);
|
||||||
|
try {
|
||||||
|
txn2.setAutoCommit(false);
|
||||||
|
// The second transaction tries to read the value
|
||||||
|
try {
|
||||||
|
getCounter(txn2);
|
||||||
|
fail();
|
||||||
|
} catch (SQLException expected) {
|
||||||
|
// Expected: the table is locked by the first transaction
|
||||||
|
}
|
||||||
|
// Abort the transactions
|
||||||
|
txn1.rollback();
|
||||||
|
txn2.rollback();
|
||||||
|
} finally {
|
||||||
|
txn2.close();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
txn1.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Connection openConnection(boolean mvcc) throws SQLException {
|
||||||
|
return DriverManager.getConnection(mvcc ? withMvcc : withoutMvcc);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createTableAndInsertRow(Connection c) throws SQLException {
|
||||||
|
Statement s = c.createStatement();
|
||||||
|
s.executeUpdate(DROP_TABLE);
|
||||||
|
s.executeUpdate(CREATE_TABLE);
|
||||||
|
s.executeUpdate(INSERT_ROW);
|
||||||
|
s.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getCounter(Connection c) throws SQLException {
|
||||||
|
Statement s = c.createStatement();
|
||||||
|
ResultSet rs = s.executeQuery(GET_COUNTER);
|
||||||
|
assertTrue(rs.next());
|
||||||
|
int counter = rs.getInt(1);
|
||||||
|
assertFalse(rs.next());
|
||||||
|
rs.close();
|
||||||
|
s.close();
|
||||||
|
return counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int setCounter(Connection c, int counter)
|
||||||
|
throws SQLException {
|
||||||
|
PreparedStatement ps = c.prepareStatement(SET_COUNTER);
|
||||||
|
ps.setInt(1, counter);
|
||||||
|
int rowsAffected = ps.executeUpdate();
|
||||||
|
ps.close();
|
||||||
|
return rowsAffected;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user