Merge branch 'wip-MDL-29332-master-3' of git://github.com/marinaglancy/moodle

This commit is contained in:
Eloy Lafuente (stronk7) 2016-09-19 11:51:08 +02:00
commit cecba1d92e
11 changed files with 157 additions and 22 deletions

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="lib/db" VERSION="20160404" COMMENT="XMLDB file for core Moodle tables"
<XMLDB PATH="lib/db" VERSION="20160804" COMMENT="XMLDB file for core Moodle tables"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd"
>
@ -1353,9 +1353,6 @@
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="attemptstepid" TYPE="foreign" FIELDS="attemptstepid" REFTABLE="question_attempt_steps" REFFIELDS="id"/>
</KEYS>
<INDEXES>
<INDEX NAME="attemptstepid-name" UNIQUE="true" FIELDS="attemptstepid, name"/>
</INDEXES>
</TABLE>
<TABLE NAME="question_statistics" COMMENT="Statistics for individual questions used in an activity.">
<FIELDS>

View File

@ -2223,5 +2223,20 @@ function xmldb_main_upgrade($oldversion) {
upgrade_main_savepoint(true, 2016091900.00);
}
if ($oldversion < 2016091900.02) {
// Define index attemptstepid-name (unique) to be dropped from question_attempt_step_data.
$table = new xmldb_table('question_attempt_step_data');
$index = new xmldb_index('attemptstepid-name', XMLDB_INDEX_UNIQUE, array('attemptstepid', 'name'));
// Conditionally launch drop index attemptstepid-name.
if ($dbman->index_exists($table, $index)) {
$dbman->drop_index($table, $index);
}
// Main savepoint reached.
upgrade_main_savepoint(true, 2016091900.02);
}
return true;
}

View File

