From 85cc1285e5fb36bf0984ad5f7ea9d8dc20e81b26 Mon Sep 17 00:00:00 2001 From: Lonnie Ezell Date: Sun, 4 Dec 2016 23:32:35 -0600 Subject: [PATCH] Porting over database transaction code from CI3. Fixes #268 --- system/Database/BaseConnection.php | 284 ++++++++++++++++++++- system/Database/MySQLi/Connection.php | 49 ++++ system/Database/Postgre/Connection.php | 36 +++ tests/_support/Database/MockConnection.php | 38 ++- 4 files changed, 397 insertions(+), 10 deletions(-) diff --git a/system/Database/BaseConnection.php b/system/Database/BaseConnection.php index e7a765c58127..5a2239f2d8eb 100644 --- a/system/Database/BaseConnection.php +++ b/system/Database/BaseConnection.php @@ -279,6 +279,45 @@ abstract class BaseConnection implements ConnectionInterface */ protected $pretend = false; + /** + * Transaction enabled flag + * + * @var bool + */ + public $trans_enabled = true; + + /** + * Strict transaction mode flag + * + * @var bool + */ + public $trans_strict = true; + + /** + * Transaction depth level + * + * @var int + */ + protected $_trans_depth = 0; + + /** + * Transaction status flag + * + * Used with transactions to determine if a rollback should occur. + * + * @var bool + */ + protected $_trans_status = true; + + /** + * Transaction failure flag + * + * Used with transactions to determine if a transaction has failed. + * + * @var bool + */ + protected $_trans_failure = false; + //-------------------------------------------------------------------- /** @@ -542,8 +581,39 @@ public function query(string $sql, $binds = null, $queryClass = 'CodeIgniter\\Da { $query->setDuration($startTime, $startTime); + // This will trigger a rollback if transactions are being used + if ($this->_trans_depth !== 0) + { + $this->_trans_status = false; + } + // @todo deal with errors + if ($this->DBDebug) + { + // We call this function in order to roll-back queries + // if transactions are enabled. If we don't call this here + // the error message will trigger an exit, causing the + // transactions to remain in limbo. + while ($this->_trans_depth !== 0) + { + $transDepth = $this->_trans_depth; + $this->transComplete(); + + if ($transDepth === $this->_trans_depth) + { + // @todo log + // log_message('error', 'Database: Failure during an automated transaction commit/rollback!'); + break; + } + } + + // display the errors.... + // @todo display the error... + + return false; + } + if (! $this->pretend) { // Let others do something with this query. @@ -592,6 +662,201 @@ public function simpleQuery(string $sql) //-------------------------------------------------------------------- + /** + * Enable/disable Transaction Strict Mode + * + * When strict mode is enabled, if you are running multiple groups of + * transactions, if one group fails all subsequent groups will be + * rolled back. + * + * If strict mode is disabled, each group is treated autonomously, + * meaning a failure of one group will not affect any others + * + * @param bool $mode = true + * + * @return $this + */ + public function trans_strict(bool $mode=true) + { + $this->trans_strict = $mode; + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Start Transaction + * + * @param bool $test_mode = FALSE + * @return bool + */ + public function transStart($test_mode = false) + { + if ( ! $this->trans_enabled) + { + return false; + } + + return $this->transBegin($test_mode); + } + + //-------------------------------------------------------------------- + + /** + * Complete Transaction + * + * @return bool + */ + public function transComplete() + { + if ( ! $this->trans_enabled) + { + return false; + } + + // The query() function will set this flag to FALSE in the event that a query failed + if ($this->_trans_status === false OR $this->_trans_failure === true) + { + $this->transRollback(); + + // If we are NOT running in strict mode, we will reset + // the _trans_status flag so that subsequent groups of + // transactions will be permitted. + if ($this->trans_strict === false) + { + $this->_trans_status = true; + } + +// log_message('debug', 'DB Transaction Failure'); + return FALSE; + } + + return $this->transCommit(); + } + + //-------------------------------------------------------------------- + + /** + * Lets you retrieve the transaction flag to determine if it has failed + * + * @return bool + */ + public function transStatus(): bool + { + return $this->_trans_status; + } + + //-------------------------------------------------------------------- + + /** + * Begin Transaction + * + * @param bool $test_mode + * @return bool + */ + public function transBegin(bool $test_mode = false): bool + { + if ( ! $this->trans_enabled) + { + return false; + } + // When transactions are nested we only begin/commit/rollback the outermost ones + elseif ($this->_trans_depth > 0) + { + $this->_trans_depth++; + return true; + } + + // Reset the transaction failure flag. + // If the $test_mode flag is set to TRUE transactions will be rolled back + // even if the queries produce a successful result. + $this->_trans_failure = ($test_mode === true); + + if ($this->_transBegin()) + { + $this->_trans_depth++; + return true; + } + + return false; + } + + //-------------------------------------------------------------------- + + /** + * Commit Transaction + * + * @return bool + */ + public function transCommit(): bool + { + if ( ! $this->trans_enabled || $this->_trans_depth === 0) + { + return false; + } + // When transactions are nested we only begin/commit/rollback the outermost ones + elseif ($this->_trans_depth > 1 || $this->_transCommit()) + { + $this->_trans_depth--; + return true; + } + + return false; + } + + //-------------------------------------------------------------------- + + /** + * Rollback Transaction + * + * @return bool + */ + public function transRollback(): bool + { + if ( ! $this->trans_enabled OR $this->_trans_depth === 0) + { + return false; + } + // When transactions are nested we only begin/commit/rollback the outermost ones + elseif ($this->_trans_depth > 1 OR $this->_transRollback()) + { + $this->_trans_depth--; + return true; + } + + return false; + } + + //-------------------------------------------------------------------- + + /** + * Begin Transaction + * + * @return bool + */ + abstract protected function _transBegin(): bool; + + //-------------------------------------------------------------------- + + /** + * Commit Transaction + * + * @return bool + */ + abstract protected function _transCommit(): bool; + + //-------------------------------------------------------------------- + + /** + * Rollback Transaction + * + * @return bool + */ + abstract protected function _transRollback(): bool; + + //-------------------------------------------------------------------- + /** * Returns an instance of the query builder for this connection. * @@ -975,15 +1240,16 @@ public function escapeIdentifiers($item) //-------------------------------------------------------------------- - /** - * DB Prefix - * - * Prepends a database prefix if one exists in configuration - * - * @param string the table - * - * @return string - */ + /** + * DB Prefix + * + * Prepends a database prefix if one exists in configuration + * + * @param string $table the table + * + * @return string + * @throws \CodeIgniter\DatabaseException + */ public function prefixTable($table = '') { if ($table === '') diff --git a/system/Database/MySQLi/Connection.php b/system/Database/MySQLi/Connection.php index 09b9b9aff121..74c69a9af461 100644 --- a/system/Database/MySQLi/Connection.php +++ b/system/Database/MySQLi/Connection.php @@ -469,4 +469,53 @@ public function insertID() //-------------------------------------------------------------------- + /** + * Begin Transaction + * + * @return bool + */ + protected function _transBegin():bool + { + $this->connID->autocommit(false); + + return $this->connID->begin_transaction(); + } + + //-------------------------------------------------------------------- + + /** + * Commit Transaction + * + * @return bool + */ + protected function _transCommit(): bool + { + if ($this->connID->commit()) + { + $this->connID->autocommit(true); + return true; + } + + return false; + } + + //-------------------------------------------------------------------- + + /** + * Rollback Transaction + * + * @return bool + */ + protected function _transRollback(): bool + { + if ($this->connID->rollback()) + { + $this->connID->autocommit(true); + return true; + } + + return false; + } + + //-------------------------------------------------------------------- } diff --git a/system/Database/Postgre/Connection.php b/system/Database/Postgre/Connection.php index b564f17fcb58..94359642e086 100644 --- a/system/Database/Postgre/Connection.php +++ b/system/Database/Postgre/Connection.php @@ -450,4 +450,40 @@ protected function setClientEncoding($charset) } //-------------------------------------------------------------------- + + /** + * Begin Transaction + * + * @return bool + */ + protected function _transBegin(): bool + { + return (bool)pg_query($this->connID, 'BEGIN'); + } + + // -------------------------------------------------------------------- + + /** + * Commit Transaction + * + * @return bool + */ + protected function _transCommit(): bool + { + return (bool)pg_query($this->connID, 'COMMIT'); + } + + // -------------------------------------------------------------------- + + /** + * Rollback Transaction + * + * @return bool + */ + protected function _transRollback(): bool + { + return (bool)pg_query($this->connID, 'ROLLBACK'); + } + + // -------------------------------------------------------------------- } diff --git a/tests/_support/Database/MockConnection.php b/tests/_support/Database/MockConnection.php index 687e3f71e29a..fe7eee6aa46f 100644 --- a/tests/_support/Database/MockConnection.php +++ b/tests/_support/Database/MockConnection.php @@ -166,7 +166,7 @@ public function error() */ public function insertID() { - return $this->conn_id->insert_id; + return $this->connID->insert_id; } //-------------------------------------------------------------------- @@ -209,4 +209,40 @@ protected function _close() //-------------------------------------------------------------------- + + /** + * Begin Transaction + * + * @return bool + */ + protected function _transBegin(): bool + { + return true; + } + + //-------------------------------------------------------------------- + + /** + * Commit Transaction + * + * @return bool + */ + protected function _transCommit(): bool + { + return true; + } + + //-------------------------------------------------------------------- + + /** + * Rollback Transaction + * + * @return bool + */ + protected function _transRollback(): bool + { + return true; + } + + //-------------------------------------------------------------------- }