diff --git a/system/Database/SQLite3/Connection.php b/system/Database/SQLite3/Connection.php index 945434184f33..a95289f8d57d 100644 --- a/system/Database/SQLite3/Connection.php +++ b/system/Database/SQLite3/Connection.php @@ -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(); @@ -418,12 +425,32 @@ 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++; + } + } } /** @@ -431,7 +458,22 @@ protected function _transBegin(): bool */ 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); + } + + } } /** @@ -439,7 +481,20 @@ protected function _transCommit(): bool */ 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); + } + } } /**