Skip to content

Commit

Permalink
Finish transaction implementation for SQLite.
Browse files Browse the repository at this point in the history
  • Loading branch information
vnayar authored and SingingBush committed Dec 16, 2023
1 parent 6063c10 commit 736b2cd
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 20 deletions.
22 changes: 12 additions & 10 deletions source/ddbc/drivers/pgsqlddbc.d
Expand Up @@ -223,10 +223,10 @@ version(USE_PGSQL) {
bool autocommit = true;
bool useSsl = true;
Mutex mutex;


PGSQLStatement [] activeStatements;

void closeUnclosedStatements() {
PGSQLStatement [] list = activeStatements.dup;
foreach(stmt; list) {
Expand Down Expand Up @@ -331,15 +331,17 @@ version(USE_PGSQL) {

override void commit() {
checkClosed();

lock();
scope(exit) unlock();

Statement stmt = createStatement();
scope(exit) stmt.close();
stmt.executeUpdate("COMMIT");
if (!autocommit) {
stmt.executeUpdate("BEGIN");
Statement stmt2 = createStatement();
scope(exit) stmt2.close();
stmt2.executeUpdate("BEGIN");
}
}

Expand Down Expand Up @@ -388,18 +390,18 @@ version(USE_PGSQL) {
override bool isClosed() {
return closed;
}

override void rollback() {
checkClosed();

lock();
scope(exit) unlock();

Statement stmt = createStatement();
scope(exit) stmt.close();
stmt.executeUpdate("ROLLBACK");
if (!autocommit) {
stmt.executeUpdate("BEGIN");
stmt.executeUpdate("BEGIN");
}
}
override bool getAutoCommit() {
Expand Down
27 changes: 17 additions & 10 deletions source/ddbc/drivers/sqliteddbc.d
Expand Up @@ -246,11 +246,9 @@ version(USE_SQLITE) {
class SQLITEConnection : ddbc.core.Connection {
private:
string filename;

sqlite3 * conn;

bool closed;
bool autocommit;
bool autocommit = true;
Mutex mutex;


Expand Down Expand Up @@ -324,7 +322,8 @@ version(USE_SQLITE) {

override void commit() {
checkClosed();

if (autocommit)
return;
lock();
scope(exit) unlock();

Expand Down Expand Up @@ -378,14 +377,20 @@ version(USE_SQLITE) {

override void rollback() {
checkClosed();

if (autocommit)
return;
lock();
scope(exit) unlock();

Statement stmt = createStatement();
scope(exit) stmt.close();
//TODO:
//stmt.executeUpdate("ROLLBACK");

stmt.executeUpdate("ROLLBACK");
if (!autocommit) {
Statement stmt2 = createStatement();
scope(exit) stmt2.close();
stmt2.executeUpdate("BEGIN");
}
}
override bool getAutoCommit() {
return autocommit;
Expand All @@ -399,9 +404,11 @@ version(USE_SQLITE) {

Statement stmt = createStatement();
scope(exit) stmt.close();
// autocommit cannot be generally disabled in Sqlite3, thus disable it
// by always starting a transaction with the "BEGIN" command.
if (autoCommit == false) {
if (autoCommit) {
// If switching on autocommit, commit any ongoing transaction.
stmt.executeUpdate("COMMIT");
} else {
// If switching off autocommit, start a transaction.
stmt.executeUpdate("BEGIN");
}
this.autocommit = autoCommit;
Expand Down
77 changes: 77 additions & 0 deletions test/ddbctest/main.d
Expand Up @@ -680,7 +680,84 @@ class SQLitePodTest : DdbcTestFixture {
}
}

// Test parts of the interfaces related to transactions.
class SQLiteTransactionTest : DdbcTestFixture {
mixin UnitTest;

this() {
super(
"sqlite::memory:",
"CREATE TABLE records (id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL)",
"DROP TABLE IF EXISTS records");
}

@Test
public void testAutocommitOn() {
// This is the default state, it is merely made explicit here.
conn.setAutoCommit(true);
Statement stmt = conn.createStatement();
scope(exit) stmt.close();

stmt.executeUpdate(`INSERT INTO records (name) VALUES ('Bob')`);
conn.rollback();
stmt.executeUpdate(`INSERT INTO records (name) VALUES ('Jim')`);
conn.commit();

ddbc.core.ResultSet resultSet;
resultSet = stmt.executeQuery(`SELECT * FROM records WHERE name = 'Bob'`);
assert(resultSet.next());
resultSet = stmt.executeQuery(`SELECT * FROM records WHERE name = 'Jim'`);
assert(resultSet.next());
}

@Test
public void testAutocommitOff() {
// With autocommit set to false, transactions must be explicitly committed.
conn.setAutoCommit(false);
conn.setAutoCommit(false); // Duplicate calls should not cause errors.
Statement stmt = conn.createStatement();
scope(exit) stmt.close();

stmt.executeUpdate(`INSERT INTO records (name) VALUES ('Greg')`);
conn.rollback();
stmt.executeUpdate(`INSERT INTO records (name) VALUES ('Tom')`);
conn.commit();

ddbc.core.ResultSet resultSet;
resultSet = stmt.executeQuery(`SELECT * FROM records WHERE name = 'Greg'`);
assert(!resultSet.next());
resultSet = stmt.executeQuery(`SELECT * FROM records WHERE name = 'Tom'`);
assert(resultSet.next());
}

@Test
public void testAutocommitOffOn() {
// A test with a user changing autocommit in between statements.
conn.setAutoCommit(false);
Statement stmt = conn.createStatement();
scope(exit) stmt.close();

stmt.executeUpdate(`INSERT INTO records (name) VALUES ('Abe')`);
conn.setAutoCommit(true);
stmt.executeUpdate(`INSERT INTO records (name) VALUES ('Bart')`);

ddbc.core.ResultSet resultSet;
resultSet = stmt.executeQuery(`SELECT * FROM records WHERE name = 'Abe'`);
assert(resultSet.next());
resultSet = stmt.executeQuery(`SELECT * FROM records WHERE name = 'Bart'`);
assert(resultSet.next());
}

@Test
public void testTransactionIsolation() {
// Setting isolation level is only effective in transactions.
conn.setAutoCommit(false);
// In SQLite, SERIALIZABLE is the default and not settable.
assert(conn.getTransactionIsolation() == TransactionIsolation.SERIALIZABLE);
conn.setTransactionIsolation(TransactionIsolation.REPEATABLE_READ);
assert(conn.getTransactionIsolation() == TransactionIsolation.SERIALIZABLE);
}
}

// either use the 'Main' mixin or call 'dunit_main(args)'
mixin Main;

0 comments on commit 736b2cd

Please sign in to comment.