@ -2050,6 +2050,33 @@ abstract class moodle_database {
return $this->sql_order_by_text($fieldname, $numchars);
}
/**
* Returns an equal (=) or not equal (<>) part of a query.
*
* Note the use of this method may lead to slower queries (full scans) so
* use it only when needed and against already reduced data sets.
*
* @since Moodle 3.2
*
* @param string $fieldname Usually the name of the table column.
* @param string $param Usually the bound query parameter (?, :named).
* @param bool $casesensitive Use case sensitive search when set to true (default).
* @param bool $accentsensitive Use accent sensitive search when set to true (default). (not all databases support accent insensitive)
* @param bool $notequal True means not equal (<>)
* @return string The SQL code fragment.
*/
public function sql_equal($fieldname, $param, $casesensitive = true, $accentsensitive = true, $notequal = false) {
// Note that, by default, it's assumed that the correct sql equal operations are
// case sensitive. Only databases not observing this behavior must override the method.
// Also, accent sensitiveness only will be handled by databases supporting it.
$equalop = $notequal ? '<>' : '=';
if ($casesensitive) {
return "$fieldname $equalop $param";
} else {
return "LOWER($fieldname) $equalop LOWER($param)";
}
}
/**
* Returns 'LIKE' part of a query.
*

View File

@ -1221,6 +1221,24 @@ class mssql_native_moodle_database extends moodle_database {
return $this->collation;
}
public function sql_equal($fieldname, $param, $casesensitive = true, $accentsensitive = true, $notequal = false) {
$equalop = $notequal ? '<>' : '=';
$collation = $this->get_collation();
if ($casesensitive) {
$collation = str_replace('_CI', '_CS', $collation);
} else {
$collation = str_replace('_CS', '_CI', $collation);
}
if ($accentsensitive) {
$collation = str_replace('_AI', '_AS', $collation);
} else {
$collation = str_replace('_AS', '_AI', $collation);
}
return "$fieldname COLLATE $collation $equalop $param";
}
/**
* Returns 'LIKE' part of a query.
*

View File

@ -1511,6 +1511,24 @@ class mysqli_native_moodle_database extends moodle_database {
return ' CAST(' . $fieldname . ' AS DECIMAL(65,7)) ';
}
public function sql_equal($fieldname, $param, $casesensitive = true, $accentsensitive = true, $notequal = false) {
$equalop = $notequal ? '<>' : '=';
if ($casesensitive) {
// Current MySQL versions do not support case sensitive and accent insensitive.
return "$fieldname COLLATE utf8_bin $equalop $param";
} else if ($accentsensitive) {
// Case insensitive and accent sensitive, we can force a binary comparison once all texts are using the same case.
return "LOWER($fieldname) COLLATE utf8_bin $equalop LOWER($param)";
} else {
// Case insensitive and accent insensitive. All collations are that way, but utf8_bin.
$collation = '';
if ($this->get_dbcollation() == 'utf8_bin') {
$collation = 'COLLATE utf8_unicode_ci';
}
return "$fieldname $collation $equalop $param";
}
}
/**
* Returns 'LIKE' part of a query.
*

View File

@ -1288,6 +1288,24 @@ class sqlsrv_native_moodle_database extends moodle_database {
return $this->collation;
}
public function sql_equal($fieldname, $param, $casesensitive = true, $accentsensitive = true, $notequal = false) {
$equalop = $notequal ? '<>' : '=';
$collation = $this->get_collation();
if ($casesensitive) {
$collation = str_replace('_CI', '_CS', $collation);
} else {
$collation = str_replace('_CS', '_CI', $collation);
}
if ($accentsensitive) {
$collation = str_replace('_AI', '_AS', $collation);
} else {
$collation = str_replace('_AS', '_AI', $collation);
}
return "$fieldname COLLATE $collation $equalop $param";
}
/**
* Returns 'LIKE' part of a query.
*

View File

@ -3829,7 +3829,7 @@ class core_dml_testcase extends database_driver_testcase {
}
}
public function test_sql_binary_equal() {
public function test_sql_equal() {
$DB = $this->tdb;
$dbman = $DB->get_manager();
@ -3838,20 +3838,51 @@ class core_dml_testcase extends database_driver_testcase {
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null);
$table->add_field('name2', XMLDB_TYPE_CHAR, '255', null, null, null, null);
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
$dbman->create_table($table);
$DB->insert_record($tablename, array('name'=>'aaa'));
$DB->insert_record($tablename, array('name'=>'aáa'));
$DB->insert_record($tablename, array('name'=>'aäa'));
$DB->insert_record($tablename, array('name'=>'bbb'));
$DB->insert_record($tablename, array('name'=>'BBB'));
$DB->insert_record($tablename, array('name' => 'one', 'name2' => 'one'));
$DB->insert_record($tablename, array('name' => 'ONE', 'name2' => 'ONE'));
$DB->insert_record($tablename, array('name' => 'two', 'name2' => 'TWO'));
$DB->insert_record($tablename, array('name' => 'öne', 'name2' => 'one'));
$DB->insert_record($tablename, array('name' => 'öne', 'name2' => 'ÖNE'));
$records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE name = ?", array('bbb'));
$this->assertEquals(1, count($records), 'SQL operator "=" is expected to be case sensitive');
// Case sensitive and accent sensitive (equal and not equal).
$sql = "SELECT * FROM {{$tablename}} WHERE " . $DB->sql_equal('name', '?', true, true, false);
$records = $DB->get_records_sql($sql, array('one'));
$this->assertCount(1, $records);
$sql = "SELECT * FROM {{$tablename}} WHERE " . $DB->sql_equal('name', ':name', true, true, true);
$records = $DB->get_records_sql($sql, array('name' => 'one'));
$this->assertCount(4, $records);
// And with column comparison instead of params.
$sql = "SELECT * FROM {{$tablename}} WHERE " . $DB->sql_equal('name', 'name2', true, true, false);
$records = $DB->get_records_sql($sql);
$this->assertCount(2, $records);
$records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE name = ?", array('aaa'));
$this->assertEquals(1, count($records), 'SQL operator "=" is expected to be accent sensitive');
// Case insensitive and accent sensitive (equal and not equal).
$sql = "SELECT * FROM {{$tablename}} WHERE " . $DB->sql_equal('name', '?', false, true, false);
$records = $DB->get_records_sql($sql, array('one'));
$this->assertCount(2, $records);
$sql = "SELECT * FROM {{$tablename}} WHERE " . $DB->sql_equal('name', ':name', false, true, true);
$records = $DB->get_records_sql($sql, array('name' => 'one'));
$this->assertCount(3, $records);
// And with column comparison instead of params.
$sql = "SELECT * FROM {{$tablename}} WHERE " . $DB->sql_equal('name', 'name2', false, true, false);
$records = $DB->get_records_sql($sql);
$this->assertCount(4, $records);
// TODO: Accent insensitive is not cross-db, only some drivers support it, so just verify the queries work.
$sql = "SELECT * FROM {{$tablename}} WHERE " . $DB->sql_equal('name', '?', true, false);
$records = $DB->get_records_sql($sql, array('one'));
$this->assertGreaterThanOrEqual(1, count($records)); // At very least, there is 1 record with CS/AI "one".
$sql = "SELECT * FROM {{$tablename}} WHERE " . $DB->sql_equal('name', '?', false, false);
$records = $DB->get_records_sql($sql, array('one'));
$this->assertGreaterThanOrEqual(2, count($records)); // At very least, there are 2 records with CI/AI "one".
// And with column comparison instead of params.
$sql = "SELECT * FROM {{$tablename}} WHERE " . $DB->sql_equal('name', 'name2', false, false);
$records = $DB->get_records_sql($sql);
$this->assertGreaterThanOrEqual(4, count($records)); // At very least, there are 4 records with CI/AI names matching.
}
public function test_sql_like() {

View File

@ -78,6 +78,8 @@ information provided here is intended especially for developers.
- $CFG->wwwroot: http://example.com/moodle
- $CFG->alternateloginurl : /my/super/login.php
- Login url will be: http://example.com/moodle/my/super/login.php (moodle root based)
* Database (DML) layer:
- new sql_equal() method available for places where case sensitive/insensitive varchar comparisons are required.
=== 3.1 ===

View File

@ -287,10 +287,19 @@ class question_engine_data_mapper {
*/
public function update_question_attempt_metadata(question_attempt $qa, array $names) {
global $DB;
list($condition, $params) = $DB->get_in_or_equal($names);
$params[] = $qa->get_step(0)->get_id();
if (!$names) {
return [];
}
// Use case-sensitive function sql_equal() and not get_in_or_equal().
// Some databases may use case-insensitive collation, we don't want to delete 'X' instead of 'x'.
$sqls = [];
$params = [$qa->get_step(0)->get_id()];
foreach ($names as $name) {
$sqls[] = $DB->sql_equal('name', '?');
$params[] = $name;
}
$DB->delete_records_select('question_attempt_step_data',
'name ' . $condition . ' AND attemptstepid = ?', $params);
'attemptstepid = ? AND (' . join(' OR ', $sqls) . ')', $params);
return $this->insert_question_attempt_metadata($qa, $names);
}

