From d40bc57dded8005c53376ebfa796a1e81e6fe0cb Mon Sep 17 00:00:00 2001 From: Sergey Tsalkov Date: Tue, 18 Sep 2012 19:08:37 -0700 Subject: [PATCH] add nested transactions (mysql >= 5.5 only) --- db.class.php | 52 +++++++++++++++++++++++-- simpletest/TransactionTest.php | 24 ++++++++++++ simpletest/TransactionTest_55.php | 63 +++++++++++++++++++++++++++++++ simpletest/test.php | 14 ++++++- 4 files changed, 148 insertions(+), 5 deletions(-) create mode 100644 simpletest/TransactionTest.php create mode 100644 simpletest/TransactionTest_55.php diff --git a/db.class.php b/db.class.php index 6e70b49..290de68 100644 --- a/db.class.php +++ b/db.class.php @@ -35,6 +35,7 @@ class DB { public static $throw_exception_on_error = false; public static $nonsql_error_handler = null; public static $throw_exception_on_nonsql_error = false; + public static $nested_transactions = false; // internal protected static $mdb = null; @@ -54,10 +55,12 @@ class DB { if ($mdb->throw_exception_on_error !== DB::$throw_exception_on_error) $mdb->throw_exception_on_error = DB::$throw_exception_on_error; if ($mdb->nonsql_error_handler !== DB::$nonsql_error_handler) $mdb->nonsql_error_handler = DB::$nonsql_error_handler; if ($mdb->throw_exception_on_nonsql_error !== DB::$throw_exception_on_nonsql_error) $mdb->throw_exception_on_nonsql_error = DB::$throw_exception_on_nonsql_error; + if ($mdb->nested_transactions !== DB::$nested_transactions) $mdb->nested_transactions = DB::$nested_transactions; return $mdb; } + public static function get() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'get'), $args); } public static function query() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'query'), $args); } public static function quickPrepare() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'quickPrepare'), $args); } public static function queryFirstRow() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'queryFirstRow'), $args); } @@ -94,6 +97,8 @@ class DB { public static function sqlEval() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'sqlEval'), $args); } public static function nonSQLError() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'nonSQLError'), $args); } + public static function serverVersion() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'serverVersion'), $args); } + public static function debugMode($handler = true) { DB::$success_handler = $handler; } @@ -119,9 +124,11 @@ class MeekroDB { public $throw_exception_on_error = false; public $nonsql_error_handler = null; public $throw_exception_on_nonsql_error = false; + public $nested_transactions = false; // internal public $internal_mysql = null; + public $server_info = null; public $insert_id = 0; public $num_rows = 0; public $affected_rows = 0; @@ -129,6 +136,7 @@ class MeekroDB { public $queryResultType = null; public $old_db = null; public $current_db = null; + public $nested_transactions_count = 0; public function __construct($host=null, $user=null, $password=null, $dbName=null, $port=null, $encoding=null) { @@ -161,6 +169,7 @@ class MeekroDB { $mysql->set_charset($this->encoding); $this->internal_mysql = $mysql; + $this->server_info = $mysql->server_info; } return $mysql; @@ -184,6 +193,7 @@ class MeekroDB { $this->success_handler = $handler; } + public function serverVersion() { return $this->server_info; } public function insertId() { return $this->insert_id; } public function affectedRows() { return $this->affected_rows; } public function count() { $args = func_get_args(); return call_user_func_array(array($this, 'numRows'), $args); } @@ -199,15 +209,51 @@ class MeekroDB { public function startTransaction() { - $this->queryNull('START TRANSACTION'); + if ($this->nested_transactions && $this->serverVersion() < '5.5') { + return $this->nonSQLError("Nested transactions are only available on MySQL 5.5 and greater. You are using MySQL " . $this->serverVersion()); + } + + if (!$this->nested_transactions || $this->nested_transactions_count == 0) { + $this->queryNull('START TRANSACTION'); + } else { + $this->queryNull("SAVEPOINT LEVEL{$this->nested_transactions_count}"); + } + + if ($this->nested_transactions) return ++$this->nested_transactions_count; } public function commit() { - $this->queryNull('COMMIT'); + if ($this->nested_transactions && $this->serverVersion() < '5.5') { + return $this->nonSQLError("Nested transactions are only available on MySQL 5.5 and greater. You are using MySQL " . $this->serverVersion()); + } + + if ($this->nested_transactions && $this->nested_transactions_count > 0) + $this->nested_transactions_count--; + + if (!$this->nested_transactions || $this->nested_transactions_count == 0) { + $this->queryNull('COMMIT'); + } else { + $this->queryNull("RELEASE SAVEPOINT LEVEL{$this->nested_transactions_count}"); + } + + return $this->nested_transactions_count; } public function rollback() { - $this->queryNull('ROLLBACK'); + if ($this->nested_transactions && $this->serverVersion() < '5.5') { + return $this->nonSQLError("Nested transactions are only available on MySQL 5.5 and greater. You are using MySQL " . $this->serverVersion()); + } + + if ($this->nested_transactions && $this->nested_transactions_count > 0) + $this->nested_transactions_count--; + + if (!$this->nested_transactions || $this->nested_transactions_count == 0) { + $this->queryNull('ROLLBACK'); + } else { + $this->queryNull("ROLLBACK TO SAVEPOINT LEVEL{$this->nested_transactions_count}"); + } + + return $this->nested_transactions_count; } public function escape($str) { diff --git a/simpletest/TransactionTest.php b/simpletest/TransactionTest.php new file mode 100644 index 0000000..363a4d4 --- /dev/null +++ b/simpletest/TransactionTest.php @@ -0,0 +1,24 @@ +assert($age == 700); + + DB::rollback(); + + $age = DB::queryFirstField("SELECT age FROM accounts WHERE username=%s", 'Abe'); + $this->assert($age == 700); + } + +} +?> diff --git a/simpletest/TransactionTest_55.php b/simpletest/TransactionTest_55.php new file mode 100644 index 0000000..cc53d64 --- /dev/null +++ b/simpletest/TransactionTest_55.php @@ -0,0 +1,63 @@ +assert($depth === 1); + DB::query("UPDATE accounts SET age=%i WHERE username=%s", 700, 'Abe'); + + $depth = DB::startTransaction(); + $this->assert($depth === 2); + DB::query("UPDATE accounts SET age=%i WHERE username=%s", 800, 'Abe'); + + $depth = DB::startTransaction(); + $this->assert($depth === 3); + DB::query("UPDATE accounts SET age=%i WHERE username=%s", 500, 'Abe'); + $depth = DB::commit(); + + $this->assert($depth === 2); + + $age = DB::queryFirstField("SELECT age FROM accounts WHERE username=%s", 'Abe'); + $this->assert($age == 500); + + $depth = DB::rollback(); + $this->assert($depth === 1); + + $age = DB::queryFirstField("SELECT age FROM accounts WHERE username=%s", 'Abe'); + $this->assert($age == 700); + + $depth = DB::commit(); + $this->assert($depth === 0); + + $age = DB::queryFirstField("SELECT age FROM accounts WHERE username=%s", 'Abe'); + $this->assert($age == 700); + + + DB::$nested_transactions = false; + } + + function test_2_transactions() { + DB::$nested_transactions = true; + + DB::query("UPDATE accounts SET age=%i WHERE username=%s", 600, 'Abe'); + + DB::startTransaction(); + DB::query("UPDATE accounts SET age=%i WHERE username=%s", 700, 'Abe'); + DB::startTransaction(); + DB::query("UPDATE accounts SET age=%i WHERE username=%s", 800, 'Abe'); + DB::rollback(); + + $age = DB::queryFirstField("SELECT age FROM accounts WHERE username=%s", 'Abe'); + $this->assert($age == 700); + + DB::rollback(); + + $age = DB::queryFirstField("SELECT age FROM accounts WHERE username=%s", 'Abe'); + $this->assert($age == 600); + + DB::$nested_transactions = false; + } + +} +?> diff --git a/simpletest/test.php b/simpletest/test.php index 4bee4e8..81493ae 100755 --- a/simpletest/test.php +++ b/simpletest/test.php @@ -25,23 +25,25 @@ else $is_php_53 = false; error_reporting(E_ALL | E_STRICT); require_once '../db.class.php'; -DB::$user = 'meekrodb_test_us'; - include 'test_setup.php'; //test config values go here +DB::$user = $set_db_user; DB::$password = $set_password; DB::$dbName = $set_db; DB::$host = $set_host; +DB::get(); //connect to mysql require_once 'BasicTest.php'; require_once 'ObjectTest.php'; require_once 'WhereClauseTest.php'; require_once 'ErrorTest.php'; +require_once 'TransactionTest.php'; $classes_to_test = array( 'BasicTest', 'WhereClauseTest', 'ObjectTest', 'ErrorTest', + 'TransactionTest', ); if ($is_php_53) { @@ -51,6 +53,14 @@ if ($is_php_53) { echo "PHP 5.3 not detected, skipping 5.3 tests..\n"; } +$mysql_version = DB::serverVersion(); +if ($mysql_version >= '5.5') { + require_once 'TransactionTest_55.php'; + $classes_to_test[] = 'TransactionTest_55'; +} else { + echo "MySQL 5.5 not available (version is $mysql_version) -- skipping MySQL 5.5 tests\n"; +} + $time_start = microtime_float(); foreach ($classes_to_test as $class) { $object = new $class();