Skip to content

Commit

Permalink
feat(sqlite3): add support for nested transactions using SAVEPOINT
Browse files Browse the repository at this point in the history
It's possible to provide nested transaction semantics when using SQLite3
by using SAVEPOINT statements to manage transactions deeper than 1 level.

The implementation starts a transaction when transBegin() is first called as before.
But then all nested transactions will be managed by creating,
releasing (commiting) or rolling back savepoint.
Only when the outermost transaction is completed a COMMIT or ROLLBACK
will be executed, either committing the transaction and all inner
_committed_ transactions, or rolling everything back.

- SQLite Transaction: https://sqlite.org/lang_transaction.html
- SQLite Savepoints: https://sqlite.org/lang_savepoint.html
  • Loading branch information
Moritz Küttel committed Apr 27, 2024
1 parent bc0d997 commit 367d5d9
Showing 1 changed file with 58 additions and 3 deletions.
61 changes: 58 additions & 3 deletions system/Database/SQLite3/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ class Connection extends BaseConnection
*/
protected $busyTimeout;

/**
* How many savepoints have been created as part of managing nested transactions.
*
* @var int 0 = no savepoints, 1 = top level transaction + 0 savepoints, 2 = top level transaction + 1 savepoint, etc.
*/
protected $savepointLevel = 0;

public function initialize()
{
parent::initialize();
Expand Down Expand Up @@ -418,28 +425,76 @@ public function insertID(): int
return $this->connID->lastInsertRowID();
}

/**
* Generates the SQL for managing savepoints, which
* are used to support nested transactions with SQLite3
*/
private function _savepoint($savepoint, string $action = ''): string {
return ($action === '' ? '' : $action . ' ') . 'SAVEPOINT `__ci4_' . $savepoint . '`';
}


/**
* Begin Transaction
*/
protected function _transBegin(): bool
{
return $this->connID->exec('BEGIN TRANSACTION');
$created = false;
$savepoint = $this->savepointLevel;
try {

return $created = ($this->savepointLevel === 0
? $this->connID->exec('BEGIN TRANSACTION')
: $this->connID->exec($this->_savepoint($savepoint + 1)));
} finally {
if ($created) {
$this->savepointLevel++;
}
}
}

/**
* Commit Transaction
*/
protected function _transCommit(): bool
{
return $this->connID->exec('END TRANSACTION');
if ($this->savepointLevel === 0) {
return false;
}

$committed = false;
$savepoint = $this->savepointLevel;
try {
return $committed = ($this->savepointLevel <= 1
? $this->connID->exec('END TRANSACTION')
: $this->connID->exec($this->_savepoint($savepoint, 'RELEASE')));
} finally {
if ($committed) {
$this->savepointLevel = max($this->savepointLevel - 1, 0);
}

}
}

/**
* Rollback Transaction
*/
protected function _transRollback(): bool
{
return $this->connID->exec('ROLLBACK');
if ($this->savepointLevel === 0) {
return false;
}
$rolledBack = false;
$savepoint = $this->savepointLevel;
try {
return $rolledBack = ($this->savepointLevel <= 1
? $this->connID->exec('ROLLBACK')
: $this->connID->exec($this->_savepoint($savepoint, 'ROLLBACK TO')));
} finally {
if ($rolledBack) {
$this->savepointLevel = max($this->savepointLevel - 1, 0);
}
}
}

/**
Expand Down

0 comments on commit 367d5d9

Please sign in to comment.