View File

@ -281,7 +281,7 @@ class qtype_calculated extends question_type {
if ($sharedatasetdefs = $DB->get_records_select(
'question_dataset_definitions',
"type = '1'
AND name = ?
AND " . $DB->sql_equal('name', '?') . "
AND category = ?
ORDER BY id DESC ", array($dataset->name, $question->category)
)) { // So there is at least one.
@ -1400,7 +1400,7 @@ class qtype_calculated extends question_type {
// can manage to automatically take care of
// some possible realtime concurrence.
if ($olderdatasetdefs = $DB->get_records_select('question_dataset_definitions',
"type = ? AND name = ? AND category = ? AND id < ?
"type = ? AND " . $DB->sql_equal('name', '?') . " AND category = ? AND id < ?
ORDER BY id DESC",
array($datasetdef->type, $datasetdef->name,
$datasetdef->category, $datasetdef->id))) {
@ -1484,7 +1484,7 @@ class qtype_calculated extends question_type {
// Construct question local options.
$sql = "SELECT a.*
FROM {question_dataset_definitions} a, {question_datasets} b
WHERE a.id = b.datasetdefinition AND a.type = '1' AND b.question = ? AND a.name = ?";
WHERE a.id = b.datasetdefinition AND a.type = '1' AND b.question = ? AND " . $DB->sql_equal('a.name', '?');
$currentdatasetdef = $DB->get_record_sql($sql, array($form->id, $name));
if (!$currentdatasetdef) {
$currentdatasetdef = new stdClass();
@ -1506,7 +1506,7 @@ class qtype_calculated extends question_type {
WHERE a.id = b.datasetdefinition
AND a.type = '1'
AND a.category = ?
AND a.name = ?", array($form->category, $name));
AND " . $DB->sql_equal('a.name', '?'), array($form->category, $name));
$type = 1;
$key = "{$type}-{$form->category}-{$name}";
if (!empty($categorydatasetdefs)) {

View File

@ -29,7 +29,7 @@
defined('MOODLE_INTERNAL') || die();
$version = 2016091900.01; // YYYYMMDD = weekly release date of this DEV branch.
$version = 2016091900.02; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.