$creatorrole = false;
}
- $DB->begin_sql();
+ $transaction = $DB->start_delegated_transaction();
$xcount = 0;
$maxxcount = 100;
role_unassign($creatorrole->id, $user->id, 0, $sitecontext->id, 'cas');
}
}
-
- if ($xcount++ > $maxxcount) {
- $DB->commit_sql();
- $DB->begin_sql();
- $xcount = 0;
- }
}
- $DB->commit_sql();
+ $transaction->allow_commit();
unset($users); // free mem
}
} else { // end do updates
$creatorrole = false;
}
- $DB->begin_sql();
+ $transaction = $DB->start_delegated_transaction();
foreach ($add_users as $user) {
$user = $this->get_userinfo_asobj($user->username);
role_assign($creatorrole->id, $user->id, 0, $sitecontext->id, 0, 0, 0, 'cas');
}
}
- $DB->commit_sql();
+ $transaction->allow_commit();
unset($add_users); // free mem
} else {
print "No users to be added\n";
if (!empty($add_users)) {
print_string('auth_dbuserstoadd','auth_db',count($add_users)); echo "\n";
- $DB->begin_sql();
+ $transaction = $DB->start_delegated_transaction();
foreach($add_users as $user) {
$username = $user;
$user = $this->get_userinfo_asobj($user);
echo "\t"; print_string('auth_dbinsertusererror', 'auth_db', $user->username); echo "\n";
}
}
- $DB->commit_sql();
+ $transaction->allow_commit();
unset($add_users); // free mem
}
return true;
$creatorrole = false;
}
- $DB->begin_sql();
+ $transaction = $DB->start_delegated_transaction();
$xcount = 0;
$maxxcount = 100;
role_unassign($creatorrole->id, $user->id, 0, $sitecontext->id, 'ldap');
}
}
-
- if ($xcount++ > $maxxcount) {
- $DB->commit_sql();
- $DB->begin_sql();
- $xcount = 0;
- }
}
- $DB->commit_sql();
+ $transaction->allow_commit();
unset($users); // free mem
}
} else { // end do updates
$creatorrole = false;
}
- $DB->begin_sql();
+ $transaction = $DB->start_delegated_transaction();
foreach ($add_users as $user) {
$user = $this->get_userinfo_asobj($user->username);
role_assign($creatorrole->id, $user->id, 0, $sitecontext->id, 0, 0, 0, 'ldap');
}
}
- $DB->commit_sql();
+ $transaction->allow_commit();
unset($add_users); // free mem
} else {
print "No users to be added\n";
$start = ob_start();
$returnString = '';
- $DB->begin_sql();
+ $transaction = $DB->start_delegated_transaction();
$useridarray = array();
foreach($array as $logEntry) {
}
}
$MNET_REMOTE_CLIENT->commit();
- $DB->commit_sql();
+ $transaction->allow_commit();
$end = ob_end_clean();
return true;
}
- $DB->begin_sql();
+ $transaction = $DB->start_delegated_transaction();
+
$extcourses = array();
while ($extcourse_obj = (object)$rs->FetchRow()) { // there are more course records
$extcourse_obj = (object)array_change_key_case((array)$extcourse_obj , CASE_LOWER);
$ers->close(); // release the handle
}
- $DB->commit_sql();
+ $transaction->allow_commit();
// we are done now, a bit of housekeeping
fix_course_sortorder();
if (!confirm_sesskey() ) {
print_error('confirmsesskeybad','error',$returnurl);
}
- $DB->begin_sql();
+
foreach($groupidarray as $groupid) {
groups_delete_group($groupid);
}
- $DB->commit_sql();
+
redirect($returnurl);
} else {
$PAGE->set_title(get_string('deleteselectedgroup', 'group'));
$params = self::validate_parameters(self::create_groups_parameters(), array('groups'=>$groups));
- // ideally create all groups or none at all, unfortunately myisam engine does not support transactions :-(
- $DB->begin_sql();
- try {
-//TODO: there is a potential problem with events propagating actions to external systems :-(
- $groups = array();
-
- foreach ($params['groups'] as $group) {
- $group = (object)$group;
-
- if (trim($group->name) == '') {
- throw new invalid_parameter_exception('Invalid group name');
- }
- if ($DB->get_record('groups', array('courseid'=>$group->courseid, 'name'=>$group->name))) {
- throw new invalid_parameter_exception('Group with the same name already exists in the course');
- }
-
- // now security checks
- $context = get_context_instance(CONTEXT_COURSE, $group->courseid);
- self::validate_context($context);
- require_capability('moodle/course:managegroups', $context);
-
- // finally create the group
- $group->id = groups_create_group($group, false);
- $groups[] = (array)$group;
+ $transaction = $DB->start_delegated_transaction();
+
+ $groups = array();
+
+ foreach ($params['groups'] as $group) {
+ $group = (object)$group;
+
+ if (trim($group->name) == '') {
+ throw new invalid_parameter_exception('Invalid group name');
+ }
+ if ($DB->get_record('groups', array('courseid'=>$group->courseid, 'name'=>$group->name))) {
+ throw new invalid_parameter_exception('Group with the same name already exists in the course');
}
- } catch (Exception $ex) {
- $DB->rollback_sql();
- throw $ex;
+
+ // now security checks
+ $context = get_context_instance(CONTEXT_COURSE, $group->courseid);
+ self::validate_context($context);
+ require_capability('moodle/course:managegroups', $context);
+
+ // finally create the group
+ $group->id = groups_create_group($group, false);
+ $groups[] = (array)$group;
}
- $DB->commit_sql();
+
+ $transaction->allow_commit();
return $groups;
}
$params = self::validate_parameters(self::delete_groups_parameters(), array('groupids'=>$groupids));
- $DB->begin_sql();
- try {
+ $transaction = $DB->start_delegated_transaction();
+
// TODO: this is problematic because the DB rollback does not handle deleting of images!!
// there is also potential problem with events propagating action to external systems :-(
- foreach ($params['groupids'] as $groupid) {
- // validate params
- $groupid = validate_param($groupid, PARAM_INTEGER);
- if (!$group = groups_get_group($groupid, 'id, courseid', IGNORE_MISSING)) {
- // silently ignore attempts to delete nonexisting groups
- continue;
- }
-
- // now security checks
- $context = get_context_instance(CONTEXT_COURSE, $group->courseid);
- self::validate_context($context);
- require_capability('moodle/course:managegroups', $context);
-
- groups_delete_group($group);
+ foreach ($params['groupids'] as $groupid) {
+ // validate params
+ $groupid = validate_param($groupid, PARAM_INTEGER);
+ if (!$group = groups_get_group($groupid, 'id, courseid', IGNORE_MISSING)) {
+ // silently ignore attempts to delete nonexisting groups
+ continue;
}
- } catch (Exception $ex) {
- $DB->rollback_sql();
- throw $ex;
+
+ // now security checks
+ $context = get_context_instance(CONTEXT_COURSE, $group->courseid);
+ self::validate_context($context);
+ require_capability('moodle/course:managegroups', $context);
+
+ groups_delete_group($group);
}
- $DB->commit_sql();
+
+ $transaction->allow_commit();
}
/**
$params = self::validate_parameters(self::add_groupmembers_parameters(), array('members'=>$members));
- $DB->begin_sql();
- try {
-// TODO: there is a potential problem with events propagating action to external systems :-(
- foreach ($params['members'] as $member) {
- // validate params
- $groupid = $member['groupid'];
- $userid = $member['userid'];
+ $transaction = $DB->start_delegated_transaction();
+ // TODO: there is a potential problem with events propagating action to external systems :-(
+ foreach ($params['members'] as $member) {
+ // validate params
+ $groupid = $member['groupid'];
+ $userid = $member['userid'];
- $group = groups_get_group($groupid, 'id, courseid', MUST_EXIST);
- $user = $DB->get_record('user', array('id'=>$userid, 'deleted'=>0, 'mnethostid'=>$CFG->mnet_localhost_id), '*', MUST_EXIST);
+ $group = groups_get_group($groupid, 'id, courseid', MUST_EXIST);
+ $user = $DB->get_record('user', array('id'=>$userid, 'deleted'=>0, 'mnethostid'=>$CFG->mnet_localhost_id), '*', MUST_EXIST);
- // now security checks
- $context = get_context_instance(CONTEXT_COURSE, $group->courseid);
- self::validate_context($context);
- require_capability('moodle/course:managegroups', $context);
+ // now security checks
+ $context = get_context_instance(CONTEXT_COURSE, $group->courseid);
+ self::validate_context($context);
+ require_capability('moodle/course:managegroups', $context);
- // now make sure user is enrolled in course - this is mandatory requirement,
- // unfortunately this is extermely slow
- require_capability('moodle/course:view', $context, $userid, false);
+ // now make sure user is enrolled in course - this is mandatory requirement,
+ // unfortunately this is extermely slow
+ require_capability('moodle/course:view', $context, $userid, false);
- groups_add_member($group, $user);
- }
- } catch (Exception $ex) {
- $DB->rollback_sql();
- throw $ex;
+ groups_add_member($group, $user);
}
- $DB->commit_sql();
+
+ $transaction->allow_commit();
}
/**
$params = self::validate_parameters(self::delete_groupmembers_parameters(), array('members'=>$members));
- $DB->begin_sql();
- try {
+ $transaction = $DB->start_delegated_transaction();
+
// TODO: there is a potential problem with events propagating action to external systems :-(
foreach ($params['members'] as $member) {
- // validate params
- $groupid = $member['groupid'];
- $userid = $member['userid'];
+ // validate params
+ $groupid = $member['groupid'];
+ $userid = $member['userid'];
- $group = groups_get_group($groupid, 'id, courseid', MUST_EXIST);
- $user = $DB->get_record('user', array('id'=>$userid, 'deleted'=>0, 'mnethostid'=>$CFG->mnet_localhost_id), '*', MUST_EXIST);
+ $group = groups_get_group($groupid, 'id, courseid', MUST_EXIST);
+ $user = $DB->get_record('user', array('id'=>$userid, 'deleted'=>0, 'mnethostid'=>$CFG->mnet_localhost_id), '*', MUST_EXIST);
- // now security checks
- $context = get_context_instance(CONTEXT_COURSE, $group->courseid);
- self::validate_context($context);
- require_capability('moodle/course:managegroups', $context);
+ // now security checks
+ $context = get_context_instance(CONTEXT_COURSE, $group->courseid);
+ self::validate_context($context);
+ require_capability('moodle/course:managegroups', $context);
- groups_remove_member($group, $user);
- }
- } catch (Exception $ex) {
- $DB->rollback_sql();
- throw $ex;
+ groups_remove_member($group, $user);
}
- $DB->commit_sql();
+
+ $transaction->allow_commit();
}
/**
$string['ddlunknownerror'] = 'Unknown DDL library error';
$string['ddlxmlfileerror'] = 'XML database file errors found';
$string['dmlreadexception'] = 'Error reading from database';
+$string['dmltransactionexception'] = 'Database transaction error';
$string['dmlwriteexception'] = 'Error writing to database';
$string['destinationcmnotexit'] = 'The destination course module does not exist';
$string['detectedbrokenplugin'] = 'Plugin \"$a\" is defective, can not continue, sorry.';
ON c.instanceid = t.id
WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_BLOCK."
";
+
+ // transactions used only for performance reasons here
+ $transaction = $DB->start_delegated_transaction();
+
if ($rs = $DB->get_recordset_sql($sql)) {
- $DB->begin_sql();
- $ok = true;
foreach ($rs as $ctx) {
- if (!delete_context($ctx->contextlevel, $ctx->instanceid)) {
- $ok = false;
- break;
- }
+ delete_context($ctx->contextlevel, $ctx->instanceid);
}
$rs->close();
- if ($ok) {
- $DB->commit_sql();
- return true;
- } else {
- $DB->rollback_sql();
- return false;
- }
}
+
+ $transaction->allow_commit();
return true;
}
require_once($CFG->libdir.'/dml/database_column_info.php');
require_once($CFG->libdir.'/dml/moodle_recordset.php');
+require_once($CFG->libdir.'/dml/moodle_transaction.php');
/// GLOBAL CONSTANTS /////////////////////////////////////////////////////////
/** @var bool true if db used for db sessions */
protected $used_for_db_sessions = false;
- /** @var bool Flag indicating transaction in progress */
- protected $intransaction = false;
+ /** @var array open transactions */
+ private $transactions = array();
+ /** @var bool force rollback of all current transactions */
+ private $force_rollback = false;
/** @var int internal temporary variable */
private $fix_sql_params_i;
* Do NOT use connect() again, create a new instance if needed.
*/
public function dispose() {
- if ($this->intransaction) {
- // unfortunately we can not access global $CFG any more and can not print debug
+ if ($this->transactions) {
+ // unfortunately we can not access global $CFG any more and can not print debug,
+ // the diagnostic info should be printed in footer instead
+ $this->force_transaction_rollback();
error_log('Active database transaction detected when disposing database!');
}
if ($this->used_for_db_sessions) {
}
/// transactions
+
+ /**
+ * Are transactions supported?
+ * It is not responsible to run productions servers
+ * on databases without transaction support ;-)
+ *
+ * Override in driver if needed.
+ *
+ * @return bool
+ */
+ protected function transactions_supported() {
+ // protected for now, this might be changed to public if really necessary
+ return true;
+ }
+
/**
* Returns true if transaction in progress
* @return bool
*/
- function is_transaction_started() {
- return $this->intransaction;
+ public function is_transaction_started() {
+ return !empty($this->transactions);
+ }
+
+ /**
+ * Throws exception if transaction in progress.
+ * This test does not force rollback of active transactions.
+ * @return void
+ */
+ public function transactions_forbidden() {
+ if ($this->is_transaction_started()) {
+ throw new dml_transaction_exception('This code can not be excecuted in transaction');
+ }
}
/**
- * on DBs that support it, switch to transaction mode and begin a transaction
- * you'll need to ensure you call commit_sql() or your changes *will* be lost.
+ * On DBs that support it, switch to transaction mode and begin a transaction
+ * you'll need to ensure you call commit() or your changes *will* be lost.
*
* this is _very_ useful for massive updates
*
- * Please note only one level of transactions is supported, please do not use
- * transaction in moodle core! Transaction are intended for web services
- * enrolment and auth synchronisation scripts, etc.
- *
- * @return bool success
+ * Delegated database transactions can be nested, but only one actual database
+ * transaction is used for the outer-most delegated transaction. This method
+ * returns a transaction object which you should keep until the end of the
+ * delegated transaction. The actual database transaction will
+ * only be committed if all the nested delegated transactions commit
+ * successfully. If any part of the transaction rolls back then the whole
+ * thing is rolled back.
+ *
+ * @return moodle_transaction
+ */
+ public function start_delegated_transaction() {
+ $transaction = new moodle_transaction($this);
+ $this->transactions[] = $transaction;
+ if (count($this->transactions) == 1) {
+ $this->begin_transaction();
+ }
+ return $transaction;
+ }
+
+ /**
+ * Driver specific start of real database transaction,
+ * this can not be used directly in code.
+ * @return void
*/
- public function begin_sql() {
- if ($this->intransaction) {
- debugging('Transaction already in progress');
- return false;
+ protected abstract function begin_transaction();
+
+ /**
+ * Indicates delegated transaction finished successfully.
+ * The real database transaction is committed only if
+ * all delegated transactions committed.
+ * @return void
+ */
+ public function commit_delegated_transaction(moodle_transaction $transaction) {
+ if ($transaction->is_disposed()) {
+ throw new dml_transaction_exception('Transactions already disposed', $transaction);
}
- $this->intransaction = true;
- return true;
+ // mark as disposed so that it can not be used again
+ $transaction->dispose();
+
+ if (empty($this->transactions)) {
+ throw new dml_transaction_exception('Transaction not started', $transaction);
+ }
+
+ if ($this->force_rollback) {
+ throw new dml_transaction_exception('Tried to commit transaction after lower level rollback', $transaction);
+ }
+
+ if ($transaction !== $this->transactions[count($this->transactions) - 1]) {
+ // one incorrect commit at any level rollbacks everything
+ $this->force_rollback = true;
+ throw new dml_transaction_exception('Invalid transaction commit attempt', $transaction);
+ }
+
+ if (count($this->transactions) == 1) {
+ // only commit the top most level
+ $this->commit_transaction();
+ }
+ array_pop($this->transactions);
}
/**
- * on DBs that support it, commit the transaction
- * @return bool success
+ * Driver specific commit of real database transaction,
+ * this can not be used directly in code.
+ * @return void
*/
- public function commit_sql() {
- if (!$this->intransaction) {
- debugging('Transaction not in progress');
- return false;
+ protected abstract function commit_transaction();
+
+ /**
+ * Call when delegated transaction failed, this rolls back
+ * all delegated transactions up to the top most level.
+ *
+ * In many cases you do not need to call this method manually,
+ * because all open delegated transactions are rolled back
+ * automatically if exceptions not caucht.
+ *
+ * @param moodle_transaction $transaction
+ * @param Exception $e exception that caused the problem
+ * @return does not return, exception is rethrown
+ */
+ public function rollback_delegated_transaction(moodle_transaction $transaction, Exception $e) {
+ if ($transaction->is_disposed()) {
+ throw new dml_transaction_exception('Transactions already disposed', $transaction);
}
- $this->intransaction = false;
- return true;
+ // mark as disposed so that it can not be used again
+ $transaction->dispose();
+
+ // one rollback at any level rollbacks everything
+ $this->force_rollback = true;
+
+ if (empty($this->transactions) or $transaction !== $this->transactions[count($this->transactions) - 1]) {
+ // this may or may not be a coding problem, better just rethrow the exception,
+ // because we do not want to loose the original $e
+ throw $e;
+ }
+
+ if (count($this->transactions) == 1) {
+ // only rollback the top most level
+ $this->rollback_transaction();
+ }
+ array_pop($this->transactions);
+ if (empty($this->transactions)) {
+ // finally top most level rolled back
+ $this->force_rollback = false;
+ }
+ throw $e;
}
/**
- * on DBs that support it, rollback the transaction
- * @return bool success
+ * Driver specific bort of real database transaction,
+ * this can not be used directly in code.
+ * @return void
*/
- public function rollback_sql() {
- if (!$this->intransaction) {
- debugging('Transaction not in progress');
- return false;
+ protected abstract function rollback_transaction();
+
+ /**
+ * Force rollback of all delegted transaction.
+ * Does not trow any exceptions and does not log anything.
+ *
+ * This method should be used only from default exception handlers and other
+ * core code.
+ *
+ * @return void
+ */
+ public function force_transaction_rollback() {
+ if ($this->transactions) {
+ try {
+ $this->rollback_transaction();
+ } catch (dml_exception $e) {
+ // ignore any sql errors here, the connection might be broken
+ }
}
- $this->intransaction = false;
- return true;
+
+ // now enable transactions again
+ $this->transactions = array(); // unfortunately all unfinished exceptions are kept in memory
+ $this->force_rollback = false;
}
/// session locking
--- /dev/null
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+
+/**
+ * Delegated database transaction support.
+ *
+ * @package moodlecore
+ * @subpackage DML
+ * @copyright 2009 Petr Skoda (http://skodak.org)
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * Delegated transaction class.
+ */
+class moodle_transaction {
+ private $start_backtrace;
+ private $database = null;
+
+ /**
+ * Delegated transaction constructor,
+ * can be called only from moodle_database class.
+ * Unfortunately PHP's protected keyword is useless.
+ * @param moodle_database $database
+ */
+ public function __construct($database) {
+ $this->database = $database;
+ $this->start_backtrace = debug_backtrace();
+ }
+
+ /**
+ * Is the delegated transaction already used?
+ * @return bool true if commit and rollback allowed, false if already done
+ */
+ public function is_disposed() {
+ return empty($this->database);
+ }
+
+ /**
+ * Mark transaction as disposed, no more
+ * commits and rollbacks allowed.
+ * To be used only from moodle_database class
+ * @return unknown_type
+ */
+ public function dispose() {
+ return $this->database = null;
+ }
+
+ /**
+ * Commit delegated transaction.
+ * The real database commit SQL is executed
+ * only after commiting all delegated transactions.
+ *
+ * Incorrect order of nested commits or rollback
+ * at any level is resulting in rollback of SQL transaction.
+ *
+ * @return void
+ */
+ public function allow_commit() {
+ if ($this->is_disposed()) {
+ throw new dml_transaction_exception('Transactions already disposed', $this);
+ }
+ $this->database->commit_delegated_transaction($this);
+ }
+
+ /**
+ * Rollback all current delegated transactions.
+ *
+ * @param Exception $e mandatory exception
+ * @return void
+ */
+ public function rollback(Exception $e) {
+ if ($this->is_disposed()) {
+ throw new dml_transaction_exception('Transactions already disposed', $this);
+ }
+ $this->database->rollback_delegated_transaction($this, $e);
+ }
+}
\ No newline at end of file
/// transactions
/**
- * on DBs that support it, switch to transaction mode and begin a transaction
- * you'll need to ensure you call commit_sql() or your changes *will* be lost.
- *
- * this is _very_ useful for massive updates
+ * Driver specific start of real database transaction,
+ * this can not be used directly in code.
+ * @return void
*/
- public function begin_sql() {
- if (!parent::begin_sql()) {
- return false;
- }
+ protected function begin_transaction() {
$sql = "BEGIN TRANSACTION"; // Will be using READ COMMITTED isolation
$this->query_start($sql, NULL, SQL_QUERY_AUX);
$result = mssql_query($sql, $this->mssql);
$this->query_end($result);
$this->free_result($result);
-
- return true;
}
/**
- * on DBs that support it, commit the transaction
+ * Driver specific commit of real database transaction,
+ * this can not be used directly in code.
+ * @return void
*/
- public function commit_sql() {
- if (!parent::commit_sql()) {
- return false;
- }
+ protected function commit_transaction() {
$sql = "COMMIT TRANSACTION";
$this->query_start($sql, NULL, SQL_QUERY_AUX);
$result = mssql_query($sql, $this->mssql);
$this->query_end($result);
$this->free_result($result);
-
- return true;
}
/**
- * on DBs that support it, rollback the transaction
+ * Driver specific abort of real database transaction,
+ * this can not be used directly in code.
+ * @return void
*/
- public function rollback_sql() {
- if (!parent::rollback_sql()) {
- return false;
- }
+ protected function rollback_transaction() {
$sql = "ROLLBACK TRANSACTION";
$this->query_start($sql, NULL, SQL_QUERY_AUX);
$result = mssql_query($sql, $this->mssql);
$this->query_end($result);
$this->free_result($result);
-
- return true;
}
}
protected $mysqli = null;
private $temptables; // Control existing temptables (mysql_moodle_temptables object)
+ private $transactions_supported = null;
+
/**
* Attempt to create the database
* @param string $dbhost
/// transactions
/**
- * on DBs that support it, switch to transaction mode and begin a transaction
- * you'll need to ensure you call commit_sql() or your changes *will* be lost.
+ * Are transactions supported?
+ * It is not responsible to run productions servers
+ * on databases without transaction support ;-)
+ *
+ * MyISAM does not support support transactions.
*
- * this is _very_ useful for massive updates
+ * @return bool
*/
- public function begin_sql() {
+ protected function transactions_supported() {
+ if (!is_null($this->transactions_supported)) {
+ return $this->transactions_supported;
+ }
+
// Only will accept transactions if using InnoDB storage engine (more engines can be added easily BDB, Falcon...)
+ $this->transactions_supported = false;
+
$sql = "SELECT @@storage_engine";
$this->query_start($sql, NULL, SQL_QUERY_AUX);
$result = $this->mysqli->query($sql);
$this->query_end($result);
+
if ($rec = $result->fetch_assoc()) {
- if (!in_array($rec['@@storage_engine'], array('InnoDB'))) {
- return false;
+ if (in_array($rec['@@storage_engine'], array('InnoDB'))) {
+ $this->transactions_supported = true;
}
- } else {
- return false;
}
$result->close();
- if (!parent::begin_sql()) {
- return false;
+ return $this->transactions_supported;
+ }
+
+ /**
+ * Driver specific start of real database transaction,
+ * this can not be used directly in code.
+ * @return void
+ */
+ protected function begin_transaction() {
+ if (!$this->transactions_supported()) {
+ return;
}
$sql = "SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED";
$this->query_start($sql, NULL, SQL_QUERY_AUX);
$result = $this->mysqli->query($sql);
$this->query_end($result);
-
- return true;
}
/**
- * on DBs that support it, commit the transaction
+ * Driver specific commit of real database transaction,
+ * this can not be used directly in code.
+ * @return void
*/
- public function commit_sql() {
- if (!parent::commit_sql()) {
- return false;
+ protected function commit_transaction() {
+ if (!$this->transactions_supported()) {
+ return;
}
+
$sql = "COMMIT";
$this->query_start($sql, NULL, SQL_QUERY_AUX);
$result = $this->mysqli->query($sql);
$this->query_end($result);
-
- return true;
}
/**
- * on DBs that support it, rollback the transaction
+ * Driver specific abort of real database transaction,
+ * this can not be used directly in code.
+ * @return void
*/
- public function rollback_sql() {
- if (!parent::rollback_sql()) {
- return false;
+ protected function rollback_transaction() {
+ if (!$this->transactions_supported()) {
+ return;
}
+
$sql = "ROLLBACK";
$this->query_start($sql, NULL, SQL_QUERY_AUX);
$result = $this->mysqli->query($sql);
/// transactions
/**
- * on DBs that support it, switch to transaction mode and begin a transaction
- * you'll need to ensure you call commit_sql() or your changes *will* be lost.
- *
- * this is _very_ useful for massive updates
+ * Driver specific start of real database transaction,
+ * this can not be used directly in code.
+ * @return void
*/
- public function begin_sql() {
- if (!parent::begin_sql()) {
- return false;
- }
+ protected function begin_transaction() {
$this->commit_status = OCI_DEFAULT; //Done! ;-)
- return true;
}
/**
- * on DBs that support it, commit the transaction
+ * Driver specific commit of real database transaction,
+ * this can not be used directly in code.
+ * @return void
*/
- public function commit_sql() {
- if (!parent::commit_sql()) {
- return false;
- }
-
+ protected function commit_transaction() {
$this->query_start('--oracle_commit', NULL, SQL_QUERY_AUX);
$result = oci_commit($this->oci);
$this->commit_status = OCI_COMMIT_ON_SUCCESS;
$this->query_end($result);
- return true;
}
/**
- * on DBs that support it, rollback the transaction
+ * Driver specific abort of real database transaction,
+ * this can not be used directly in code.
+ * @return void
*/
- public function rollback_sql() {
- if (!parent::rollback_sql()) {
- return false;
- }
-
+ protected function rollback_transaction() {
$this->query_start('--oracle_rollback', NULL, SQL_QUERY_AUX);
$result = oci_rollback($this->oci);
$this->commit_status = OCI_COMMIT_ON_SUCCESS;
$this->query_end($result);
- return true;
}
}
print_error('TODO');
}
- public function begin_sql() {
- if (!parent::begin_sql()) {
- return false;
- }
-
+ protected function begin_transaction() {
$this->query_start('', NULL, SQL_QUERY_AUX);
- $result = true;
-
try {
$this->pdb->beginTransaction();
} catch(PDOException $ex) {
$this->lastError = $ex->getMessage();
- $result = false;
}
$this->query_end($result);
- return $result;
}
- public function commit_sql() {
- if (!parent::commit_sql()) {
- return false;
- }
+ protected function commit_transaction() {
$this->query_start('', NULL, SQL_QUERY_AUX);
- $result = true;
try {
$this->pdb->commit();
} catch(PDOException $ex) {
$this->lastError = $ex->getMessage();
- $result = false;
}
$this->query_end($result);
- return $result;
}
- public function rollback_sql() {
- if (!parent::rollback_sql()) {
- return false;
- }
-
+ protected function rollback_transaction() {
$this->query_start('', NULL, SQL_QUERY_AUX);
- $result = true;
try {
$this->pdb->rollBack();
} catch(PDOException $ex) {
$this->lastError = $ex->getMessage();
- $result = false;
}
$this->query_end($result);
- return $result;
}
/**
/// transactions
/**
- * on DBs that support it, switch to transaction mode and begin a transaction
- * you'll need to ensure you call commit_sql() or your changes *will* be lost.
- *
- * this is _very_ useful for massive updates
+ * Driver specific start of real database transaction,
+ * this can not be used directly in code.
+ * @return void
*/
- public function begin_sql() {
- if (!parent::begin_sql()) {
- return false;
- }
+ protected function begin_transaction() {
$sql = "BEGIN ISOLATION LEVEL READ COMMITTED";
$this->query_start($sql, NULL, SQL_QUERY_AUX);
$result = pg_query($this->pgsql, $sql);
$this->query_end($result);
pg_free_result($result);
- return true;
}
/**
- * on DBs that support it, commit the transaction
+ * Driver specific commit of real database transaction,
+ * this can not be used directly in code.
+ * @return void
*/
- public function commit_sql() {
- if (!parent::commit_sql()) {
- return false;
- }
+ protected function commit_transaction() {
$sql = "COMMIT";
$this->query_start($sql, NULL, SQL_QUERY_AUX);
$result = pg_query($this->pgsql, $sql);
$this->query_end($result);
pg_free_result($result);
- return true;
}
/**
- * on DBs that support it, rollback the transaction
+ * Driver specific abort of real database transaction,
+ * this can not be used directly in code.
+ * @return void
*/
- public function rollback_sql() {
- if (!parent::rollback_sql()) {
- return false;
- }
+ protected function rollback_transaction() {
$sql = "ROLLBACK";
$this->query_start($sql, NULL, SQL_QUERY_AUX);
$result = pg_query($this->pgsql, $sql);
$this->query_end($result);
pg_free_result($result);
- return true;
}
/**
}
- function test_begin_sql() {
+ /**
+ * Test some more complex SQL syntax which moodle uses and depends on to work
+ * useful to determine if new database libraries can be supported.
+ */
+ public function test_get_records_sql_complicated() {
+ global $CFG;
$DB = $this->tdb;
$dbman = $DB->get_manager();
$dbman->create_table($table);
$this->tables[$tablename] = $table;
- $active = $DB->begin_sql();
- if ($active) {
- // test only if driver supports transactions
- $data = (object)array('course'=>3);
- $DB->insert_record($tablename, $data);
- $this->assertEqual(1, $DB->count_records($tablename));
- $DB->commit_sql();
- } else {
- $this->assertTrue(true, 'DB Transactions not supported. Test skipped');
+ $DB->insert_record($tablename, array('course' => 3));
+ $DB->insert_record($tablename, array('course' => 3));
+ $DB->insert_record($tablename, array('course' => 5));
+ $DB->insert_record($tablename, array('course' => 2));
+
+ // we have sql like this in moodle, this syntax breaks on older versions of sqlite for example..
+ $sql = 'SELECT a.id AS id, a.course AS course
+ FROM {'.$tablename.'} a
+ JOIN (SELECT * FROM {'.$tablename.'}) b
+ ON a.id = b.id
+ WHERE a.course = ?';
+
+ $this->assertTrue($records = $DB->get_records_sql($sql, array(3)));
+ $this->assertEqual(2, count($records));
+ $this->assertEqual(1, reset($records)->id);
+ $this->assertEqual(2, next($records)->id);
+ }
+
+ function test_onelevel_commit() {
+ $DB = $this->tdb;
+ $dbman = $DB->get_manager();
+
+ $table = $this->get_test_table();
+ $tablename = $table->getName();
+
+ $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+ $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
+ $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+ $dbman->create_table($table);
+ $this->tables[$tablename] = $table;
+
+ $transaction = $DB->start_delegated_transaction();
+ $data = (object)array('course'=>3);
+ $this->assertEqual(0, $DB->count_records($tablename));
+ $DB->insert_record($tablename, $data);
+ $this->assertEqual(1, $DB->count_records($tablename));
+ $transaction->allow_commit();
+ $this->assertEqual(1, $DB->count_records($tablename));
+ }
+
+ function test_onelevel_rollback() {
+ $DB = $this->tdb;
+ $dbman = $DB->get_manager();
+
+ $table = $this->get_test_table();
+ $tablename = $table->getName();
+
+ $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+ $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
+ $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+ $dbman->create_table($table);
+ $this->tables[$tablename] = $table;
+
+ // this might in fact encourage ppl to migrate from myisam to innodb
+
+ $transaction = $DB->start_delegated_transaction();
+ $data = (object)array('course'=>3);
+ $this->assertEqual(0, $DB->count_records($tablename));
+ $DB->insert_record($tablename, $data);
+ $this->assertEqual(1, $DB->count_records($tablename));
+ try {
+ $transaction->rollback(new Exception('test'));
+ $this->fail('transaction rollback must rethrow exception');
+ } catch (Exception $e) {
}
+ $this->assertEqual(0, $DB->count_records($tablename));
}
- function test_commit_sql() {
+ function test_nested_transactions() {
$DB = $this->tdb;
$dbman = $DB->get_manager();
$dbman->create_table($table);
$this->tables[$tablename] = $table;
- $active = $DB->begin_sql();
- if ($active) {
- // test only if driver supports transactions
- $data = (object)array('course'=>3);
- $DB->insert_record($tablename, $data);
- $DB->commit_sql();
- $this->assertEqual(1, $DB->count_records($tablename));
- } else {
- $this->assertTrue(true, 'BD Transactions not supported. Test skipped');
+ // two level commit
+ $this->assertFalse($DB->is_transaction_started());
+ $transaction1 = $DB->start_delegated_transaction();
+ $this->assertTrue($DB->is_transaction_started());
+ $data = (object)array('course'=>3);
+ $DB->insert_record($tablename, $data);
+ $transaction2 = $DB->start_delegated_transaction();
+ $data = (object)array('course'=>4);
+ $DB->insert_record($tablename, $data);
+ $transaction2->allow_commit();
+ $this->assertTrue($DB->is_transaction_started());
+ $transaction1->allow_commit();
+ $this->assertFalse($DB->is_transaction_started());
+ $this->assertEqual(2, $DB->count_records($tablename));
+
+ $DB->delete_records($tablename);
+
+ // rollback from top level
+ $transaction1 = $DB->start_delegated_transaction();
+ $data = (object)array('course'=>3);
+ $DB->insert_record($tablename, $data);
+ $transaction2 = $DB->start_delegated_transaction();
+ $data = (object)array('course'=>4);
+ $DB->insert_record($tablename, $data);
+ $transaction2->allow_commit();
+ try {
+ $transaction1->rollback(new Exception('test'));
+ $this->fail('transaction rollback must rethrow exception');
+ } catch (Exception $e) {
+ $this->assertEqual(get_class($e), 'Exception');
+ }
+ $this->assertEqual(0, $DB->count_records($tablename));
+
+ $DB->delete_records($tablename);
+
+ // rollback from nested level
+ $transaction1 = $DB->start_delegated_transaction();
+ $data = (object)array('course'=>3);
+ $DB->insert_record($tablename, $data);
+ $transaction2 = $DB->start_delegated_transaction();
+ $data = (object)array('course'=>4);
+ $DB->insert_record($tablename, $data);
+ try {
+ $transaction2->rollback(new Exception('test'));
+ $this->fail('transaction rollback must rethrow exception');
+ } catch (Exception $e) {
+ $this->assertEqual(get_class($e), 'Exception');
}
+ $this->assertEqual(2, $DB->count_records($tablename)); // not rolled back yet
+ try {
+ $transaction1->allow_commit();
+ } catch (Exception $e) {
+ $this->assertEqual(get_class($e), 'dml_transaction_exception');
+ }
+ $this->assertEqual(2, $DB->count_records($tablename)); // not rolled back yet
+ // the forced rollback is done from the default_exception hadnler and similar places,
+ // let's do it manually here
+ $this->assertTrue($DB->is_transaction_started());
+ $DB->force_transaction_rollback();
+ $this->assertFalse($DB->is_transaction_started());
+ $this->assertEqual(0, $DB->count_records($tablename)); // finally rolled back
+
+ $DB->delete_records($tablename);
}
- function test_rollback_sql() {
+ function test_transactions_forbidden() {
$DB = $this->tdb;
$dbman = $DB->get_manager();
$dbman->create_table($table);
$this->tables[$tablename] = $table;
- $active = $DB->begin_sql();
- if ($active) {
- // test only if driver supports transactions
- $data = (object)array('course'=>3);
- $DB->insert_record($tablename, $data);
- $DB->rollback_sql();
- $this->assertEqual(0, $DB->count_records($tablename));
- } else {
- $this->assertTrue(true, 'DB Transactions not supported. Test skipped');
+ $DB->transactions_forbidden();
+ $transaction = $DB->start_delegated_transaction();
+ $data = (object)array('course'=>1);
+ $DB->insert_record($tablename, $data);
+ try {
+ $DB->transactions_forbidden();
+ } catch (Exception $e) {
+ $this->assertEqual(get_class($e), 'dml_transaction_exception');
}
+ // the previous test does not force rollback
+ $transaction->allow_commit();
+ $this->assertFalse($DB->is_transaction_started());
+ $this->assertEqual(1, $DB->count_records($tablename));
}
- /**
- * Test some more complex SQL syntax which moodle uses and depends on to work
- * useful to determine if new database libraries can be supported.
- */
- public function test_get_records_sql_complicated() {
- global $CFG;
+ function test_wrong_transactions() {
$DB = $this->tdb;
$dbman = $DB->get_manager();
$dbman->create_table($table);
$this->tables[$tablename] = $table;
- $DB->insert_record($tablename, array('course' => 3));
- $DB->insert_record($tablename, array('course' => 3));
- $DB->insert_record($tablename, array('course' => 5));
- $DB->insert_record($tablename, array('course' => 2));
- // we have sql like this in moodle, this syntax breaks on older versions of sqlite for example..
- $sql = 'SELECT a.id AS id, a.course AS course
- FROM {'.$tablename.'} a
- JOIN (SELECT * FROM {'.$tablename.'}) b
- ON a.id = b.id
- WHERE a.course = ?';
+ // wrong order of nested commits
+ $transaction1 = $DB->start_delegated_transaction();
+ $data = (object)array('course'=>3);
+ $DB->insert_record($tablename, $data);
+ $transaction2 = $DB->start_delegated_transaction();
+ $data = (object)array('course'=>4);
+ $DB->insert_record($tablename, $data);
+ try {
+ $transaction1->allow_commit();
+ $this->fail('wrong order of commits must throw exception');
+ } catch (Exception $e) {
+ $this->assertEqual(get_class($e), 'dml_transaction_exception');
+ }
+ try {
+ $transaction2->allow_commit();
+ $this->fail('first wrong commit forces rollback');
+ } catch (Exception $e) {
+ $this->assertEqual(get_class($e), 'dml_transaction_exception');
+ }
+ // this is done in default exception hadnler usually
+ $this->assertTrue($DB->is_transaction_started());
+ $this->assertEqual(2, $DB->count_records($tablename)); // not rolled back yet
+ $DB->force_transaction_rollback();
+ $this->assertEqual(0, $DB->count_records($tablename));
+ $DB->delete_records($tablename);
- $this->assertTrue($records = $DB->get_records_sql($sql, array(3)));
- $this->assertEqual(2, count($records));
- $this->assertEqual(1, reset($records)->id);
- $this->assertEqual(2, next($records)->id);
+
+ // wrong order of nested rollbacks
+ $transaction1 = $DB->start_delegated_transaction();
+ $data = (object)array('course'=>3);
+ $DB->insert_record($tablename, $data);
+ $transaction2 = $DB->start_delegated_transaction();
+ $data = (object)array('course'=>4);
+ $DB->insert_record($tablename, $data);
+ try {
+ // this first rollback should prevent all otehr rollbacks
+ $transaction1->rollback(new Exception('test'));
+ } catch (Exception $e) {
+ $this->assertEqual(get_class($e), 'Exception');
+ }
+ try {
+ $transaction2->rollback(new Exception('test'));
+ } catch (Exception $e) {
+ $this->assertEqual(get_class($e), 'Exception');
+ }
+ try {
+ $transaction1->rollback(new Exception('test'));
+ } catch (Exception $e) {
+ // the rollback was used already once, no way to use it again
+ $this->assertEqual(get_class($e), 'dml_transaction_exception');
+ }
+ // this is done in default exception hadnler usually
+ $this->assertTrue($DB->is_transaction_started());
+ $DB->force_transaction_rollback();
+ $DB->delete_records($tablename);
+
+
+ // unknown transaction object
+ $transaction1 = $DB->start_delegated_transaction();
+ $data = (object)array('course'=>3);
+ $DB->insert_record($tablename, $data);
+ $transaction2 = new moodle_transaction($DB);
+ try {
+ $transaction2->allow_commit();
+ $this->fail('foreign transaction must fail');
+ } catch (Exception $e) {
+ $this->assertEqual(get_class($e), 'dml_transaction_exception');
+ }
+ try {
+ $transaction1->allow_commit();
+ $this->fail('first wrong commit forces rollback');
+ } catch (Exception $e) {
+ $this->assertEqual(get_class($e), 'dml_transaction_exception');
+ }
+ $DB->force_transaction_rollback();
+ $DB->delete_records($tablename);
}
}
public function sql_concat(){}
public function sql_concat_join($separator="' '", $elements=array()){}
public function sql_substr($expr, $start, $length=false){}
+ public function begin_transaction() {}
+ public function commit_transaction() {}
+ public function rollback_transaction() {}
}
}
}
+/**
+ * DML transaction exception - triggered by probles related to DB transactions
+ */
+class dml_transaction_exception extends dml_exception {
+ /** @var moodle_transaction */
+ public $transaction;
+
+ /**
+ * Constructor
+ * @param array $start_backtrace
+ */
+ function __construct($debuginfo=null, $transaction=null) {
+ $this->transaction = $transaction; // TODO: MDL-20625 use the info from $transaction for debugging purposes
+ parent::__construct('dmltransactionexception', NULL, $debuginfo);
+ }
+}
+
/**
* Sets up global $DB moodle_database instance
*
* How to use transactions.
*/
protected $transactionmode = 'allinone';
+ /** Transaction object */
+ protected $transaction;
/**
* Object constructor.
throw new dbtransfer_exception('importschemaexception', $details);
}
if ($this->transactionmode == 'allinone') {
- $this->mdb->begin_sql();
+ $this->transaction = $this->mdb->start_delegated_transaction();
}
}
*/
public function begin_table_import($tablename, $schemaHash) {
if ($this->transactionmode == 'pertable') {
- $this->mdb->begin_sql();
+ $this->transaction = $this->mdb->start_delegated_transaction();
}
if (!$table = $this->schema->getTable($tablename)) {
throw new dbtransfer_exception('unknowntableexception', $tablename);
}
}
if ($this->transactionmode == 'pertable') {
- $this->mdb->commit_sql();
+ $this->transaction->allow_commit();
}
}
*/
public function finish_database_import() {
if ($this->transactionmode == 'allinone') {
- $this->mdb->commit_sql();
+ $this->transaction->allow_commit();
}
}
require_once($CFG->libdir.'/gradelib.php');
require_once($CFG->dirroot.'/message/lib.php');
- // TODO: decide if this transaction is really needed
- $DB->begin_sql();
-
- try {
- // delete all grades - backup is kept in grade_grades_history table
- if ($grades = grade_grade::fetch_all(array('userid'=>$user->id))) {
- foreach ($grades as $grade) {
- $grade->delete('userdelete');
- }
+ // delete all grades - backup is kept in grade_grades_history table
+ if ($grades = grade_grade::fetch_all(array('userid'=>$user->id))) {
+ foreach ($grades as $grade) {
+ $grade->delete('userdelete');
}
+ }
- //move unread messages from this user to read
- message_move_userfrom_unread2read($user->id);
-
- // remove from all groups
- $DB->delete_records('groups_members', array('userid'=>$user->id));
+ //move unread messages from this user to read
+ message_move_userfrom_unread2read($user->id);
- // unenrol from all roles in all contexts
- role_unassign(0, $user->id); // this might be slow but it is really needed - modules might do some extra cleanup!
+ // remove from all groups
+ $DB->delete_records('groups_members', array('userid'=>$user->id));
- // now do a final accesslib cleanup - removes all role assingments in user context and context itself
- delete_context(CONTEXT_USER, $user->id);
+ // unenrol from all roles in all contexts
+ role_unassign(0, $user->id); // this might be slow but it is really needed - modules might do some extra cleanup!
- require_once($CFG->dirroot.'/tag/lib.php');
- tag_set('user', $user->id, array());
+ // now do a final accesslib cleanup - removes all role assingments in user context and context itself
+ delete_context(CONTEXT_USER, $user->id);
- // workaround for bulk deletes of users with the same email address
- $delname = "$user->email.".time();
- while ($DB->record_exists('user', array('username'=>$delname))) { // no need to use mnethostid here
- $delname++;
- }
+ require_once($CFG->dirroot.'/tag/lib.php');
+ tag_set('user', $user->id, array());
- // mark internal user record as "deleted"
- $updateuser = new object();
- $updateuser->id = $user->id;
- $updateuser->deleted = 1;
- $updateuser->username = $delname; // Remember it just in case
- $updateuser->email = ''; // Clear this field to free it up
- $updateuser->idnumber = ''; // Clear this field to free it up
- $updateuser->timemodified = time();
-
- $DB->update_record('user', $updateuser);
+ // workaround for bulk deletes of users with the same email address
+ $delname = "$user->email.".time();
+ while ($DB->record_exists('user', array('username'=>$delname))) { // no need to use mnethostid here
+ $delname++;
+ }
- $DB->commit_sql();
+ // mark internal user record as "deleted"
+ $updateuser = new object();
+ $updateuser->id = $user->id;
+ $updateuser->deleted = 1;
+ $updateuser->username = $delname; // Remember it just in case
+ $updateuser->email = ''; // Clear this field to free it up
+ $updateuser->idnumber = ''; // Clear this field to free it up
+ $updateuser->timemodified = time();
- // notify auth plugin - do not block the delete even when plugin fails
- $authplugin = get_auth_plugin($user->auth);
- $authplugin->user_delete($user);
+ $DB->update_record('user', $updateuser);
- events_trigger('user_deleted', $user);
+ // notify auth plugin - do not block the delete even when plugin fails
+ $authplugin = get_auth_plugin($user->auth);
+ $authplugin->user_delete($user);
- } catch (Exception $e) {
- $DB->rollback_sql();
- throw $e;
- }
+ events_trigger('user_deleted', $user);
return true;
}
return;
}
+ // TODO: MDL-20625 this looks dangerous and problematic because we never know
+ // the order of calling of constructors ==> the transaction warning will not be included
+
// It seems you cannot rely on $CFG, and hence the debugging function here,
// becuase $CFG may be destroyed before this object is.
if ($this->isdebugging) {
* @return string HTML fragment
*/
public function footer() {
- global $CFG;
+ global $CFG, $DB;
$output = $this->opencontainers->pop_all_but_last(true);
$footer = $this->opencontainers->pop('header/footer');
+ if (debugging() and $DB and $DB->is_transaction_started()) {
+ // TODO: MDL-20625 print warning - transaction will be rolled back
+ }
+
// Provide some performance info if required
$performanceinfo = '';
if (defined('MDL_PERF') || (!empty($CFG->perfdebug) and $CFG->perfdebug > 7)) {
function abort_all_db_transactions() {
global $CFG, $DB, $SCRIPT;
+ // default exception handler MUST not throw any exceptions!!
+
if ($DB && $DB->is_transaction_started()) {
error_log('Database transaction aborted automatically in ' . $CFG->dirroot . $SCRIPT);
- try {
- // note: transaction blocks should never change current $_SESSION
- $DB->rollback_sql();
- } catch (Exception $ignored) {
- // default exception handler MUST not throw any exceptions!!
- }
+ // note: transaction blocks should never change current $_SESSION
+ $DB->force_transaction_rollback();
}
}
// Activities contexts.
$mods = array();
$prog = new progress_bar('modbar', 500, true);
- $DB->begin_sql();
+ $transaction = $DB->start_delegated_transaction();
for ($i = 0; $i < $nummodules; $i++) {
$context = insert_context(CONTEXT_MODULE, $i, $courses[array_rand($courses)]);
$mods[$context->id] = $context;
$prog->update($i, $nummodules, '');
}
}
- $DB->commit_sql();
+ $transaction->allow_commit();
echo $OUTPUT->notification('Created ' . $nummodules . ' module contexts.', 'notifysuccess'); flush();
$contexts = $categories + $courses + $mods;
// Local overrides.
$localstates = array(TEXTFILTER_OFF => 0, TEXTFILTER_ON => 0);
$prog = new progress_bar('locbar', 500, true);
- $DB->begin_sql();
+ $transaction = $DB->start_delegated_transaction();
for ($i = 0; $i < $numoverrides; $i++) {
filter_set_local_state(array_rand($installedfilters), array_rand($contexts), array_rand($localstates));
if ($i % 50) {
$prog->update($i, $numoverrides, '');
}
}
- $DB->commit_sql();
+ $transaction->allow_commit();
echo $OUTPUT->notification('Set ' . $numoverrides . ' local overrides.', 'notifysuccess'); flush();
// Local config.
$variablenames = array('frog' => 0, 'toad' => 0, 'elver' => 0, 'eft' => 0, 'tadpole' => 0);
$prog = new progress_bar('confbar', 500, true);
- $DB->begin_sql();
+ $transaction = $DB->start_delegated_transaction();
for ($i = 0; $i < $numconfigs; $i++) {
filter_set_local_config(array_rand($installedfilters), array_rand($contexts),
array_rand($variablenames), random_string(rand(20, 40)));
$prog->update($i, $numconfigs, '');
}
}
- $DB->commit_sql();
+ $transaction->allow_commit();
echo $OUTPUT->notification('Set ' . $numconfigs . ' local configs.', 'notifysuccess'); flush();
}
*
* @param float $newgrade the new maximum grade for the quiz.
* @param object $quiz the quiz we are updating. Passed by reference so its grade field can be updated too.
- * @return boolean indicating success or failure.
+ * @return boolean indicating success or failure. TODO: MDL-20625
*/
function quiz_set_grade($newgrade, &$quiz) {
global $DB;
}
// Use a transaction, so that on those databases that support it, this is safer.
- $DB->begin_sql();
-
- // Update the quiz table.
- $success = $DB->set_field('quiz', 'grade', $newgrade, array('id' => $quiz->instance));
-
- // Rescaling the other data is only possible if the old grade was non-zero.
- if ($quiz->grade > 1e-7) {
- global $CFG;
-
- $factor = $newgrade/$quiz->grade;
- $quiz->grade = $newgrade;
-
- // Update the quiz_grades table.
- $timemodified = time();
- $success = $success && $DB->execute("
- UPDATE {quiz_grades}
- SET grade = ? * grade, timemodified = ?
- WHERE quiz = ?
- ", array($factor, $timemodified, $quiz->id));
-
- // Update the quiz_feedback table.
- $success = $success && $DB->execute("
- UPDATE {quiz_feedback}
- SET mingrade = ? * mingrade, maxgrade = ? * maxgrade
- WHERE quizid = ?
- ", array($factor, $factor, $quiz->id));
- }
+ $transaction = $DB->start_delegated_transaction();
+
+ try {
+ // Update the quiz table.
+ $DB->set_field('quiz', 'grade', $newgrade, array('id' => $quiz->instance));
+
+ // Rescaling the other data is only possible if the old grade was non-zero.
+ if ($quiz->grade > 1e-7) {
+ global $CFG;
+
+ $factor = $newgrade/$quiz->grade;
+ $quiz->grade = $newgrade;
+
+ // Update the quiz_grades table.
+ $timemodified = time();
+ $DB->execute("
+ UPDATE {quiz_grades}
+ SET grade = ? * grade, timemodified = ?
+ WHERE quiz = ?
+ ", array($factor, $timemodified, $quiz->id));
+
+ // Update the quiz_feedback table.
+ $DB->execute("
+ UPDATE {quiz_feedback}
+ SET mingrade = ? * mingrade, maxgrade = ? * maxgrade
+ WHERE quizid = ?
+ ", array($factor, $factor, $quiz->id));
+ }
- // update grade item and send all grades to gradebook
- quiz_grade_item_update($quiz);
- quiz_update_grades($quiz);
+ // update grade item and send all grades to gradebook
+ quiz_grade_item_update($quiz);
+ quiz_update_grades($quiz);
- if ($success) {
- return $DB->commit_sql();
- } else {
- $DB->rollback_sql();
- return false;
+ $transaction->allow_commit();
+ return true;
+
+ } catch (Exception $e) {
+ //TODO: MDL-20625 this part was returning false, but now throws exception
+ $transaction->rollback($e);
}
}