mirror of
https://github.com/moodle/moodle.git
synced 2025-01-17 21:49:15 +01:00
MDL-20625 new delegated transaction support in DML
This commit is contained in:
parent
88d39d6992
commit
d5a8d9aa71
@ -796,7 +796,7 @@ if ( !is_object($PHPCAS_CLIENT) ) {
|
||||
$creatorrole = false;
|
||||
}
|
||||
|
||||
$DB->begin_sql();
|
||||
$transaction = $DB->start_delegated_transaction();
|
||||
$xcount = 0;
|
||||
$maxxcount = 100;
|
||||
|
||||
@ -816,14 +816,8 @@ if ( !is_object($PHPCAS_CLIENT) ) {
|
||||
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
|
||||
@ -851,7 +845,7 @@ if ( !is_object($PHPCAS_CLIENT) ) {
|
||||
$creatorrole = false;
|
||||
}
|
||||
|
||||
$DB->begin_sql();
|
||||
$transaction = $DB->start_delegated_transaction();
|
||||
foreach ($add_users as $user) {
|
||||
$user = $this->get_userinfo_asobj($user->username);
|
||||
|
||||
@ -879,7 +873,7 @@ if ( !is_object($PHPCAS_CLIENT) ) {
|
||||
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";
|
||||
|
@ -344,7 +344,7 @@ class auth_plugin_db extends auth_plugin_base {
|
||||
|
||||
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);
|
||||
@ -375,7 +375,7 @@ class auth_plugin_db extends auth_plugin_base {
|
||||
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;
|
||||
|
@ -729,7 +729,7 @@ class auth_plugin_ldap extends auth_plugin_base {
|
||||
$creatorrole = false;
|
||||
}
|
||||
|
||||
$DB->begin_sql();
|
||||
$transaction = $DB->start_delegated_transaction();
|
||||
$xcount = 0;
|
||||
$maxxcount = 100;
|
||||
|
||||
@ -749,14 +749,8 @@ class auth_plugin_ldap extends auth_plugin_base {
|
||||
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
|
||||
@ -785,7 +779,7 @@ class auth_plugin_ldap extends auth_plugin_base {
|
||||
$creatorrole = false;
|
||||
}
|
||||
|
||||
$DB->begin_sql();
|
||||
$transaction = $DB->start_delegated_transaction();
|
||||
foreach ($add_users as $user) {
|
||||
$user = $this->get_userinfo_asobj($user->username);
|
||||
|
||||
@ -816,7 +810,7 @@ class auth_plugin_ldap extends auth_plugin_base {
|
||||
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";
|
||||
|
@ -854,7 +854,7 @@ class auth_plugin_mnet extends auth_plugin_base {
|
||||
$start = ob_start();
|
||||
|
||||
$returnString = '';
|
||||
$DB->begin_sql();
|
||||
$transaction = $DB->start_delegated_transaction();
|
||||
$useridarray = array();
|
||||
|
||||
foreach($array as $logEntry) {
|
||||
@ -883,7 +883,7 @@ class auth_plugin_mnet extends auth_plugin_base {
|
||||
}
|
||||
}
|
||||
$MNET_REMOTE_CLIENT->commit();
|
||||
$DB->commit_sql();
|
||||
$transaction->allow_commit();
|
||||
|
||||
$end = ob_end_clean();
|
||||
|
||||
|
@ -219,7 +219,8 @@ function sync_enrolments($role = null) {
|
||||
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);
|
||||
@ -416,7 +417,7 @@ function sync_enrolments($role = null) {
|
||||
$ers->close(); // release the handle
|
||||
}
|
||||
|
||||
$DB->commit_sql();
|
||||
$transaction->allow_commit();
|
||||
|
||||
// we are done now, a bit of housekeeping
|
||||
fix_course_sortorder();
|
||||
|
@ -49,11 +49,11 @@ if ($confirm && data_submitted()) {
|
||||
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'));
|
||||
|
@ -60,36 +60,31 @@ class moodle_group_external extends external_api {
|
||||
|
||||
$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();
|
||||
$transaction = $DB->start_delegated_transaction();
|
||||
|
||||
foreach ($params['groups'] as $group) {
|
||||
$group = (object)$group;
|
||||
$groups = array();
|
||||
|
||||
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');
|
||||
}
|
||||
foreach ($params['groups'] as $group) {
|
||||
$group = (object)$group;
|
||||
|
||||
// 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;
|
||||
if (trim($group->name) == '') {
|
||||
throw new invalid_parameter_exception('Invalid group name');
|
||||
}
|
||||
} catch (Exception $ex) {
|
||||
$DB->rollback_sql();
|
||||
throw $ex;
|
||||
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;
|
||||
}
|
||||
$DB->commit_sql();
|
||||
|
||||
$transaction->allow_commit();
|
||||
|
||||
return $groups;
|
||||
}
|
||||
@ -242,30 +237,27 @@ class moodle_group_external extends external_api {
|
||||
|
||||
$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();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -361,33 +353,29 @@ class moodle_group_external extends external_api {
|
||||
|
||||
$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();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -429,29 +417,26 @@ class moodle_group_external extends external_api {
|
||||
|
||||
$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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -178,6 +178,7 @@ $string['ddltablenotexist'] = 'Table \"$a\" does not exist';
|
||||
$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.';
|
||||
|
@ -2455,24 +2455,18 @@ function cleanup_contexts() {
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,7 @@
|
||||
|
||||
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 /////////////////////////////////////////////////////////
|
||||
|
||||
@ -109,8 +110,10 @@ abstract class moodle_database {
|
||||
/** @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;
|
||||
@ -281,8 +284,10 @@ abstract class moodle_database {
|
||||
* 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) {
|
||||
@ -1883,59 +1888,181 @@ abstract class moodle_database {
|
||||
}
|
||||
|
||||
/// 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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() 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.
|
||||
* 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 bool success
|
||||
* @return moodle_transaction
|
||||
*/
|
||||
public function begin_sql() {
|
||||
if ($this->intransaction) {
|
||||
debugging('Transaction already in progress');
|
||||
return false;
|
||||
public function start_delegated_transaction() {
|
||||
$transaction = new moodle_transaction($this);
|
||||
$this->transactions[] = $transaction;
|
||||
if (count($this->transactions) == 1) {
|
||||
$this->begin_transaction();
|
||||
}
|
||||
$this->intransaction = true;
|
||||
return true;
|
||||
return $transaction;
|
||||
}
|
||||
|
||||
/**
|
||||
* on DBs that support it, commit the transaction
|
||||
* @return bool success
|
||||
* Driver specific start 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 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 = false;
|
||||
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, rollback the transaction
|
||||
* @return bool success
|
||||
* Driver specific commit 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 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Driver specific bort of real database transaction,
|
||||
* this can not be used directly in code.
|
||||
* @return void
|
||||
*/
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// now enable transactions again
|
||||
$this->transactions = array(); // unfortunately all unfinished exceptions are kept in memory
|
||||
$this->force_rollback = false;
|
||||
}
|
||||
|
||||
/// session locking
|
||||
|
93
lib/dml/moodle_transaction.php
Normal file
93
lib/dml/moodle_transaction.php
Normal file
@ -0,0 +1,93 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
@ -1199,56 +1199,44 @@ s only returning name of SQL substring function, it now requires all parameters.
|
||||
/// 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;
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,8 @@ class mysqli_native_moodle_database extends moodle_database {
|
||||
protected $mysqli = null;
|
||||
private $temptables; // Control existing temptables (mysql_moodle_temptables object)
|
||||
|
||||
private $transactions_supported = null;
|
||||
|
||||
/**
|
||||
* Attempt to create the database
|
||||
* @param string $dbhost
|
||||
@ -1039,28 +1041,45 @@ class mysqli_native_moodle_database extends moodle_database {
|
||||
|
||||
/// 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 ;-)
|
||||
*
|
||||
* this is _very_ useful for massive updates
|
||||
* MyISAM does not support support transactions.
|
||||
*
|
||||
* @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";
|
||||
@ -1072,32 +1091,34 @@ class mysqli_native_moodle_database extends moodle_database {
|
||||
$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);
|
||||
|
@ -1527,46 +1527,35 @@ class oci_native_moodle_database extends moodle_database {
|
||||
|
||||
/// 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;
|
||||
}
|
||||
}
|
||||
|
@ -542,57 +542,36 @@ abstract class pdo_moodle_database extends moodle_database {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1122,54 +1122,45 @@ class pgsql_native_moodle_database extends moodle_database {
|
||||
|
||||
/// 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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2297,81 +2297,6 @@ class dml_test extends UnitTestCase {
|
||||
|
||||
}
|
||||
|
||||
function test_begin_sql() {
|
||||
$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;
|
||||
|
||||
$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');
|
||||
}
|
||||
}
|
||||
|
||||
function test_commit_sql() {
|
||||
$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;
|
||||
|
||||
$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');
|
||||
}
|
||||
}
|
||||
|
||||
function test_rollback_sql() {
|
||||
$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;
|
||||
|
||||
$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');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test some more complex SQL syntax which moodle uses and depends on to work
|
||||
* useful to determine if new database libraries can be supported.
|
||||
@ -2407,6 +2332,254 @@ class dml_test extends UnitTestCase {
|
||||
$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_nested_transactions() {
|
||||
$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;
|
||||
|
||||
// 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_transactions_forbidden() {
|
||||
$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;
|
||||
|
||||
$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));
|
||||
}
|
||||
|
||||
function test_wrong_transactions() {
|
||||
$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;
|
||||
|
||||
|
||||
// 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);
|
||||
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2454,4 +2627,7 @@ class moodle_database_for_testing extends moodle_database {
|
||||
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() {}
|
||||
}
|
||||
|
@ -196,6 +196,23 @@ class dml_write_exception extends dml_exception {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
|
@ -57,6 +57,8 @@ class database_importer {
|
||||
* How to use transactions.
|
||||
*/
|
||||
protected $transactionmode = 'allinone';
|
||||
/** Transaction object */
|
||||
protected $transaction;
|
||||
|
||||
/**
|
||||
* Object constructor.
|
||||
@ -123,7 +125,7 @@ class database_importer {
|
||||
throw new dbtransfer_exception('importschemaexception', $details);
|
||||
}
|
||||
if ($this->transactionmode == 'allinone') {
|
||||
$this->mdb->begin_sql();
|
||||
$this->transaction = $this->mdb->start_delegated_transaction();
|
||||
}
|
||||
}
|
||||
|
||||
@ -140,7 +142,7 @@ class database_importer {
|
||||
*/
|
||||
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);
|
||||
@ -171,7 +173,7 @@ class database_importer {
|
||||
}
|
||||
}
|
||||
if ($this->transactionmode == 'pertable') {
|
||||
$this->mdb->commit_sql();
|
||||
$this->transaction->allow_commit();
|
||||
}
|
||||
}
|
||||
|
||||
@ -182,7 +184,7 @@ class database_importer {
|
||||
*/
|
||||
public function finish_database_import() {
|
||||
if ($this->transactionmode == 'allinone') {
|
||||
$this->mdb->commit_sql();
|
||||
$this->transaction->allow_commit();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3392,62 +3392,51 @@ function delete_user($user) {
|
||||
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));
|
||||
|
||||
// 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!
|
||||
|
||||
// now do a final accesslib cleanup - removes all role assingments in user context and context itself
|
||||
delete_context(CONTEXT_USER, $user->id);
|
||||
|
||||
require_once($CFG->dirroot.'/tag/lib.php');
|
||||
tag_set('user', $user->id, array());
|
||||
|
||||
// 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++;
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
$DB->commit_sql();
|
||||
|
||||
// notify auth plugin - do not block the delete even when plugin fails
|
||||
$authplugin = get_auth_plugin($user->auth);
|
||||
$authplugin->user_delete($user);
|
||||
|
||||
events_trigger('user_deleted', $user);
|
||||
|
||||
} catch (Exception $e) {
|
||||
$DB->rollback_sql();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
//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));
|
||||
|
||||
// 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!
|
||||
|
||||
// now do a final accesslib cleanup - removes all role assingments in user context and context itself
|
||||
delete_context(CONTEXT_USER, $user->id);
|
||||
|
||||
require_once($CFG->dirroot.'/tag/lib.php');
|
||||
tag_set('user', $user->id, array());
|
||||
|
||||
// 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++;
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
// notify auth plugin - do not block the delete even when plugin fails
|
||||
$authplugin = get_auth_plugin($user->auth);
|
||||
$authplugin->user_delete($user);
|
||||
|
||||
events_trigger('user_deleted', $user);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -859,6 +859,9 @@ class xhtml_container_stack {
|
||||
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) {
|
||||
|
@ -874,12 +874,16 @@ class moodle_core_renderer extends moodle_renderer_base {
|
||||
* @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)) {
|
||||
|
@ -208,14 +208,12 @@ function default_exception_handler($ex) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -185,7 +185,7 @@ function populate_test_database($syscontext, $numcategories, $numcourses, $nummo
|
||||
// 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;
|
||||
@ -193,7 +193,7 @@ function populate_test_database($syscontext, $numcategories, $numcourses, $nummo
|
||||
$prog->update($i, $nummodules, '');
|
||||
}
|
||||
}
|
||||
$DB->commit_sql();
|
||||
$transaction->allow_commit();
|
||||
echo $OUTPUT->notification('Created ' . $nummodules . ' module contexts.', 'notifysuccess'); flush();
|
||||
|
||||
$contexts = $categories + $courses + $mods;
|
||||
@ -212,20 +212,20 @@ function populate_test_database($syscontext, $numcategories, $numcourses, $nummo
|
||||
// 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)));
|
||||
@ -233,7 +233,7 @@ function populate_test_database($syscontext, $numcategories, $numcourses, $nummo
|
||||
$prog->update($i, $numconfigs, '');
|
||||
}
|
||||
}
|
||||
$DB->commit_sql();
|
||||
$transaction->allow_commit();
|
||||
echo $OUTPUT->notification('Set ' . $numconfigs . ' local configs.', 'notifysuccess'); flush();
|
||||
}
|
||||
|
||||
|
@ -496,7 +496,7 @@ function quiz_has_feedback($quiz) {
|
||||
*
|
||||
* @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;
|
||||
@ -507,43 +507,45 @@ function quiz_set_grade($newgrade, &$quiz) {
|
||||
}
|
||||
|
||||
// Use a transaction, so that on those databases that support it, this is safer.
|
||||
$DB->begin_sql();
|
||||
$transaction = $DB->start_delegated_transaction();
|
||||
|
||||
// Update the quiz table.
|
||||
$success = $DB->set_field('quiz', 'grade', $newgrade, array('id' => $quiz->instance));
|
||||
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;
|
||||
// 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;
|
||||
$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_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.
|
||||
$success = $success && $DB->execute("
|
||||
UPDATE {quiz_feedback}
|
||||
SET mingrade = ? * mingrade, maxgrade = ? * maxgrade
|
||||
WHERE quizid = ?
|
||||
", array($factor, $factor, $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);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user