mirror of
https://github.com/moodle/moodle.git
synced 2025-03-14 04:30:15 +01:00
Merge branch 'MDL-26458_PULL' of git://github.com/nebgor/moodle
This commit is contained in:
commit
4eca364392
@ -1015,6 +1015,19 @@ class dml_test extends UnitTestCase {
|
||||
$inskey6 = $DB->insert_record($tablename, array('course' => 1));
|
||||
$inskey7 = $DB->insert_record($tablename, array('course' => 0));
|
||||
|
||||
$table2 = $this->get_test_table("2");
|
||||
$tablename2 = $table2->getName();
|
||||
$table2->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
|
||||
$table2->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
|
||||
$table2->add_field('nametext', XMLDB_TYPE_TEXT, 'small', null, null, null, null);
|
||||
$table2->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
|
||||
$dbman->create_table($table2);
|
||||
|
||||
$DB->insert_record($tablename2, array('course'=>3, 'nametext'=>'badabing'));
|
||||
$DB->insert_record($tablename2, array('course'=>4, 'nametext'=>'badabang'));
|
||||
$DB->insert_record($tablename2, array('course'=>5, 'nametext'=>'badabung'));
|
||||
$DB->insert_record($tablename2, array('course'=>6, 'nametext'=>'badabong'));
|
||||
|
||||
$records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE course = ?", array(3));
|
||||
$this->assertEqual(2, count($records));
|
||||
$this->assertEqual($inskey1, reset($records)->id);
|
||||
@ -1068,6 +1081,28 @@ class dml_test extends UnitTestCase {
|
||||
$this->assertEqual($inskey3, reset($records)->id);
|
||||
$this->assertEqual($inskey2, end($records)->id);
|
||||
|
||||
// test 2 tables with aliases and limits with order bys
|
||||
$sql = "SELECT t1.id, t1.course AS cid, t2.nametext FROM {{$tablename}} t1, {{$tablename2}} t2
|
||||
WHERE t2.course=t1.course ORDER BY t1.course, ". $DB->sql_compare_text('t2.nametext');
|
||||
$records = $DB->get_records_sql($sql, null, 2, 2); // Skip courses 3 and 6, get 4 and 5
|
||||
$this->assertEqual(2, count($records));
|
||||
$this->assertEqual('5', end($records)->cid);
|
||||
$this->assertEqual('4', reset($records)->cid);
|
||||
|
||||
// test 2 tables with aliases and limits with order bys (limit which is out of range)
|
||||
$records = $DB->get_records_sql($sql, null, 2, 21098765432109876543210); // Skip course {3,6}, get {4,5}
|
||||
$this->assertEqual(2, count($records));
|
||||
$this->assertEqual('5', end($records)->cid);
|
||||
$this->assertEqual('4', reset($records)->cid);
|
||||
|
||||
// test 2 tables with aliases and limits with order bys (limit which is out of range)
|
||||
$records = $DB->get_records_sql($sql, null, 21098765432109876543210, 2); // Skip all courses
|
||||
$this->assertEqual(0, count($records));
|
||||
|
||||
// test 2 tables with aliases and limits with order bys (limit which is out of range)
|
||||
$records = $DB->get_records_sql($sql, null, 21098765432109876543210, 21098765432109876543210); // Skip all courses
|
||||
$this->assertEqual(0, count($records));
|
||||
|
||||
// TODO: Test limits in queries having DISTINCT clauses
|
||||
|
||||
// note: fetching nulls, empties, LOBs already tested by test_update_record() no needed here
|
||||
@ -2040,6 +2075,7 @@ class dml_test extends UnitTestCase {
|
||||
$record = $DB->get_record($tablename, array('course' => 2));
|
||||
$this->assertEqual($newclob, $record->onetext, 'Test "small" CLOB update (full contents output disabled)');
|
||||
$this->assertEqual($newblob, $record->onebinary, 'Test "small" BLOB update (full contents output disabled)');
|
||||
|
||||
}
|
||||
|
||||
public function test_set_field() {
|
||||
@ -2401,6 +2437,70 @@ class dml_test extends UnitTestCase {
|
||||
$this->assertTrue($DB->record_exists_sql("SELECT * FROM {{$tablename}} WHERE course = ?", array(3)));
|
||||
}
|
||||
|
||||
public function test_recordset_locks_delete() {
|
||||
$DB = $this->tdb;
|
||||
$dbman = $DB->get_manager();
|
||||
|
||||
//Setup
|
||||
$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);
|
||||
|
||||
$DB->insert_record($tablename, array('course' => 1));
|
||||
$DB->insert_record($tablename, array('course' => 2));
|
||||
$DB->insert_record($tablename, array('course' => 3));
|
||||
$DB->insert_record($tablename, array('course' => 4));
|
||||
$DB->insert_record($tablename, array('course' => 5));
|
||||
$DB->insert_record($tablename, array('course' => 6));
|
||||
|
||||
// Test against db write locking while on an open recordset
|
||||
$rs = $DB->get_recordset($tablename, array(), null, 'course', 2, 2); // get courses = {3,4}
|
||||
foreach ($rs as $record) {
|
||||
$cid = $record->course;
|
||||
$DB->delete_records($tablename, array('course' => $cid));
|
||||
$this->assertFalse($DB->record_exists($tablename, array('course' => $cid)));
|
||||
}
|
||||
$rs->close();
|
||||
|
||||
$this->assertEqual(4, $DB->count_records($tablename, array()));
|
||||
}
|
||||
|
||||
public function test_recordset_locks_update() {
|
||||
$DB = $this->tdb;
|
||||
$dbman = $DB->get_manager();
|
||||
|
||||
//Setup
|
||||
$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);
|
||||
|
||||
$DB->insert_record($tablename, array('course' => 1));
|
||||
$DB->insert_record($tablename, array('course' => 2));
|
||||
$DB->insert_record($tablename, array('course' => 3));
|
||||
$DB->insert_record($tablename, array('course' => 4));
|
||||
$DB->insert_record($tablename, array('course' => 5));
|
||||
$DB->insert_record($tablename, array('course' => 6));
|
||||
|
||||
// Test against db write locking while on an open recordset
|
||||
$rs = $DB->get_recordset($tablename, array(), null, 'course', 2, 2); // get courses = {3,4}
|
||||
foreach ($rs as $record) {
|
||||
$cid = $record->course;
|
||||
$DB->set_field($tablename, 'course', 10, array('course' => $cid));
|
||||
$this->assertFalse($DB->record_exists($tablename, array('course' => $cid)));
|
||||
}
|
||||
$rs->close();
|
||||
|
||||
$this->assertEqual(2, $DB->count_records($tablename, array('course' => 10)));
|
||||
}
|
||||
|
||||
public function test_delete_records() {
|
||||
$DB = $this->tdb;
|
||||
$dbman = $DB->get_manager();
|
||||
|
@ -347,13 +347,18 @@ class sqlsrv_native_moodle_database extends moodle_database {
|
||||
* @param mixed $params array of params for binding. If NULL, they are ignored.
|
||||
* @param mixed $sql_query_type - Type of operation
|
||||
* @param mixed $free_result - Default true, transaction query will be freed.
|
||||
* @param mixed $scrollable - Default false, to use for quickly seeking to target records
|
||||
*/
|
||||
private function do_query($sql, $params, $sql_query_type, $free_result = true) {
|
||||
private function do_query($sql, $params, $sql_query_type, $free_result = true, $scrollable = false) {
|
||||
list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
|
||||
|
||||
$sql = $this->emulate_bound_params($sql, $params);
|
||||
$this->query_start($sql, $params, $sql_query_type);
|
||||
$result = sqlsrv_query($this->sqlsrv, $sql);
|
||||
if (!$scrollable) { // Only supporting next row
|
||||
$result = sqlsrv_query($this->sqlsrv, $sql);
|
||||
} else { // Suporting absolute/relative rows
|
||||
$result = sqlsrv_query($this->sqlsrv, $sql, array(), array('Scrollable' => SQLSRV_CURSOR_STATIC));
|
||||
}
|
||||
|
||||
if ($result === false) {
|
||||
// TODO do something with error or just use if DEV or DEBUG?
|
||||
@ -756,118 +761,18 @@ class sqlsrv_native_moodle_database extends moodle_database {
|
||||
$limitnum = max(0, $limitnum);
|
||||
|
||||
if ($limitfrom or $limitnum) {
|
||||
$sql = $this->limit_to_top_n($sql, $limitfrom, $limitnum);
|
||||
}
|
||||
$result = $this->do_query($sql, $params, SQL_QUERY_SELECT, false);
|
||||
return $this->create_recordset($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a emulation for LIMIT(offset, limit)
|
||||
*
|
||||
* @param string $sql
|
||||
* @param int $offset
|
||||
* @param int $limit
|
||||
* @return string sql
|
||||
*/
|
||||
private function limit_to_top_n($sql, $offset, $limit) {
|
||||
// If there is no limit we can return immediately
|
||||
if ($limit < 1 && $offset < 1) {
|
||||
return $sql;
|
||||
}
|
||||
|
||||
// Make sure they are at least 0
|
||||
$limit = max(0, (int)$limit);
|
||||
$offset = max(0, (int)$offset);
|
||||
// This is an sqlserver bigint - -1 and will be used as a value
|
||||
// for top when essentially we want everything.
|
||||
// This needs to be a string so that it doesn't get malformed.
|
||||
$bigint = '9223372036854775806';
|
||||
|
||||
// If limit is 0 set it to BITINT - 1
|
||||
if (empty($limit)) {
|
||||
$limit = $bigint;
|
||||
} else {
|
||||
$limit = $offset + $limit;
|
||||
}
|
||||
|
||||
// Set up defaults for the next bit.
|
||||
$columns = '*'; // Default to all columns
|
||||
$columnnames = '*'; // As above
|
||||
$firstcolumn = 'id'; // The default first column is id
|
||||
$orderby = ''; // The order by of the main query
|
||||
|
||||
// We need to do a couple of maintenance tasks on the columns seeing as
|
||||
// they are going to be wrapped in a sub select.
|
||||
//
|
||||
// 1. Collect the first column to use for an order by if there isn't an
|
||||
// explicit order by within the query.
|
||||
// 2. Give all constant columns a proper alias, this is required because
|
||||
// of the subselect.
|
||||
if (preg_match('#^(\s*SELECT\s+)(DISTINCT\s+)?(.*?)(\sFROM\s)#is', $sql, $columnmatches)) {
|
||||
// Make sure we have some columns
|
||||
if (!empty($columnmatches[3])) {
|
||||
$columns = explode(',', $columnmatches[3]);
|
||||
$firstcolumn = null;
|
||||
$constantcount = 0;
|
||||
foreach ($columns as $key=>$column) {
|
||||
// Trim, trim, trim, except during Movember.
|
||||
$column = trim($column);
|
||||
if (preg_match('#\sAS\s+(\w+)\s*$#si', $column, $matches)) {
|
||||
// Make sure we use the column alias if available.
|
||||
$column = $matches[1];
|
||||
} else if (preg_match("#^('[^']*'|\d+)$#", $column)) {
|
||||
// Give constants an alias in the main query and use the
|
||||
// alias for the new outer queries.
|
||||
$constantcount++;
|
||||
$column = 'constant_'.$constantcount;
|
||||
$columns[$key] .= ' AS '.$column;
|
||||
}
|
||||
|
||||
// Store the first column for later abuse.
|
||||
if ($firstcolumn === null) {
|
||||
$firstcolumn = $column;
|
||||
}
|
||||
}
|
||||
// Glue things back together
|
||||
$columns = join(', ', $columns);
|
||||
// Switch out the fixed main columns (added constant aliases).
|
||||
$sql = str_replace($columnmatches[0], $columnmatches[1].$columnmatches[2].$columns.$columnmatches[4], $sql);
|
||||
if ($limitnum >= 1) { // Only apply TOP clause if we have any limitnum (limitfrom offset is handled later)
|
||||
$fetch = $limitfrom + $limitnum;
|
||||
$sql = preg_replace('/^([\s(])*SELECT([\s]+(DISTINCT|ALL))?(?!\s*TOP\s*\()/i',
|
||||
"\\1SELECT\\2 TOP $fetch", $sql);
|
||||
}
|
||||
}
|
||||
$result = $this->do_query($sql, $params, SQL_QUERY_SELECT, false, (bool)$limitfrom);
|
||||
|
||||
// Collect the orderby from the main query to use in the row number order by.
|
||||
if (preg_match('#\sORDER\s+BY\s+([^)]+?)(GROUP\s+BY|$)#i', $sql, $matches)) {
|
||||
// We need to remove it from the main query as well.
|
||||
$sql = str_replace($matches[0], ' '.$matches[2], $sql);
|
||||
$orderby = $matches[1];
|
||||
} else {
|
||||
// Default orderby to the first column.
|
||||
$orderby = $firstcolumn;
|
||||
if ($limitfrom) { // Skip $limitfrom records
|
||||
sqlsrv_fetch($result, SQLSRV_SCROLL_ABSOLUTE, $limitfrom - 1);
|
||||
}
|
||||
// Remove any table aliases from the order by.
|
||||
$orderby = preg_replace('#[^\s,]*\.([^\s,]*)#', '$1', $orderby);
|
||||
|
||||
// If the orderby is all tables everything will break, default to id.
|
||||
if ($orderby == '*') {
|
||||
$orderby = 'id';
|
||||
}
|
||||
|
||||
// Now we need to build the queries up so that we collect a row count field and then sort on it.
|
||||
// To do this we need to nest the query twice. The first nesting selects all the rows from the
|
||||
// query and then proceeds to use OVER to generate a row number.
|
||||
// The second nesting we limit by selecting where rownumber between offset and limit
|
||||
// In both cases we will select the original query fields using q.* this is important
|
||||
// as there can be any number of crafty things going on. It does however mean that we
|
||||
// end up with the first field being sqlsrvrownumber however sqlsrv_native_moodle_recordset
|
||||
// strips that off during processing if it exists.
|
||||
// Build the inner outer query.
|
||||
$sql = "SELECT TOP $bigint ROW_NUMBER() OVER(ORDER BY $orderby) AS sqlsrvrownumber, q.* FROM ($sql) AS q";
|
||||
// Build the outer most query.
|
||||
$sql = "SELECT q.* FROM ($sql) AS q WHERE q.sqlsrvrownumber > $offset AND q.sqlsrvrownumber <= $limit";
|
||||
|
||||
// Return the now mangled query for use.
|
||||
return $sql;
|
||||
return $this->create_recordset($result);
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
x
Reference in New Issue
Block a user