package net.sourceforge.mayfly.acceptance;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
public class TransactionTest extends SqlTestCase {
public void testAutoCommitDefaultsToTrue() throws Exception {
assertEquals(true, connection.getAutoCommit());
}
public void testCommit() throws Exception {
connection.setAutoCommit(false);
execute("create table foo (x integer)");
execute("insert into foo(x) values(5)");
connection.commit();
assertResultSet(new String[] { " 5 " }, query("select x from foo"));
dialect.endTransaction(connection);
}
public void testAutoCommitIsPerConnection() throws Exception {
Connection connection2 = dialect.openAdditionalConnection();
try {
connection2.setAutoCommit(false);
assertEquals(false, connection2.getAutoCommit());
assertEquals(true, connection.getAutoCommit());
}
finally {
connection2.close();
}
}
public void testRollback() throws Exception {
if (!dialect.haveTransactions()) {
return;
}
connection.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
execute("create table foo (x integer)" +
dialect.tableTypeForTransactions());
connection.setAutoCommit(false);
execute("insert into foo(x) values(5)");
execute("insert into foo(x) values(7)");
assertResultSet(new String[] { " 5 ", " 7 " }, query("select x from foo"));
connection.rollback();
// This will the symptom if using MySQL without InnoDB:
// assertResultSet(new String[] { " 5 ", " 7 " }, query("select x from foo"));
assertResultSet(new String[] { }, query("select x from foo"));
dialect.endTransaction(connection);
}
public void testRollbackAndAutoCommit() throws Exception {
if (!dialect.haveTransactions()) {
return;
}
execute("create table foo (x integer)");
execute("insert into foo(x) values(5)");
execute("insert into foo(x) values(7)");
assertResultSet(new String[] { " 5 ", " 7 " }, query("select x from foo"));
if (dialect.autoCommitMustBeOffToCallRollback()) {
try {
connection.rollback();
fail();
}
catch (SQLException e) {
assertMessage("auto-commit must be off to call rollback", e);
}
}
else {
connection.rollback();
}
connection.setAutoCommit(false);
connection.rollback();
assertResultSet(new String[] { " 5 ", " 7 " }, query("select x from foo"));
dialect.endTransaction(connection);
}
// Need to deal with whether SET SCHEMA is transactional (see Derby docs)
// setAutoCommit(true) with a transaction in progress
// commit or rollback with a result set, prepared statement, etc open
// (this is mentioned in Derby docs, I think)
public void testUncommittedInsert() throws Exception {
if (!dialect.haveTransactions() || dialect.willWaitForWriterToCommit()) {
return;
}
connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
execute("create table foo (x integer)"
+ dialect.tableTypeForTransactions());
Connection writingConnection = dialect.openAdditionalConnection();
writingConnection.setAutoCommit(false);
execute("insert into foo (x) values (5)", writingConnection);
if (dialect.willReadUncommitted()) {
assertResultSet(new String[] { "5" }, query("select x from foo"));
}
else {
assertResultSet(new String[] { }, query("select x from foo"));
assertResultSet(new String[] { "5" }, "select x from foo", writingConnection);
}
writingConnection.commit();
assertResultSet(new String[] { "5" }, query("select x from foo"));
writingConnection.close();
}
public void testUncommittedUpdate() throws Exception {
if (!dialect.haveTransactions() || dialect.willWaitForWriterToCommit()) {
return;
}
connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
execute("create table foo (x integer)" +
dialect.tableTypeForTransactions());
execute("insert into foo (x) values (5)");
Connection writingConnection = dialect.openAdditionalConnection();
writingConnection.setAutoCommit(false);
execute("update foo set x = 8 where x = 5", writingConnection);
if (dialect.willReadUncommitted()) {
assertResultSet(new String[] { "8" }, query("select x from foo"));
}
else {
assertResultSet(new String[] { "5" }, query("select x from foo"));
assertResultSet(new String[] { "8" }, "select x from foo", writingConnection);
}
writingConnection.commit();
assertResultSet(new String[] { "8" }, query("select x from foo"));
writingConnection.close();
}
public void testTwoInsertsGetMerged() throws Exception {
if (dialect.willWaitForWriterToCommitOnTwoRowInserts()) {
return;
}
execute("create table foo (x integer)");
Connection connection2 = dialect.openAdditionalConnection();
connection.setAutoCommit(false);
connection2.setAutoCommit(false);
execute("insert into foo (x) values (5)");
execute("insert into foo (x) values (7)", connection2);
connection.commit();
connection2.commit();
assertResultSet(new String[] { "5", "7" }, query ("select x from foo"));
dialect.endTransaction(connection);
connection2.close();
}
public void testReadIsNotRepeatable() throws Exception {
checkRepeatableRead(false, Connection.TRANSACTION_READ_COMMITTED);
}
public void testReadIsRepeatable() throws Exception {
checkRepeatableRead(dialect.canProvideRepeatableRead(),
Connection.TRANSACTION_REPEATABLE_READ);
}
/** transaction 1 reads (or just starts a transaction? what triggers start?)
* transaction 2 updates a row that 1 has already read
* transaction 1 re-reads
* So is the update seen/not-seen? ("repeatable read" property)
*/
private void checkRepeatableRead(
boolean expectRepeatableRead, int isolationLevel)
throws Exception {
if (!dialect.haveTransactions() || dialect.willWaitForWriterToCommit()) {
/* I think the willWaitForWriteToCommit issue is that connection2
will be waiting for connection to complete. So it isn't
"wait for writer" but "wait for reader which has a lock" */
return;
}
execute("create table foo (x integer)" +
dialect.tableTypeForTransactions());
execute("insert into foo(x) values(5)");
connection.setTransactionIsolation(isolationLevel);
/* Note that the concept of a commit in this case applies even to
a transaction which is only reading. */
connection.setAutoCommit(false);
Connection connection2 = dialect.openAdditionalConnection();
// Doesn't seem to matter what connection2's transaction isolation is.
// connection2.setTransactionIsolation(
// Connection.TRANSACTION_REPEATABLE_READ);
try {
// transaction 1 reads (or just starts a transaction? what triggers start?)
assertResultSet(new String[] { "5" }, query("select x from foo"));
// transaction 2 updates a row that 1 has already read
execute("update foo set x = 8", connection2);
// transaction 1 re-reads
// So is the update seen/not-seen? ("repeatable read" property)
assertResultSet(
new String[] {
expectRepeatableRead ? "5" : "8"
}, query("select x from foo"));
connection.commit();
assertResultSet(new String[] { "8" }, query("select x from foo"));
}
finally {
dialect.endTransaction(connection);
connection2.close();
}
}
// two connections - uncommitted update seen/not-seen by other
// uncommitted create table seen/not-seen by other
// uncommitted create schema seen/not-seen by other
/* transaction 1 reads
* transaction 2 inserts a row
* transaction 1 re-reads
* So is the insert seen/not-seen
* ("phantom read" property)
*/
// Multiple writes -- see hypersonic documentation for whether we
// give an exception when two transactions commit a change to the
// same row.
// Conflict/merge situation - we have two commits (or a commit and
// a rollback). One is an ALTER TABLE; one is an update to a row in that table.
// Hypersonic documentation discusses this.
// SET CONSTRAINTS IMMEDIATE and SET CONSTRAINTS DEFERRED
// Does IMMEDIATE mean a racy kind of thing where it might matter
// what some other transaction does? Or is it just a way of saying
// that we check against what is visible in our own transaction, and
// then check again on commit?
public void testForUpdate() throws Exception {
execute("create table foo(x integer)");
execute("insert into foo(x) values(5)");
String selectForUpdate = "select x from foo for update";
if (dialect.haveForUpdate()) {
assertResultSet(new String[] { "5" },
query(selectForUpdate));
/* I think here we have a second connection
try to UPDATE FOO SET X = 6, or delete the row,
and the second
connection should fail or block or something.
*/
execute("update foo set x = 7");
assertResultSet(new String[] { " 7 " }, query("select x from foo"));
}
else {
expectQueryFailure(
selectForUpdate, "expected end of file but got FOR");
}
}
public static void assertResultSet(String[] expectedRows, String sql, Connection connection)
throws SQLException {
Statement myStatement = connection.createStatement();
assertResultSet(expectedRows, myStatement.executeQuery(sql));
myStatement.close();
}
}