Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 39 and 310] [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * DML layer tests.
  19   *
  20   * @package    core_dml
  21   * @category   phpunit
  22   * @copyright  2008 Nicolas Connault
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  defined('MOODLE_INTERNAL') || die();
  27  
  28  class core_dml_testcase extends database_driver_testcase {
  29  
  30      protected function setUp() {
  31          parent::setUp();
  32          $dbman = $this->tdb->get_manager(); // Loads DDL libs.
  33      }
  34  
  35      /**
  36       * Get a xmldb_table object for testing, deleting any existing table
  37       * of the same name, for example if one was left over from a previous test
  38       * run that crashed.
  39       *
  40       * @param string $suffix table name suffix, use if you need more test tables
  41       * @return xmldb_table the table object.
  42       */
  43      private function get_test_table($suffix = '') {
  44          $tablename = "test_table";
  45          if ($suffix !== '') {
  46              $tablename .= $suffix;
  47          }
  48  
  49          $table = new xmldb_table($tablename);
  50          $table->setComment("This is a test'n drop table. You can drop it safely");
  51          return $table;
  52      }
  53  
  54      public function test_diagnose() {
  55          $DB = $this->tdb;
  56          $result = $DB->diagnose();
  57          $this->assertNull($result, 'Database self diagnostics failed %s');
  58      }
  59  
  60      public function test_get_server_info() {
  61          $DB = $this->tdb;
  62          $result = $DB->get_server_info();
  63          $this->assertInternalType('array', $result);
  64          $this->assertArrayHasKey('description', $result);
  65          $this->assertArrayHasKey('version', $result);
  66      }
  67  
  68      public function test_get_in_or_equal() {
  69          $DB = $this->tdb;
  70  
  71          // SQL_PARAMS_QM - IN or =.
  72  
  73          // Correct usage of multiple values.
  74          $in_values = array('value1', 'value2', '3', 4, null, false, true);
  75          list($usql, $params) = $DB->get_in_or_equal($in_values);
  76          $this->assertSame('IN ('.implode(',', array_fill(0, count($in_values), '?')).')', $usql);
  77          $this->assertEquals(count($in_values), count($params));
  78          foreach ($params as $key => $value) {
  79              $this->assertSame($in_values[$key], $value);
  80          }
  81  
  82          // Correct usage of single value (in an array).
  83          $in_values = array('value1');
  84          list($usql, $params) = $DB->get_in_or_equal($in_values);
  85          $this->assertEquals("= ?", $usql);
  86          $this->assertCount(1, $params);
  87          $this->assertEquals($in_values[0], $params[0]);
  88  
  89          // Correct usage of single value.
  90          $in_value = 'value1';
  91          list($usql, $params) = $DB->get_in_or_equal($in_values);
  92          $this->assertEquals("= ?", $usql);
  93          $this->assertCount(1, $params);
  94          $this->assertEquals($in_value, $params[0]);
  95  
  96          // SQL_PARAMS_QM - NOT IN or <>.
  97  
  98          // Correct usage of multiple values.
  99          $in_values = array('value1', 'value2', 'value3', 'value4');
 100          list($usql, $params) = $DB->get_in_or_equal($in_values, SQL_PARAMS_QM, null, false);
 101          $this->assertEquals("NOT IN (?,?,?,?)", $usql);
 102          $this->assertCount(4, $params);
 103          foreach ($params as $key => $value) {
 104              $this->assertEquals($in_values[$key], $value);
 105          }
 106  
 107          // Correct usage of single value (in array().
 108          $in_values = array('value1');
 109          list($usql, $params) = $DB->get_in_or_equal($in_values, SQL_PARAMS_QM, null, false);
 110          $this->assertEquals("<> ?", $usql);
 111          $this->assertCount(1, $params);
 112          $this->assertEquals($in_values[0], $params[0]);
 113  
 114          // Correct usage of single value.
 115          $in_value = 'value1';
 116          list($usql, $params) = $DB->get_in_or_equal($in_values, SQL_PARAMS_QM, null, false);
 117          $this->assertEquals("<> ?", $usql);
 118          $this->assertCount(1, $params);
 119          $this->assertEquals($in_value, $params[0]);
 120  
 121          // SQL_PARAMS_NAMED - IN or =.
 122  
 123          // Correct usage of multiple values.
 124          $in_values = array('value1', 'value2', 'value3', 'value4');
 125          list($usql, $params) = $DB->get_in_or_equal($in_values, SQL_PARAMS_NAMED, 'param', true);
 126          $this->assertCount(4, $params);
 127          reset($in_values);
 128          $ps = array();
 129          foreach ($params as $key => $value) {
 130              $this->assertEquals(current($in_values), $value);
 131              next($in_values);
 132              $ps[] = ':'.$key;
 133          }
 134          $this->assertEquals("IN (".implode(',', $ps).")", $usql);
 135  
 136          // Correct usage of single values (in array).
 137          $in_values = array('value1');
 138          list($usql, $params) = $DB->get_in_or_equal($in_values, SQL_PARAMS_NAMED, 'param', true);
 139          $this->assertCount(1, $params);
 140          $value = reset($params);
 141          $key = key($params);
 142          $this->assertEquals("= :$key", $usql);
 143          $this->assertEquals($in_value, $value);
 144  
 145          // Correct usage of single value.
 146          $in_value = 'value1';
 147          list($usql, $params) = $DB->get_in_or_equal($in_values, SQL_PARAMS_NAMED, 'param', true);
 148          $this->assertCount(1, $params);
 149          $value = reset($params);
 150          $key = key($params);
 151          $this->assertEquals("= :$key", $usql);
 152          $this->assertEquals($in_value, $value);
 153  
 154          // SQL_PARAMS_NAMED - NOT IN or <>.
 155  
 156          // Correct usage of multiple values.
 157          $in_values = array('value1', 'value2', 'value3', 'value4');
 158          list($usql, $params) = $DB->get_in_or_equal($in_values, SQL_PARAMS_NAMED, 'param', false);
 159          $this->assertCount(4, $params);
 160          reset($in_values);
 161          $ps = array();
 162          foreach ($params as $key => $value) {
 163              $this->assertEquals(current($in_values), $value);
 164              next($in_values);
 165              $ps[] = ':'.$key;
 166          }
 167          $this->assertEquals("NOT IN (".implode(',', $ps).")", $usql);
 168  
 169          // Correct usage of single values (in array).
 170          $in_values = array('value1');
 171          list($usql, $params) = $DB->get_in_or_equal($in_values, SQL_PARAMS_NAMED, 'param', false);
 172          $this->assertCount(1, $params);
 173          $value = reset($params);
 174          $key = key($params);
 175          $this->assertEquals("<> :$key", $usql);
 176          $this->assertEquals($in_value, $value);
 177  
 178          // Correct usage of single value.
 179          $in_value = 'value1';
 180          list($usql, $params) = $DB->get_in_or_equal($in_values, SQL_PARAMS_NAMED, 'param', false);
 181          $this->assertCount(1, $params);
 182          $value = reset($params);
 183          $key = key($params);
 184          $this->assertEquals("<> :$key", $usql);
 185          $this->assertEquals($in_value, $value);
 186  
 187          // Make sure the param names are unique.
 188          list($usql1, $params1) = $DB->get_in_or_equal(array(1, 2, 3), SQL_PARAMS_NAMED, 'param');
 189          list($usql2, $params2) = $DB->get_in_or_equal(array(1, 2, 3), SQL_PARAMS_NAMED, 'param');
 190          $params1 = array_keys($params1);
 191          $params2 = array_keys($params2);
 192          $common = array_intersect($params1, $params2);
 193          $this->assertCount(0, $common);
 194  
 195          // Some incorrect tests.
 196  
 197          // Incorrect usage passing not-allowed params type.
 198          $in_values = array(1, 2, 3);
 199          try {
 200              list($usql, $params) = $DB->get_in_or_equal($in_values, SQL_PARAMS_DOLLAR, 'param', false);
 201              $this->fail('An Exception is missing, expected due to not supported SQL_PARAMS_DOLLAR');
 202          } catch (moodle_exception $e) {
 203              $this->assertInstanceOf('dml_exception', $e);
 204              $this->assertSame('typenotimplement', $e->errorcode);
 205          }
 206  
 207          // Incorrect usage passing empty array.
 208          $in_values = array();
 209          try {
 210              list($usql, $params) = $DB->get_in_or_equal($in_values, SQL_PARAMS_NAMED, 'param', false);
 211              $this->fail('An Exception is missing, expected due to empty array of items');
 212          } catch (moodle_exception $e) {
 213              $this->assertInstanceOf('coding_exception', $e);
 214          }
 215  
 216          // Test using $onemptyitems.
 217  
 218          // Correct usage passing empty array and $onemptyitems = null (equal = true, QM).
 219          $in_values = array();
 220          list($usql, $params) = $DB->get_in_or_equal($in_values, SQL_PARAMS_QM, 'param', true, null);
 221          $this->assertSame(' IS NULL', $usql);
 222          $this->assertSame(array(), $params);
 223  
 224          // Correct usage passing empty array and $onemptyitems = null (equal = false, NAMED).
 225          $in_values = array();
 226          list($usql, $params) = $DB->get_in_or_equal($in_values, SQL_PARAMS_NAMED, 'param', false, null);
 227          $this->assertSame(' IS NOT NULL', $usql);
 228          $this->assertSame(array(), $params);
 229  
 230          // Correct usage passing empty array and $onemptyitems = true (equal = true, QM).
 231          $in_values = array();
 232          list($usql, $params) = $DB->get_in_or_equal($in_values, SQL_PARAMS_QM, 'param', true, true);
 233          $this->assertSame('= ?', $usql);
 234          $this->assertSame(array(true), $params);
 235  
 236          // Correct usage passing empty array and $onemptyitems = true (equal = false, NAMED).
 237          $in_values = array();
 238          list($usql, $params) = $DB->get_in_or_equal($in_values, SQL_PARAMS_NAMED, 'param', false, true);
 239          $this->assertCount(1, $params);
 240          $value = reset($params);
 241          $key = key($params);
 242          $this->assertSame('<> :'.$key, $usql);
 243          $this->assertSame($value, true);
 244  
 245          // Correct usage passing empty array and $onemptyitems = -1 (equal = true, QM).
 246          $in_values = array();
 247          list($usql, $params) = $DB->get_in_or_equal($in_values, SQL_PARAMS_QM, 'param', true, -1);
 248          $this->assertSame('= ?', $usql);
 249          $this->assertSame(array(-1), $params);
 250  
 251          // Correct usage passing empty array and $onemptyitems = -1 (equal = false, NAMED).
 252          $in_values = array();
 253          list($usql, $params) = $DB->get_in_or_equal($in_values, SQL_PARAMS_NAMED, 'param', false, -1);
 254          $this->assertCount(1, $params);
 255          $value = reset($params);
 256          $key = key($params);
 257          $this->assertSame('<> :'.$key, $usql);
 258          $this->assertSame($value, -1);
 259  
 260          // Correct usage passing empty array and $onemptyitems = 'onevalue' (equal = true, QM).
 261          $in_values = array();
 262          list($usql, $params) = $DB->get_in_or_equal($in_values, SQL_PARAMS_QM, 'param', true, 'onevalue');
 263          $this->assertSame('= ?', $usql);
 264          $this->assertSame(array('onevalue'), $params);
 265  
 266          // Correct usage passing empty array and $onemptyitems = 'onevalue' (equal = false, NAMED).
 267          $in_values = array();
 268          list($usql, $params) = $DB->get_in_or_equal($in_values, SQL_PARAMS_NAMED, 'param', false, 'onevalue');
 269          $this->assertCount(1, $params);
 270          $value = reset($params);
 271          $key = key($params);
 272          $this->assertSame('<> :'.$key, $usql);
 273          $this->assertSame($value, 'onevalue');
 274      }
 275  
 276      public function test_fix_table_names() {
 277          $DB = new moodle_database_for_testing();
 278          $prefix = $DB->get_prefix();
 279  
 280          // Simple placeholder.
 281          $placeholder = "{user_123}";
 282          $this->assertSame($prefix."user_123", $DB->public_fix_table_names($placeholder));
 283  
 284          // Wrong table name.
 285          $placeholder = "{user-a}";
 286          $this->assertSame($placeholder, $DB->public_fix_table_names($placeholder));
 287  
 288          // Wrong table name.
 289          $placeholder = "{123user}";
 290          $this->assertSame($placeholder, $DB->public_fix_table_names($placeholder));
 291  
 292          // Full SQL.
 293          $sql = "SELECT * FROM {user}, {funny_table_name}, {mdl_stupid_table} WHERE {user}.id = {funny_table_name}.userid";
 294          $expected = "SELECT * FROM {$prefix}user, {$prefix}funny_table_name, {$prefix}mdl_stupid_table WHERE {$prefix}user.id = {$prefix}funny_table_name.userid";
 295          $this->assertSame($expected, $DB->public_fix_table_names($sql));
 296      }
 297  
 298      public function test_fix_sql_params() {
 299          $DB = $this->tdb;
 300          $prefix = $DB->get_prefix();
 301  
 302          $table = $this->get_test_table();
 303          $tablename = $table->getName();
 304  
 305          // Correct table placeholder substitution.
 306          $sql = "SELECT * FROM {{$tablename}}";
 307          $sqlarray = $DB->fix_sql_params($sql);
 308          $this->assertEquals("SELECT * FROM {$prefix}".$tablename, $sqlarray[0]);
 309  
 310          // Conversions of all param types.
 311          $sql = array();
 312          $sql[SQL_PARAMS_NAMED]  = "SELECT * FROM {$prefix}testtable WHERE name = :param1, course = :param2";
 313          $sql[SQL_PARAMS_QM]     = "SELECT * FROM {$prefix}testtable WHERE name = ?, course = ?";
 314          $sql[SQL_PARAMS_DOLLAR] = "SELECT * FROM {$prefix}testtable WHERE name = \$1, course = \$2";
 315  
 316          $params = array();
 317          $params[SQL_PARAMS_NAMED]  = array('param1'=>'first record', 'param2'=>1);
 318          $params[SQL_PARAMS_QM]     = array('first record', 1);
 319          $params[SQL_PARAMS_DOLLAR] = array('first record', 1);
 320  
 321          list($rsql, $rparams, $rtype) = $DB->fix_sql_params($sql[SQL_PARAMS_NAMED], $params[SQL_PARAMS_NAMED]);
 322          $this->assertSame($rsql, $sql[$rtype]);
 323          $this->assertSame($rparams, $params[$rtype]);
 324  
 325          list($rsql, $rparams, $rtype) = $DB->fix_sql_params($sql[SQL_PARAMS_QM], $params[SQL_PARAMS_QM]);
 326          $this->assertSame($rsql, $sql[$rtype]);
 327          $this->assertSame($rparams, $params[$rtype]);
 328  
 329          list($rsql, $rparams, $rtype) = $DB->fix_sql_params($sql[SQL_PARAMS_DOLLAR], $params[SQL_PARAMS_DOLLAR]);
 330          $this->assertSame($rsql, $sql[$rtype]);
 331          $this->assertSame($rparams, $params[$rtype]);
 332  
 333          // Malformed table placeholder.
 334          $sql = "SELECT * FROM [testtable]";
 335          $sqlarray = $DB->fix_sql_params($sql);
 336          $this->assertSame($sql, $sqlarray[0]);
 337  
 338          // Mixed param types (colon and dollar).
 339          $sql = "SELECT * FROM {{$tablename}} WHERE name = :param1, course = \$1";
 340          $params = array('param1' => 'record1', 'param2' => 3);
 341          try {
 342              $DB->fix_sql_params($sql, $params);
 343              $this->fail("Expecting an exception, none occurred");
 344          } catch (moodle_exception $e) {
 345              $this->assertInstanceOf('dml_exception', $e);
 346          }
 347  
 348          // Mixed param types (question and dollar).
 349          $sql = "SELECT * FROM {{$tablename}} WHERE name = ?, course = \$1";
 350          $params = array('param1' => 'record2', 'param2' => 5);
 351          try {
 352              $DB->fix_sql_params($sql, $params);
 353              $this->fail("Expecting an exception, none occurred");
 354          } catch (moodle_exception $e) {
 355              $this->assertInstanceOf('dml_exception', $e);
 356          }
 357  
 358          // Too few params in sql.
 359          $sql = "SELECT * FROM {{$tablename}} WHERE name = ?, course = ?, id = ?";
 360          $params = array('record2', 3);
 361          try {
 362              $DB->fix_sql_params($sql, $params);
 363              $this->fail("Expecting an exception, none occurred");
 364          } catch (moodle_exception $e) {
 365              $this->assertInstanceOf('dml_exception', $e);
 366          }
 367  
 368          // Too many params in array: no error, just use what is necessary.
 369          $params[] = 1;
 370          $params[] = time();
 371          $sqlarray = $DB->fix_sql_params($sql, $params);
 372          $this->assertInternalType('array', $sqlarray);
 373          $this->assertCount(3, $sqlarray[1]);
 374  
 375          // Named params missing from array.
 376          $sql = "SELECT * FROM {{$tablename}} WHERE name = :name, course = :course";
 377          $params = array('wrongname' => 'record1', 'course' => 1);
 378          try {
 379              $DB->fix_sql_params($sql, $params);
 380              $this->fail("Expecting an exception, none occurred");
 381          } catch (moodle_exception $e) {
 382              $this->assertInstanceOf('dml_exception', $e);
 383          }
 384  
 385          // Duplicate named param in query - this is a very important feature!!
 386          // it helps with debugging of sloppy code.
 387          $sql = "SELECT * FROM {{$tablename}} WHERE name = :name, course = :name";
 388          $params = array('name' => 'record2', 'course' => 3);
 389          try {
 390              $DB->fix_sql_params($sql, $params);
 391              $this->fail("Expecting an exception, none occurred");
 392          } catch (moodle_exception $e) {
 393              $this->assertInstanceOf('dml_exception', $e);
 394          }
 395  
 396          // Extra named param is ignored.
 397          $sql = "SELECT * FROM {{$tablename}} WHERE name = :name, course = :course";
 398          $params = array('name' => 'record1', 'course' => 1, 'extrastuff'=>'haha');
 399          $sqlarray = $DB->fix_sql_params($sql, $params);
 400          $this->assertInternalType('array', $sqlarray);
 401          $this->assertCount(2, $sqlarray[1]);
 402  
 403          // Params exceeding 30 chars length.
 404          $sql = "SELECT * FROM {{$tablename}} WHERE name = :long_placeholder_with_more_than_30";
 405          $params = array('long_placeholder_with_more_than_30' => 'record1');
 406          try {
 407              $DB->fix_sql_params($sql, $params);
 408              $this->fail("Expecting an exception, none occurred");
 409          } catch (moodle_exception $e) {
 410              $this->assertInstanceOf('coding_exception', $e);
 411          }
 412  
 413          // Booleans in NAMED params are casting to 1/0 int.
 414          $sql = "SELECT * FROM {{$tablename}} WHERE course = ? OR course = ?";
 415          $params = array(true, false);
 416          list($sql, $params) = $DB->fix_sql_params($sql, $params);
 417          $this->assertTrue(reset($params) === 1);
 418          $this->assertTrue(next($params) === 0);
 419  
 420          // Booleans in QM params are casting to 1/0 int.
 421          $sql = "SELECT * FROM {{$tablename}} WHERE course = :course1 OR course = :course2";
 422          $params = array('course1' => true, 'course2' => false);
 423          list($sql, $params) = $DB->fix_sql_params($sql, $params);
 424          $this->assertTrue(reset($params) === 1);
 425          $this->assertTrue(next($params) === 0);
 426  
 427          // Booleans in DOLLAR params are casting to 1/0 int.
 428          $sql = "SELECT * FROM {{$tablename}} WHERE course = \$1 OR course = \$2";
 429          $params = array(true, false);
 430          list($sql, $params) = $DB->fix_sql_params($sql, $params);
 431          $this->assertTrue(reset($params) === 1);
 432          $this->assertTrue(next($params) === 0);
 433  
 434          // No data types are touched except bool.
 435          $sql = "SELECT * FROM {{$tablename}} WHERE name IN (?,?,?,?,?,?)";
 436          $inparams = array('abc', 'ABC', null, '1', 1, 1.4);
 437          list($sql, $params) = $DB->fix_sql_params($sql, $inparams);
 438          $this->assertSame(array_values($params), array_values($inparams));
 439      }
 440  
 441      public function test_strtok() {
 442          // Strtok was previously used by bound emulation, make sure it is not used any more.
 443          $DB = $this->tdb;
 444          $dbman = $this->tdb->get_manager();
 445  
 446          $table = $this->get_test_table();
 447          $tablename = $table->getName();
 448  
 449          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
 450          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
 451          $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, 'lala');
 452          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
 453          $dbman->create_table($table);
 454  
 455          $str = 'a?b?c?d';
 456          $this->assertSame(strtok($str, '?'), 'a');
 457  
 458          $DB->get_records($tablename, array('id'=>1));
 459  
 460          $this->assertSame(strtok('?'), 'b');
 461      }
 462  
 463      public function test_tweak_param_names() {
 464          // Note the tweak_param_names() method is only available in the oracle driver,
 465          // hence we look for expected results indirectly, by testing various DML methods.
 466          // with some "extreme" conditions causing the tweak to happen.
 467          $DB = $this->tdb;
 468          $dbman = $this->tdb->get_manager();
 469  
 470          $table = $this->get_test_table();
 471          $tablename = $table->getName();
 472  
 473          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
 474          // Add some columns with 28 chars in the name.
 475          $table->add_field('long_int_columnname_with_28c', XMLDB_TYPE_INTEGER, '10');
 476          $table->add_field('long_dec_columnname_with_28c', XMLDB_TYPE_NUMBER, '10,2');
 477          $table->add_field('long_str_columnname_with_28c', XMLDB_TYPE_CHAR, '100');
 478          // Add some columns with 30 chars in the name.
 479          $table->add_field('long_int_columnname_with_30cxx', XMLDB_TYPE_INTEGER, '10');
 480          $table->add_field('long_dec_columnname_with_30cxx', XMLDB_TYPE_NUMBER, '10,2');
 481          $table->add_field('long_str_columnname_with_30cxx', XMLDB_TYPE_CHAR, '100');
 482  
 483          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
 484  
 485          $dbman->create_table($table);
 486  
 487          $this->assertTrue($dbman->table_exists($tablename));
 488  
 489          // Test insert record.
 490          $rec1 = new stdClass();
 491          $rec1->long_int_columnname_with_28c = 28;
 492          $rec1->long_dec_columnname_with_28c = 28.28;
 493          $rec1->long_str_columnname_with_28c = '28';
 494          $rec1->long_int_columnname_with_30cxx = 30;
 495          $rec1->long_dec_columnname_with_30cxx = 30.30;
 496          $rec1->long_str_columnname_with_30cxx = '30';
 497  
 498          // Insert_record().
 499          $rec1->id = $DB->insert_record($tablename, $rec1);
 500          $this->assertEquals($rec1, $DB->get_record($tablename, array('id' => $rec1->id)));
 501  
 502          // Update_record().
 503          $DB->update_record($tablename, $rec1);
 504          $this->assertEquals($rec1, $DB->get_record($tablename, array('id' => $rec1->id)));
 505  
 506          // Set_field().
 507          $rec1->long_int_columnname_with_28c = 280;
 508          $DB->set_field($tablename, 'long_int_columnname_with_28c', $rec1->long_int_columnname_with_28c,
 509              array('id' => $rec1->id, 'long_int_columnname_with_28c' => 28));
 510          $rec1->long_dec_columnname_with_28c = 280.28;
 511          $DB->set_field($tablename, 'long_dec_columnname_with_28c', $rec1->long_dec_columnname_with_28c,
 512              array('id' => $rec1->id, 'long_dec_columnname_with_28c' => 28.28));
 513          $rec1->long_str_columnname_with_28c = '280';
 514          $DB->set_field($tablename, 'long_str_columnname_with_28c', $rec1->long_str_columnname_with_28c,
 515              array('id' => $rec1->id, 'long_str_columnname_with_28c' => '28'));
 516          $rec1->long_int_columnname_with_30cxx = 300;
 517          $DB->set_field($tablename, 'long_int_columnname_with_30cxx', $rec1->long_int_columnname_with_30cxx,
 518              array('id' => $rec1->id, 'long_int_columnname_with_30cxx' => 30));
 519          $rec1->long_dec_columnname_with_30cxx = 300.30;
 520          $DB->set_field($tablename, 'long_dec_columnname_with_30cxx', $rec1->long_dec_columnname_with_30cxx,
 521              array('id' => $rec1->id, 'long_dec_columnname_with_30cxx' => 30.30));
 522          $rec1->long_str_columnname_with_30cxx = '300';
 523          $DB->set_field($tablename, 'long_str_columnname_with_30cxx', $rec1->long_str_columnname_with_30cxx,
 524              array('id' => $rec1->id, 'long_str_columnname_with_30cxx' => '30'));
 525          $this->assertEquals($rec1, $DB->get_record($tablename, array('id' => $rec1->id)));
 526  
 527          // Delete_records().
 528          $rec2 = $DB->get_record($tablename, array('id' => $rec1->id));
 529          $rec2->id = $DB->insert_record($tablename, $rec2);
 530          $this->assertEquals(2, $DB->count_records($tablename));
 531          $DB->delete_records($tablename, (array) $rec2);
 532          $this->assertEquals(1, $DB->count_records($tablename));
 533  
 534          // Get_recordset().
 535          $rs = $DB->get_recordset($tablename, (array) $rec1);
 536          $iterations = 0;
 537          foreach ($rs as $rec2) {
 538              $iterations++;
 539          }
 540          $rs->close();
 541          $this->assertEquals(1, $iterations);
 542          $this->assertEquals($rec1, $rec2);
 543  
 544          // Get_records().
 545          $recs = $DB->get_records($tablename, (array) $rec1);
 546          $this->assertCount(1, $recs);
 547          $this->assertEquals($rec1, reset($recs));
 548  
 549          // Get_fieldset_select().
 550          $select = 'id = :id AND
 551                     long_int_columnname_with_28c = :long_int_columnname_with_28c AND
 552                     long_dec_columnname_with_28c = :long_dec_columnname_with_28c AND
 553                     long_str_columnname_with_28c = :long_str_columnname_with_28c AND
 554                     long_int_columnname_with_30cxx = :long_int_columnname_with_30cxx AND
 555                     long_dec_columnname_with_30cxx = :long_dec_columnname_with_30cxx AND
 556                     long_str_columnname_with_30cxx = :long_str_columnname_with_30cxx';
 557          $fields = $DB->get_fieldset_select($tablename, 'long_int_columnname_with_28c', $select, (array)$rec1);
 558          $this->assertCount(1, $fields);
 559          $this->assertEquals($rec1->long_int_columnname_with_28c, reset($fields));
 560          $fields = $DB->get_fieldset_select($tablename, 'long_dec_columnname_with_28c', $select, (array)$rec1);
 561          $this->assertEquals($rec1->long_dec_columnname_with_28c, reset($fields));
 562          $fields = $DB->get_fieldset_select($tablename, 'long_str_columnname_with_28c', $select, (array)$rec1);
 563          $this->assertEquals($rec1->long_str_columnname_with_28c, reset($fields));
 564          $fields = $DB->get_fieldset_select($tablename, 'long_int_columnname_with_30cxx', $select, (array)$rec1);
 565          $this->assertEquals($rec1->long_int_columnname_with_30cxx, reset($fields));
 566          $fields = $DB->get_fieldset_select($tablename, 'long_dec_columnname_with_30cxx', $select, (array)$rec1);
 567          $this->assertEquals($rec1->long_dec_columnname_with_30cxx, reset($fields));
 568          $fields = $DB->get_fieldset_select($tablename, 'long_str_columnname_with_30cxx', $select, (array)$rec1);
 569          $this->assertEquals($rec1->long_str_columnname_with_30cxx, reset($fields));
 570  
 571          // Overlapping placeholders (progressive str_replace).
 572          $overlapselect = 'id = :p AND
 573                     long_int_columnname_with_28c = :param1 AND
 574                     long_dec_columnname_with_28c = :param2 AND
 575                     long_str_columnname_with_28c = :param_with_29_characters_long AND
 576                     long_int_columnname_with_30cxx = :param_with_30_characters_long_ AND
 577                     long_dec_columnname_with_30cxx = :param_ AND
 578                     long_str_columnname_with_30cxx = :param__';
 579          $overlapparams = array(
 580              'p' => $rec1->id,
 581              'param1' => $rec1->long_int_columnname_with_28c,
 582              'param2' => $rec1->long_dec_columnname_with_28c,
 583              'param_with_29_characters_long' => $rec1->long_str_columnname_with_28c,
 584              'param_with_30_characters_long_' => $rec1->long_int_columnname_with_30cxx,
 585              'param_' => $rec1->long_dec_columnname_with_30cxx,
 586              'param__' => $rec1->long_str_columnname_with_30cxx);
 587          $recs = $DB->get_records_select($tablename, $overlapselect, $overlapparams);
 588          $this->assertCount(1, $recs);
 589          $this->assertEquals($rec1, reset($recs));
 590  
 591          // Execute().
 592          $DB->execute("DELETE FROM {{$tablename}} WHERE $select", (array)$rec1);
 593          $this->assertEquals(0, $DB->count_records($tablename));
 594      }
 595  
 596      public function test_get_tables() {
 597          $DB = $this->tdb;
 598          $dbman = $this->tdb->get_manager();
 599  
 600          // Need to test with multiple DBs.
 601          $table = $this->get_test_table();
 602          $tablename = $table->getName();
 603  
 604          $original_count = count($DB->get_tables());
 605  
 606          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
 607          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
 608  
 609          $dbman->create_table($table);
 610          $this->assertTrue(count($DB->get_tables()) == $original_count + 1);
 611  
 612          $dbman->drop_table($table);
 613          $this->assertTrue(count($DB->get_tables()) == $original_count);
 614      }
 615  
 616      public function test_get_indexes() {
 617          $DB = $this->tdb;
 618          $dbman = $this->tdb->get_manager();
 619  
 620          $table = $this->get_test_table();
 621          $tablename = $table->getName();
 622  
 623          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
 624          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
 625          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
 626          $table->add_index('course', XMLDB_INDEX_NOTUNIQUE, array('course'));
 627          $table->add_index('course-id', XMLDB_INDEX_UNIQUE, array('course', 'id'));
 628          $dbman->create_table($table);
 629  
 630          $indices = $DB->get_indexes($tablename);
 631          $this->assertInternalType('array', $indices);
 632          $this->assertCount(2, $indices);
 633          // We do not care about index names for now.
 634          $first = array_shift($indices);
 635          $second = array_shift($indices);
 636          if (count($first['columns']) == 2) {
 637              $composed = $first;
 638              $single   = $second;
 639          } else {
 640              $composed = $second;
 641              $single   = $first;
 642          }
 643          $this->assertFalse($single['unique']);
 644          $this->assertTrue($composed['unique']);
 645          $this->assertCount(1, $single['columns']);
 646          $this->assertCount(2, $composed['columns']);
 647          $this->assertSame('course', $single['columns'][0]);
 648          $this->assertSame('course', $composed['columns'][0]);
 649          $this->assertSame('id', $composed['columns'][1]);
 650      }
 651  
 652      public function test_get_columns() {
 653          $DB = $this->tdb;
 654          $dbman = $this->tdb->get_manager();
 655  
 656          $table = $this->get_test_table();
 657          $tablename = $table->getName();
 658  
 659          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
 660          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
 661          $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, 'lala');
 662          $table->add_field('description', XMLDB_TYPE_TEXT, 'small', null, null, null, null);
 663          $table->add_field('oneint', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
 664          $table->add_field('oneintnodefault', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null);
 665          $table->add_field('enumfield', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, 'test2');
 666          $table->add_field('onenum', XMLDB_TYPE_NUMBER, '10,2', null, null, null, 200);
 667          $table->add_field('onenumnodefault', XMLDB_TYPE_NUMBER, '10,2', null, null, null);
 668          $table->add_field('onefloat', XMLDB_TYPE_FLOAT, '10,2', null, XMLDB_NOTNULL, null, 300);
 669          $table->add_field('onefloatnodefault', XMLDB_TYPE_FLOAT, '10,2', null, XMLDB_NOTNULL, null);
 670          $table->add_field('anotherfloat', XMLDB_TYPE_FLOAT, null, null, null, null, 400);
 671          $table->add_field('negativedfltint', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '-1');
 672          $table->add_field('negativedfltnumber', XMLDB_TYPE_NUMBER, '10', null, XMLDB_NOTNULL, null, '-2');
 673          $table->add_field('negativedfltfloat', XMLDB_TYPE_FLOAT, '10', null, XMLDB_NOTNULL, null, '-3');
 674          $table->add_field('someint1', XMLDB_TYPE_INTEGER, '1', null, null, null, '0');
 675          $table->add_field('someint2', XMLDB_TYPE_INTEGER, '2', null, null, null, '0');
 676          $table->add_field('someint3', XMLDB_TYPE_INTEGER, '3', null, null, null, '0');
 677          $table->add_field('someint4', XMLDB_TYPE_INTEGER, '4', null, null, null, '0');
 678          $table->add_field('someint5', XMLDB_TYPE_INTEGER, '5', null, null, null, '0');
 679          $table->add_field('someint6', XMLDB_TYPE_INTEGER, '6', null, null, null, '0');
 680          $table->add_field('someint7', XMLDB_TYPE_INTEGER, '7', null, null, null, '0');
 681          $table->add_field('someint8', XMLDB_TYPE_INTEGER, '8', null, null, null, '0');
 682          $table->add_field('someint9', XMLDB_TYPE_INTEGER, '9', null, null, null, '0');
 683          $table->add_field('someint10', XMLDB_TYPE_INTEGER, '10', null, null, null, '0');
 684          $table->add_field('someint18', XMLDB_TYPE_INTEGER, '18', null, null, null, '0');
 685          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
 686          $dbman->create_table($table);
 687  
 688          $columns = $DB->get_columns($tablename);
 689          $this->assertInternalType('array', $columns);
 690  
 691          $fields = $table->getFields();
 692          $this->assertCount(count($columns), $fields);
 693  
 694          $field = $columns['id'];
 695          $this->assertSame('R', $field->meta_type);
 696          $this->assertTrue($field->auto_increment);
 697          $this->assertTrue($field->unique);
 698  
 699          $field = $columns['course'];
 700          $this->assertSame('I', $field->meta_type);
 701          $this->assertFalse($field->auto_increment);
 702          $this->assertTrue($field->has_default);
 703          $this->assertEquals(0, $field->default_value);
 704          $this->assertTrue($field->not_null);
 705  
 706          for ($i=1; $i<=10; $i++) {
 707              $field = $columns['someint'.$i];
 708              $this->assertSame('I', $field->meta_type);
 709              $this->assertGreaterThanOrEqual($i, $field->max_length);
 710          }
 711          $field = $columns['someint18'];
 712          $this->assertSame('I', $field->meta_type);
 713          $this->assertGreaterThanOrEqual(18, $field->max_length);
 714  
 715          $field = $columns['name'];
 716          $this->assertSame('C', $field->meta_type);
 717          $this->assertFalse($field->auto_increment);
 718          $this->assertEquals(255, $field->max_length);
 719          $this->assertTrue($field->has_default);
 720          $this->assertSame('lala', $field->default_value);
 721          $this->assertFalse($field->not_null);
 722  
 723          $field = $columns['description'];
 724          $this->assertSame('X', $field->meta_type);
 725          $this->assertFalse($field->auto_increment);
 726          $this->assertFalse($field->has_default);
 727          $this->assertNull($field->default_value);
 728          $this->assertFalse($field->not_null);
 729  
 730          $field = $columns['oneint'];
 731          $this->assertSame('I', $field->meta_type);
 732          $this->assertFalse($field->auto_increment);
 733          $this->assertTrue($field->has_default);
 734          $this->assertEquals(0, $field->default_value);
 735          $this->assertTrue($field->not_null);
 736  
 737          $field = $columns['oneintnodefault'];
 738          $this->assertSame('I', $field->meta_type);
 739          $this->assertFalse($field->auto_increment);
 740          $this->assertFalse($field->has_default);
 741          $this->assertNull($field->default_value);
 742          $this->assertTrue($field->not_null);
 743  
 744          $field = $columns['enumfield'];
 745          $this->assertSame('C', $field->meta_type);
 746          $this->assertFalse($field->auto_increment);
 747          $this->assertSame('test2', $field->default_value);
 748          $this->assertTrue($field->not_null);
 749  
 750          $field = $columns['onenum'];
 751          $this->assertSame('N', $field->meta_type);
 752          $this->assertFalse($field->auto_increment);
 753          $this->assertEquals(10, $field->max_length);
 754          $this->assertEquals(2, $field->scale);
 755          $this->assertTrue($field->has_default);
 756          $this->assertEquals(200.0, $field->default_value);
 757          $this->assertFalse($field->not_null);
 758  
 759          $field = $columns['onenumnodefault'];
 760          $this->assertSame('N', $field->meta_type);
 761          $this->assertFalse($field->auto_increment);
 762          $this->assertEquals(10, $field->max_length);
 763          $this->assertEquals(2, $field->scale);
 764          $this->assertFalse($field->has_default);
 765          $this->assertNull($field->default_value);
 766          $this->assertFalse($field->not_null);
 767  
 768          $field = $columns['onefloat'];
 769          $this->assertSame('N', $field->meta_type);
 770          $this->assertFalse($field->auto_increment);
 771          $this->assertTrue($field->has_default);
 772          $this->assertEquals(300.0, $field->default_value);
 773          $this->assertTrue($field->not_null);
 774  
 775          $field = $columns['onefloatnodefault'];
 776          $this->assertSame('N', $field->meta_type);
 777          $this->assertFalse($field->auto_increment);
 778          $this->assertFalse($field->has_default);
 779          $this->assertNull($field->default_value);
 780          $this->assertTrue($field->not_null);
 781  
 782          $field = $columns['anotherfloat'];
 783          $this->assertSame('N', $field->meta_type);
 784          $this->assertFalse($field->auto_increment);
 785          $this->assertTrue($field->has_default);
 786          $this->assertEquals(400.0, $field->default_value);
 787          $this->assertFalse($field->not_null);
 788  
 789          // Test negative defaults in numerical columns.
 790          $field = $columns['negativedfltint'];
 791          $this->assertTrue($field->has_default);
 792          $this->assertEquals(-1, $field->default_value);
 793  
 794          $field = $columns['negativedfltnumber'];
 795          $this->assertTrue($field->has_default);
 796          $this->assertEquals(-2, $field->default_value);
 797  
 798          $field = $columns['negativedfltfloat'];
 799          $this->assertTrue($field->has_default);
 800          $this->assertEquals(-3, $field->default_value);
 801  
 802          for ($i = 0; $i < count($columns); $i++) {
 803              if ($i == 0) {
 804                  $next_column = reset($columns);
 805                  $next_field  = reset($fields);
 806              } else {
 807                  $next_column = next($columns);
 808                  $next_field  = next($fields);
 809              }
 810  
 811              $this->assertEquals($next_column->name, $next_field->getName());
 812          }
 813  
 814          // Test get_columns for non-existing table returns empty array. MDL-30147.
 815          $columns = $DB->get_columns('xxxx');
 816          $this->assertEquals(array(), $columns);
 817  
 818          // Create something similar to "context_temp" with id column without sequence.
 819          $dbman->drop_table($table);
 820          $table = $this->get_test_table();
 821          $tablename = $table->getName();
 822          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
 823          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
 824          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
 825          $dbman->create_table($table);
 826  
 827          $columns = $DB->get_columns($tablename);
 828          $this->assertFalse($columns['id']->auto_increment);
 829      }
 830  
 831      public function test_get_manager() {
 832          $DB = $this->tdb;
 833          $dbman = $this->tdb->get_manager();
 834  
 835          $this->assertInstanceOf('database_manager', $dbman);
 836      }
 837  
 838      public function test_setup_is_unicodedb() {
 839          $DB = $this->tdb;
 840          $this->assertTrue($DB->setup_is_unicodedb());
 841      }
 842  
 843      public function test_set_debug() { // Tests get_debug() too.
 844          $DB = $this->tdb;
 845          $dbman = $this->tdb->get_manager();
 846  
 847          $table = $this->get_test_table();
 848          $tablename = $table->getName();
 849  
 850          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
 851          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
 852          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
 853          $dbman->create_table($table);
 854  
 855          $sql = "SELECT * FROM {{$tablename}}";
 856  
 857          $prevdebug = $DB->get_debug();
 858  
 859          ob_start();
 860          $DB->set_debug(true);
 861          $this->assertTrue($DB->get_debug());
 862          $DB->execute($sql);
 863          $DB->set_debug(false);
 864          $this->assertFalse($DB->get_debug());
 865          $debuginfo = ob_get_contents();
 866          ob_end_clean();
 867          $this->assertFalse($debuginfo === '');
 868  
 869          ob_start();
 870          $DB->execute($sql);
 871          $debuginfo = ob_get_contents();
 872          ob_end_clean();
 873          $this->assertTrue($debuginfo === '');
 874  
 875          $DB->set_debug($prevdebug);
 876      }
 877  
 878      public function test_execute() {
 879          $DB = $this->tdb;
 880          $dbman = $this->tdb->get_manager();
 881  
 882          $table1 = $this->get_test_table('1');
 883          $tablename1 = $table1->getName();
 884          $table1->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
 885          $table1->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
 886          $table1->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, '0');
 887          $table1->add_index('course', XMLDB_INDEX_NOTUNIQUE, array('course'));
 888          $table1->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
 889          $dbman->create_table($table1);
 890  
 891          $table2 = $this->get_test_table('2');
 892          $tablename2 = $table2->getName();
 893          $table2->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
 894          $table2->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
 895          $table2->add_field('onetext', XMLDB_TYPE_TEXT, 'big', null, null, null);
 896          $table2->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
 897          $dbman->create_table($table2);
 898  
 899          $DB->insert_record($tablename1, array('course' => 3, 'name' => 'aaa'));
 900          $DB->insert_record($tablename1, array('course' => 1, 'name' => 'bbb'));
 901          $DB->insert_record($tablename1, array('course' => 7, 'name' => 'ccc'));
 902          $DB->insert_record($tablename1, array('course' => 3, 'name' => 'ddd'));
 903  
 904          // Select results are ignored.
 905          $sql = "SELECT * FROM {{$tablename1}} WHERE course = :course";
 906          $this->assertTrue($DB->execute($sql, array('course'=>3)));
 907  
 908          // Throw exception on error.
 909          $sql = "XXUPDATE SET XSSD";
 910          try {
 911              $DB->execute($sql);
 912              $this->fail("Expecting an exception, none occurred");
 913          } catch (moodle_exception $e) {
 914              $this->assertInstanceOf('dml_exception', $e);
 915          }
 916  
 917          // Update records.
 918          $sql = "UPDATE {{$tablename1}}
 919                     SET course = 6
 920                   WHERE course = ?";
 921          $this->assertTrue($DB->execute($sql, array('3')));
 922          $this->assertEquals(2, $DB->count_records($tablename1, array('course' => 6)));
 923  
 924          // Update records with subquery condition.
 925          // Confirm that the option not using table aliases is cross-db.
 926          $sql = "UPDATE {{$tablename1}}
 927                     SET course = 0
 928                   WHERE NOT EXISTS (
 929                             SELECT course
 930                               FROM {{$tablename2}} tbl2
 931                              WHERE tbl2.course = {{$tablename1}}.course
 932                                AND 1 = 0)"; // Really we don't update anything, but verify the syntax is allowed.
 933          $this->assertTrue($DB->execute($sql));
 934  
 935          // Insert from one into second table.
 936          $sql = "INSERT INTO {{$tablename2}} (course)
 937  
 938                  SELECT course
 939                    FROM {{$tablename1}}";
 940          $this->assertTrue($DB->execute($sql));
 941          $this->assertEquals(4, $DB->count_records($tablename2));
 942  
 943          // Insert a TEXT with raw SQL, binding TEXT params.
 944          $course = 9999;
 945          $onetext = file_get_contents(__DIR__ . '/fixtures/clob.txt');
 946          $sql = "INSERT INTO {{$tablename2}} (course, onetext)
 947                  VALUES (:course, :onetext)";
 948          $DB->execute($sql, array('course' => $course, 'onetext' => $onetext));
 949          $records = $DB->get_records($tablename2, array('course' => $course));
 950          $this->assertCount(1, $records);
 951          $record = reset($records);
 952          $this->assertSame($onetext, $record->onetext);
 953  
 954          // Update a TEXT with raw SQL, binding TEXT params.
 955          $newcourse = 10000;
 956          $newonetext = file_get_contents(__DIR__ . '/fixtures/clob.txt') . '- updated';
 957          $sql = "UPDATE {{$tablename2}} SET course = :newcourse, onetext = :newonetext
 958                  WHERE course = :oldcourse";
 959          $DB->execute($sql, array('oldcourse' => $course, 'newcourse' => $newcourse, 'newonetext' => $newonetext));
 960          $records = $DB->get_records($tablename2, array('course' => $course));
 961          $this->assertCount(0, $records);
 962          $records = $DB->get_records($tablename2, array('course' => $newcourse));
 963          $this->assertCount(1, $records);
 964          $record = reset($records);
 965          $this->assertSame($newonetext, $record->onetext);
 966      }
 967  
 968      public function test_get_recordset() {
 969          $DB = $this->tdb;
 970          $dbman = $DB->get_manager();
 971  
 972          $table = $this->get_test_table();
 973          $tablename = $table->getName();
 974  
 975          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
 976          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
 977          $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, '0');
 978          $table->add_field('onetext', XMLDB_TYPE_TEXT, 'big', null, null, null);
 979          $table->add_index('course', XMLDB_INDEX_NOTUNIQUE, array('course'));
 980          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
 981          $dbman->create_table($table);
 982  
 983          $data = array(array('course' => 3, 'name' => 'record1', 'onetext'=>'abc'),
 984              array('course' => 3, 'name' => 'record2', 'onetext'=>'abcd'),
 985              array('course' => 5, 'name' => 'record3', 'onetext'=>'abcde'));
 986  
 987          foreach ($data as $key => $record) {
 988              $data[$key]['id'] = $DB->insert_record($tablename, $record);
 989          }
 990  
 991          // Standard recordset iteration.
 992          $rs = $DB->get_recordset($tablename);
 993          $this->assertInstanceOf('moodle_recordset', $rs);
 994          reset($data);
 995          foreach ($rs as $record) {
 996              $data_record = current($data);
 997              foreach ($record as $k => $v) {
 998                  $this->assertEquals($data_record[$k], $v);
 999              }
1000              next($data);
1001          }
1002          $rs->close();
1003  
1004          // Iterator style usage.
1005          $rs = $DB->get_recordset($tablename);
1006          $this->assertInstanceOf('moodle_recordset', $rs);
1007          reset($data);
1008          while ($rs->valid()) {
1009              $record = $rs->current();
1010              $data_record = current($data);
1011              foreach ($record as $k => $v) {
1012                  $this->assertEquals($data_record[$k], $v);
1013              }
1014              next($data);
1015              $rs->next();
1016          }
1017          $rs->close();
1018  
1019          // Make sure rewind is ignored.
1020          $rs = $DB->get_recordset($tablename);
1021          $this->assertInstanceOf('moodle_recordset', $rs);
1022          reset($data);
1023          $i = 0;
1024          foreach ($rs as $record) {
1025              $i++;
1026              $rs->rewind();
1027              if ($i > 10) {
1028                  $this->fail('revind not ignored in recordsets');
1029                  break;
1030              }
1031              $data_record = current($data);
1032              foreach ($record as $k => $v) {
1033                  $this->assertEquals($data_record[$k], $v);
1034              }
1035              next($data);
1036          }
1037          $rs->close();
1038  
1039          // Test for exception throwing on text conditions being compared. (MDL-24863, unwanted auto conversion of param to int).
1040          $conditions = array('onetext' => '1');
1041          try {
1042              $rs = $DB->get_recordset($tablename, $conditions);
1043              $this->fail('An Exception is missing, expected due to equating of text fields');
1044          } catch (moodle_exception $e) {
1045              $this->assertInstanceOf('dml_exception', $e);
1046              $this->assertSame('textconditionsnotallowed', $e->errorcode);
1047          }
1048  
1049          // Test nested iteration.
1050          $rs1 = $DB->get_recordset($tablename);
1051          $i = 0;
1052          foreach ($rs1 as $record1) {
1053              $rs2 = $DB->get_recordset($tablename);
1054              $i++;
1055              $j = 0;
1056              foreach ($rs2 as $record2) {
1057                  $j++;
1058              }
1059              $rs2->close();
1060              $this->assertCount($j, $data);
1061          }
1062          $rs1->close();
1063          $this->assertCount($i, $data);
1064  
1065          // Notes:
1066          //  * limits are tested in test_get_recordset_sql()
1067          //  * where_clause() is used internally and is tested in test_get_records()
1068      }
1069  
1070      public function test_get_recordset_static() {
1071          $DB = $this->tdb;
1072          $dbman = $DB->get_manager();
1073  
1074          $table = $this->get_test_table();
1075          $tablename = $table->getName();
1076  
1077          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1078          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1079          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1080          $dbman->create_table($table);
1081  
1082          $DB->insert_record($tablename, array('course' => 1));
1083          $DB->insert_record($tablename, array('course' => 2));
1084          $DB->insert_record($tablename, array('course' => 3));
1085          $DB->insert_record($tablename, array('course' => 4));
1086  
1087          $rs = $DB->get_recordset($tablename, array(), 'id');
1088  
1089          $DB->set_field($tablename, 'course', 666, array('course'=>1));
1090          $DB->delete_records($tablename, array('course'=>2));
1091  
1092          $i = 0;
1093          foreach ($rs as $record) {
1094              $i++;
1095              $this->assertEquals($i, $record->course);
1096          }
1097          $rs->close();
1098          $this->assertEquals(4, $i);
1099  
1100          // Now repeat with limits because it may use different code.
1101          $DB->delete_records($tablename, array());
1102  
1103          $DB->insert_record($tablename, array('course' => 1));
1104          $DB->insert_record($tablename, array('course' => 2));
1105          $DB->insert_record($tablename, array('course' => 3));
1106          $DB->insert_record($tablename, array('course' => 4));
1107  
1108          $rs = $DB->get_recordset($tablename, array(), 'id', '*', 0, 3);
1109  
1110          $DB->set_field($tablename, 'course', 666, array('course'=>1));
1111          $DB->delete_records($tablename, array('course'=>2));
1112  
1113          $i = 0;
1114          foreach ($rs as $record) {
1115              $i++;
1116              $this->assertEquals($i, $record->course);
1117          }
1118          $rs->close();
1119          $this->assertEquals(3, $i);
1120      }
1121  
1122      public function test_get_recordset_iterator_keys() {
1123          $DB = $this->tdb;
1124          $dbman = $DB->get_manager();
1125  
1126          $table = $this->get_test_table();
1127          $tablename = $table->getName();
1128  
1129          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1130          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1131          $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, '0');
1132          $table->add_index('course', XMLDB_INDEX_NOTUNIQUE, array('course'));
1133          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1134          $dbman->create_table($table);
1135  
1136          $data = array(array('course' => 3, 'name' => 'record1'),
1137              array('course' => 3, 'name' => 'record2'),
1138              array('course' => 5, 'name' => 'record3'));
1139          foreach ($data as $key => $record) {
1140              $data[$key]['id'] = $DB->insert_record($tablename, $record);
1141          }
1142  
1143          // Test repeated numeric keys are returned ok.
1144          $rs = $DB->get_recordset($tablename, null, null, 'course, name, id');
1145  
1146          reset($data);
1147          $count = 0;
1148          foreach ($rs as $key => $record) {
1149              $data_record = current($data);
1150              $this->assertEquals($data_record['course'], $key);
1151              next($data);
1152              $count++;
1153          }
1154          $rs->close();
1155          $this->assertEquals(3, $count);
1156  
1157          // Test string keys are returned ok.
1158          $rs = $DB->get_recordset($tablename, null, null, 'name, course, id');
1159  
1160          reset($data);
1161          $count = 0;
1162          foreach ($rs as $key => $record) {
1163              $data_record = current($data);
1164              $this->assertEquals($data_record['name'], $key);
1165              next($data);
1166              $count++;
1167          }
1168          $rs->close();
1169          $this->assertEquals(3, $count);
1170  
1171          // Test numeric not starting in 1 keys are returned ok.
1172          $rs = $DB->get_recordset($tablename, null, 'id DESC', 'id, course, name');
1173  
1174          $data = array_reverse($data);
1175          reset($data);
1176          $count = 0;
1177          foreach ($rs as $key => $record) {
1178              $data_record = current($data);
1179              $this->assertEquals($data_record['id'], $key);
1180              next($data);
1181              $count++;
1182          }
1183          $rs->close();
1184          $this->assertEquals(3, $count);
1185      }
1186  
1187      public function test_get_recordset_list() {
1188          $DB = $this->tdb;
1189          $dbman = $DB->get_manager();
1190  
1191          $table = $this->get_test_table();
1192          $tablename = $table->getName();
1193  
1194          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1195          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, null, null, '0');
1196          $table->add_index('course', XMLDB_INDEX_NOTUNIQUE, array('course'));
1197          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1198          $dbman->create_table($table);
1199  
1200          $DB->insert_record($tablename, array('course' => 3));
1201          $DB->insert_record($tablename, array('course' => 3));
1202          $DB->insert_record($tablename, array('course' => 5));
1203          $DB->insert_record($tablename, array('course' => 2));
1204          $DB->insert_record($tablename, array('course' => null));
1205          $DB->insert_record($tablename, array('course' => 1));
1206          $DB->insert_record($tablename, array('course' => 0));
1207  
1208          $rs = $DB->get_recordset_list($tablename, 'course', array(3, 2));
1209          $counter = 0;
1210          foreach ($rs as $record) {
1211              $counter++;
1212          }
1213          $this->assertEquals(3, $counter);
1214          $rs->close();
1215  
1216          $rs = $DB->get_recordset_list($tablename, 'course', array(3));
1217          $counter = 0;
1218          foreach ($rs as $record) {
1219              $counter++;
1220          }
1221          $this->assertEquals(2, $counter);
1222          $rs->close();
1223  
1224          $rs = $DB->get_recordset_list($tablename, 'course', array(null));
1225          $counter = 0;
1226          foreach ($rs as $record) {
1227              $counter++;
1228          }
1229          $this->assertEquals(1, $counter);
1230          $rs->close();
1231  
1232          $rs = $DB->get_recordset_list($tablename, 'course', array(6, null));
1233          $counter = 0;
1234          foreach ($rs as $record) {
1235              $counter++;
1236          }
1237          $this->assertEquals(1, $counter);
1238          $rs->close();
1239  
1240          $rs = $DB->get_recordset_list($tablename, 'course', array(null, 5, 5, 5));
1241          $counter = 0;
1242          foreach ($rs as $record) {
1243              $counter++;
1244          }
1245          $this->assertEquals(2, $counter);
1246          $rs->close();
1247  
1248          $rs = $DB->get_recordset_list($tablename, 'course', array(true));
1249          $counter = 0;
1250          foreach ($rs as $record) {
1251              $counter++;
1252          }
1253          $this->assertEquals(1, $counter);
1254          $rs->close();
1255  
1256          $rs = $DB->get_recordset_list($tablename, 'course', array(false));
1257          $counter = 0;
1258          foreach ($rs as $record) {
1259              $counter++;
1260          }
1261          $this->assertEquals(1, $counter);
1262          $rs->close();
1263  
1264          $rs = $DB->get_recordset_list($tablename, 'course', array()); // Must return 0 rows without conditions. MDL-17645.
1265  
1266          $counter = 0;
1267          foreach ($rs as $record) {
1268              $counter++;
1269          }
1270          $rs->close();
1271          $this->assertEquals(0, $counter);
1272  
1273          // Notes:
1274          //  * limits are tested in test_get_recordset_sql()
1275          //  * where_clause() is used internally and is tested in test_get_records()
1276      }
1277  
1278      public function test_get_recordset_select() {
1279          $DB = $this->tdb;
1280          $dbman = $DB->get_manager();
1281  
1282          $table = $this->get_test_table();
1283          $tablename = $table->getName();
1284  
1285          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1286          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1287          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1288          $dbman->create_table($table);
1289  
1290          $DB->insert_record($tablename, array('course' => 3));
1291          $DB->insert_record($tablename, array('course' => 3));
1292          $DB->insert_record($tablename, array('course' => 5));
1293          $DB->insert_record($tablename, array('course' => 2));
1294  
1295          $rs = $DB->get_recordset_select($tablename, '');
1296          $counter = 0;
1297          foreach ($rs as $record) {
1298              $counter++;
1299          }
1300          $rs->close();
1301          $this->assertEquals(4, $counter);
1302  
1303          $this->assertNotEmpty($rs = $DB->get_recordset_select($tablename, 'course = 3'));
1304          $counter = 0;
1305          foreach ($rs as $record) {
1306              $counter++;
1307          }
1308          $rs->close();
1309          $this->assertEquals(2, $counter);
1310  
1311          // Notes:
1312          //  * limits are tested in test_get_recordset_sql()
1313      }
1314  
1315      public function test_get_recordset_sql() {
1316          $DB = $this->tdb;
1317          $dbman = $DB->get_manager();
1318  
1319          $table = $this->get_test_table();
1320          $tablename = $table->getName();
1321  
1322          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1323          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1324          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1325          $dbman->create_table($table);
1326  
1327          $inskey1 = $DB->insert_record($tablename, array('course' => 3));
1328          $inskey2 = $DB->insert_record($tablename, array('course' => 5));
1329          $inskey3 = $DB->insert_record($tablename, array('course' => 4));
1330          $inskey4 = $DB->insert_record($tablename, array('course' => 3));
1331          $inskey5 = $DB->insert_record($tablename, array('course' => 2));
1332          $inskey6 = $DB->insert_record($tablename, array('course' => 1));
1333          $inskey7 = $DB->insert_record($tablename, array('course' => 0));
1334  
1335          $rs = $DB->get_recordset_sql("SELECT * FROM {{$tablename}} WHERE course = ?", array(3));
1336          $counter = 0;
1337          foreach ($rs as $record) {
1338              $counter++;
1339          }
1340          $rs->close();
1341          $this->assertEquals(2, $counter);
1342  
1343          // Limits - only need to test this case, the rest have been tested by test_get_records_sql()
1344          // only limitfrom = skips that number of records.
1345          $rs = $DB->get_recordset_sql("SELECT * FROM {{$tablename}} ORDER BY id", null, 2, 0);
1346          $records = array();
1347          foreach ($rs as $key => $record) {
1348              $records[$key] = $record;
1349          }
1350          $rs->close();
1351          $this->assertCount(5, $records);
1352          $this->assertEquals($inskey3, reset($records)->id);
1353          $this->assertEquals($inskey7, end($records)->id);
1354  
1355          // Note: fetching nulls, empties, LOBs already tested by test_insert_record() no needed here.
1356      }
1357  
1358      public function test_export_table_recordset() {
1359          $DB = $this->tdb;
1360          $dbman = $DB->get_manager();
1361  
1362          $table = $this->get_test_table();
1363          $tablename = $table->getName();
1364  
1365          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1366          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1367          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1368          $dbman->create_table($table);
1369  
1370          $ids = array();
1371          $ids[] = $DB->insert_record($tablename, array('course' => 3));
1372          $ids[] = $DB->insert_record($tablename, array('course' => 5));
1373          $ids[] = $DB->insert_record($tablename, array('course' => 4));
1374          $ids[] = $DB->insert_record($tablename, array('course' => 3));
1375          $ids[] = $DB->insert_record($tablename, array('course' => 2));
1376          $ids[] = $DB->insert_record($tablename, array('course' => 1));
1377          $ids[] = $DB->insert_record($tablename, array('course' => 0));
1378  
1379          $rs = $DB->export_table_recordset($tablename);
1380          $rids = array();
1381          foreach ($rs as $record) {
1382              $rids[] = $record->id;
1383          }
1384          $rs->close();
1385          $this->assertEquals($ids, $rids, '', 0, 0, true);
1386      }
1387  
1388      public function test_get_records() {
1389          $DB = $this->tdb;
1390          $dbman = $DB->get_manager();
1391  
1392          $table = $this->get_test_table();
1393          $tablename = $table->getName();
1394  
1395          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1396          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1397          $table->add_field('onetext', XMLDB_TYPE_TEXT, 'big', null, null, null);
1398          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1399          $dbman->create_table($table);
1400  
1401          $DB->insert_record($tablename, array('course' => 3));
1402          $DB->insert_record($tablename, array('course' => 3));
1403          $DB->insert_record($tablename, array('course' => 5));
1404          $DB->insert_record($tablename, array('course' => 2));
1405  
1406          // All records.
1407          $records = $DB->get_records($tablename);
1408          $this->assertCount(4, $records);
1409          $this->assertEquals(3, $records[1]->course);
1410          $this->assertEquals(3, $records[2]->course);
1411          $this->assertEquals(5, $records[3]->course);
1412          $this->assertEquals(2, $records[4]->course);
1413  
1414          // Records matching certain conditions.
1415          $records = $DB->get_records($tablename, array('course' => 3));
1416          $this->assertCount(2, $records);
1417          $this->assertEquals(3, $records[1]->course);
1418          $this->assertEquals(3, $records[2]->course);
1419  
1420          // All records sorted by course.
1421          $records = $DB->get_records($tablename, null, 'course');
1422          $this->assertCount(4, $records);
1423          $current_record = reset($records);
1424          $this->assertEquals(4, $current_record->id);
1425          $current_record = next($records);
1426          $this->assertEquals(1, $current_record->id);
1427          $current_record = next($records);
1428          $this->assertEquals(2, $current_record->id);
1429          $current_record = next($records);
1430          $this->assertEquals(3, $current_record->id);
1431  
1432          // All records, but get only one field.
1433          $records = $DB->get_records($tablename, null, '', 'id');
1434          $this->assertFalse(isset($records[1]->course));
1435          $this->assertTrue(isset($records[1]->id));
1436          $this->assertCount(4, $records);
1437  
1438          // Booleans into params.
1439          $records = $DB->get_records($tablename, array('course' => true));
1440          $this->assertCount(0, $records);
1441          $records = $DB->get_records($tablename, array('course' => false));
1442          $this->assertCount(0, $records);
1443  
1444          // Test for exception throwing on text conditions being compared. (MDL-24863, unwanted auto conversion of param to int).
1445          $conditions = array('onetext' => '1');
1446          try {
1447              $records = $DB->get_records($tablename, $conditions);
1448              if (debugging()) {
1449                  // Only in debug mode - hopefully all devs test code in debug mode...
1450                  $this->fail('An Exception is missing, expected due to equating of text fields');
1451              }
1452          } catch (moodle_exception $e) {
1453              $this->assertInstanceOf('dml_exception', $e);
1454              $this->assertSame('textconditionsnotallowed', $e->errorcode);
1455          }
1456  
1457          // Test get_records passing non-existing table.
1458          // with params.
1459          try {
1460              $records = $DB->get_records('xxxx', array('id' => 0));
1461              $this->fail('An Exception is missing, expected due to query against non-existing table');
1462          } catch (moodle_exception $e) {
1463              $this->assertInstanceOf('dml_exception', $e);
1464              if (debugging()) {
1465                  // Information for developers only, normal users get general error message.
1466                  $this->assertSame('ddltablenotexist', $e->errorcode);
1467              }
1468          }
1469  
1470          try {
1471              $records = $DB->get_records('xxxx', array('id' => '1'));
1472              $this->fail('An Exception is missing, expected due to query against non-existing table');
1473          } catch (moodle_exception $e) {
1474              $this->assertInstanceOf('dml_exception', $e);
1475              if (debugging()) {
1476                  // Information for developers only, normal users get general error message.
1477                  $this->assertSame('ddltablenotexist', $e->errorcode);
1478              }
1479          }
1480  
1481          // Test get_records passing non-existing column.
1482          try {
1483              $records = $DB->get_records($tablename, array('xxxx' => 0));
1484              $this->fail('An Exception is missing, expected due to query against non-existing column');
1485          } catch (moodle_exception $e) {
1486              $this->assertInstanceOf('dml_exception', $e);
1487              if (debugging()) {
1488                  // Information for developers only, normal users get general error message.
1489                  $this->assertSame('ddlfieldnotexist', $e->errorcode);
1490              }
1491          }
1492  
1493          // Note: delegate limits testing to test_get_records_sql().
1494      }
1495  
1496      public function test_get_records_list() {
1497          $DB = $this->tdb;
1498          $dbman = $DB->get_manager();
1499  
1500          $table = $this->get_test_table();
1501          $tablename = $table->getName();
1502  
1503          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1504          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1505          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1506          $dbman->create_table($table);
1507  
1508          $DB->insert_record($tablename, array('course' => 3));
1509          $DB->insert_record($tablename, array('course' => 3));
1510          $DB->insert_record($tablename, array('course' => 5));
1511          $DB->insert_record($tablename, array('course' => 2));
1512  
1513          $records = $DB->get_records_list($tablename, 'course', array(3, 2));
1514          $this->assertInternalType('array', $records);
1515          $this->assertCount(3, $records);
1516          $this->assertEquals(1, reset($records)->id);
1517          $this->assertEquals(2, next($records)->id);
1518          $this->assertEquals(4, next($records)->id);
1519  
1520          $this->assertSame(array(), $records = $DB->get_records_list($tablename, 'course', array())); // Must return 0 rows without conditions. MDL-17645.
1521          $this->assertCount(0, $records);
1522  
1523          // Note: delegate limits testing to test_get_records_sql().
1524      }
1525  
1526      public function test_get_records_sql() {
1527          $DB = $this->tdb;
1528          $dbman = $DB->get_manager();
1529  
1530          $table = $this->get_test_table();
1531          $tablename = $table->getName();
1532  
1533          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1534          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1535          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1536          $dbman->create_table($table);
1537  
1538          $inskey1 = $DB->insert_record($tablename, array('course' => 3));
1539          $inskey2 = $DB->insert_record($tablename, array('course' => 5));
1540          $inskey3 = $DB->insert_record($tablename, array('course' => 4));
1541          $inskey4 = $DB->insert_record($tablename, array('course' => 3));
1542          $inskey5 = $DB->insert_record($tablename, array('course' => 2));
1543          $inskey6 = $DB->insert_record($tablename, array('course' => 1));
1544          $inskey7 = $DB->insert_record($tablename, array('course' => 0));
1545  
1546          $table2 = $this->get_test_table("2");
1547          $tablename2 = $table2->getName();
1548          $table2->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1549          $table2->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1550          $table2->add_field('nametext', XMLDB_TYPE_TEXT, 'small', null, null, null, null);
1551          $table2->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1552          $dbman->create_table($table2);
1553  
1554          $DB->insert_record($tablename2, array('course'=>3, 'nametext'=>'badabing'));
1555          $DB->insert_record($tablename2, array('course'=>4, 'nametext'=>'badabang'));
1556          $DB->insert_record($tablename2, array('course'=>5, 'nametext'=>'badabung'));
1557          $DB->insert_record($tablename2, array('course'=>6, 'nametext'=>'badabong'));
1558  
1559          $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE course = ?", array(3));
1560          $this->assertCount(2, $records);
1561          $this->assertEquals($inskey1, reset($records)->id);
1562          $this->assertEquals($inskey4, next($records)->id);
1563  
1564          // Awful test, requires debug enabled and sent to browser. Let's do that and restore after test.
1565          $records = $DB->get_records_sql("SELECT course AS id, course AS course FROM {{$tablename}}", null);
1566          $this->assertDebuggingCalled();
1567          $this->assertCount(6, $records);
1568          set_debugging(DEBUG_MINIMAL);
1569          $records = $DB->get_records_sql("SELECT course AS id, course AS course FROM {{$tablename}}", null);
1570          $this->assertDebuggingNotCalled();
1571          $this->assertCount(6, $records);
1572          set_debugging(DEBUG_DEVELOPER);
1573  
1574          // Negative limits = no limits.
1575          $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} ORDER BY id", null, -1, -1);
1576          $this->assertCount(7, $records);
1577  
1578          // Zero limits = no limits.
1579          $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} ORDER BY id", null, 0, 0);
1580          $this->assertCount(7, $records);
1581  
1582          // Only limitfrom = skips that number of records.
1583          $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} ORDER BY id", null, 2, 0);
1584          $this->assertCount(5, $records);
1585          $this->assertEquals($inskey3, reset($records)->id);
1586          $this->assertEquals($inskey7, end($records)->id);
1587  
1588          // Only limitnum = fetches that number of records.
1589          $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} ORDER BY id", null, 0, 3);
1590          $this->assertCount(3, $records);
1591          $this->assertEquals($inskey1, reset($records)->id);
1592          $this->assertEquals($inskey3, end($records)->id);
1593  
1594          // Both limitfrom and limitnum = skips limitfrom records and fetches limitnum ones.
1595          $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} ORDER BY id", null, 3, 2);
1596          $this->assertCount(2, $records);
1597          $this->assertEquals($inskey4, reset($records)->id);
1598          $this->assertEquals($inskey5, end($records)->id);
1599  
1600          // Both limitfrom and limitnum in query having subqueris.
1601          // Note the subquery skips records with course = 0 and 3.
1602          $sql = "SELECT * FROM {{$tablename}}
1603                   WHERE course NOT IN (
1604                       SELECT course FROM {{$tablename}}
1605                        WHERE course IN (0, 3))
1606                  ORDER BY course";
1607          $records = $DB->get_records_sql($sql, null, 0, 2); // Skip 0, get 2.
1608          $this->assertCount(2, $records);
1609          $this->assertEquals($inskey6, reset($records)->id);
1610          $this->assertEquals($inskey5, end($records)->id);
1611          $records = $DB->get_records_sql($sql, null, 2, 2); // Skip 2, get 2.
1612          $this->assertCount(2, $records);
1613          $this->assertEquals($inskey3, reset($records)->id);
1614          $this->assertEquals($inskey2, end($records)->id);
1615  
1616          // Test 2 tables with aliases and limits with order bys.
1617          $sql = "SELECT t1.id, t1.course AS cid, t2.nametext
1618                    FROM {{$tablename}} t1, {{$tablename2}} t2
1619                   WHERE t2.course=t1.course
1620                ORDER BY t1.course, ". $DB->sql_compare_text('t2.nametext');
1621          $records = $DB->get_records_sql($sql, null, 2, 2); // Skip courses 3 and 6, get 4 and 5.
1622          $this->assertCount(2, $records);
1623          $this->assertSame('5', end($records)->cid);
1624          $this->assertSame('4', reset($records)->cid);
1625  
1626          // Test 2 tables with aliases and limits with the highest INT limit works.
1627          $records = $DB->get_records_sql($sql, null, 2, PHP_INT_MAX); // Skip course {3,6}, get {4,5}.
1628          $this->assertCount(2, $records);
1629          $this->assertSame('5', end($records)->cid);
1630          $this->assertSame('4', reset($records)->cid);
1631  
1632          // Test 2 tables with aliases and limits with order bys (limit which is highest INT number).
1633          $records = $DB->get_records_sql($sql, null, PHP_INT_MAX, 2); // Skip all courses.
1634          $this->assertCount(0, $records);
1635  
1636          // Test 2 tables with aliases and limits with order bys (limit which s highest INT number).
1637          $records = $DB->get_records_sql($sql, null, PHP_INT_MAX, PHP_INT_MAX); // Skip all courses.
1638          $this->assertCount(0, $records);
1639  
1640          // TODO: Test limits in queries having DISTINCT clauses.
1641  
1642          // Note: fetching nulls, empties, LOBs already tested by test_update_record() no needed here.
1643      }
1644  
1645      public function test_get_records_menu() {
1646          $DB = $this->tdb;
1647          $dbman = $DB->get_manager();
1648  
1649          $table = $this->get_test_table();
1650          $tablename = $table->getName();
1651  
1652          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1653          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1654          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1655          $dbman->create_table($table);
1656  
1657          $DB->insert_record($tablename, array('course' => 3));
1658          $DB->insert_record($tablename, array('course' => 3));
1659          $DB->insert_record($tablename, array('course' => 5));
1660          $DB->insert_record($tablename, array('course' => 2));
1661  
1662          $records = $DB->get_records_menu($tablename, array('course' => 3));
1663          $this->assertInternalType('array', $records);
1664          $this->assertCount(2, $records);
1665          $this->assertNotEmpty($records[1]);
1666          $this->assertNotEmpty($records[2]);
1667          $this->assertEquals(3, $records[1]);
1668          $this->assertEquals(3, $records[2]);
1669  
1670          // Note: delegate limits testing to test_get_records_sql().
1671      }
1672  
1673      public function test_get_records_select_menu() {
1674          $DB = $this->tdb;
1675          $dbman = $DB->get_manager();
1676  
1677          $table = $this->get_test_table();
1678          $tablename = $table->getName();
1679  
1680          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1681          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1682          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1683          $dbman->create_table($table);
1684  
1685          $DB->insert_record($tablename, array('course' => 3));
1686          $DB->insert_record($tablename, array('course' => 2));
1687          $DB->insert_record($tablename, array('course' => 3));
1688          $DB->insert_record($tablename, array('course' => 5));
1689  
1690          $records = $DB->get_records_select_menu($tablename, "course > ?", array(2));
1691          $this->assertInternalType('array', $records);
1692  
1693          $this->assertCount(3, $records);
1694          $this->assertArrayHasKey(1, $records);
1695          $this->assertArrayNotHasKey(2, $records);
1696          $this->assertArrayHasKey(3, $records);
1697          $this->assertArrayHasKey(4, $records);
1698          $this->assertSame('3', $records[1]);
1699          $this->assertSame('3', $records[3]);
1700          $this->assertSame('5', $records[4]);
1701  
1702          // Note: delegate limits testing to test_get_records_sql().
1703      }
1704  
1705      public function test_get_records_sql_menu() {
1706          $DB = $this->tdb;
1707          $dbman = $DB->get_manager();
1708  
1709          $table = $this->get_test_table();
1710          $tablename = $table->getName();
1711  
1712          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1713          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1714          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1715          $dbman->create_table($table);
1716  
1717          $DB->insert_record($tablename, array('course' => 3));
1718          $DB->insert_record($tablename, array('course' => 2));
1719          $DB->insert_record($tablename, array('course' => 3));
1720          $DB->insert_record($tablename, array('course' => 5));
1721  
1722          $records = $DB->get_records_sql_menu("SELECT * FROM {{$tablename}} WHERE course > ?", array(2));
1723          $this->assertInternalType('array', $records);
1724  
1725          $this->assertCount(3, $records);
1726          $this->assertArrayHasKey(1, $records);
1727          $this->assertArrayNotHasKey(2, $records);
1728          $this->assertArrayHasKey(3, $records);
1729          $this->assertArrayHasKey(4, $records);
1730          $this->assertSame('3', $records[1]);
1731          $this->assertSame('3', $records[3]);
1732          $this->assertSame('5', $records[4]);
1733  
1734          // Note: delegate limits testing to test_get_records_sql().
1735      }
1736  
1737      public function test_get_record() {
1738          $DB = $this->tdb;
1739          $dbman = $DB->get_manager();
1740  
1741          $table = $this->get_test_table();
1742          $tablename = $table->getName();
1743  
1744          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1745          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1746          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1747          $dbman->create_table($table);
1748  
1749          $DB->insert_record($tablename, array('course' => 3));
1750          $DB->insert_record($tablename, array('course' => 2));
1751  
1752          $record = $DB->get_record($tablename, array('id' => 2));
1753          $this->assertInstanceOf('stdClass', $record);
1754  
1755          $this->assertEquals(2, $record->course);
1756          $this->assertEquals(2, $record->id);
1757      }
1758  
1759  
1760      public function test_get_record_select() {
1761          $DB = $this->tdb;
1762          $dbman = $DB->get_manager();
1763  
1764          $table = $this->get_test_table();
1765          $tablename = $table->getName();
1766  
1767          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1768          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1769          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1770          $dbman->create_table($table);
1771  
1772          $DB->insert_record($tablename, array('course' => 3));
1773          $DB->insert_record($tablename, array('course' => 2));
1774  
1775          $record = $DB->get_record_select($tablename, "id = ?", array(2));
1776          $this->assertInstanceOf('stdClass', $record);
1777  
1778          $this->assertEquals(2, $record->course);
1779  
1780          // Note: delegates limit testing to test_get_records_sql().
1781      }
1782  
1783      public function test_get_record_sql() {
1784          $DB = $this->tdb;
1785          $dbman = $DB->get_manager();
1786  
1787          $table = $this->get_test_table();
1788          $tablename = $table->getName();
1789  
1790          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1791          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1792          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1793          $dbman->create_table($table);
1794  
1795          $DB->insert_record($tablename, array('course' => 3));
1796          $DB->insert_record($tablename, array('course' => 2));
1797  
1798          // Standard use.
1799          $record = $DB->get_record_sql("SELECT * FROM {{$tablename}} WHERE id = ?", array(2));
1800          $this->assertInstanceOf('stdClass', $record);
1801          $this->assertEquals(2, $record->course);
1802          $this->assertEquals(2, $record->id);
1803  
1804          // Backwards compatibility with $ignoremultiple.
1805          $this->assertFalse((bool)IGNORE_MISSING);
1806          $this->assertTrue((bool)IGNORE_MULTIPLE);
1807  
1808          // Record not found - ignore.
1809          $this->assertFalse($DB->get_record_sql("SELECT * FROM {{$tablename}} WHERE id = ?", array(666), IGNORE_MISSING));
1810          $this->assertFalse($DB->get_record_sql("SELECT * FROM {{$tablename}} WHERE id = ?", array(666), IGNORE_MULTIPLE));
1811  
1812          // Record not found error.
1813          try {
1814              $DB->get_record_sql("SELECT * FROM {{$tablename}} WHERE id = ?", array(666), MUST_EXIST);
1815              $this->fail("Exception expected");
1816          } catch (dml_missing_record_exception $e) {
1817              $this->assertTrue(true);
1818          }
1819  
1820          $this->assertNotEmpty($DB->get_record_sql("SELECT * FROM {{$tablename}}", array(), IGNORE_MISSING));
1821          $this->assertDebuggingCalled();
1822          set_debugging(DEBUG_MINIMAL);
1823          $this->assertNotEmpty($DB->get_record_sql("SELECT * FROM {{$tablename}}", array(), IGNORE_MISSING));
1824          $this->assertDebuggingNotCalled();
1825          set_debugging(DEBUG_DEVELOPER);
1826  
1827          // Multiple matches ignored.
1828          $this->assertNotEmpty($DB->get_record_sql("SELECT * FROM {{$tablename}}", array(), IGNORE_MULTIPLE));
1829  
1830          // Multiple found error.
1831          try {
1832              $DB->get_record_sql("SELECT * FROM {{$tablename}}", array(), MUST_EXIST);
1833              $this->fail("Exception expected");
1834          } catch (dml_multiple_records_exception $e) {
1835              $this->assertTrue(true);
1836          }
1837      }
1838  
1839      public function test_get_field() {
1840          $DB = $this->tdb;
1841          $dbman = $DB->get_manager();
1842  
1843          $table = $this->get_test_table();
1844          $tablename = $table->getName();
1845  
1846          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1847          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1848          $table->add_field('onetext', XMLDB_TYPE_TEXT, 'big', null, null, null);
1849          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1850          $dbman->create_table($table);
1851  
1852          $id1 = $DB->insert_record($tablename, array('course' => 3));
1853          $DB->insert_record($tablename, array('course' => 5));
1854          $DB->insert_record($tablename, array('course' => 5));
1855  
1856          $this->assertEquals(3, $DB->get_field($tablename, 'course', array('id' => $id1)));
1857          $this->assertEquals(3, $DB->get_field($tablename, 'course', array('course' => 3)));
1858  
1859          $this->assertFalse($DB->get_field($tablename, 'course', array('course' => 11), IGNORE_MISSING));
1860          try {
1861              $DB->get_field($tablename, 'course', array('course' => 4), MUST_EXIST);
1862              $this->fail('Exception expected due to missing record');
1863          } catch (dml_exception $ex) {
1864              $this->assertTrue(true);
1865          }
1866  
1867          $this->assertEquals(5, $DB->get_field($tablename, 'course', array('course' => 5), IGNORE_MULTIPLE));
1868          $this->assertDebuggingNotCalled();
1869  
1870          $this->assertEquals(5, $DB->get_field($tablename, 'course', array('course' => 5), IGNORE_MISSING));
1871          $this->assertDebuggingCalled();
1872  
1873          // Test for exception throwing on text conditions being compared. (MDL-24863, unwanted auto conversion of param to int).
1874          $conditions = array('onetext' => '1');
1875          try {
1876              $DB->get_field($tablename, 'course', $conditions);
1877              if (debugging()) {
1878                  // Only in debug mode - hopefully all devs test code in debug mode...
1879                  $this->fail('An Exception is missing, expected due to equating of text fields');
1880              }
1881          } catch (moodle_exception $e) {
1882              $this->assertInstanceOf('dml_exception', $e);
1883              $this->assertSame('textconditionsnotallowed', $e->errorcode);
1884          }
1885      }
1886  
1887      public function test_get_field_select() {
1888          $DB = $this->tdb;
1889          $dbman = $DB->get_manager();
1890  
1891          $table = $this->get_test_table();
1892          $tablename = $table->getName();
1893  
1894          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1895          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1896          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1897          $dbman->create_table($table);
1898  
1899          $DB->insert_record($tablename, array('course' => 3));
1900  
1901          $this->assertEquals(3, $DB->get_field_select($tablename, 'course', "id = ?", array(1)));
1902      }
1903  
1904      public function test_get_field_sql() {
1905          $DB = $this->tdb;
1906          $dbman = $DB->get_manager();
1907  
1908          $table = $this->get_test_table();
1909          $tablename = $table->getName();
1910  
1911          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1912          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1913          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1914          $dbman->create_table($table);
1915  
1916          $DB->insert_record($tablename, array('course' => 3));
1917  
1918          $this->assertEquals(3, $DB->get_field_sql("SELECT course FROM {{$tablename}} WHERE id = ?", array(1)));
1919      }
1920  
1921      public function test_get_fieldset_select() {
1922          $DB = $this->tdb;
1923          $dbman = $DB->get_manager();
1924  
1925          $table = $this->get_test_table();
1926          $tablename = $table->getName();
1927  
1928          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1929          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1930          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1931          $dbman->create_table($table);
1932  
1933          $DB->insert_record($tablename, array('course' => 1));
1934          $DB->insert_record($tablename, array('course' => 3));
1935          $DB->insert_record($tablename, array('course' => 2));
1936          $DB->insert_record($tablename, array('course' => 6));
1937  
1938          $fieldset = $DB->get_fieldset_select($tablename, 'course', "course > ?", array(1));
1939          $this->assertInternalType('array', $fieldset);
1940  
1941          $this->assertCount(3, $fieldset);
1942          $this->assertEquals(3, $fieldset[0]);
1943          $this->assertEquals(2, $fieldset[1]);
1944          $this->assertEquals(6, $fieldset[2]);
1945      }
1946  
1947      public function test_get_fieldset_sql() {
1948          $DB = $this->tdb;
1949          $dbman = $DB->get_manager();
1950  
1951          $table = $this->get_test_table();
1952          $tablename = $table->getName();
1953  
1954          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1955          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1956          $table->add_field('onebinary', XMLDB_TYPE_BINARY, 'big', null, null, null);
1957          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1958          $dbman->create_table($table);
1959  
1960          $binarydata = '\\'.chr(241);
1961  
1962          $DB->insert_record($tablename, array('course' => 1, 'onebinary' => $binarydata));
1963          $DB->insert_record($tablename, array('course' => 3, 'onebinary' => $binarydata));
1964          $DB->insert_record($tablename, array('course' => 2, 'onebinary' => $binarydata));
1965          $DB->insert_record($tablename, array('course' => 6, 'onebinary' => $binarydata));
1966  
1967          $fieldset = $DB->get_fieldset_sql("SELECT * FROM {{$tablename}} WHERE course > ?", array(1));
1968          $this->assertInternalType('array', $fieldset);
1969  
1970          $this->assertCount(3, $fieldset);
1971          $this->assertEquals(2, $fieldset[0]);
1972          $this->assertEquals(3, $fieldset[1]);
1973          $this->assertEquals(4, $fieldset[2]);
1974  
1975          $fieldset = $DB->get_fieldset_sql("SELECT onebinary FROM {{$tablename}} WHERE course > ?", array(1));
1976          $this->assertInternalType('array', $fieldset);
1977  
1978          $this->assertCount(3, $fieldset);
1979          $this->assertEquals($binarydata, $fieldset[0]);
1980          $this->assertEquals($binarydata, $fieldset[1]);
1981          $this->assertEquals($binarydata, $fieldset[2]);
1982      }
1983  
1984      public function test_insert_record_raw() {
1985          $DB = $this->tdb;
1986          $dbman = $DB->get_manager();
1987  
1988          $table = $this->get_test_table();
1989          $tablename = $table->getName();
1990  
1991          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1992          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1993          $table->add_field('onechar', XMLDB_TYPE_CHAR, '100', null, null, null, 'onestring');
1994          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1995          $dbman->create_table($table);
1996  
1997          $record = (object)array('course' => 1, 'onechar' => 'xx');
1998          $before = clone($record);
1999          $result = $DB->insert_record_raw($tablename, $record);
2000          $this->assertSame(1, $result);
2001          $this->assertEquals($record, $before);
2002  
2003          $record = $DB->get_record($tablename, array('course' => 1));
2004          $this->assertInstanceOf('stdClass', $record);
2005          $this->assertSame('xx', $record->onechar);
2006  
2007          $result = $DB->insert_record_raw($tablename, array('course' => 2, 'onechar' => 'yy'), false);
2008          $this->assertTrue($result);
2009  
2010          // Note: bulk not implemented yet.
2011          $DB->insert_record_raw($tablename, array('course' => 3, 'onechar' => 'zz'), true, true);
2012          $record = $DB->get_record($tablename, array('course' => 3));
2013          $this->assertInstanceOf('stdClass', $record);
2014          $this->assertSame('zz', $record->onechar);
2015  
2016          // Custom sequence (id) - returnid is ignored.
2017          $result = $DB->insert_record_raw($tablename, array('id' => 10, 'course' => 3, 'onechar' => 'bb'), true, false, true);
2018          $this->assertTrue($result);
2019          $record = $DB->get_record($tablename, array('id' => 10));
2020          $this->assertInstanceOf('stdClass', $record);
2021          $this->assertSame('bb', $record->onechar);
2022  
2023          // Custom sequence - missing id error.
2024          try {
2025              $DB->insert_record_raw($tablename, array('course' => 3, 'onechar' => 'bb'), true, false, true);
2026              $this->fail('Exception expected due to missing record');
2027          } catch (coding_exception $ex) {
2028              $this->assertTrue(true);
2029          }
2030  
2031          // Wrong column error.
2032          try {
2033              $DB->insert_record_raw($tablename, array('xxxxx' => 3, 'onechar' => 'bb'));
2034              $this->fail('Exception expected due to invalid column');
2035          } catch (dml_exception $ex) {
2036              $this->assertTrue(true);
2037          }
2038  
2039          // Create something similar to "context_temp" with id column without sequence.
2040          $dbman->drop_table($table);
2041          $table = $this->get_test_table();
2042          $tablename = $table->getName();
2043          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
2044          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
2045          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
2046          $dbman->create_table($table);
2047  
2048          $record = (object)array('id'=>5, 'course' => 1);
2049          $DB->insert_record_raw($tablename, $record, false, false, true);
2050          $record = $DB->get_record($tablename, array());
2051          $this->assertEquals(5, $record->id);
2052      }
2053  
2054      public function test_insert_record() {
2055          // All the information in this test is fetched from DB by get_recordset() so we
2056          // have such method properly tested against nulls, empties and friends...
2057  
2058          $DB = $this->tdb;
2059          $dbman = $DB->get_manager();
2060  
2061          $table = $this->get_test_table();
2062          $tablename = $table->getName();
2063  
2064          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
2065          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
2066          $table->add_field('oneint', XMLDB_TYPE_INTEGER, '10', null, null, null, 100);
2067          $table->add_field('onenum', XMLDB_TYPE_NUMBER, '10,2', null, null, null, 200);
2068          $table->add_field('onechar', XMLDB_TYPE_CHAR, '100', null, null, null, 'onestring');
2069          $table->add_field('onetext', XMLDB_TYPE_TEXT, 'big', null, null, null);
2070          $table->add_field('onebinary', XMLDB_TYPE_BINARY, 'big', null, null, null);
2071          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
2072          $dbman->create_table($table);
2073  
2074          $this->assertSame(1, $DB->insert_record($tablename, array('course' => 1), true));
2075          $record = $DB->get_record($tablename, array('course' => 1));
2076          $this->assertEquals(1, $record->id);
2077          $this->assertEquals(100, $record->oneint); // Just check column defaults have been applied.
2078          $this->assertEquals(200, $record->onenum);
2079          $this->assertSame('onestring', $record->onechar);
2080          $this->assertNull($record->onetext);
2081          $this->assertNull($record->onebinary);
2082  
2083          // Without returning id, bulk not implemented.
2084          $result = $this->assertTrue($DB->insert_record($tablename, array('course' => 99), false, true));
2085          $record = $DB->get_record($tablename, array('course' => 99));
2086          $this->assertEquals(2, $record->id);
2087          $this->assertEquals(99, $record->course);
2088  
2089          // Check nulls are set properly for all types.
2090          $record = new stdClass();
2091          $record->oneint = null;
2092          $record->onenum = null;
2093          $record->onechar = null;
2094          $record->onetext = null;
2095          $record->onebinary = null;
2096          $recid = $DB->insert_record($tablename, $record);
2097          $record = $DB->get_record($tablename, array('id' => $recid));
2098          $this->assertEquals(0, $record->course);
2099          $this->assertNull($record->oneint);
2100          $this->assertNull($record->onenum);
2101          $this->assertNull($record->onechar);
2102          $this->assertNull($record->onetext);
2103          $this->assertNull($record->onebinary);
2104  
2105          // Check zeros are set properly for all types.
2106          $record = new stdClass();
2107          $record->oneint = 0;
2108          $record->onenum = 0;
2109          $recid = $DB->insert_record($tablename, $record);
2110          $record = $DB->get_record($tablename, array('id' => $recid));
2111          $this->assertEquals(0, $record->oneint);
2112          $this->assertEquals(0, $record->onenum);
2113  
2114          // Check booleans are set properly for all types.
2115          $record = new stdClass();
2116          $record->oneint = true; // Trues.
2117          $record->onenum = true;
2118          $record->onechar = true;
2119          $record->onetext = true;
2120          $recid = $DB->insert_record($tablename, $record);
2121          $record = $DB->get_record($tablename, array('id' => $recid));
2122          $this->assertEquals(1, $record->oneint);
2123          $this->assertEquals(1, $record->onenum);
2124          $this->assertEquals(1, $record->onechar);
2125          $this->assertEquals(1, $record->onetext);
2126  
2127          $record = new stdClass();
2128          $record->oneint = false; // Falses.
2129          $record->onenum = false;
2130          $record->onechar = false;
2131          $record->onetext = false;
2132          $recid = $DB->insert_record($tablename, $record);
2133          $record = $DB->get_record($tablename, array('id' => $recid));
2134          $this->assertEquals(0, $record->oneint);
2135          $this->assertEquals(0, $record->onenum);
2136          $this->assertEquals(0, $record->onechar);
2137          $this->assertEquals(0, $record->onetext);
2138  
2139          // Check string data causes exception in numeric types.
2140          $record = new stdClass();
2141          $record->oneint = 'onestring';
2142          $record->onenum = 0;
2143          try {
2144              $DB->insert_record($tablename, $record);
2145              $this->fail("Expecting an exception, none occurred");
2146          } catch (moodle_exception $e) {
2147              $this->assertInstanceOf('dml_exception', $e);
2148          }
2149          $record = new stdClass();
2150          $record->oneint = 0;
2151          $record->onenum = 'onestring';
2152          try {
2153              $DB->insert_record($tablename, $record);
2154              $this->fail("Expecting an exception, none occurred");
2155          } catch (moodle_exception $e) {
2156              $this->assertInstanceOf('dml_exception', $e);
2157          }
2158  
2159          // Check empty string data is stored as 0 in numeric datatypes.
2160          $record = new stdClass();
2161          $record->oneint = ''; // Empty string.
2162          $record->onenum = 0;
2163          $recid = $DB->insert_record($tablename, $record);
2164          $record = $DB->get_record($tablename, array('id' => $recid));
2165          $this->assertTrue(is_numeric($record->oneint) && $record->oneint == 0);
2166  
2167          $record = new stdClass();
2168          $record->oneint = 0;
2169          $record->onenum = ''; // Empty string.
2170          $recid = $DB->insert_record($tablename, $record);
2171          $record = $DB->get_record($tablename, array('id' => $recid));
2172          $this->assertTrue(is_numeric($record->onenum) && $record->onenum == 0);
2173  
2174          // Check empty strings are set properly in string types.
2175          $record = new stdClass();
2176          $record->oneint = 0;
2177          $record->onenum = 0;
2178          $record->onechar = '';
2179          $record->onetext = '';
2180          $recid = $DB->insert_record($tablename, $record);
2181          $record = $DB->get_record($tablename, array('id' => $recid));
2182          $this->assertTrue($record->onechar === '');
2183          $this->assertTrue($record->onetext === '');
2184  
2185          // Check operation ((210.10 + 39.92) - 150.02) against numeric types.
2186          $record = new stdClass();
2187          $record->oneint = ((210.10 + 39.92) - 150.02);
2188          $record->onenum = ((210.10 + 39.92) - 150.02);
2189          $recid = $DB->insert_record($tablename, $record);
2190          $record = $DB->get_record($tablename, array('id' => $recid));
2191          $this->assertEquals(100, $record->oneint);
2192          $this->assertEquals(100, $record->onenum);
2193  
2194          // Check various quotes/backslashes combinations in string types.
2195          $teststrings = array(
2196              'backslashes and quotes alone (even): "" \'\' \\\\',
2197              'backslashes and quotes alone (odd): """ \'\'\' \\\\\\',
2198              'backslashes and quotes sequences (even): \\"\\" \\\'\\\'',
2199              'backslashes and quotes sequences (odd): \\"\\"\\" \\\'\\\'\\\'');
2200          foreach ($teststrings as $teststring) {
2201              $record = new stdClass();
2202              $record->onechar = $teststring;
2203              $record->onetext = $teststring;
2204              $recid = $DB->insert_record($tablename, $record);
2205              $record = $DB->get_record($tablename, array('id' => $recid));
2206              $this->assertEquals($teststring, $record->onechar);
2207              $this->assertEquals($teststring, $record->onetext);
2208          }
2209  
2210          // Check LOBs in text/binary columns.
2211          $clob = file_get_contents(__DIR__ . '/fixtures/clob.txt');
2212          $blob = file_get_contents(__DIR__ . '/fixtures/randombinary');
2213          $record = new stdClass();
2214          $record->onetext = $clob;
2215          $record->onebinary = $blob;
2216          $recid = $DB->insert_record($tablename, $record);
2217          $rs = $DB->get_recordset($tablename, array('id' => $recid));
2218          $record = $rs->current();
2219          $rs->close();
2220          $this->assertEquals($clob, $record->onetext, 'Test CLOB insert (full contents output disabled)');
2221          $this->assertEquals($blob, $record->onebinary, 'Test BLOB insert (full contents output disabled)');
2222  
2223          // And "small" LOBs too, just in case.
2224          $newclob = substr($clob, 0, 500);
2225          $newblob = substr($blob, 0, 250);
2226          $record = new stdClass();
2227          $record->onetext = $newclob;
2228          $record->onebinary = $newblob;
2229          $recid = $DB->insert_record($tablename, $record);
2230          $rs = $DB->get_recordset($tablename, array('id' => $recid));
2231          $record = $rs->current();
2232          $rs->close();
2233          $this->assertEquals($newclob, $record->onetext, 'Test "small" CLOB insert (full contents output disabled)');
2234          $this->assertEquals($newblob, $record->onebinary, 'Test "small" BLOB insert (full contents output disabled)');
2235          $this->assertEquals(false, $rs->key()); // Ensure recordset key() method to be working ok after closing.
2236  
2237          // And "diagnostic" LOBs too, just in case.
2238          $newclob = '\'"\\;/ěščřžýáíé';
2239          $newblob = '\'"\\;/ěščřžýáíé';
2240          $record = new stdClass();
2241          $record->onetext = $newclob;
2242          $record->onebinary = $newblob;
2243          $recid = $DB->insert_record($tablename, $record);
2244          $rs = $DB->get_recordset($tablename, array('id' => $recid));
2245          $record = $rs->current();
2246          $rs->close();
2247          $this->assertSame($newclob, $record->onetext);
2248          $this->assertSame($newblob, $record->onebinary);
2249          $this->assertEquals(false, $rs->key()); // Ensure recordset key() method to be working ok after closing.
2250  
2251          // Test data is not modified.
2252          $record = new stdClass();
2253          $record->id     = -1; // Has to be ignored.
2254          $record->course = 3;
2255          $record->lalala = 'lalal'; // Unused.
2256          $before = clone($record);
2257          $DB->insert_record($tablename, $record);
2258          $this->assertEquals($record, $before);
2259  
2260          // Make sure the id is always increasing and never reuses the same id.
2261          $id1 = $DB->insert_record($tablename, array('course' => 3));
2262          $id2 = $DB->insert_record($tablename, array('course' => 3));
2263          $this->assertTrue($id1 < $id2);
2264          $DB->delete_records($tablename, array('id'=>$id2));
2265          $id3 = $DB->insert_record($tablename, array('course' => 3));
2266          $this->assertTrue($id2 < $id3);
2267          $DB->delete_records($tablename, array());
2268          $id4 = $DB->insert_record($tablename, array('course' => 3));
2269          $this->assertTrue($id3 < $id4);
2270  
2271          // Test saving a float in a CHAR column, and reading it back.
2272          $id = $DB->insert_record($tablename, array('onechar' => 1.0));
2273          $this->assertEquals(1.0, $DB->get_field($tablename, 'onechar', array('id' => $id)));
2274          $id = $DB->insert_record($tablename, array('onechar' => 1e20));
2275          $this->assertEquals(1e20, $DB->get_field($tablename, 'onechar', array('id' => $id)));
2276          $id = $DB->insert_record($tablename, array('onechar' => 1e-4));
2277          $this->assertEquals(1e-4, $DB->get_field($tablename, 'onechar', array('id' => $id)));
2278          $id = $DB->insert_record($tablename, array('onechar' => 1e-5));
2279          $this->assertEquals(1e-5, $DB->get_field($tablename, 'onechar', array('id' => $id)));
2280          $id = $DB->insert_record($tablename, array('onechar' => 1e-300));
2281          $this->assertEquals(1e-300, $DB->get_field($tablename, 'onechar', array('id' => $id)));
2282          $id = $DB->insert_record($tablename, array('onechar' => 1e300));
2283          $this->assertEquals(1e300, $DB->get_field($tablename, 'onechar', array('id' => $id)));
2284  
2285          // Test saving a float in a TEXT column, and reading it back.
2286          $id = $DB->insert_record($tablename, array('onetext' => 1.0));
2287          $this->assertEquals(1.0, $DB->get_field($tablename, 'onetext', array('id' => $id)));
2288          $id = $DB->insert_record($tablename, array('onetext' => 1e20));
2289          $this->assertEquals(1e20, $DB->get_field($tablename, 'onetext', array('id' => $id)));
2290          $id = $DB->insert_record($tablename, array('onetext' => 1e-4));
2291          $this->assertEquals(1e-4, $DB->get_field($tablename, 'onetext', array('id' => $id)));
2292          $id = $DB->insert_record($tablename, array('onetext' => 1e-5));
2293          $this->assertEquals(1e-5, $DB->get_field($tablename, 'onetext', array('id' => $id)));
2294          $id = $DB->insert_record($tablename, array('onetext' => 1e-300));
2295          $this->assertEquals(1e-300, $DB->get_field($tablename, 'onetext', array('id' => $id)));
2296          $id = $DB->insert_record($tablename, array('onetext' => 1e300));
2297          $this->assertEquals(1e300, $DB->get_field($tablename, 'onetext', array('id' => $id)));
2298  
2299          // Test that inserting data violating one unique key leads to error.
2300          // Empty the table completely.
2301          $this->assertTrue($DB->delete_records($tablename));
2302  
2303          // Add one unique constraint (index).
2304          $key = new xmldb_key('testuk', XMLDB_KEY_UNIQUE, array('course', 'oneint'));
2305          $dbman->add_key($table, $key);
2306  
2307          // Let's insert one record violating the constraint multiple times.
2308          $record = (object)array('course' => 1, 'oneint' => 1);
2309          $this->assertTrue($DB->insert_record($tablename, $record, false)); // Insert 1st. No problem expected.
2310  
2311          // Re-insert same record, not returning id. dml_exception expected.
2312          try {
2313              $DB->insert_record($tablename, $record, false);
2314              $this->fail("Expecting an exception, none occurred");
2315          } catch (moodle_exception $e) {
2316              $this->assertInstanceOf('dml_exception', $e);
2317          }
2318  
2319          // Re-insert same record, returning id. dml_exception expected.
2320          try {
2321              $DB->insert_record($tablename, $record, true);
2322              $this->fail("Expecting an exception, none occurred");
2323          } catch (moodle_exception $e) {
2324              $this->assertInstanceOf('dml_exception', $e);
2325          }
2326  
2327          // Try to insert a record into a non-existent table. dml_exception expected.
2328          try {
2329              $DB->insert_record('nonexistenttable', $record, true);
2330              $this->fail("Expecting an exception, none occurred");
2331          } catch (exception $e) {
2332              $this->assertTrue($e instanceof dml_exception);
2333          }
2334      }
2335  
2336      public function test_insert_records() {
2337          $DB = $this->tdb;
2338          $dbman = $DB->get_manager();
2339  
2340          $table = $this->get_test_table();
2341          $tablename = $table->getName();
2342  
2343          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
2344          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
2345          $table->add_field('oneint', XMLDB_TYPE_INTEGER, '10', null, null, null, 100);
2346          $table->add_field('onenum', XMLDB_TYPE_NUMBER, '10,2', null, null, null, 200);
2347          $table->add_field('onechar', XMLDB_TYPE_CHAR, '100', null, null, null, 'onestring');
2348          $table->add_field('onetext', XMLDB_TYPE_TEXT, 'big', null, null, null);
2349          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
2350          $dbman->create_table($table);
2351  
2352          $this->assertCount(0, $DB->get_records($tablename));
2353  
2354          $record = new stdClass();
2355          $record->id = '1';
2356          $record->course = '1';
2357          $record->oneint = null;
2358          $record->onenum = 1.0;
2359          $record->onechar = 'a';
2360          $record->onetext = 'aaa';
2361  
2362          $expected = array();
2363          $records = array();
2364          for ($i = 1; $i <= 2000; $i++) { // This may take a while, it should be higher than defaults in DML drivers.
2365              $rec = clone($record);
2366              $rec->id = (string)$i;
2367              $rec->oneint = (string)$i;
2368              $expected[$i] = $rec;
2369              $rec = clone($rec);
2370              unset($rec->id);
2371              $records[$i] = $rec;
2372          }
2373  
2374          $DB->insert_records($tablename, $records);
2375          $stored = $DB->get_records($tablename, array(), 'id ASC');
2376          $this->assertEquals($expected, $stored);
2377  
2378          // Test there can be some extra properties including id.
2379          $count = $DB->count_records($tablename);
2380          $rec1 = (array)$record;
2381          $rec1['xxx'] = 1;
2382          $rec2 = (array)$record;
2383          $rec2['xxx'] = 2;
2384  
2385          $records = array($rec1, $rec2);
2386          $DB->insert_records($tablename, $records);
2387          $this->assertEquals($count + 2, $DB->count_records($tablename));
2388  
2389          // Test not all properties are necessary.
2390          $rec1 = (array)$record;
2391          unset($rec1['course']);
2392          $rec2 = (array)$record;
2393          unset($rec2['course']);
2394  
2395          $records = array($rec1, $rec2);
2396          $DB->insert_records($tablename, $records);
2397  
2398          // Make sure no changes in data object structure are tolerated.
2399          $rec1 = (array)$record;
2400          unset($rec1['id']);
2401          $rec2 = (array)$record;
2402          unset($rec2['id']);
2403  
2404          $records = array($rec1, $rec2);
2405          $DB->insert_records($tablename, $records);
2406  
2407          $rec2['xx'] = '1';
2408          $records = array($rec1, $rec2);
2409          try {
2410              $DB->insert_records($tablename, $records);
2411              $this->fail('coding_exception expected when insert_records receives different object data structures');
2412          } catch (moodle_exception $e) {
2413              $this->assertInstanceOf('coding_exception', $e);
2414          }
2415  
2416          unset($rec2['xx']);
2417          unset($rec2['course']);
2418          $rec2['course'] = '1';
2419          $records = array($rec1, $rec2);
2420          try {
2421              $DB->insert_records($tablename, $records);
2422              $this->fail('coding_exception expected when insert_records receives different object data structures');
2423          } catch (moodle_exception $e) {
2424              $this->assertInstanceOf('coding_exception', $e);
2425          }
2426  
2427          $records = 1;
2428          try {
2429              $DB->insert_records($tablename, $records);
2430              $this->fail('coding_exception expected when insert_records receives non-traversable data');
2431          } catch (moodle_exception $e) {
2432              $this->assertInstanceOf('coding_exception', $e);
2433          }
2434  
2435          $records = array(1);
2436          try {
2437              $DB->insert_records($tablename, $records);
2438              $this->fail('coding_exception expected when insert_records receives non-objet record');
2439          } catch (moodle_exception $e) {
2440              $this->assertInstanceOf('coding_exception', $e);
2441          }
2442      }
2443  
2444      public function test_insert_record_with_nullable_unique_index() {
2445          $DB = $this->tdb;
2446          $dbman = $DB->get_manager();
2447  
2448          $table = $this->get_test_table();
2449          $tablename = $table->getName();
2450  
2451          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
2452          $table->add_field('notnull1', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
2453          $table->add_field('nullable1', XMLDB_TYPE_INTEGER, '10', null, null, null, null);
2454          $table->add_field('nullable2', XMLDB_TYPE_INTEGER, '10', null, null, null, null);
2455          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
2456          $table->add_index('notnull1-nullable1-nullable2', XMLDB_INDEX_UNIQUE,
2457                  array('notnull1', 'nullable1', 'nullable2'));
2458          $dbman->create_table($table);
2459  
2460          // Insert one record. Should be OK (no exception).
2461          $DB->insert_record($tablename, (object) ['notnull1' => 1, 'nullable1' => 1, 'nullable2' => 1]);
2462  
2463          $this->assertEquals(1, $DB->count_records($table->getName()));
2464          $this->assertEquals(1, $DB->count_records($table->getName(), ['nullable1' => 1]));
2465  
2466          // Inserting a duplicate should fail.
2467          try {
2468              $DB->insert_record($tablename, (object) ['notnull1' => 1, 'nullable1' => 1, 'nullable2' => 1]);
2469              $this->fail('dml_write_exception expected when a record violates a unique index');
2470          } catch (moodle_exception $e) {
2471              $this->assertInstanceOf('dml_write_exception', $e);
2472          }
2473  
2474          $this->assertEquals(1, $DB->count_records($table->getName()));
2475          $this->assertEquals(1, $DB->count_records($table->getName(), ['nullable1' => 1]));
2476  
2477          // Inserting a record with nulls in the nullable columns should work.
2478          $DB->insert_record($tablename, (object) ['notnull1' => 1, 'nullable1' => null, 'nullable2' => null]);
2479  
2480          $this->assertEquals(2, $DB->count_records($table->getName()));
2481          $this->assertEquals(1, $DB->count_records($table->getName(), ['nullable1' => 1]));
2482          $this->assertEquals(1, $DB->count_records($table->getName(), ['nullable1' => null]));
2483  
2484          // And it should be possible to insert a duplicate.
2485          $DB->insert_record($tablename, (object) ['notnull1' => 1, 'nullable1' => null, 'nullable2' => null]);
2486  
2487          $this->assertEquals(3, $DB->count_records($table->getName()));
2488          $this->assertEquals(1, $DB->count_records($table->getName(), ['nullable1' => 1]));
2489          $this->assertEquals(2, $DB->count_records($table->getName(), ['nullable1' => null]));
2490  
2491          // Same, but with only one of the nullable columns being null.
2492          $DB->insert_record($tablename, (object) ['notnull1' => 1, 'nullable1' => 1, 'nullable2' => null]);
2493  
2494          $this->assertEquals(4, $DB->count_records($table->getName()));
2495          $this->assertEquals(2, $DB->count_records($table->getName(), ['nullable1' => 1]));
2496          $this->assertEquals(2, $DB->count_records($table->getName(), ['nullable1' => null]));
2497  
2498          $DB->insert_record($tablename, (object) ['notnull1' => 1, 'nullable1' => 1, 'nullable2' => null]);
2499  
2500          $this->assertEquals(5, $DB->count_records($table->getName()));
2501          $this->assertEquals(3, $DB->count_records($table->getName(), ['nullable1' => 1]));
2502          $this->assertEquals(2, $DB->count_records($table->getName(), ['nullable1' => null]));
2503  
2504      }
2505  
2506      public function test_import_record() {
2507          // All the information in this test is fetched from DB by get_recordset() so we
2508          // have such method properly tested against nulls, empties and friends...
2509  
2510          $DB = $this->tdb;
2511          $dbman = $DB->get_manager();
2512  
2513          $table = $this->get_test_table();
2514          $tablename = $table->getName();
2515  
2516          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
2517          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
2518          $table->add_field('oneint', XMLDB_TYPE_INTEGER, '10', null, null, null, 100);
2519          $table->add_field('onenum', XMLDB_TYPE_NUMBER, '10,2', null, null, null, 200);
2520          $table->add_field('onechar', XMLDB_TYPE_CHAR, '100', null, null, null, 'onestring');
2521          $table->add_field('onetext', XMLDB_TYPE_TEXT, 'big', null, null, null);
2522          $table->add_field('onebinary', XMLDB_TYPE_BINARY, 'big', null, null, null);
2523          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
2524          $dbman->create_table($table);
2525  
2526          $this->assertSame(1, $DB->insert_record($tablename, array('course' => 1), true));
2527          $record = $DB->get_record($tablename, array('course' => 1));
2528          $this->assertEquals(1, $record->id);
2529          $this->assertEquals(100, $record->oneint); // Just check column defaults have been applied.
2530          $this->assertEquals(200, $record->onenum);
2531          $this->assertSame('onestring', $record->onechar);
2532          $this->assertNull($record->onetext);
2533          $this->assertNull($record->onebinary);
2534  
2535          // Ignore extra columns.
2536          $record = (object)array('id'=>13, 'course'=>2, 'xxxx'=>788778);
2537          $before = clone($record);
2538          $this->assertTrue($DB->import_record($tablename, $record));
2539          $this->assertEquals($record, $before);
2540          $records = $DB->get_records($tablename);
2541          $this->assertEquals(2, $records[13]->course);
2542  
2543          // Check nulls are set properly for all types.
2544          $record = new stdClass();
2545          $record->id = 20;
2546          $record->oneint = null;
2547          $record->onenum = null;
2548          $record->onechar = null;
2549          $record->onetext = null;
2550          $record->onebinary = null;
2551          $this->assertTrue($DB->import_record($tablename, $record));
2552          $record = $DB->get_record($tablename, array('id' => 20));
2553          $this->assertEquals(0, $record->course);
2554          $this->assertNull($record->oneint);
2555          $this->assertNull($record->onenum);
2556          $this->assertNull($record->onechar);
2557          $this->assertNull($record->onetext);
2558          $this->assertNull($record->onebinary);
2559  
2560          // Check zeros are set properly for all types.
2561          $record = new stdClass();
2562          $record->id = 23;
2563          $record->oneint = 0;
2564          $record->onenum = 0;
2565          $this->assertTrue($DB->import_record($tablename, $record));
2566          $record = $DB->get_record($tablename, array('id' => 23));
2567          $this->assertEquals(0, $record->oneint);
2568          $this->assertEquals(0, $record->onenum);
2569  
2570          // Check string data causes exception in numeric types.
2571          $record = new stdClass();
2572          $record->id = 32;
2573          $record->oneint = 'onestring';
2574          $record->onenum = 0;
2575          try {
2576              $DB->import_record($tablename, $record);
2577              $this->fail("Expecting an exception, none occurred");
2578          } catch (moodle_exception $e) {
2579              $this->assertInstanceOf('dml_exception', $e);
2580          }
2581          $record = new stdClass();
2582          $record->id = 35;
2583          $record->oneint = 0;
2584          $record->onenum = 'onestring';
2585          try {
2586              $DB->import_record($tablename, $record);
2587              $this->fail("Expecting an exception, none occurred");
2588          } catch (moodle_exception $e) {
2589              $this->assertInstanceOf('dml_exception', $e);
2590          }
2591  
2592          // Check empty strings are set properly in string types.
2593          $record = new stdClass();
2594          $record->id = 44;
2595          $record->oneint = 0;
2596          $record->onenum = 0;
2597          $record->onechar = '';
2598          $record->onetext = '';
2599          $this->assertTrue($DB->import_record($tablename, $record));
2600          $record = $DB->get_record($tablename, array('id' => 44));
2601          $this->assertTrue($record->onechar === '');
2602          $this->assertTrue($record->onetext === '');
2603  
2604          // Check operation ((210.10 + 39.92) - 150.02) against numeric types.
2605          $record = new stdClass();
2606          $record->id = 47;
2607          $record->oneint = ((210.10 + 39.92) - 150.02);
2608          $record->onenum = ((210.10 + 39.92) - 150.02);
2609          $this->assertTrue($DB->import_record($tablename, $record));
2610          $record = $DB->get_record($tablename, array('id' => 47));
2611          $this->assertEquals(100, $record->oneint);
2612          $this->assertEquals(100, $record->onenum);
2613  
2614          // Check various quotes/backslashes combinations in string types.
2615          $i = 50;
2616          $teststrings = array(
2617              'backslashes and quotes alone (even): "" \'\' \\\\',
2618              'backslashes and quotes alone (odd): """ \'\'\' \\\\\\',
2619              'backslashes and quotes sequences (even): \\"\\" \\\'\\\'',
2620              'backslashes and quotes sequences (odd): \\"\\"\\" \\\'\\\'\\\'');
2621          foreach ($teststrings as $teststring) {
2622              $record = new stdClass();
2623              $record->id = $i;
2624              $record->onechar = $teststring;
2625              $record->onetext = $teststring;
2626              $this->assertTrue($DB->import_record($tablename, $record));
2627              $record = $DB->get_record($tablename, array('id' => $i));
2628              $this->assertEquals($teststring, $record->onechar);
2629              $this->assertEquals($teststring, $record->onetext);
2630              $i = $i + 3;
2631          }
2632  
2633          // Check LOBs in text/binary columns.
2634          $clob = file_get_contents(__DIR__ . '/fixtures/clob.txt');
2635          $record = new stdClass();
2636          $record->id = 70;
2637          $record->onetext = $clob;
2638          $record->onebinary = '';
2639          $this->assertTrue($DB->import_record($tablename, $record));
2640          $rs = $DB->get_recordset($tablename, array('id' => 70));
2641          $record = $rs->current();
2642          $rs->close();
2643          $this->assertEquals($clob, $record->onetext, 'Test CLOB insert (full contents output disabled)');
2644  
2645          $blob = file_get_contents(__DIR__ . '/fixtures/randombinary');
2646          $record = new stdClass();
2647          $record->id = 71;
2648          $record->onetext = '';
2649          $record->onebinary = $blob;
2650          $this->assertTrue($DB->import_record($tablename, $record));
2651          $rs = $DB->get_recordset($tablename, array('id' => 71));
2652          $record = $rs->current();
2653          $rs->close();
2654          $this->assertEquals($blob, $record->onebinary, 'Test BLOB insert (full contents output disabled)');
2655  
2656          // And "small" LOBs too, just in case.
2657          $newclob = substr($clob, 0, 500);
2658          $newblob = substr($blob, 0, 250);
2659          $record = new stdClass();
2660          $record->id = 73;
2661          $record->onetext = $newclob;
2662          $record->onebinary = $newblob;
2663          $this->assertTrue($DB->import_record($tablename, $record));
2664          $rs = $DB->get_recordset($tablename, array('id' => 73));
2665          $record = $rs->current();
2666          $rs->close();
2667          $this->assertEquals($newclob, $record->onetext, 'Test "small" CLOB insert (full contents output disabled)');
2668          $this->assertEquals($newblob, $record->onebinary, 'Test "small" BLOB insert (full contents output disabled)');
2669          $this->assertEquals(false, $rs->key()); // Ensure recordset key() method to be working ok after closing.
2670      }
2671  
2672      public function test_update_record_raw() {
2673          $DB = $this->tdb;
2674          $dbman = $DB->get_manager();
2675  
2676          $table = $this->get_test_table();
2677          $tablename = $table->getName();
2678  
2679          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
2680          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
2681          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
2682          $dbman->create_table($table);
2683  
2684          $DB->insert_record($tablename, array('course' => 1));
2685          $DB->insert_record($tablename, array('course' => 3));
2686  
2687          $record = $DB->get_record($tablename, array('course' => 1));
2688          $record->course = 2;
2689          $this->assertTrue($DB->update_record_raw($tablename, $record));
2690          $this->assertEquals(0, $DB->count_records($tablename, array('course' => 1)));
2691          $this->assertEquals(1, $DB->count_records($tablename, array('course' => 2)));
2692          $this->assertEquals(1, $DB->count_records($tablename, array('course' => 3)));
2693  
2694          $record = $DB->get_record($tablename, array('course' => 3));
2695          $record->xxxxx = 2;
2696          try {
2697              $DB->update_record_raw($tablename, $record);
2698              $this->fail("Expecting an exception, none occurred");
2699          } catch (moodle_exception $e) {
2700              $this->assertInstanceOf('moodle_exception', $e);
2701          }
2702  
2703          $record = $DB->get_record($tablename, array('course' => 3));
2704          unset($record->id);
2705          try {
2706              $DB->update_record_raw($tablename, $record);
2707              $this->fail("Expecting an exception, none occurred");
2708          } catch (moodle_exception $e) {
2709              $this->assertInstanceOf('coding_exception', $e);
2710          }
2711      }
2712  
2713      public function test_update_record() {
2714  
2715          // All the information in this test is fetched from DB by get_record() so we
2716          // have such method properly tested against nulls, empties and friends...
2717  
2718          $DB = $this->tdb;
2719          $dbman = $DB->get_manager();
2720  
2721          $table = $this->get_test_table();
2722          $tablename = $table->getName();
2723  
2724          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
2725          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
2726          $table->add_field('oneint', XMLDB_TYPE_INTEGER, '10', null, null, null, 100);
2727          $table->add_field('onenum', XMLDB_TYPE_NUMBER, '10,2', null, null, null, 200);
2728          $table->add_field('onechar', XMLDB_TYPE_CHAR, '100', null, null, null, 'onestring');
2729          $table->add_field('onetext', XMLDB_TYPE_TEXT, 'big', null, null, null);
2730          $table->add_field('onebinary', XMLDB_TYPE_BINARY, 'big', null, null, null);
2731          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
2732          $dbman->create_table($table);
2733  
2734          $DB->insert_record($tablename, array('course' => 1));
2735          $record = $DB->get_record($tablename, array('course' => 1));
2736          $record->course = 2;
2737  
2738          $this->assertTrue($DB->update_record($tablename, $record));
2739          $this->assertFalse($record = $DB->get_record($tablename, array('course' => 1)));
2740          $this->assertNotEmpty($record = $DB->get_record($tablename, array('course' => 2)));
2741          $this->assertEquals(100, $record->oneint); // Just check column defaults have been applied.
2742          $this->assertEquals(200, $record->onenum);
2743          $this->assertSame('onestring', $record->onechar);
2744          $this->assertNull($record->onetext);
2745          $this->assertNull($record->onebinary);
2746  
2747          // Check nulls are set properly for all types.
2748          $record->oneint = null;
2749          $record->onenum = null;
2750          $record->onechar = null;
2751          $record->onetext = null;
2752          $record->onebinary = null;
2753          $DB->update_record($tablename, $record);
2754          $record = $DB->get_record($tablename, array('course' => 2));
2755          $this->assertNull($record->oneint);
2756          $this->assertNull($record->onenum);
2757          $this->assertNull($record->onechar);
2758          $this->assertNull($record->onetext);
2759          $this->assertNull($record->onebinary);
2760  
2761          // Check zeros are set properly for all types.
2762          $record->oneint = 0;
2763          $record->onenum = 0;
2764          $DB->update_record($tablename, $record);
2765          $record = $DB->get_record($tablename, array('course' => 2));
2766          $this->assertEquals(0, $record->oneint);
2767          $this->assertEquals(0, $record->onenum);
2768  
2769          // Check booleans are set properly for all types.
2770          $record->oneint = true; // Trues.
2771          $record->onenum = true;
2772          $record->onechar = true;
2773          $record->onetext = true;
2774          $DB->update_record($tablename, $record);
2775          $record = $DB->get_record($tablename, array('course' => 2));
2776          $this->assertEquals(1, $record->oneint);
2777          $this->assertEquals(1, $record->onenum);
2778          $this->assertEquals(1, $record->onechar);
2779          $this->assertEquals(1, $record->onetext);
2780  
2781          $record->oneint = false; // Falses.
2782          $record->onenum = false;
2783          $record->onechar = false;
2784          $record->onetext = false;
2785          $DB->update_record($tablename, $record);
2786          $record = $DB->get_record($tablename, array('course' => 2));
2787          $this->assertEquals(0, $record->oneint);
2788          $this->assertEquals(0, $record->onenum);
2789          $this->assertEquals(0, $record->onechar);
2790          $this->assertEquals(0, $record->onetext);
2791  
2792          // Check string data causes exception in numeric types.
2793          $record->oneint = 'onestring';
2794          $record->onenum = 0;
2795          try {
2796              $DB->update_record($tablename, $record);
2797              $this->fail("Expecting an exception, none occurred");
2798          } catch (moodle_exception $e) {
2799              $this->assertInstanceOf('dml_exception', $e);
2800          }
2801          $record->oneint = 0;
2802          $record->onenum = 'onestring';
2803          try {
2804              $DB->update_record($tablename, $record);
2805              $this->fail("Expecting an exception, none occurred");
2806          } catch (moodle_exception $e) {
2807              $this->assertInstanceOf('dml_exception', $e);
2808          }
2809  
2810          // Check empty string data is stored as 0 in numeric datatypes.
2811          $record->oneint = ''; // Empty string.
2812          $record->onenum = 0;
2813          $DB->update_record($tablename, $record);
2814          $record = $DB->get_record($tablename, array('course' => 2));
2815          $this->assertTrue(is_numeric($record->oneint) && $record->oneint == 0);
2816  
2817          $record->oneint = 0;
2818          $record->onenum = ''; // Empty string.
2819          $DB->update_record($tablename, $record);
2820          $record = $DB->get_record($tablename, array('course' => 2));
2821          $this->assertTrue(is_numeric($record->onenum) && $record->onenum == 0);
2822  
2823          // Check empty strings are set properly in string types.
2824          $record->oneint = 0;
2825          $record->onenum = 0;
2826          $record->onechar = '';
2827          $record->onetext = '';
2828          $DB->update_record($tablename, $record);
2829          $record = $DB->get_record($tablename, array('course' => 2));
2830          $this->assertTrue($record->onechar === '');
2831          $this->assertTrue($record->onetext === '');
2832  
2833          // Check operation ((210.10 + 39.92) - 150.02) against numeric types.
2834          $record->oneint = ((210.10 + 39.92) - 150.02);
2835          $record->onenum = ((210.10 + 39.92) - 150.02);
2836          $DB->update_record($tablename, $record);
2837          $record = $DB->get_record($tablename, array('course' => 2));
2838          $this->assertEquals(100, $record->oneint);
2839          $this->assertEquals(100, $record->onenum);
2840  
2841          // Check various quotes/backslashes combinations in string types.
2842          $teststrings = array(
2843              'backslashes and quotes alone (even): "" \'\' \\\\',
2844              'backslashes and quotes alone (odd): """ \'\'\' \\\\\\',
2845              'backslashes and quotes sequences (even): \\"\\" \\\'\\\'',
2846              'backslashes and quotes sequences (odd): \\"\\"\\" \\\'\\\'\\\'');
2847          foreach ($teststrings as $teststring) {
2848              $record->onechar = $teststring;
2849              $record->onetext = $teststring;
2850              $DB->update_record($tablename, $record);
2851              $record = $DB->get_record($tablename, array('course' => 2));
2852              $this->assertEquals($teststring, $record->onechar);
2853              $this->assertEquals($teststring, $record->onetext);
2854          }
2855  
2856          // Check LOBs in text/binary columns.
2857          $clob = file_get_contents(__DIR__ . '/fixtures/clob.txt');
2858          $blob = file_get_contents(__DIR__ . '/fixtures/randombinary');
2859          $record->onetext = $clob;
2860          $record->onebinary = $blob;
2861          $DB->update_record($tablename, $record);
2862          $record = $DB->get_record($tablename, array('course' => 2));
2863          $this->assertEquals($clob, $record->onetext, 'Test CLOB update (full contents output disabled)');
2864          $this->assertEquals($blob, $record->onebinary, 'Test BLOB update (full contents output disabled)');
2865  
2866          // And "small" LOBs too, just in case.
2867          $newclob = substr($clob, 0, 500);
2868          $newblob = substr($blob, 0, 250);
2869          $record->onetext = $newclob;
2870          $record->onebinary = $newblob;
2871          $DB->update_record($tablename, $record);
2872          $record = $DB->get_record($tablename, array('course' => 2));
2873          $this->assertEquals($newclob, $record->onetext, 'Test "small" CLOB update (full contents output disabled)');
2874          $this->assertEquals($newblob, $record->onebinary, 'Test "small" BLOB update (full contents output disabled)');
2875  
2876          // Test saving a float in a CHAR column, and reading it back.
2877          $id = $DB->insert_record($tablename, array('onechar' => 'X'));
2878          $DB->update_record($tablename, array('id' => $id, 'onechar' => 1.0));
2879          $this->assertEquals(1.0, $DB->get_field($tablename, 'onechar', array('id' => $id)));
2880          $DB->update_record($tablename, array('id' => $id, 'onechar' => 1e20));
2881          $this->assertEquals(1e20, $DB->get_field($tablename, 'onechar', array('id' => $id)));
2882          $DB->update_record($tablename, array('id' => $id, 'onechar' => 1e-4));
2883          $this->assertEquals(1e-4, $DB->get_field($tablename, 'onechar', array('id' => $id)));
2884          $DB->update_record($tablename, array('id' => $id, 'onechar' => 1e-5));
2885          $this->assertEquals(1e-5, $DB->get_field($tablename, 'onechar', array('id' => $id)));
2886          $DB->update_record($tablename, array('id' => $id, 'onechar' => 1e-300));
2887          $this->assertEquals(1e-300, $DB->get_field($tablename, 'onechar', array('id' => $id)));
2888          $DB->update_record($tablename, array('id' => $id, 'onechar' => 1e300));
2889          $this->assertEquals(1e300, $DB->get_field($tablename, 'onechar', array('id' => $id)));
2890  
2891          // Test saving a float in a TEXT column, and reading it back.
2892          $id = $DB->insert_record($tablename, array('onetext' => 'X'));
2893          $DB->update_record($tablename, array('id' => $id, 'onetext' => 1.0));
2894          $this->assertEquals(1.0, $DB->get_field($tablename, 'onetext', array('id' => $id)));
2895          $DB->update_record($tablename, array('id' => $id, 'onetext' => 1e20));
2896          $this->assertEquals(1e20, $DB->get_field($tablename, 'onetext', array('id' => $id)));
2897          $DB->update_record($tablename, array('id' => $id, 'onetext' => 1e-4));
2898          $this->assertEquals(1e-4, $DB->get_field($tablename, 'onetext', array('id' => $id)));
2899          $DB->update_record($tablename, array('id' => $id, 'onetext' => 1e-5));
2900          $this->assertEquals(1e-5, $DB->get_field($tablename, 'onetext', array('id' => $id)));
2901          $DB->update_record($tablename, array('id' => $id, 'onetext' => 1e-300));
2902          $this->assertEquals(1e-300, $DB->get_field($tablename, 'onetext', array('id' => $id)));
2903          $DB->update_record($tablename, array('id' => $id, 'onetext' => 1e300));
2904          $this->assertEquals(1e300, $DB->get_field($tablename, 'onetext', array('id' => $id)));
2905      }
2906  
2907      public function test_set_field() {
2908          $DB = $this->tdb;
2909          $dbman = $DB->get_manager();
2910  
2911          $table = $this->get_test_table();
2912          $tablename = $table->getName();
2913  
2914          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
2915          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
2916          $table->add_field('onechar', XMLDB_TYPE_CHAR, '100', null, null, null);
2917          $table->add_field('onetext', XMLDB_TYPE_TEXT, 'big', null, null, null);
2918          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
2919          $dbman->create_table($table);
2920  
2921          // Simple set_field.
2922          $id1 = $DB->insert_record($tablename, array('course' => 1));
2923          $id2 = $DB->insert_record($tablename, array('course' => 1));
2924          $id3 = $DB->insert_record($tablename, array('course' => 3));
2925          $this->assertTrue($DB->set_field($tablename, 'course', 2, array('id' => $id1)));
2926          $this->assertEquals(2, $DB->get_field($tablename, 'course', array('id' => $id1)));
2927          $this->assertEquals(1, $DB->get_field($tablename, 'course', array('id' => $id2)));
2928          $this->assertEquals(3, $DB->get_field($tablename, 'course', array('id' => $id3)));
2929          $DB->delete_records($tablename, array());
2930  
2931          // Multiple fields affected.
2932          $id1 = $DB->insert_record($tablename, array('course' => 1));
2933          $id2 = $DB->insert_record($tablename, array('course' => 1));
2934          $id3 = $DB->insert_record($tablename, array('course' => 3));
2935          $DB->set_field($tablename, 'course', '5', array('course' => 1));
2936          $this->assertEquals(5, $DB->get_field($tablename, 'course', array('id' => $id1)));
2937          $this->assertEquals(5, $DB->get_field($tablename, 'course', array('id' => $id2)));
2938          $this->assertEquals(3, $DB->get_field($tablename, 'course', array('id' => $id3)));
2939          $DB->delete_records($tablename, array());
2940  
2941          // No field affected.
2942          $id1 = $DB->insert_record($tablename, array('course' => 1));
2943          $id2 = $DB->insert_record($tablename, array('course' => 1));
2944          $id3 = $DB->insert_record($tablename, array('course' => 3));
2945          $DB->set_field($tablename, 'course', '5', array('course' => 0));
2946          $this->assertEquals(1, $DB->get_field($tablename, 'course', array('id' => $id1)));
2947          $this->assertEquals(1, $DB->get_field($tablename, 'course', array('id' => $id2)));
2948          $this->assertEquals(3, $DB->get_field($tablename, 'course', array('id' => $id3)));
2949          $DB->delete_records($tablename, array());
2950  
2951          // All fields - no condition.
2952          $id1 = $DB->insert_record($tablename, array('course' => 1));
2953          $id2 = $DB->insert_record($tablename, array('course' => 1));
2954          $id3 = $DB->insert_record($tablename, array('course' => 3));
2955          $DB->set_field($tablename, 'course', 5, array());
2956          $this->assertEquals(5, $DB->get_field($tablename, 'course', array('id' => $id1)));
2957          $this->assertEquals(5, $DB->get_field($tablename, 'course', array('id' => $id2)));
2958          $this->assertEquals(5, $DB->get_field($tablename, 'course', array('id' => $id3)));
2959  
2960          // Test for exception throwing on text conditions being compared. (MDL-24863, unwanted auto conversion of param to int).
2961          $conditions = array('onetext' => '1');
2962          try {
2963              $DB->set_field($tablename, 'onechar', 'frog', $conditions);
2964              if (debugging()) {
2965                  // Only in debug mode - hopefully all devs test code in debug mode...
2966                  $this->fail('An Exception is missing, expected due to equating of text fields');
2967              }
2968          } catch (moodle_exception $e) {
2969              $this->assertInstanceOf('dml_exception', $e);
2970              $this->assertSame('textconditionsnotallowed', $e->errorcode);
2971          }
2972  
2973          // Test saving a float in a CHAR column, and reading it back.
2974          $id = $DB->insert_record($tablename, array('onechar' => 'X'));
2975          $DB->set_field($tablename, 'onechar', 1.0, array('id' => $id));
2976          $this->assertEquals(1.0, $DB->get_field($tablename, 'onechar', array('id' => $id)));
2977          $DB->set_field($tablename, 'onechar', 1e20, array('id' => $id));
2978          $this->assertEquals(1e20, $DB->get_field($tablename, 'onechar', array('id' => $id)));
2979          $DB->set_field($tablename, 'onechar', 1e-4, array('id' => $id));
2980          $this->assertEquals(1e-4, $DB->get_field($tablename, 'onechar', array('id' => $id)));
2981          $DB->set_field($tablename, 'onechar', 1e-5, array('id' => $id));
2982          $this->assertEquals(1e-5, $DB->get_field($tablename, 'onechar', array('id' => $id)));
2983          $DB->set_field($tablename, 'onechar', 1e-300, array('id' => $id));
2984          $this->assertEquals(1e-300, $DB->get_field($tablename, 'onechar', array('id' => $id)));
2985          $DB->set_field($tablename, 'onechar', 1e300, array('id' => $id));
2986          $this->assertEquals(1e300, $DB->get_field($tablename, 'onechar', array('id' => $id)));
2987  
2988          // Test saving a float in a TEXT column, and reading it back.
2989          $id = $DB->insert_record($tablename, array('onetext' => 'X'));
2990          $DB->set_field($tablename, 'onetext', 1.0, array('id' => $id));
2991          $this->assertEquals(1.0, $DB->get_field($tablename, 'onetext', array('id' => $id)));
2992          $DB->set_field($tablename, 'onetext', 1e20, array('id' => $id));
2993          $this->assertEquals(1e20, $DB->get_field($tablename, 'onetext', array('id' => $id)));
2994          $DB->set_field($tablename, 'onetext', 1e-4, array('id' => $id));
2995          $this->assertEquals(1e-4, $DB->get_field($tablename, 'onetext', array('id' => $id)));
2996          $DB->set_field($tablename, 'onetext', 1e-5, array('id' => $id));
2997          $this->assertEquals(1e-5, $DB->get_field($tablename, 'onetext', array('id' => $id)));
2998          $DB->set_field($tablename, 'onetext', 1e-300, array('id' => $id));
2999          $this->assertEquals(1e-300, $DB->get_field($tablename, 'onetext', array('id' => $id)));
3000          $DB->set_field($tablename, 'onetext', 1e300, array('id' => $id));
3001          $this->assertEquals(1e300, $DB->get_field($tablename, 'onetext', array('id' => $id)));
3002  
3003          // Note: All the nulls, booleans, empties, quoted and backslashes tests
3004          // go to set_field_select() because set_field() is just one wrapper over it.
3005      }
3006  
3007      public function test_set_field_select() {
3008  
3009          // All the information in this test is fetched from DB by get_field() so we
3010          // have such method properly tested against nulls, empties and friends...
3011  
3012          $DB = $this->tdb;
3013          $dbman = $DB->get_manager();
3014  
3015          $table = $this->get_test_table();
3016          $tablename = $table->getName();
3017  
3018          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3019          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
3020          $table->add_field('oneint', XMLDB_TYPE_INTEGER, '10', null, null, null);
3021          $table->add_field('onenum', XMLDB_TYPE_NUMBER, '10,2', null, null, null);
3022          $table->add_field('onechar', XMLDB_TYPE_CHAR, '100', null, null, null);
3023          $table->add_field('onetext', XMLDB_TYPE_TEXT, 'big', null, null, null);
3024          $table->add_field('onebinary', XMLDB_TYPE_BINARY, 'big', null, null, null);
3025          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3026          $dbman->create_table($table);
3027  
3028          $DB->insert_record($tablename, array('course' => 1));
3029  
3030          $this->assertTrue($DB->set_field_select($tablename, 'course', 2, 'id = ?', array(1)));
3031          $this->assertEquals(2, $DB->get_field($tablename, 'course', array('id' => 1)));
3032  
3033          // Check nulls are set properly for all types.
3034          $DB->set_field_select($tablename, 'oneint', null, 'id = ?', array(1)); // Trues.
3035          $DB->set_field_select($tablename, 'onenum', null, 'id = ?', array(1));
3036          $DB->set_field_select($tablename, 'onechar', null, 'id = ?', array(1));
3037          $DB->set_field_select($tablename, 'onetext', null, 'id = ?', array(1));
3038          $DB->set_field_select($tablename, 'onebinary', null, 'id = ?', array(1));
3039          $this->assertNull($DB->get_field($tablename, 'oneint', array('id' => 1)));
3040          $this->assertNull($DB->get_field($tablename, 'onenum', array('id' => 1)));
3041          $this->assertNull($DB->get_field($tablename, 'onechar', array('id' => 1)));
3042          $this->assertNull($DB->get_field($tablename, 'onetext', array('id' => 1)));
3043          $this->assertNull($DB->get_field($tablename, 'onebinary', array('id' => 1)));
3044  
3045          // Check zeros are set properly for all types.
3046          $DB->set_field_select($tablename, 'oneint', 0, 'id = ?', array(1));
3047          $DB->set_field_select($tablename, 'onenum', 0, 'id = ?', array(1));
3048          $this->assertEquals(0, $DB->get_field($tablename, 'oneint', array('id' => 1)));
3049          $this->assertEquals(0, $DB->get_field($tablename, 'onenum', array('id' => 1)));
3050  
3051          // Check booleans are set properly for all types.
3052          $DB->set_field_select($tablename, 'oneint', true, 'id = ?', array(1)); // Trues.
3053          $DB->set_field_select($tablename, 'onenum', true, 'id = ?', array(1));
3054          $DB->set_field_select($tablename, 'onechar', true, 'id = ?', array(1));
3055          $DB->set_field_select($tablename, 'onetext', true, 'id = ?', array(1));
3056          $this->assertEquals(1, $DB->get_field($tablename, 'oneint', array('id' => 1)));
3057          $this->assertEquals(1, $DB->get_field($tablename, 'onenum', array('id' => 1)));
3058          $this->assertEquals(1, $DB->get_field($tablename, 'onechar', array('id' => 1)));
3059          $this->assertEquals(1, $DB->get_field($tablename, 'onetext', array('id' => 1)));
3060  
3061          $DB->set_field_select($tablename, 'oneint', false, 'id = ?', array(1)); // Falses.
3062          $DB->set_field_select($tablename, 'onenum', false, 'id = ?', array(1));
3063          $DB->set_field_select($tablename, 'onechar', false, 'id = ?', array(1));
3064          $DB->set_field_select($tablename, 'onetext', false, 'id = ?', array(1));
3065          $this->assertEquals(0, $DB->get_field($tablename, 'oneint', array('id' => 1)));
3066          $this->assertEquals(0, $DB->get_field($tablename, 'onenum', array('id' => 1)));
3067          $this->assertEquals(0, $DB->get_field($tablename, 'onechar', array('id' => 1)));
3068          $this->assertEquals(0, $DB->get_field($tablename, 'onetext', array('id' => 1)));
3069  
3070          // Check string data causes exception in numeric types.
3071          try {
3072              $DB->set_field_select($tablename, 'oneint', 'onestring', 'id = ?', array(1));
3073              $this->fail("Expecting an exception, none occurred");
3074          } catch (moodle_exception $e) {
3075              $this->assertInstanceOf('dml_exception', $e);
3076          }
3077          try {
3078              $DB->set_field_select($tablename, 'onenum', 'onestring', 'id = ?', array(1));
3079              $this->fail("Expecting an exception, none occurred");
3080          } catch (moodle_exception $e) {
3081              $this->assertInstanceOf('dml_exception', $e);
3082          }
3083  
3084          // Check empty string data is stored as 0 in numeric datatypes.
3085          $DB->set_field_select($tablename, 'oneint', '', 'id = ?', array(1));
3086          $field = $DB->get_field($tablename, 'oneint', array('id' => 1));
3087          $this->assertTrue(is_numeric($field) && $field == 0);
3088  
3089          $DB->set_field_select($tablename, 'onenum', '', 'id = ?', array(1));
3090          $field = $DB->get_field($tablename, 'onenum', array('id' => 1));
3091          $this->assertTrue(is_numeric($field) && $field == 0);
3092  
3093          // Check empty strings are set properly in string types.
3094          $DB->set_field_select($tablename, 'onechar', '', 'id = ?', array(1));
3095          $DB->set_field_select($tablename, 'onetext', '', 'id = ?', array(1));
3096          $this->assertTrue($DB->get_field($tablename, 'onechar', array('id' => 1)) === '');
3097          $this->assertTrue($DB->get_field($tablename, 'onetext', array('id' => 1)) === '');
3098  
3099          // Check operation ((210.10 + 39.92) - 150.02) against numeric types.
3100          $DB->set_field_select($tablename, 'oneint', ((210.10 + 39.92) - 150.02), 'id = ?', array(1));
3101          $DB->set_field_select($tablename, 'onenum', ((210.10 + 39.92) - 150.02), 'id = ?', array(1));
3102          $this->assertEquals(100, $DB->get_field($tablename, 'oneint', array('id' => 1)));
3103          $this->assertEquals(100, $DB->get_field($tablename, 'onenum', array('id' => 1)));
3104  
3105          // Check various quotes/backslashes combinations in string types.
3106          $teststrings = array(
3107              'backslashes and quotes alone (even): "" \'\' \\\\',
3108              'backslashes and quotes alone (odd): """ \'\'\' \\\\\\',
3109              'backslashes and quotes sequences (even): \\"\\" \\\'\\\'',
3110              'backslashes and quotes sequences (odd): \\"\\"\\" \\\'\\\'\\\'');
3111          foreach ($teststrings as $teststring) {
3112              $DB->set_field_select($tablename, 'onechar', $teststring, 'id = ?', array(1));
3113              $DB->set_field_select($tablename, 'onetext', $teststring, 'id = ?', array(1));
3114              $this->assertEquals($teststring, $DB->get_field($tablename, 'onechar', array('id' => 1)));
3115              $this->assertEquals($teststring, $DB->get_field($tablename, 'onetext', array('id' => 1)));
3116          }
3117  
3118          // Check LOBs in text/binary columns.
3119          $clob = file_get_contents(__DIR__ . '/fixtures/clob.txt');
3120          $blob = file_get_contents(__DIR__ . '/fixtures/randombinary');
3121          $DB->set_field_select($tablename, 'onetext', $clob, 'id = ?', array(1));
3122          $DB->set_field_select($tablename, 'onebinary', $blob, 'id = ?', array(1));
3123          $this->assertEquals($clob, $DB->get_field($tablename, 'onetext', array('id' => 1)), 'Test CLOB set_field (full contents output disabled)');
3124          $this->assertEquals($blob, $DB->get_field($tablename, 'onebinary', array('id' => 1)), 'Test BLOB set_field (full contents output disabled)');
3125  
3126          // Empty data in binary columns works.
3127          $DB->set_field_select($tablename, 'onebinary', '', 'id = ?', array(1));
3128          $this->assertEquals('', $DB->get_field($tablename, 'onebinary', array('id' => 1)), 'Blobs need to accept empty values.');
3129  
3130          // And "small" LOBs too, just in case.
3131          $newclob = substr($clob, 0, 500);
3132          $newblob = substr($blob, 0, 250);
3133          $DB->set_field_select($tablename, 'onetext', $newclob, 'id = ?', array(1));
3134          $DB->set_field_select($tablename, 'onebinary', $newblob, 'id = ?', array(1));
3135          $this->assertEquals($newclob, $DB->get_field($tablename, 'onetext', array('id' => 1)), 'Test "small" CLOB set_field (full contents output disabled)');
3136          $this->assertEquals($newblob, $DB->get_field($tablename, 'onebinary', array('id' => 1)), 'Test "small" BLOB set_field (full contents output disabled)');
3137  
3138          // This is the failure from MDL-24863. This was giving an error on MSSQL,
3139          // which converts the '1' to an integer, which cannot then be compared with
3140          // onetext cast to a varchar. This should be fixed and working now.
3141          $newchar = 'frog';
3142          // Test for exception throwing on text conditions being compared. (MDL-24863, unwanted auto conversion of param to int).
3143          $params = array('onetext' => '1');
3144          try {
3145              $DB->set_field_select($tablename, 'onechar', $newchar, $DB->sql_compare_text('onetext') . ' = ?', $params);
3146              $this->assertTrue(true, 'No exceptions thrown with numerical text param comparison for text field.');
3147          } catch (dml_exception $e) {
3148              $this->assertFalse(true, 'We have an unexpected exception.');
3149              throw $e;
3150          }
3151      }
3152  
3153      public function test_count_records() {
3154          $DB = $this->tdb;
3155  
3156          $dbman = $DB->get_manager();
3157  
3158          $table = $this->get_test_table();
3159          $tablename = $table->getName();
3160  
3161          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3162          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
3163          $table->add_field('onetext', XMLDB_TYPE_TEXT, 'big', null, null, null);
3164          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3165          $dbman->create_table($table);
3166  
3167          $this->assertSame(0, $DB->count_records($tablename));
3168  
3169          $DB->insert_record($tablename, array('course' => 3));
3170          $DB->insert_record($tablename, array('course' => 4));
3171          $DB->insert_record($tablename, array('course' => 5));
3172  
3173          $this->assertSame(3, $DB->count_records($tablename));
3174  
3175          // Test for exception throwing on text conditions being compared. (MDL-24863, unwanted auto conversion of param to int).
3176          $conditions = array('onetext' => '1');
3177          try {
3178              $DB->count_records($tablename, $conditions);
3179              if (debugging()) {
3180                  // Only in debug mode - hopefully all devs test code in debug mode...
3181                  $this->fail('An Exception is missing, expected due to equating of text fields');
3182              }
3183          } catch (moodle_exception $e) {
3184              $this->assertInstanceOf('dml_exception', $e);
3185              $this->assertSame('textconditionsnotallowed', $e->errorcode);
3186          }
3187      }
3188  
3189      public function test_count_records_select() {
3190          $DB = $this->tdb;
3191  
3192          $dbman = $DB->get_manager();
3193  
3194          $table = $this->get_test_table();
3195          $tablename = $table->getName();
3196  
3197          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3198          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
3199          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3200          $dbman->create_table($table);
3201  
3202          $this->assertSame(0, $DB->count_records($tablename));
3203  
3204          $DB->insert_record($tablename, array('course' => 3));
3205          $DB->insert_record($tablename, array('course' => 4));
3206          $DB->insert_record($tablename, array('course' => 5));
3207  
3208          $this->assertSame(2, $DB->count_records_select($tablename, 'course > ?', array(3)));
3209      }
3210  
3211      public function test_count_records_sql() {
3212          $DB = $this->tdb;
3213          $dbman = $DB->get_manager();
3214  
3215          $table = $this->get_test_table();
3216          $tablename = $table->getName();
3217  
3218          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3219          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
3220          $table->add_field('onechar', XMLDB_TYPE_CHAR, '100', null, null, null);
3221          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3222          $dbman->create_table($table);
3223  
3224          $this->assertSame(0, $DB->count_records($tablename));
3225  
3226          $DB->insert_record($tablename, array('course' => 3, 'onechar' => 'a'));
3227          $DB->insert_record($tablename, array('course' => 4, 'onechar' => 'b'));
3228          $DB->insert_record($tablename, array('course' => 5, 'onechar' => 'c'));
3229  
3230          $this->assertSame(2, $DB->count_records_sql("SELECT COUNT(*) FROM {{$tablename}} WHERE course > ?", array(3)));
3231  
3232          // Test invalid use.
3233          try {
3234              $DB->count_records_sql("SELECT onechar FROM {{$tablename}} WHERE course = ?", array(3));
3235              $this->fail('Exception expected when non-number field used in count_records_sql');
3236          } catch (moodle_exception $e) {
3237              $this->assertInstanceOf('coding_exception', $e);
3238          }
3239  
3240          try {
3241              $DB->count_records_sql("SELECT course FROM {{$tablename}} WHERE 1 = 2");
3242              $this->fail('Exception expected when non-number field used in count_records_sql');
3243          } catch (moodle_exception $e) {
3244              $this->assertInstanceOf('coding_exception', $e);
3245          }
3246      }
3247  
3248      public function test_record_exists() {
3249          $DB = $this->tdb;
3250          $dbman = $DB->get_manager();
3251  
3252          $table = $this->get_test_table();
3253          $tablename = $table->getName();
3254  
3255          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3256          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
3257          $table->add_field('onetext', XMLDB_TYPE_TEXT, 'big', null, null, null);
3258          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3259          $dbman->create_table($table);
3260  
3261          $this->assertEquals(0, $DB->count_records($tablename));
3262  
3263          $this->assertFalse($DB->record_exists($tablename, array('course' => 3)));
3264          $DB->insert_record($tablename, array('course' => 3));
3265  
3266          $this->assertTrue($DB->record_exists($tablename, array('course' => 3)));
3267  
3268          // Test for exception throwing on text conditions being compared. (MDL-24863, unwanted auto conversion of param to int).
3269          $conditions = array('onetext' => '1');
3270          try {
3271              $DB->record_exists($tablename, $conditions);
3272              if (debugging()) {
3273                  // Only in debug mode - hopefully all devs test code in debug mode...
3274                  $this->fail('An Exception is missing, expected due to equating of text fields');
3275              }
3276          } catch (moodle_exception $e) {
3277              $this->assertInstanceOf('dml_exception', $e);
3278              $this->assertSame('textconditionsnotallowed', $e->errorcode);
3279          }
3280      }
3281  
3282      public function test_record_exists_select() {
3283          $DB = $this->tdb;
3284          $dbman = $DB->get_manager();
3285  
3286          $table = $this->get_test_table();
3287          $tablename = $table->getName();
3288  
3289          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3290          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
3291          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3292          $dbman->create_table($table);
3293  
3294          $this->assertEquals(0, $DB->count_records($tablename));
3295  
3296          $this->assertFalse($DB->record_exists_select($tablename, "course = ?", array(3)));
3297          $DB->insert_record($tablename, array('course' => 3));
3298  
3299          $this->assertTrue($DB->record_exists_select($tablename, "course = ?", array(3)));
3300      }
3301  
3302      public function test_record_exists_sql() {
3303          $DB = $this->tdb;
3304          $dbman = $DB->get_manager();
3305  
3306          $table = $this->get_test_table();
3307          $tablename = $table->getName();
3308  
3309          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3310          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
3311          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3312          $dbman->create_table($table);
3313  
3314          $this->assertEquals(0, $DB->count_records($tablename));
3315  
3316          $this->assertFalse($DB->record_exists_sql("SELECT * FROM {{$tablename}} WHERE course = ?", array(3)));
3317          $DB->insert_record($tablename, array('course' => 3));
3318  
3319          $this->assertTrue($DB->record_exists_sql("SELECT * FROM {{$tablename}} WHERE course = ?", array(3)));
3320      }
3321  
3322      public function test_recordset_locks_delete() {
3323          $DB = $this->tdb;
3324          $dbman = $DB->get_manager();
3325  
3326          // Setup.
3327          $table = $this->get_test_table();
3328          $tablename = $table->getName();
3329  
3330          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3331          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
3332          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3333          $dbman->create_table($table);
3334  
3335          $DB->insert_record($tablename, array('course' => 1));
3336          $DB->insert_record($tablename, array('course' => 2));
3337          $DB->insert_record($tablename, array('course' => 3));
3338          $DB->insert_record($tablename, array('course' => 4));
3339          $DB->insert_record($tablename, array('course' => 5));
3340          $DB->insert_record($tablename, array('course' => 6));
3341  
3342          // Test against db write locking while on an open recordset.
3343          $rs = $DB->get_recordset($tablename, array(), null, 'course', 2, 2); // Get courses = {3,4}.
3344          foreach ($rs as $record) {
3345              $cid = $record->course;
3346              $DB->delete_records($tablename, array('course' => $cid));
3347              $this->assertFalse($DB->record_exists($tablename, array('course' => $cid)));
3348          }
3349          $rs->close();
3350  
3351          $this->assertEquals(4, $DB->count_records($tablename, array()));
3352      }
3353  
3354      public function test_recordset_locks_update() {
3355          $DB = $this->tdb;
3356          $dbman = $DB->get_manager();
3357  
3358          // Setup.
3359          $table = $this->get_test_table();
3360          $tablename = $table->getName();
3361  
3362          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3363          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
3364          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3365          $dbman->create_table($table);
3366  
3367          $DB->insert_record($tablename, array('course' => 1));
3368          $DB->insert_record($tablename, array('course' => 2));
3369          $DB->insert_record($tablename, array('course' => 3));
3370          $DB->insert_record($tablename, array('course' => 4));
3371          $DB->insert_record($tablename, array('course' => 5));
3372          $DB->insert_record($tablename, array('course' => 6));
3373  
3374          // Test against db write locking while on an open recordset.
3375          $rs = $DB->get_recordset($tablename, array(), null, 'course', 2, 2); // Get courses = {3,4}.
3376          foreach ($rs as $record) {
3377              $cid = $record->course;
3378              $DB->set_field($tablename, 'course', 10, array('course' => $cid));
3379              $this->assertFalse($DB->record_exists($tablename, array('course' => $cid)));
3380          }
3381          $rs->close();
3382  
3383          $this->assertEquals(2, $DB->count_records($tablename, array('course' => 10)));
3384      }
3385  
3386      public function test_delete_records() {
3387          $DB = $this->tdb;
3388          $dbman = $DB->get_manager();
3389  
3390          $table = $this->get_test_table();
3391          $tablename = $table->getName();
3392  
3393          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3394          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
3395          $table->add_field('onetext', XMLDB_TYPE_TEXT, 'big', null, null, null);
3396          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3397          $dbman->create_table($table);
3398  
3399          $DB->insert_record($tablename, array('course' => 3));
3400          $DB->insert_record($tablename, array('course' => 2));
3401          $DB->insert_record($tablename, array('course' => 2));
3402  
3403          // Delete all records.
3404          $this->assertTrue($DB->delete_records($tablename));
3405          $this->assertEquals(0, $DB->count_records($tablename));
3406  
3407          // Delete subset of records.
3408          $DB->insert_record($tablename, array('course' => 3));
3409          $DB->insert_record($tablename, array('course' => 2));
3410          $DB->insert_record($tablename, array('course' => 2));
3411  
3412          $this->assertTrue($DB->delete_records($tablename, array('course' => 2)));
3413          $this->assertEquals(1, $DB->count_records($tablename));
3414  
3415          // Delete all.
3416          $this->assertTrue($DB->delete_records($tablename, array()));
3417          $this->assertEquals(0, $DB->count_records($tablename));
3418  
3419          // Test for exception throwing on text conditions being compared. (MDL-24863, unwanted auto conversion of param to int).
3420          $conditions = array('onetext'=>'1');
3421          try {
3422              $DB->delete_records($tablename, $conditions);
3423              if (debugging()) {
3424                  // Only in debug mode - hopefully all devs test code in debug mode...
3425                  $this->fail('An Exception is missing, expected due to equating of text fields');
3426              }
3427          } catch (moodle_exception $e) {
3428              $this->assertInstanceOf('dml_exception', $e);
3429              $this->assertSame('textconditionsnotallowed', $e->errorcode);
3430          }
3431  
3432          // Test for exception throwing on text conditions being compared. (MDL-24863, unwanted auto conversion of param to int).
3433          $conditions = array('onetext' => 1);
3434          try {
3435              $DB->delete_records($tablename, $conditions);
3436              if (debugging()) {
3437                  // Only in debug mode - hopefully all devs test code in debug mode...
3438                  $this->fail('An Exception is missing, expected due to equating of text fields');
3439              }
3440          } catch (moodle_exception $e) {
3441              $this->assertInstanceOf('dml_exception', $e);
3442              $this->assertSame('textconditionsnotallowed', $e->errorcode);
3443          }
3444      }
3445  
3446      public function test_delete_records_select() {
3447          $DB = $this->tdb;
3448          $dbman = $DB->get_manager();
3449  
3450          $table = $this->get_test_table();
3451          $tablename = $table->getName();
3452  
3453          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3454          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
3455          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3456          $dbman->create_table($table);
3457  
3458          $DB->insert_record($tablename, array('course' => 3));
3459          $DB->insert_record($tablename, array('course' => 2));
3460          $DB->insert_record($tablename, array('course' => 2));
3461  
3462          $this->assertTrue($DB->delete_records_select($tablename, 'course = ?', array(2)));
3463          $this->assertEquals(1, $DB->count_records($tablename));
3464      }
3465  
3466      public function test_delete_records_subquery() {
3467          $DB = $this->tdb;
3468          $dbman = $DB->get_manager();
3469  
3470          $table = $this->get_test_table();
3471          $tablename = $table->getName();
3472  
3473          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3474          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
3475          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3476          $dbman->create_table($table);
3477  
3478          $DB->insert_record($tablename, array('course' => 3));
3479          $DB->insert_record($tablename, array('course' => 2));
3480          $DB->insert_record($tablename, array('course' => 2));
3481  
3482          // This is not a useful scenario for using a subquery, but it will be sufficient for testing.
3483          // Use the 'frog' alias just to make it clearer when we are testing the alias parameter.
3484          $DB->delete_records_subquery($tablename, 'id', 'frog',
3485                  'SELECT id AS frog FROM {' . $tablename . '} WHERE course = ?', [2]);
3486          $this->assertEquals(1, $DB->count_records($tablename));
3487      }
3488  
3489      public function test_delete_records_list() {
3490          $DB = $this->tdb;
3491          $dbman = $DB->get_manager();
3492  
3493          $table = $this->get_test_table();
3494          $tablename = $table->getName();
3495  
3496          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3497          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
3498          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3499          $dbman->create_table($table);
3500  
3501          $DB->insert_record($tablename, array('course' => 1));
3502          $DB->insert_record($tablename, array('course' => 2));
3503          $DB->insert_record($tablename, array('course' => 3));
3504  
3505          $this->assertTrue($DB->delete_records_list($tablename, 'course', array(2, 3)));
3506          $this->assertEquals(1, $DB->count_records($tablename));
3507  
3508          $this->assertTrue($DB->delete_records_list($tablename, 'course', array())); // Must delete 0 rows without conditions. MDL-17645.
3509          $this->assertEquals(1, $DB->count_records($tablename));
3510      }
3511  
3512      public function test_object_params() {
3513          $DB = $this->tdb;
3514          $dbman = $DB->get_manager();
3515  
3516          $table = $this->get_test_table();
3517          $tablename = $table->getName();
3518          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3519          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
3520          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3521          $dbman->create_table($table);
3522  
3523          $o = new stdClass(); // Objects without __toString - never worked.
3524          try {
3525              $DB->fix_sql_params("SELECT {{$tablename}} WHERE course = ? ", array($o));
3526              $this->fail('coding_exception expected');
3527          } catch (moodle_exception $e) {
3528              $this->assertInstanceOf('coding_exception', $e);
3529          }
3530  
3531          // Objects with __toString() forbidden everywhere since 2.3.
3532          $o = new dml_test_object_one();
3533          try {
3534              $DB->fix_sql_params("SELECT {{$tablename}} WHERE course = ? ", array($o));
3535              $this->fail('coding_exception expected');
3536          } catch (moodle_exception $e) {
3537              $this->assertInstanceOf('coding_exception', $e);
3538          }
3539  
3540          try {
3541              $DB->execute("SELECT {{$tablename}} WHERE course = ? ", array($o));
3542              $this->fail('coding_exception expected');
3543          } catch (moodle_exception $e) {
3544              $this->assertInstanceOf('coding_exception', $e);
3545          }
3546  
3547          try {
3548              $DB->get_recordset_sql("SELECT {{$tablename}} WHERE course = ? ", array($o));
3549              $this->fail('coding_exception expected');
3550          } catch (moodle_exception $e) {
3551              $this->assertInstanceOf('coding_exception', $e);
3552          }
3553  
3554          try {
3555              $DB->get_records_sql("SELECT {{$tablename}} WHERE course = ? ", array($o));
3556              $this->fail('coding_exception expected');
3557          } catch (moodle_exception $e) {
3558              $this->assertInstanceOf('coding_exception', $e);
3559          }
3560  
3561          try {
3562              $record = new stdClass();
3563              $record->course = $o;
3564              $DB->insert_record_raw($tablename, $record);
3565              $this->fail('coding_exception expected');
3566          } catch (moodle_exception $e) {
3567              $this->assertInstanceOf('coding_exception', $e);
3568          }
3569  
3570          try {
3571              $record = new stdClass();
3572              $record->course = $o;
3573              $DB->insert_record($tablename, $record);
3574              $this->fail('coding_exception expected');
3575          } catch (moodle_exception $e) {
3576              $this->assertInstanceOf('coding_exception', $e);
3577          }
3578  
3579          try {
3580              $record = new stdClass();
3581              $record->course = $o;
3582              $DB->import_record($tablename, $record);
3583              $this->fail('coding_exception expected');
3584          } catch (moodle_exception $e) {
3585              $this->assertInstanceOf('coding_exception', $e);
3586          }
3587  
3588          try {
3589              $record = new stdClass();
3590              $record->id = 1;
3591              $record->course = $o;
3592              $DB->update_record_raw($tablename, $record);
3593              $this->fail('coding_exception expected');
3594          } catch (moodle_exception $e) {
3595              $this->assertInstanceOf('coding_exception', $e);
3596          }
3597  
3598          try {
3599              $record = new stdClass();
3600              $record->id = 1;
3601              $record->course = $o;
3602              $DB->update_record($tablename, $record);
3603              $this->fail('coding_exception expected');
3604          } catch (moodle_exception $e) {
3605              $this->assertInstanceOf('coding_exception', $e);
3606          }
3607  
3608          try {
3609              $DB->set_field_select($tablename, 'course', 1, "course = ? ", array($o));
3610              $this->fail('coding_exception expected');
3611          } catch (moodle_exception $e) {
3612              $this->assertInstanceOf('coding_exception', $e);
3613          }
3614  
3615          try {
3616              $DB->delete_records_select($tablename, "course = ? ", array($o));
3617              $this->fail('coding_exception expected');
3618          } catch (moodle_exception $e) {
3619              $this->assertInstanceOf('coding_exception', $e);
3620          }
3621      }
3622  
3623      public function test_sql_null_from_clause() {
3624          $DB = $this->tdb;
3625          $sql = "SELECT 1 AS id ".$DB->sql_null_from_clause();
3626          $this->assertEquals(1, $DB->get_field_sql($sql));
3627      }
3628  
3629      public function test_sql_bitand() {
3630          $DB = $this->tdb;
3631          $dbman = $DB->get_manager();
3632  
3633          $table = $this->get_test_table();
3634          $tablename = $table->getName();
3635  
3636          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3637          $table->add_field('col1', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
3638          $table->add_field('col2', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
3639          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3640          $dbman->create_table($table);
3641  
3642          $DB->insert_record($tablename, array('col1' => 3, 'col2' => 10));
3643  
3644          $sql = "SELECT ".$DB->sql_bitand(10, 3)." AS res ".$DB->sql_null_from_clause();
3645          $this->assertEquals(2, $DB->get_field_sql($sql));
3646  
3647          $sql = "SELECT id, ".$DB->sql_bitand('col1', 'col2')." AS res FROM {{$tablename}}";
3648          $result = $DB->get_records_sql($sql);
3649          $this->assertCount(1, $result);
3650          $this->assertEquals(2, reset($result)->res);
3651  
3652          $sql = "SELECT id, ".$DB->sql_bitand('col1', '?')." AS res FROM {{$tablename}}";
3653          $result = $DB->get_records_sql($sql, array(10));
3654          $this->assertCount(1, $result);
3655          $this->assertEquals(2, reset($result)->res);
3656      }
3657  
3658      public function test_sql_bitnot() {
3659          $DB = $this->tdb;
3660  
3661          $not = $DB->sql_bitnot(2);
3662          $notlimited = $DB->sql_bitand($not, 7); // Might be positive or negative number which can not fit into PHP INT!
3663  
3664          $sql = "SELECT $notlimited AS res ".$DB->sql_null_from_clause();
3665          $this->assertEquals(5, $DB->get_field_sql($sql));
3666      }
3667  
3668      public function test_sql_bitor() {
3669          $DB = $this->tdb;
3670          $dbman = $DB->get_manager();
3671  
3672          $table = $this->get_test_table();
3673          $tablename = $table->getName();
3674  
3675          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3676          $table->add_field('col1', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
3677          $table->add_field('col2', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
3678          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3679          $dbman->create_table($table);
3680  
3681          $DB->insert_record($tablename, array('col1' => 3, 'col2' => 10));
3682  
3683          $sql = "SELECT ".$DB->sql_bitor(10, 3)." AS res ".$DB->sql_null_from_clause();
3684          $this->assertEquals(11, $DB->get_field_sql($sql));
3685  
3686          $sql = "SELECT id, ".$DB->sql_bitor('col1', 'col2')." AS res FROM {{$tablename}}";
3687          $result = $DB->get_records_sql($sql);
3688          $this->assertCount(1, $result);
3689          $this->assertEquals(11, reset($result)->res);
3690  
3691          $sql = "SELECT id, ".$DB->sql_bitor('col1', '?')." AS res FROM {{$tablename}}";
3692          $result = $DB->get_records_sql($sql, array(10));
3693          $this->assertCount(1, $result);
3694          $this->assertEquals(11, reset($result)->res);
3695      }
3696  
3697      public function test_sql_bitxor() {
3698          $DB = $this->tdb;
3699          $dbman = $DB->get_manager();
3700  
3701          $table = $this->get_test_table();
3702          $tablename = $table->getName();
3703  
3704          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3705          $table->add_field('col1', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
3706          $table->add_field('col2', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
3707          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3708          $dbman->create_table($table);
3709  
3710          $DB->insert_record($tablename, array('col1' => 3, 'col2' => 10));
3711  
3712          $sql = "SELECT ".$DB->sql_bitxor(10, 3)." AS res ".$DB->sql_null_from_clause();
3713          $this->assertEquals(9, $DB->get_field_sql($sql));
3714  
3715          $sql = "SELECT id, ".$DB->sql_bitxor('col1', 'col2')." AS res FROM {{$tablename}}";
3716          $result = $DB->get_records_sql($sql);
3717          $this->assertCount(1, $result);
3718          $this->assertEquals(9, reset($result)->res);
3719  
3720          $sql = "SELECT id, ".$DB->sql_bitxor('col1', '?')." AS res FROM {{$tablename}}";
3721          $result = $DB->get_records_sql($sql, array(10));
3722          $this->assertCount(1, $result);
3723          $this->assertEquals(9, reset($result)->res);
3724      }
3725  
3726      public function test_sql_modulo() {
3727          $DB = $this->tdb;
3728          $sql = "SELECT ".$DB->sql_modulo(10, 7)." AS res ".$DB->sql_null_from_clause();
3729          $this->assertEquals(3, $DB->get_field_sql($sql));
3730      }
3731  
3732      public function test_sql_ceil() {
3733          $DB = $this->tdb;
3734          $sql = "SELECT ".$DB->sql_ceil(665.666)." AS res ".$DB->sql_null_from_clause();
3735          $this->assertEquals(666, $DB->get_field_sql($sql));
3736      }
3737  
3738      public function test_cast_char2int() {
3739          $DB = $this->tdb;
3740          $dbman = $DB->get_manager();
3741  
3742          $table1 = $this->get_test_table("1");
3743          $tablename1 = $table1->getName();
3744  
3745          $table1->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3746          $table1->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null);
3747          $table1->add_field('nametext', XMLDB_TYPE_TEXT, 'small', null, null, null, null);
3748          $table1->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3749          $dbman->create_table($table1);
3750  
3751          $DB->insert_record($tablename1, array('name'=>'0100', 'nametext'=>'0200'));
3752          $DB->insert_record($tablename1, array('name'=>'10',   'nametext'=>'20'));
3753  
3754          $table2 = $this->get_test_table("2");
3755          $tablename2 = $table2->getName();
3756          $table2->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3757          $table2->add_field('res', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
3758          $table2->add_field('restext', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
3759          $table2->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3760          $dbman->create_table($table2);
3761  
3762          $DB->insert_record($tablename2, array('res'=>100, 'restext'=>200));
3763  
3764          // Casting varchar field.
3765          $sql = "SELECT *
3766                    FROM {".$tablename1."} t1
3767                    JOIN {".$tablename2."} t2 ON ".$DB->sql_cast_char2int("t1.name")." = t2.res ";
3768          $records = $DB->get_records_sql($sql);
3769          $this->assertCount(1, $records);
3770          // Also test them in order clauses.
3771          $sql = "SELECT * FROM {{$tablename1}} ORDER BY ".$DB->sql_cast_char2int('name');
3772          $records = $DB->get_records_sql($sql);
3773          $this->assertCount(2, $records);
3774          $this->assertSame('10', reset($records)->name);
3775          $this->assertSame('0100', next($records)->name);
3776  
3777          // Casting text field.
3778          $sql = "SELECT *
3779                    FROM {".$tablename1."} t1
3780                    JOIN {".$tablename2."} t2 ON ".$DB->sql_cast_char2int("t1.nametext", true)." = t2.restext ";
3781          $records = $DB->get_records_sql($sql);
3782          $this->assertCount(1, $records);
3783          // Also test them in order clauses.
3784          $sql = "SELECT * FROM {{$tablename1}} ORDER BY ".$DB->sql_cast_char2int('nametext', true);
3785          $records = $DB->get_records_sql($sql);
3786          $this->assertCount(2, $records);
3787          $this->assertSame('20', reset($records)->nametext);
3788          $this->assertSame('0200', next($records)->nametext);
3789      }
3790  
3791      public function test_cast_char2real() {
3792          $DB = $this->tdb;
3793          $dbman = $DB->get_manager();
3794  
3795          $table = $this->get_test_table();
3796          $tablename = $table->getName();
3797  
3798          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3799          $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null);
3800          $table->add_field('nametext', XMLDB_TYPE_TEXT, 'small', null, null, null, null);
3801          $table->add_field('res', XMLDB_TYPE_NUMBER, '12, 7', null, null, null, null);
3802          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3803          $dbman->create_table($table);
3804  
3805          $DB->insert_record($tablename, array('name'=>'10.10', 'nametext'=>'10.10', 'res'=>5.1));
3806          $DB->insert_record($tablename, array('name'=>'91.10', 'nametext'=>'91.10', 'res'=>666));
3807          $DB->insert_record($tablename, array('name'=>'011.13333333', 'nametext'=>'011.13333333', 'res'=>10.1));
3808  
3809          // Casting varchar field.
3810          $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_cast_char2real('name')." > res";
3811          $records = $DB->get_records_sql($sql);
3812          $this->assertCount(2, $records);
3813          // Also test them in order clauses.
3814          $sql = "SELECT * FROM {{$tablename}} ORDER BY ".$DB->sql_cast_char2real('name');
3815          $records = $DB->get_records_sql($sql);
3816          $this->assertCount(3, $records);
3817          $this->assertSame('10.10', reset($records)->name);
3818          $this->assertSame('011.13333333', next($records)->name);
3819          $this->assertSame('91.10', next($records)->name);
3820          // And verify we can operate with them without too much problem with at least 6 decimals scale accuracy.
3821          $sql = "SELECT AVG(" . $DB->sql_cast_char2real('name') . ") FROM {{$tablename}}";
3822          $this->assertEquals(37.44444443333333, (float)$DB->get_field_sql($sql), '', 1.0E-6);
3823  
3824          // Casting text field.
3825          $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_cast_char2real('nametext', true)." > res";
3826          $records = $DB->get_records_sql($sql);
3827          $this->assertCount(2, $records);
3828          // Also test them in order clauses.
3829          $sql = "SELECT * FROM {{$tablename}} ORDER BY ".$DB->sql_cast_char2real('nametext', true);
3830          $records = $DB->get_records_sql($sql);
3831          $this->assertCount(3, $records);
3832          $this->assertSame('10.10', reset($records)->nametext);
3833          $this->assertSame('011.13333333', next($records)->nametext);
3834          $this->assertSame('91.10', next($records)->nametext);
3835          // And verify we can operate with them without too much problem with at least 6 decimals scale accuracy.
3836          $sql = "SELECT AVG(" . $DB->sql_cast_char2real('nametext', true) . ") FROM {{$tablename}}";
3837          $this->assertEquals(37.44444443333333, (float)$DB->get_field_sql($sql), '', 1.0E-6);
3838  
3839          // Check it works with values passed as param.
3840          $sql = "SELECT name FROM {{$tablename}} WHERE FLOOR(res - " . $DB->sql_cast_char2real(':param') . ") = 0";
3841          $this->assertEquals('011.13333333', $DB->get_field_sql($sql, array('param' => '10.09999')));
3842  
3843          // And also, although not recommended, with directly passed values.
3844          $sql = "SELECT name FROM {{$tablename}} WHERE FLOOR(res - " . $DB->sql_cast_char2real('10.09999') . ") = 0";
3845          $this->assertEquals('011.13333333', $DB->get_field_sql($sql));
3846      }
3847  
3848      public function test_sql_compare_text() {
3849          $DB = $this->tdb;
3850          $dbman = $DB->get_manager();
3851  
3852          $table = $this->get_test_table();
3853          $tablename = $table->getName();
3854  
3855          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3856          $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null);
3857          $table->add_field('description', XMLDB_TYPE_TEXT, 'big', null, null, null, null);
3858          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3859          $dbman->create_table($table);
3860  
3861          $DB->insert_record($tablename, array('name'=>'abcd',   'description'=>'abcd'));
3862          $DB->insert_record($tablename, array('name'=>'abcdef', 'description'=>'bbcdef'));
3863          $DB->insert_record($tablename, array('name'=>'aaaa', 'description'=>'aaaacccccccccccccccccc'));
3864          $DB->insert_record($tablename, array('name'=>'xxxx',   'description'=>'123456789a123456789b123456789c123456789d'));
3865  
3866          // Only some supported databases truncate TEXT fields for comparisons, currently MSSQL and Oracle.
3867          $dbtruncatestextfields = ($DB->get_dbfamily() == 'mssql' || $DB->get_dbfamily() == 'oracle');
3868  
3869          if ($dbtruncatestextfields) {
3870              // Ensure truncation behaves as expected.
3871  
3872              $sql = "SELECT " . $DB->sql_compare_text('description') . " AS field FROM {{$tablename}} WHERE name = ?";
3873              $description = $DB->get_field_sql($sql, array('xxxx'));
3874  
3875              // Should truncate to 32 chars (the default).
3876              $this->assertEquals('123456789a123456789b123456789c12', $description);
3877  
3878              $sql = "SELECT " . $DB->sql_compare_text('description', 35) . " AS field FROM {{$tablename}} WHERE name = ?";
3879              $description = $DB->get_field_sql($sql, array('xxxx'));
3880  
3881              // Should truncate to the specified number of chars.
3882              $this->assertEquals('123456789a123456789b123456789c12345', $description);
3883          }
3884  
3885          // Ensure text field comparison is successful.
3886          $sql = "SELECT * FROM {{$tablename}} WHERE name = ".$DB->sql_compare_text('description');
3887          $records = $DB->get_records_sql($sql);
3888          $this->assertCount(1, $records);
3889  
3890          $sql = "SELECT * FROM {{$tablename}} WHERE name = ".$DB->sql_compare_text('description', 4);
3891          $records = $DB->get_records_sql($sql);
3892          if ($dbtruncatestextfields) {
3893              // Should truncate description to 4 characters before comparing.
3894              $this->assertCount(2, $records);
3895          } else {
3896              // Should leave untruncated, so one less match.
3897              $this->assertCount(1, $records);
3898          }
3899  
3900          // Now test the function with really big content and params.
3901          $clob = file_get_contents(__DIR__ . '/fixtures/clob.txt');
3902          $DB->insert_record($tablename, array('name' => 'zzzz', 'description' => $clob));
3903          $sql = "SELECT * FROM {{$tablename}}
3904                   WHERE " . $DB->sql_compare_text('description') . " = " . $DB->sql_compare_text(':clob');
3905          $records = $DB->get_records_sql($sql, array('clob' => $clob));
3906          $this->assertCount(1, $records);
3907          $record = reset($records);
3908          $this->assertSame($clob, $record->description);
3909      }
3910  
3911      public function test_unique_index_collation_trouble() {
3912          // Note: this is a work in progress, we should probably move this to ddl test.
3913  
3914          $DB = $this->tdb;
3915          $dbman = $DB->get_manager();
3916  
3917          $table = $this->get_test_table();
3918          $tablename = $table->getName();
3919  
3920          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3921          $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null);
3922          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3923          $table->add_index('name', XMLDB_INDEX_UNIQUE, array('name'));
3924          $dbman->create_table($table);
3925  
3926          $DB->insert_record($tablename, array('name'=>'aaa'));
3927  
3928          try {
3929              $DB->insert_record($tablename, array('name'=>'AAA'));
3930          } catch (moodle_exception $e) {
3931              // TODO: ignore case insensitive uniqueness problems for now.
3932              // $this->fail("Unique index is case sensitive - this may cause problems in some tables");
3933          }
3934  
3935          try {
3936              $DB->insert_record($tablename, array('name'=>'aäa'));
3937              $DB->insert_record($tablename, array('name'=>'aáa'));
3938              $this->assertTrue(true);
3939          } catch (moodle_exception $e) {
3940              $family = $DB->get_dbfamily();
3941              if ($family === 'mysql' or $family === 'mssql') {
3942                  $this->fail("Unique index is accent insensitive, this may cause problems for non-ascii languages. This is usually caused by accent insensitive default collation.");
3943              } else {
3944                  // This should not happen, PostgreSQL and Oracle do not support accent insensitive uniqueness.
3945                  $this->fail("Unique index is accent insensitive, this may cause problems for non-ascii languages.");
3946              }
3947              throw($e);
3948          }
3949      }
3950  
3951      public function test_sql_equal() {
3952          $DB = $this->tdb;
3953          $dbman = $DB->get_manager();
3954  
3955          $table = $this->get_test_table();
3956          $tablename = $table->getName();
3957  
3958          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3959          $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null);
3960          $table->add_field('name2', XMLDB_TYPE_CHAR, '255', null, null, null, null);
3961          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3962          $dbman->create_table($table);
3963  
3964          $DB->insert_record($tablename, array('name' => 'one', 'name2' => 'one'));
3965          $DB->insert_record($tablename, array('name' => 'ONE', 'name2' => 'ONE'));
3966          $DB->insert_record($tablename, array('name' => 'two', 'name2' => 'TWO'));
3967          $DB->insert_record($tablename, array('name' => 'öne', 'name2' => 'one'));
3968          $DB->insert_record($tablename, array('name' => 'öne', 'name2' => 'ÖNE'));
3969  
3970          // Case sensitive and accent sensitive (equal and not equal).
3971          $sql = "SELECT * FROM {{$tablename}} WHERE " . $DB->sql_equal('name', '?', true, true, false);
3972          $records = $DB->get_records_sql($sql, array('one'));
3973          $this->assertCount(1, $records);
3974          $sql = "SELECT * FROM {{$tablename}} WHERE " . $DB->sql_equal('name', ':name', true, true, true);
3975          $records = $DB->get_records_sql($sql, array('name' => 'one'));
3976          $this->assertCount(4, $records);
3977          // And with column comparison instead of params.
3978          $sql = "SELECT * FROM {{$tablename}} WHERE " . $DB->sql_equal('name', 'name2', true, true, false);
3979          $records = $DB->get_records_sql($sql);
3980          $this->assertCount(2, $records);
3981  
3982          // Case insensitive and accent sensitive (equal and not equal).
3983          $sql = "SELECT * FROM {{$tablename}} WHERE " . $DB->sql_equal('name', '?', false, true, false);
3984          $records = $DB->get_records_sql($sql, array('one'));
3985          $this->assertCount(2, $records);
3986          $sql = "SELECT * FROM {{$tablename}} WHERE " . $DB->sql_equal('name', ':name', false, true, true);
3987          $records = $DB->get_records_sql($sql, array('name' => 'one'));
3988          $this->assertCount(3, $records);
3989          // And with column comparison instead of params.
3990          $sql = "SELECT * FROM {{$tablename}} WHERE " . $DB->sql_equal('name', 'name2', false, true, false);
3991          $records = $DB->get_records_sql($sql);
3992          $this->assertCount(4, $records);
3993  
3994          // TODO: Accent insensitive is not cross-db, only some drivers support it, so just verify the queries work.
3995          $sql = "SELECT * FROM {{$tablename}} WHERE " . $DB->sql_equal('name', '?', true, false);
3996          $records = $DB->get_records_sql($sql, array('one'));
3997          $this->assertGreaterThanOrEqual(1, count($records)); // At very least, there is 1 record with CS/AI "one".
3998          $sql = "SELECT * FROM {{$tablename}} WHERE " . $DB->sql_equal('name', '?', false, false);
3999          $records = $DB->get_records_sql($sql, array('one'));
4000          $this->assertGreaterThanOrEqual(2, count($records)); // At very least, there are 2 records with CI/AI "one".
4001          // And with column comparison instead of params.
4002          $sql = "SELECT * FROM {{$tablename}} WHERE " . $DB->sql_equal('name', 'name2', false, false);
4003          $records = $DB->get_records_sql($sql);
4004          $this->assertGreaterThanOrEqual(4, count($records)); // At very least, there are 4 records with CI/AI names matching.
4005      }
4006  
4007      public function test_sql_like() {
4008          $DB = $this->tdb;
4009          $dbman = $DB->get_manager();
4010  
4011          $table = $this->get_test_table();
4012          $tablename = $table->getName();
4013  
4014          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
4015          $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null);
4016          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
4017          $dbman->create_table($table);
4018  
4019          $DB->insert_record($tablename, array('name'=>'SuperDuperRecord'));
4020          $DB->insert_record($tablename, array('name'=>'Nodupor'));
4021          $DB->insert_record($tablename, array('name'=>'ouch'));
4022          $DB->insert_record($tablename, array('name'=>'ouc_'));
4023          $DB->insert_record($tablename, array('name'=>'ouc%'));
4024          $DB->insert_record($tablename, array('name'=>'aui'));
4025          $DB->insert_record($tablename, array('name'=>'aüi'));
4026          $DB->insert_record($tablename, array('name'=>'aÜi'));
4027  
4028          $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?', false);
4029          $records = $DB->get_records_sql($sql, array("%dup_r%"));
4030          $this->assertCount(2, $records);
4031  
4032          $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?', true);
4033          $records = $DB->get_records_sql($sql, array("%dup%"));
4034          $this->assertCount(1, $records);
4035  
4036          $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?'); // Defaults.
4037          $records = $DB->get_records_sql($sql, array("%dup%"));
4038          $this->assertCount(1, $records);
4039  
4040          $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?', true);
4041          $records = $DB->get_records_sql($sql, array("ouc\\_"));
4042          $this->assertCount(1, $records);
4043  
4044          $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?', true, true, false, '|');
4045          $records = $DB->get_records_sql($sql, array($DB->sql_like_escape("ouc%", '|')));
4046          $this->assertCount(1, $records);
4047  
4048          $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?', true, true);
4049          $records = $DB->get_records_sql($sql, array('aui'));
4050          $this->assertCount(1, $records);
4051  
4052          // Test LIKE under unusual collations.
4053          $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?', false, false);
4054          $records = $DB->get_records_sql($sql, array("%dup_r%"));
4055          $this->assertCount(2, $records);
4056  
4057          $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?', true, true, true); // NOT LIKE.
4058          $records = $DB->get_records_sql($sql, array("%o%"));
4059          $this->assertCount(3, $records);
4060  
4061          $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?', false, true, true); // NOT ILIKE.
4062          $records = $DB->get_records_sql($sql, array("%D%"));
4063          $this->assertCount(6, $records);
4064  
4065          // Verify usual escaping characters work fine.
4066          $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?', true, true, false, '\\');
4067          $records = $DB->get_records_sql($sql, array("ouc\\_"));
4068          $this->assertCount(1, $records);
4069          $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?', true, true, false, '|');
4070          $records = $DB->get_records_sql($sql, array("ouc|%"));
4071          $this->assertCount(1, $records);
4072  
4073          // TODO: we do not require accent insensitivness yet, just make sure it does not throw errors.
4074          $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?', true, false);
4075          $records = $DB->get_records_sql($sql, array('aui'));
4076          // $this->assertEquals(2, count($records), 'Accent insensitive LIKE searches may not be supported in all databases, this is not a problem.');
4077          $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?', false, false);
4078          $records = $DB->get_records_sql($sql, array('aui'));
4079          // $this->assertEquals(3, count($records), 'Accent insensitive LIKE searches may not be supported in all databases, this is not a problem.');
4080      }
4081  
4082      /**
4083       * Test DML libraries sql_like_escape method
4084       */
4085      public function test_sql_like_escape(): void {
4086          $DB = $this->tdb;
4087          $dbman = $DB->get_manager();
4088  
4089          $table = $this->get_test_table();
4090          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
4091          $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null);
4092          $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
4093          $dbman->create_table($table);
4094  
4095          $tablename = $table->getName();
4096  
4097          // Two of the records contain LIKE characters (%_), plus square brackets supported only by SQL Server (and '^-' which
4098          // should be ignored by SQL Server given they only have meaning inside square brackets).
4099          $DB->insert_record($tablename, (object) ['name' => 'lionel']);
4100          $DB->insert_record($tablename, (object) ['name' => 'lionel%_^-[0]']);
4101          $DB->insert_record($tablename, (object) ['name' => 'rick']);
4102          $DB->insert_record($tablename, (object) ['name' => 'rick%_^-[0]']);
4103  
4104          $select = $DB->sql_like('name', ':namelike');
4105          $params = ['namelike' => '%' . $DB->sql_like_escape('%_^-[0]')];
4106  
4107          // All drivers should return our two records containing wildcard characters.
4108          $this->assertEqualsCanonicalizing([
4109              'lionel%_^-[0]',
4110              'rick%_^-[0]',
4111          ], $DB->get_fieldset_select($tablename, 'name', $select, $params));
4112  
4113          // Test for unbalanced brackets.
4114          $select = $DB->sql_like('name', ':namelike');
4115          $params = ['namelike' => '%' . $DB->sql_like_escape('[') . '%'];
4116  
4117          $this->assertEqualsCanonicalizing([
4118              'lionel%_^-[0]',
4119              'rick%_^-[0]',
4120          ], $DB->get_fieldset_select($tablename, 'name', $select, $params));
4121      }
4122  
4123      public function test_coalesce() {
4124          $DB = $this->tdb;
4125  
4126          // Testing not-null occurrences, return 1st.
4127          $sql = "SELECT COALESCE('returnthis', 'orthis', 'orwhynotthis') AS test" . $DB->sql_null_from_clause();
4128          $this->assertSame('returnthis', $DB->get_field_sql($sql, array()));
4129          $sql = "SELECT COALESCE(:paramvalue, 'orthis', 'orwhynotthis') AS test" . $DB->sql_null_from_clause();
4130          $this->assertSame('returnthis', $DB->get_field_sql($sql, array('paramvalue' => 'returnthis')));
4131  
4132          // Testing null occurrences, return 2nd.
4133          $sql = "SELECT COALESCE(null, 'returnthis', 'orthis') AS test" . $DB->sql_null_from_clause();
4134          $this->assertSame('returnthis', $DB->get_field_sql($sql, array()));
4135          $sql = "SELECT COALESCE(:paramvalue, 'returnthis', 'orthis') AS test" . $DB->sql_null_from_clause();
4136          $this->assertSame('returnthis', $DB->get_field_sql($sql, array('paramvalue' => null)));
4137          $sql = "SELECT COALESCE(null, :paramvalue, 'orthis') AS test" . $DB->sql_null_from_clause();
4138          $this->assertSame('returnthis', $DB->get_field_sql($sql, array('paramvalue' => 'returnthis')));
4139  
4140          // Testing null occurrences, return 3rd.
4141          $sql = "SELECT COALESCE(null, null, 'returnthis') AS test" . $DB->sql_null_from_clause();
4142          $this->assertSame('returnthis', $DB->get_field_sql($sql, array()));
4143          $sql = "SELECT COALESCE(null, :paramvalue, 'returnthis') AS test" . $DB->sql_null_from_clause();
4144          $this->assertSame('returnthis', $DB->get_field_sql($sql, array('paramvalue' => null)));
4145          $sql = "SELECT COALESCE(null, null, :paramvalue) AS test" . $DB->sql_null_from_clause();
4146          $this->assertSame('returnthis', $DB->get_field_sql($sql, array('paramvalue' => 'returnthis')));
4147  
4148          // Testing all null occurrences, return null.
4149          // Note: under mssql, if all elements are nulls, at least one must be a "typed" null, hence
4150          // we cannot test this in a cross-db way easily, so next 2 tests are using
4151          // different queries depending of the DB family.
4152          $customnull = $DB->get_dbfamily() == 'mssql' ? 'CAST(null AS varchar)' : 'null';
4153          $sql = "SELECT COALESCE(null, null, " . $customnull . ") AS test" . $DB->sql_null_from_clause();
4154          $this->assertNull($DB->get_field_sql($sql, array()));
4155          $sql = "SELECT COALESCE(null, :paramvalue, " . $customnull . ") AS test" . $DB->sql_null_from_clause();
4156          $this->assertNull($DB->get_field_sql($sql, array('paramvalue' => null)));
4157  
4158          // Check there are not problems with whitespace strings.
4159          $sql = "SELECT COALESCE(null, :paramvalue, null) AS test" . $DB->sql_null_from_clause();
4160          $this->assertSame('', $DB->get_field_sql($sql, array('paramvalue' => '')));
4161      }
4162  
4163      public function test_sql_concat() {
4164          $DB = $this->tdb;
4165          $dbman = $DB->get_manager();
4166  
4167          // Testing all sort of values.
4168          $sql = "SELECT ".$DB->sql_concat("?", "?", "?")." AS fullname ". $DB->sql_null_from_clause();
4169          // String, some unicode chars.
4170          $params = array('name', 'áéíóú', 'name3');
4171          $this->assertSame('nameáéíóúname3', $DB->get_field_sql($sql, $params));
4172          // String, spaces and numbers.
4173          $params = array('name', '  ', 12345);
4174          $this->assertSame('name  12345', $DB->get_field_sql($sql, $params));
4175          // Float, empty and strings.
4176          $params = array(123.45, '', 'test');
4177          $this->assertSame('123.45test', $DB->get_field_sql($sql, $params));
4178          // Only integers.
4179          $params = array(12, 34, 56);
4180          $this->assertSame('123456', $DB->get_field_sql($sql, $params));
4181          // Float, null and strings.
4182          $params = array(123.45, null, 'test');
4183          $this->assertNull($DB->get_field_sql($sql, $params)); // Concatenate null with anything result = null.
4184  
4185          // Testing fieldnames + values and also integer fieldnames.
4186          $table = $this->get_test_table();
4187          $tablename = $table->getName();
4188  
4189          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
4190          $table->add_field('description', XMLDB_TYPE_TEXT, 'big', null, null, null, null);
4191          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
4192          $dbman->create_table($table);
4193  
4194          $DB->insert_record($tablename, array('description'=>'áéíóú'));
4195          $DB->insert_record($tablename, array('description'=>'dxxx'));
4196          $DB->insert_record($tablename, array('description'=>'bcde'));
4197  
4198          // Fieldnames and values mixed.
4199          $sql = 'SELECT id, ' . $DB->sql_concat('description', "'harcoded'", '?', '?') . ' AS result FROM {' . $tablename . '}';
4200          $records = $DB->get_records_sql($sql, array(123.45, 'test'));
4201          $this->assertCount(3, $records);
4202          $this->assertSame('áéíóúharcoded123.45test', $records[1]->result);
4203          // Integer fieldnames and values.
4204          $sql = 'SELECT id, ' . $DB->sql_concat('id', "'harcoded'", '?', '?') . ' AS result FROM {' . $tablename . '}';
4205          $records = $DB->get_records_sql($sql, array(123.45, 'test'));
4206          $this->assertCount(3, $records);
4207          $this->assertSame('1harcoded123.45test', $records[1]->result);
4208          // All integer fieldnames.
4209          $sql = 'SELECT id, ' . $DB->sql_concat('id', 'id', 'id') . ' AS result FROM {' . $tablename . '}';
4210          $records = $DB->get_records_sql($sql, array());
4211          $this->assertCount(3, $records);
4212          $this->assertSame('111', $records[1]->result);
4213  
4214      }
4215  
4216      public function sql_concat_join_provider() {
4217          return array(
4218              // All strings.
4219              array(
4220                  "' '",
4221                  array("'name'", "'name2'", "'name3'"),
4222                  array(),
4223                  'name name2 name3',
4224              ),
4225              // All strings using placeholders
4226              array(
4227                  "' '",
4228                  array("?", "?", "?"),
4229                  array('name', 'name2', 'name3'),
4230                  'name name2 name3',
4231              ),
4232              // All integers.
4233              array(
4234                  "' '",
4235                  array(1, 2, 3),
4236                  array(),
4237                  '1 2 3',
4238              ),
4239              // All integers using placeholders
4240              array(
4241                  "' '",
4242                  array("?", "?", "?"),
4243                  array(1, 2, 3),
4244                  '1 2 3',
4245              ),
4246              // Mix of strings and integers.
4247              array(
4248                  "' '",
4249                  array(1, "'2'", 3),
4250                  array(),
4251                  '1 2 3',
4252              ),
4253              // Mix of strings and integers using placeholders.
4254              array(
4255                  "' '",
4256                  array(1, '2', 3),
4257                  array(),
4258                  '1 2 3',
4259              ),
4260          );
4261      }
4262  
4263      /**
4264       * @dataProvider sql_concat_join_provider
4265       * @param string $concat The string to use when concatanating.
4266       * @param array $fields The fields to concatanate
4267       * @param array $params Any parameters to provide to the query
4268       * @param @string $expected The expected result
4269       */
4270      public function test_concat_join($concat, $fields, $params, $expected) {
4271          $DB = $this->tdb;
4272          $sql = "SELECT " . $DB->sql_concat_join($concat, $fields) . " AS result" . $DB->sql_null_from_clause();
4273          $result = $DB->get_field_sql($sql, $params);
4274          $this->assertEquals($expected, $result);
4275      }
4276  
4277      public function test_sql_fullname() {
4278          $DB = $this->tdb;
4279          $sql = "SELECT ".$DB->sql_fullname(':first', ':last')." AS fullname ".$DB->sql_null_from_clause();
4280          $params = array('first'=>'Firstname', 'last'=>'Surname');
4281          $this->assertEquals("Firstname Surname", $DB->get_field_sql($sql, $params));
4282      }
4283  
4284      public function test_sql_order_by_text() {
4285          $DB = $this->tdb;
4286          $dbman = $DB->get_manager();
4287  
4288          $table = $this->get_test_table();
4289          $tablename = $table->getName();
4290  
4291          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
4292          $table->add_field('description', XMLDB_TYPE_TEXT, 'big', null, null, null, null);
4293          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
4294          $dbman->create_table($table);
4295  
4296          $DB->insert_record($tablename, array('description'=>'abcd'));
4297          $DB->insert_record($tablename, array('description'=>'dxxx'));
4298          $DB->insert_record($tablename, array('description'=>'bcde'));
4299  
4300          $sql = "SELECT * FROM {{$tablename}} ORDER BY ".$DB->sql_order_by_text('description');
4301          $records = $DB->get_records_sql($sql);
4302          $first = array_shift($records);
4303          $this->assertEquals(1, $first->id);
4304          $second = array_shift($records);
4305          $this->assertEquals(3, $second->id);
4306          $last = array_shift($records);
4307          $this->assertEquals(2, $last->id);
4308      }
4309  
4310      public function test_sql_substring() {
4311          $DB = $this->tdb;
4312          $dbman = $DB->get_manager();
4313  
4314          $table = $this->get_test_table();
4315          $tablename = $table->getName();
4316  
4317          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
4318          $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null);
4319          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
4320          $dbman->create_table($table);
4321  
4322          $string = 'abcdefghij';
4323  
4324          $DB->insert_record($tablename, array('name'=>$string));
4325  
4326          $sql = "SELECT id, ".$DB->sql_substr("name", 5)." AS name FROM {{$tablename}}";
4327          $record = $DB->get_record_sql($sql);
4328          $this->assertEquals(substr($string, 5-1), $record->name);
4329  
4330          $sql = "SELECT id, ".$DB->sql_substr("name", 5, 2)." AS name FROM {{$tablename}}";
4331          $record = $DB->get_record_sql($sql);
4332          $this->assertEquals(substr($string, 5-1, 2), $record->name);
4333  
4334          try {
4335              // Silence php warning.
4336              @$DB->sql_substr("name");
4337              $this->fail("Expecting an exception, none occurred");
4338          } catch (moodle_exception $e) {
4339              $this->assertInstanceOf('coding_exception', $e);
4340          } catch (Error $error) {
4341              // PHP 7.1 throws Error even earlier.
4342              $this->assertRegExp('/Too few arguments to function/', $error->getMessage());
4343          }
4344  
4345          // Cover the function using placeholders in all positions.
4346          $start = 4;
4347          $length = 2;
4348          // 1st param (target).
4349          $sql = "SELECT id, ".$DB->sql_substr(":param1", $start)." AS name FROM {{$tablename}}";
4350          $record = $DB->get_record_sql($sql, array('param1' => $string));
4351          $this->assertEquals(substr($string, $start - 1), $record->name); // PHP's substr is 0-based.
4352          // 2nd param (start).
4353          $sql = "SELECT id, ".$DB->sql_substr("name", ":param1")." AS name FROM {{$tablename}}";
4354          $record = $DB->get_record_sql($sql, array('param1' => $start));
4355          $this->assertEquals(substr($string, $start - 1), $record->name); // PHP's substr is 0-based.
4356          // 3rd param (length).
4357          $sql = "SELECT id, ".$DB->sql_substr("name", $start, ":param1")." AS name FROM {{$tablename}}";
4358          $record = $DB->get_record_sql($sql, array('param1' => $length));
4359          $this->assertEquals(substr($string, $start - 1,  $length), $record->name); // PHP's substr is 0-based.
4360          // All together.
4361          $sql = "SELECT id, ".$DB->sql_substr(":param1", ":param2", ":param3")." AS name FROM {{$tablename}}";
4362          $record = $DB->get_record_sql($sql, array('param1' => $string, 'param2' => $start, 'param3' => $length));
4363          $this->assertEquals(substr($string, $start - 1,  $length), $record->name); // PHP's substr is 0-based.
4364  
4365          // Try also with some expression passed.
4366          $sql = "SELECT id, ".$DB->sql_substr("name", "(:param1 + 1) - 1")." AS name FROM {{$tablename}}";
4367          $record = $DB->get_record_sql($sql, array('param1' => $start));
4368          $this->assertEquals(substr($string, $start - 1), $record->name); // PHP's substr is 0-based.
4369      }
4370  
4371      public function test_sql_length() {
4372          $DB = $this->tdb;
4373          $this->assertEquals($DB->get_field_sql(
4374              "SELECT ".$DB->sql_length("'aeiou'").$DB->sql_null_from_clause()), 5);
4375          $this->assertEquals($DB->get_field_sql(
4376              "SELECT ".$DB->sql_length("'áéíóú'").$DB->sql_null_from_clause()), 5);
4377      }
4378  
4379      public function test_sql_position() {
4380          $DB = $this->tdb;
4381          $this->assertEquals($DB->get_field_sql(
4382              "SELECT ".$DB->sql_position("'ood'", "'Moodle'").$DB->sql_null_from_clause()), 2);
4383          $this->assertEquals($DB->get_field_sql(
4384              "SELECT ".$DB->sql_position("'Oracle'", "'Moodle'").$DB->sql_null_from_clause()), 0);
4385      }
4386  
4387      public function test_sql_empty() {
4388          $DB = $this->tdb;
4389          $dbman = $DB->get_manager();
4390  
4391          $table = $this->get_test_table();
4392          $tablename = $table->getName();
4393  
4394          $this->assertSame('', $DB->sql_empty()); // Since 2.5 the hack is applied automatically to all bound params.
4395          $this->assertDebuggingCalled();
4396  
4397          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
4398          $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null);
4399          $table->add_field('namenotnull', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, 'default value');
4400          $table->add_field('namenotnullnodeflt', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
4401          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
4402          $dbman->create_table($table);
4403  
4404          $DB->insert_record($tablename, array('name'=>'', 'namenotnull'=>''));
4405          $DB->insert_record($tablename, array('name'=>null));
4406          $DB->insert_record($tablename, array('name'=>'lalala'));
4407          $DB->insert_record($tablename, array('name'=>0));
4408  
4409          $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE name = ?", array(''));
4410          $this->assertCount(1, $records);
4411          $record = reset($records);
4412          $this->assertSame('', $record->name);
4413  
4414          $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE namenotnull = ?", array(''));
4415          $this->assertCount(1, $records);
4416          $record = reset($records);
4417          $this->assertSame('', $record->namenotnull);
4418  
4419          $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE namenotnullnodeflt = ?", array(''));
4420          $this->assertCount(4, $records);
4421          $record = reset($records);
4422          $this->assertSame('', $record->namenotnullnodeflt);
4423      }
4424  
4425      public function test_sql_isempty() {
4426          $DB = $this->tdb;
4427          $dbman = $DB->get_manager();
4428  
4429          $table = $this->get_test_table();
4430          $tablename = $table->getName();
4431  
4432          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
4433          $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
4434          $table->add_field('namenull', XMLDB_TYPE_CHAR, '255', null, null, null, null);
4435          $table->add_field('description', XMLDB_TYPE_TEXT, 'big', null, XMLDB_NOTNULL, null, null);
4436          $table->add_field('descriptionnull', XMLDB_TYPE_TEXT, 'big', null, null, null, null);
4437          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
4438          $dbman->create_table($table);
4439  
4440          $DB->insert_record($tablename, array('name'=>'',   'namenull'=>'',   'description'=>'',   'descriptionnull'=>''));
4441          $DB->insert_record($tablename, array('name'=>'??', 'namenull'=>null, 'description'=>'??', 'descriptionnull'=>null));
4442          $DB->insert_record($tablename, array('name'=>'la', 'namenull'=>'la', 'description'=>'la', 'descriptionnull'=>'lalala'));
4443          $DB->insert_record($tablename, array('name'=>0,    'namenull'=>0,    'description'=>0,    'descriptionnull'=>0));
4444  
4445          $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE ".$DB->sql_isempty($tablename, 'name', false, false));
4446          $this->assertCount(1, $records);
4447          $record = reset($records);
4448          $this->assertSame('', $record->name);
4449  
4450          $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE ".$DB->sql_isempty($tablename, 'namenull', true, false));
4451          $this->assertCount(1, $records);
4452          $record = reset($records);
4453          $this->assertSame('', $record->namenull);
4454  
4455          $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE ".$DB->sql_isempty($tablename, 'description', false, true));
4456          $this->assertCount(1, $records);
4457          $record = reset($records);
4458          $this->assertSame('', $record->description);
4459  
4460          $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE ".$DB->sql_isempty($tablename, 'descriptionnull', true, true));
4461          $this->assertCount(1, $records);
4462          $record = reset($records);
4463          $this->assertSame('', $record->descriptionnull);
4464      }
4465  
4466      public function test_sql_isnotempty() {
4467          $DB = $this->tdb;
4468          $dbman = $DB->get_manager();
4469  
4470          $table = $this->get_test_table();
4471          $tablename = $table->getName();
4472  
4473          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
4474          $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
4475          $table->add_field('namenull', XMLDB_TYPE_CHAR, '255', null, null, null, null);
4476          $table->add_field('description', XMLDB_TYPE_TEXT, 'big', null, XMLDB_NOTNULL, null, null);
4477          $table->add_field('descriptionnull', XMLDB_TYPE_TEXT, 'big', null, null, null, null);
4478          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
4479          $dbman->create_table($table);
4480  
4481          $DB->insert_record($tablename, array('name'=>'',   'namenull'=>'',   'description'=>'',   'descriptionnull'=>''));
4482          $DB->insert_record($tablename, array('name'=>'??', 'namenull'=>null, 'description'=>'??', 'descriptionnull'=>null));
4483          $DB->insert_record($tablename, array('name'=>'la', 'namenull'=>'la', 'description'=>'la', 'descriptionnull'=>'lalala'));
4484          $DB->insert_record($tablename, array('name'=>0,    'namenull'=>0,    'description'=>0,    'descriptionnull'=>0));
4485  
4486          $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE ".$DB->sql_isnotempty($tablename, 'name', false, false));
4487          $this->assertCount(3, $records);
4488          $record = reset($records);
4489          $this->assertSame('??', $record->name);
4490  
4491          $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE ".$DB->sql_isnotempty($tablename, 'namenull', true, false));
4492          $this->assertCount(2, $records); // Nulls aren't comparable (so they aren't "not empty"). SQL expected behaviour.
4493          $record = reset($records);
4494          $this->assertSame('la', $record->namenull); // So 'la' is the first non-empty 'namenull' record.
4495  
4496          $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE ".$DB->sql_isnotempty($tablename, 'description', false, true));
4497          $this->assertCount(3, $records);
4498          $record = reset($records);
4499          $this->assertSame('??', $record->description);
4500  
4501          $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE ".$DB->sql_isnotempty($tablename, 'descriptionnull', true, true));
4502          $this->assertCount(2, $records); // Nulls aren't comparable (so they aren't "not empty"). SQL expected behaviour.
4503          $record = reset($records);
4504          $this->assertSame('lalala', $record->descriptionnull); // So 'lalala' is the first non-empty 'descriptionnull' record.
4505      }
4506  
4507      public function test_sql_regex() {
4508          $DB = $this->tdb;
4509          $dbman = $DB->get_manager();
4510          if (!$DB->sql_regex_supported()) {
4511              $this->markTestSkipped($DB->get_name().' does not support regular expressions');
4512          }
4513  
4514          $table = $this->get_test_table();
4515          $tablename = $table->getName();
4516  
4517          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
4518          $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null);
4519          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
4520          $dbman->create_table($table);
4521  
4522          $DB->insert_record($tablename, array('name'=>'LALALA'));
4523          $DB->insert_record($tablename, array('name'=>'holaaa'));
4524          $DB->insert_record($tablename, array('name'=>'aouch'));
4525  
4526          // Regex /a$/i (case-insensitive).
4527          $sql = "SELECT * FROM {{$tablename}} WHERE name ".$DB->sql_regex()." ?";
4528          $params = array('a$');
4529          $records = $DB->get_records_sql($sql, $params);
4530          $this->assertCount(2, $records);
4531  
4532          // Regex ! (not) /.a/i (case insensitive).
4533          $sql = "SELECT * FROM {{$tablename}} WHERE name ".$DB->sql_regex(false)." ?";
4534          $params = array('.a');
4535          $records = $DB->get_records_sql($sql, $params);
4536          $this->assertCount(1, $records);
4537  
4538          // Regex /a$/ (case-sensitive).
4539          $sql = "SELECT * FROM {{$tablename}} WHERE name ".$DB->sql_regex(true, true)." ?";
4540          $params = array('a$');
4541          $records = $DB->get_records_sql($sql, $params);
4542          $this->assertCount(1, $records);
4543  
4544          // Regex ! (not) /.a/ (case sensitive).
4545          $sql = "SELECT * FROM {{$tablename}} WHERE name ".$DB->sql_regex(false, true)." ?";
4546          $params = array('.a');
4547          $records = $DB->get_records_sql($sql, $params);
4548          $this->assertCount(2, $records);
4549  
4550      }
4551  
4552      /**
4553       * Test some complicated variations of set_field_select.
4554       */
4555      public function test_set_field_select_complicated() {
4556          $DB = $this->tdb;
4557          $dbman = $DB->get_manager();
4558  
4559          $table = $this->get_test_table();
4560          $tablename = $table->getName();
4561  
4562          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
4563          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
4564          $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null);
4565          $table->add_field('content', XMLDB_TYPE_TEXT, 'big', null, XMLDB_NOTNULL);
4566          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
4567          $dbman->create_table($table);
4568  
4569          $DB->insert_record($tablename, array('course' => 3, 'content' => 'hello', 'name'=>'xyz'));
4570          $DB->insert_record($tablename, array('course' => 3, 'content' => 'world', 'name'=>'abc'));
4571          $DB->insert_record($tablename, array('course' => 5, 'content' => 'hello', 'name'=>'def'));
4572          $DB->insert_record($tablename, array('course' => 2, 'content' => 'universe', 'name'=>'abc'));
4573          // This SQL is a tricky case because we are selecting from the same table we are updating.
4574          $sql = 'id IN (SELECT outerq.id from (SELECT innerq.id from {' . $tablename . '} innerq WHERE course = 3) outerq)';
4575          $DB->set_field_select($tablename, 'name', 'ghi', $sql);
4576  
4577          $this->assertSame(2, $DB->count_records_select($tablename, 'name = ?', array('ghi')));
4578  
4579      }
4580  
4581      /**
4582       * Test some more complex SQL syntax which moodle uses and depends on to work
4583       * useful to determine if new database libraries can be supported.
4584       */
4585      public function test_get_records_sql_complicated() {
4586          $DB = $this->tdb;
4587          $dbman = $DB->get_manager();
4588  
4589          $table = $this->get_test_table();
4590          $tablename = $table->getName();
4591  
4592          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
4593          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
4594          $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null);
4595          $table->add_field('content', XMLDB_TYPE_TEXT, 'big', null, XMLDB_NOTNULL);
4596          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
4597          $dbman->create_table($table);
4598  
4599          $DB->insert_record($tablename, array('course' => 3, 'content' => 'hello', 'name'=>'xyz'));
4600          $DB->insert_record($tablename, array('course' => 3, 'content' => 'world', 'name'=>'abc'));
4601          $DB->insert_record($tablename, array('course' => 5, 'content' => 'hello', 'name'=>'def'));
4602          $DB->insert_record($tablename, array('course' => 2, 'content' => 'universe', 'name'=>'abc'));
4603  
4604          // Test grouping by expressions in the query. MDL-26819. Note that there are 4 ways:
4605          // - By column position (GROUP by 1) - Not supported by mssql & oracle
4606          // - By column name (GROUP by course) - Supported by all, but leading to wrong results
4607          // - By column alias (GROUP by casecol) - Not supported by mssql & oracle
4608          // - By complete expression (GROUP BY CASE ...) - 100% cross-db, this test checks it
4609          $sql = "SELECT (CASE WHEN course = 3 THEN 1 ELSE 0 END) AS casecol,
4610                         COUNT(1) AS countrecs,
4611                         MAX(name) AS maxname
4612                    FROM {{$tablename}}
4613                GROUP BY CASE WHEN course = 3 THEN 1 ELSE 0 END
4614                ORDER BY casecol DESC";
4615          $result = array(
4616              1 => (object)array('casecol' => 1, 'countrecs' => 2, 'maxname' => 'xyz'),
4617              0 => (object)array('casecol' => 0, 'countrecs' => 2, 'maxname' => 'def'));
4618          $records = $DB->get_records_sql($sql, null);
4619          $this->assertEquals($result, $records);
4620  
4621          // Another grouping by CASE expression just to ensure it works ok for multiple WHEN.
4622          $sql = "SELECT CASE name
4623                              WHEN 'xyz' THEN 'last'
4624                              WHEN 'def' THEN 'mid'
4625                              WHEN 'abc' THEN 'first'
4626                         END AS casecol,
4627                         COUNT(1) AS countrecs,
4628                         MAX(name) AS maxname
4629                    FROM {{$tablename}}
4630                GROUP BY CASE name
4631                             WHEN 'xyz' THEN 'last'
4632                             WHEN 'def' THEN 'mid'
4633                             WHEN 'abc' THEN 'first'
4634                         END
4635                ORDER BY casecol DESC";
4636          $result = array(
4637              'mid'  => (object)array('casecol' => 'mid', 'countrecs' => 1, 'maxname' => 'def'),
4638              'last' => (object)array('casecol' => 'last', 'countrecs' => 1, 'maxname' => 'xyz'),
4639              'first'=> (object)array('casecol' => 'first', 'countrecs' => 2, 'maxname' => 'abc'));
4640          $records = $DB->get_records_sql($sql, null);
4641          $this->assertEquals($result, $records);
4642  
4643          // Test CASE expressions in the ORDER BY clause - used by MDL-34657.
4644          $sql = "SELECT id, course, name
4645                    FROM {{$tablename}}
4646                ORDER BY CASE WHEN (course = 5 OR name  = 'xyz') THEN 0 ELSE 1 END, name, course";
4647          // First, records matching the course = 5 OR name = 'xyz', then the rest. Each.
4648          // group ordered by name and course.
4649          $result = array(
4650              3 => (object)array('id' => 3, 'course' => 5, 'name' => 'def'),
4651              1 => (object)array('id' => 1, 'course' => 3, 'name' => 'xyz'),
4652              4 => (object)array('id' => 4, 'course' => 2, 'name' => 'abc'),
4653              2 => (object)array('id' => 2, 'course' => 3, 'name' => 'abc'));
4654          $records = $DB->get_records_sql($sql, null);
4655          $this->assertEquals($result, $records);
4656          // Verify also array keys, order is important in this test.
4657          $this->assertEquals(array_keys($result), array_keys($records));
4658  
4659          // Test limits in queries with DISTINCT/ALL clauses and multiple whitespace. MDL-25268.
4660          $sql = "SELECT   DISTINCT   course
4661                    FROM {{$tablename}}
4662                   ORDER BY course";
4663          // Only limitfrom.
4664          $records = $DB->get_records_sql($sql, null, 1);
4665          $this->assertCount(2, $records);
4666          $this->assertEquals(3, reset($records)->course);
4667          $this->assertEquals(5, next($records)->course);
4668          // Only limitnum.
4669          $records = $DB->get_records_sql($sql, null, 0, 2);
4670          $this->assertCount(2, $records);
4671          $this->assertEquals(2, reset($records)->course);
4672          $this->assertEquals(3, next($records)->course);
4673          // Both limitfrom and limitnum.
4674          $records = $DB->get_records_sql($sql, null, 2, 2);
4675          $this->assertCount(1, $records);
4676          $this->assertEquals(5, reset($records)->course);
4677  
4678          // We have sql like this in moodle, this syntax breaks on older versions of sqlite for example..
4679          $sql = "SELECT a.id AS id, a.course AS course
4680                    FROM {{$tablename}} a
4681                    JOIN (SELECT * FROM {{$tablename}}) b ON a.id = b.id
4682                   WHERE a.course = ?";
4683  
4684          $records = $DB->get_records_sql($sql, array(3));
4685          $this->assertCount(2, $records);
4686          $this->assertEquals(1, reset($records)->id);
4687          $this->assertEquals(2, next($records)->id);
4688  
4689          // Do NOT try embedding sql_xxxx() helper functions in conditions array of count_records(), they don't break params/binding!
4690          $count = $DB->count_records_select($tablename, "course = :course AND ".$DB->sql_compare_text('content')." = :content", array('course' => 3, 'content' => 'hello'));
4691          $this->assertEquals(1, $count);
4692  
4693          // Test int x string comparison.
4694          $sql = "SELECT *
4695                    FROM {{$tablename}} c
4696                   WHERE name = ?";
4697          $this->assertCount(0, $DB->get_records_sql($sql, array(10)));
4698          $this->assertCount(0, $DB->get_records_sql($sql, array("10")));
4699          $DB->insert_record($tablename, array('course' => 7, 'content' => 'xx', 'name'=>'1'));
4700          $DB->insert_record($tablename, array('course' => 7, 'content' => 'yy', 'name'=>'2'));
4701          $this->assertCount(1, $DB->get_records_sql($sql, array(1)));
4702          $this->assertCount(1, $DB->get_records_sql($sql, array("1")));
4703          $this->assertCount(0, $DB->get_records_sql($sql, array(10)));
4704          $this->assertCount(0, $DB->get_records_sql($sql, array("10")));
4705          $DB->insert_record($tablename, array('course' => 7, 'content' => 'xx', 'name'=>'1abc'));
4706          $this->assertCount(1, $DB->get_records_sql($sql, array(1)));
4707          $this->assertCount(1, $DB->get_records_sql($sql, array("1")));
4708  
4709          // Test get_in_or_equal() with a big number of elements. Note that ideally
4710          // we should be detecting and warning about any use over, say, 200 elements
4711          // And recommend to change code to use subqueries and/or chunks instead.
4712          $currentcount = $DB->count_records($tablename);
4713          $numelements = 10000; // Verify that we can handle 10000 elements (crazy!)
4714          $values = range(1, $numelements);
4715  
4716          list($insql, $inparams) = $DB->get_in_or_equal($values, SQL_PARAMS_QM); // With QM params.
4717          $sql = "SELECT *
4718                    FROM {{$tablename}}
4719                   WHERE id $insql";
4720          $results = $DB->get_records_sql($sql, $inparams);
4721          $this->assertCount($currentcount, $results);
4722  
4723          list($insql, $inparams) = $DB->get_in_or_equal($values, SQL_PARAMS_NAMED); // With NAMED params.
4724          $sql = "SELECT *
4725                    FROM {{$tablename}}
4726                   WHERE id $insql";
4727          $results = $DB->get_records_sql($sql, $inparams);
4728          $this->assertCount($currentcount, $results);
4729      }
4730  
4731      public function test_replace_all_text() {
4732          $DB = $this->tdb;
4733          $dbman = $DB->get_manager();
4734  
4735          if (!$DB->replace_all_text_supported()) {
4736              $this->markTestSkipped($DB->get_name().' does not support replacing of texts');
4737          }
4738  
4739          $table = $this->get_test_table();
4740          $tablename = $table->getName();
4741  
4742          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
4743          $table->add_field('name', XMLDB_TYPE_CHAR, '20', null, null);
4744          $table->add_field('intro', XMLDB_TYPE_TEXT, 'big', null, null);
4745          // Add a CHAR field named using a word reserved for all the supported DB servers.
4746          $table->add_field('where', XMLDB_TYPE_CHAR, '20', null, null, null, 'localhost');
4747          // Add a TEXT field named using a word reserved for all the supported DB servers.
4748          $table->add_field('from', XMLDB_TYPE_TEXT, 'big', null, null);
4749          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
4750          $dbman->create_table($table);
4751  
4752          $fromfield = $dbman->generator->getEncQuoted('from');
4753          $DB->execute("INSERT INTO {".$tablename."} (name,intro,$fromfield) VALUES (NULL,NULL,'localhost')");
4754          $DB->execute("INSERT INTO {".$tablename."} (name,intro,$fromfield) VALUES ('','','localhost')");
4755          $DB->execute("INSERT INTO {".$tablename."} (name,intro,$fromfield) VALUES ('xxyy','vvzz','localhost')");
4756          $DB->execute("INSERT INTO {".$tablename."} (name,intro,$fromfield) VALUES ('aa bb aa bb','cc dd cc aa','localhost')");
4757          $DB->execute("INSERT INTO {".$tablename."} (name,intro,$fromfield) VALUES ('kkllll','kkllll','localhost')");
4758  
4759          $expected = $DB->get_records($tablename, array(), 'id ASC');
4760          $idx = 1;
4761          $id1 = $id2 = $id3 = $id4 = $id5 = 0;
4762          foreach (array_keys($expected) as $identifier) {
4763              ${"id$idx"} = (string)$identifier;
4764              $idx++;
4765          }
4766  
4767          $columns = $DB->get_columns($tablename);
4768  
4769          // Replace should work even with columns named using a reserved word.
4770          $this->assertEquals('C', $columns['where']->meta_type);
4771          $this->assertEquals('localhost', $expected[$id1]->where);
4772          $this->assertEquals('localhost', $expected[$id2]->where);
4773          $this->assertEquals('localhost', $expected[$id3]->where);
4774          $this->assertEquals('localhost', $expected[$id4]->where);
4775          $this->assertEquals('localhost', $expected[$id5]->where);
4776          $DB->replace_all_text($tablename, $columns['where'], 'localhost', '::1');
4777          $result = $DB->get_records($tablename, array(), 'id ASC');
4778          $expected[$id1]->where = '::1';
4779          $expected[$id2]->where = '::1';
4780          $expected[$id3]->where = '::1';
4781          $expected[$id4]->where = '::1';
4782          $expected[$id5]->where = '::1';
4783          $this->assertEquals($expected, $result);
4784          $this->assertEquals('X', $columns['from']->meta_type);
4785          $DB->replace_all_text($tablename, $columns['from'], 'localhost', '127.0.0.1');
4786          $result = $DB->get_records($tablename, array(), 'id ASC');
4787          $expected[$id1]->from = '127.0.0.1';
4788          $expected[$id2]->from = '127.0.0.1';
4789          $expected[$id3]->from = '127.0.0.1';
4790          $expected[$id4]->from = '127.0.0.1';
4791          $expected[$id5]->from = '127.0.0.1';
4792          $this->assertEquals($expected, $result);
4793  
4794          $DB->replace_all_text($tablename, $columns['name'], 'aa', 'o');
4795          $result = $DB->get_records($tablename, array(), 'id ASC');
4796          $expected[$id4]->name = 'o bb o bb';
4797          $this->assertEquals($expected, $result);
4798  
4799          $DB->replace_all_text($tablename, $columns['intro'], 'aa', 'o');
4800          $result = $DB->get_records($tablename, array(), 'id ASC');
4801          $expected[$id4]->intro = 'cc dd cc o';
4802          $this->assertEquals($expected, $result);
4803  
4804          $DB->replace_all_text($tablename, $columns['name'], '_', '*');
4805          $DB->replace_all_text($tablename, $columns['name'], '?', '*');
4806          $DB->replace_all_text($tablename, $columns['name'], '%', '*');
4807          $DB->replace_all_text($tablename, $columns['intro'], '_', '*');
4808          $DB->replace_all_text($tablename, $columns['intro'], '?', '*');
4809          $DB->replace_all_text($tablename, $columns['intro'], '%', '*');
4810          $result = $DB->get_records($tablename, array(), 'id ASC');
4811          $this->assertEquals($expected, $result);
4812  
4813          $long = '1234567890123456789';
4814          $DB->replace_all_text($tablename, $columns['name'], 'kk', $long);
4815          $result = $DB->get_records($tablename, array(), 'id ASC');
4816          $expected[$id5]->name = core_text::substr($long.'llll', 0, 20);
4817          $this->assertEquals($expected, $result);
4818  
4819          $DB->replace_all_text($tablename, $columns['intro'], 'kk', $long);
4820          $result = $DB->get_records($tablename, array(), 'id ASC');
4821          $expected[$id5]->intro = $long.'llll';
4822          $this->assertEquals($expected, $result);
4823      }
4824  
4825      public function test_onelevel_commit() {
4826          $DB = $this->tdb;
4827          $dbman = $DB->get_manager();
4828  
4829          $table = $this->get_test_table();
4830          $tablename = $table->getName();
4831  
4832          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
4833          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
4834          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
4835          $dbman->create_table($table);
4836  
4837          $transaction = $DB->start_delegated_transaction();
4838          $data = (object)array('course'=>3);
4839          $this->assertEquals(0, $DB->count_records($tablename));
4840          $DB->insert_record($tablename, $data);
4841          $this->assertEquals(1, $DB->count_records($tablename));
4842          $transaction->allow_commit();
4843          $this->assertEquals(1, $DB->count_records($tablename));
4844      }
4845  
4846      public function test_transaction_ignore_error_trouble() {
4847          $DB = $this->tdb;
4848          $dbman = $DB->get_manager();
4849  
4850          $table = $this->get_test_table();
4851          $tablename = $table->getName();
4852  
4853          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
4854          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
4855          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
4856          $table->add_index('course', XMLDB_INDEX_UNIQUE, array('course'));
4857          $dbman->create_table($table);
4858  
4859          // Test error on SQL_QUERY_INSERT.
4860          $transaction = $DB->start_delegated_transaction();
4861          $this->assertEquals(0, $DB->count_records($tablename));
4862          $DB->insert_record($tablename, (object)array('course'=>1));
4863          $this->assertEquals(1, $DB->count_records($tablename));
4864          try {
4865              $DB->insert_record($tablename, (object)array('course'=>1));
4866          } catch (Exception $e) {
4867              // This must be ignored and it must not roll back the whole transaction.
4868          }
4869          $DB->insert_record($tablename, (object)array('course'=>2));
4870          $this->assertEquals(2, $DB->count_records($tablename));
4871          $transaction->allow_commit();
4872          $this->assertEquals(2, $DB->count_records($tablename));
4873          $this->assertFalse($DB->is_transaction_started());
4874  
4875          // Test error on SQL_QUERY_SELECT.
4876          $DB->delete_records($tablename);
4877          $transaction = $DB->start_delegated_transaction();
4878          $this->assertEquals(0, $DB->count_records($tablename));
4879          $DB->insert_record($tablename, (object)array('course'=>1));
4880          $this->assertEquals(1, $DB->count_records($tablename));
4881          try {
4882              $DB->get_records_sql('s e l e c t');
4883          } catch (moodle_exception $e) {
4884              // This must be ignored and it must not roll back the whole transaction.
4885          }
4886          $DB->insert_record($tablename, (object)array('course'=>2));
4887          $this->assertEquals(2, $DB->count_records($tablename));
4888          $transaction->allow_commit();
4889          $this->assertEquals(2, $DB->count_records($tablename));
4890          $this->assertFalse($DB->is_transaction_started());
4891  
4892          // Test error on structure SQL_QUERY_UPDATE.
4893          $DB->delete_records($tablename);
4894          $transaction = $DB->start_delegated_transaction();
4895          $this->assertEquals(0, $DB->count_records($tablename));
4896          $DB->insert_record($tablename, (object)array('course'=>1));
4897          $this->assertEquals(1, $DB->count_records($tablename));
4898          try {
4899              $DB->execute('xxxx');
4900          } catch (moodle_exception $e) {
4901              // This must be ignored and it must not roll back the whole transaction.
4902          }
4903          $DB->insert_record($tablename, (object)array('course'=>2));
4904          $this->assertEquals(2, $DB->count_records($tablename));
4905          $transaction->allow_commit();
4906          $this->assertEquals(2, $DB->count_records($tablename));
4907          $this->assertFalse($DB->is_transaction_started());
4908  
4909          // Test error on structure SQL_QUERY_STRUCTURE.
4910          $DB->delete_records($tablename);
4911          $transaction = $DB->start_delegated_transaction();
4912          $this->assertEquals(0, $DB->count_records($tablename));
4913          $DB->insert_record($tablename, (object)array('course'=>1));
4914          $this->assertEquals(1, $DB->count_records($tablename));
4915          try {
4916              $DB->change_database_structure('xxxx');
4917          } catch (moodle_exception $e) {
4918              // This must be ignored and it must not roll back the whole transaction.
4919          }
4920          $DB->insert_record($tablename, (object)array('course'=>2));
4921          $this->assertEquals(2, $DB->count_records($tablename));
4922          $transaction->allow_commit();
4923          $this->assertEquals(2, $DB->count_records($tablename));
4924          $this->assertFalse($DB->is_transaction_started());
4925  
4926          // NOTE: SQL_QUERY_STRUCTURE is intentionally not tested here because it should never fail.
4927      }
4928  
4929      public function test_onelevel_rollback() {
4930          $DB = $this->tdb;
4931          $dbman = $DB->get_manager();
4932  
4933          $table = $this->get_test_table();
4934          $tablename = $table->getName();
4935  
4936          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
4937          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
4938          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
4939          $dbman->create_table($table);
4940  
4941          // This might in fact encourage ppl to migrate from myisam to innodb.
4942  
4943          $transaction = $DB->start_delegated_transaction();
4944          $data = (object)array('course'=>3);
4945          $this->assertEquals(0, $DB->count_records($tablename));
4946          $DB->insert_record($tablename, $data);
4947          $this->assertEquals(1, $DB->count_records($tablename));
4948          try {
4949              $transaction->rollback(new Exception('test'));
4950              $this->fail('transaction rollback must rethrow exception');
4951          } catch (Exception $e) {
4952              // Ignored.
4953          }
4954          $this->assertEquals(0, $DB->count_records($tablename));
4955      }
4956  
4957      public function test_nested_transactions() {
4958          $DB = $this->tdb;
4959          $dbman = $DB->get_manager();
4960  
4961          $table = $this->get_test_table();
4962          $tablename = $table->getName();
4963  
4964          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
4965          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
4966          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
4967          $dbman->create_table($table);
4968  
4969          // Two level commit.
4970          $this->assertFalse($DB->is_transaction_started());
4971          $transaction1 = $DB->start_delegated_transaction();
4972          $this->assertTrue($DB->is_transaction_started());
4973          $data = (object)array('course'=>3);
4974          $DB->insert_record($tablename, $data);
4975          $transaction2 = $DB->start_delegated_transaction();
4976          $data = (object)array('course'=>4);
4977          $DB->insert_record($tablename, $data);
4978          $transaction2->allow_commit();
4979          $this->assertTrue($DB->is_transaction_started());
4980          $transaction1->allow_commit();
4981          $this->assertFalse($DB->is_transaction_started());
4982          $this->assertEquals(2, $DB->count_records($tablename));
4983  
4984          $DB->delete_records($tablename);
4985  
4986          // Rollback from top level.
4987          $transaction1 = $DB->start_delegated_transaction();
4988          $data = (object)array('course'=>3);
4989          $DB->insert_record($tablename, $data);
4990          $transaction2 = $DB->start_delegated_transaction();
4991          $data = (object)array('course'=>4);
4992          $DB->insert_record($tablename, $data);
4993          $transaction2->allow_commit();
4994          try {
4995              $transaction1->rollback(new Exception('test'));
4996              $this->fail('transaction rollback must rethrow exception');
4997          } catch (Exception $e) {
4998              $this->assertEquals(get_class($e), 'Exception');
4999          }
5000          $this->assertEquals(0, $DB->count_records($tablename));
5001  
5002          $DB->delete_records($tablename);
5003  
5004          // Rollback from nested level.
5005          $transaction1 = $DB->start_delegated_transaction();
5006          $data = (object)array('course'=>3);
5007          $DB->insert_record($tablename, $data);
5008          $transaction2 = $DB->start_delegated_transaction();
5009          $data = (object)array('course'=>4);
5010          $DB->insert_record($tablename, $data);
5011          try {
5012              $transaction2->rollback(new Exception('test'));
5013              $this->fail('transaction rollback must rethrow exception');
5014          } catch (Exception $e) {
5015              $this->assertEquals(get_class($e), 'Exception');
5016          }
5017          $this->assertEquals(2, $DB->count_records($tablename)); // Not rolled back yet.
5018          try {
5019              $transaction1->allow_commit();
5020          } catch (moodle_exception $e) {
5021              $this->assertInstanceOf('dml_transaction_exception', $e);
5022          }
5023          $this->assertEquals(2, $DB->count_records($tablename)); // Not rolled back yet.
5024          // The forced rollback is done from the default_exception handler and similar places,
5025          // let's do it manually here.
5026          $this->assertTrue($DB->is_transaction_started());
5027          $DB->force_transaction_rollback();
5028          $this->assertFalse($DB->is_transaction_started());
5029          $this->assertEquals(0, $DB->count_records($tablename)); // Finally rolled back.
5030  
5031          $DB->delete_records($tablename);
5032  
5033          // Test interactions of recordset and transactions - this causes problems in SQL Server.
5034          $table2 = $this->get_test_table('2');
5035          $tablename2 = $table2->getName();
5036  
5037          $table2->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
5038          $table2->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
5039          $table2->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
5040          $dbman->create_table($table2);
5041  
5042          $DB->insert_record($tablename, array('course'=>1));
5043          $DB->insert_record($tablename, array('course'=>2));
5044          $DB->insert_record($tablename, array('course'=>3));
5045  
5046          $DB->insert_record($tablename2, array('course'=>5));
5047          $DB->insert_record($tablename2, array('course'=>6));
5048          $DB->insert_record($tablename2, array('course'=>7));
5049          $DB->insert_record($tablename2, array('course'=>8));
5050  
5051          $rs1 = $DB->get_recordset($tablename);
5052          $i = 0;
5053          foreach ($rs1 as $record1) {
5054              $i++;
5055              $rs2 = $DB->get_recordset($tablename2);
5056              $j = 0;
5057              foreach ($rs2 as $record2) {
5058                  $t = $DB->start_delegated_transaction();
5059                  $DB->set_field($tablename, 'course', $record1->course+1, array('id'=>$record1->id));
5060                  $DB->set_field($tablename2, 'course', $record2->course+1, array('id'=>$record2->id));
5061                  $t->allow_commit();
5062                  $j++;
5063              }
5064              $rs2->close();
5065              $this->assertEquals(4, $j);
5066          }
5067          $rs1->close();
5068          $this->assertEquals(3, $i);
5069  
5070          // Test nested recordsets isolation without transaction.
5071          $DB->delete_records($tablename);
5072          $DB->insert_record($tablename, array('course'=>1));
5073          $DB->insert_record($tablename, array('course'=>2));
5074          $DB->insert_record($tablename, array('course'=>3));
5075  
5076          $DB->delete_records($tablename2);
5077          $DB->insert_record($tablename2, array('course'=>5));
5078          $DB->insert_record($tablename2, array('course'=>6));
5079          $DB->insert_record($tablename2, array('course'=>7));
5080          $DB->insert_record($tablename2, array('course'=>8));
5081  
5082          $rs1 = $DB->get_recordset($tablename);
5083          $i = 0;
5084          foreach ($rs1 as $record1) {
5085              $i++;
5086              $rs2 = $DB->get_recordset($tablename2);
5087              $j = 0;
5088              foreach ($rs2 as $record2) {
5089                  $DB->set_field($tablename, 'course', $record1->course+1, array('id'=>$record1->id));
5090                  $DB->set_field($tablename2, 'course', $record2->course+1, array('id'=>$record2->id));
5091                  $j++;
5092              }
5093              $rs2->close();
5094              $this->assertEquals(4, $j);
5095          }
5096          $rs1->close();
5097          $this->assertEquals(3, $i);
5098      }
5099  
5100      public function test_transactions_forbidden() {
5101          $DB = $this->tdb;
5102          $dbman = $DB->get_manager();
5103  
5104          $table = $this->get_test_table();
5105          $tablename = $table->getName();
5106  
5107          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
5108          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
5109          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
5110          $dbman->create_table($table);
5111  
5112          $DB->transactions_forbidden();
5113          $transaction = $DB->start_delegated_transaction();
5114          $data = (object)array('course'=>1);
5115          $DB->insert_record($tablename, $data);
5116          try {
5117              $DB->transactions_forbidden();
5118          } catch (moodle_exception $e) {
5119              $this->assertInstanceOf('dml_transaction_exception', $e);
5120          }
5121          // The previous test does not force rollback.
5122          $transaction->allow_commit();
5123          $this->assertFalse($DB->is_transaction_started());
5124          $this->assertEquals(1, $DB->count_records($tablename));
5125      }
5126  
5127      public function test_wrong_transactions() {
5128          $DB = $this->tdb;
5129          $dbman = $DB->get_manager();
5130  
5131          $table = $this->get_test_table();
5132          $tablename = $table->getName();
5133  
5134          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
5135          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
5136          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
5137          $dbman->create_table($table);
5138  
5139          // Wrong order of nested commits.
5140          $transaction1 = $DB->start_delegated_transaction();
5141          $data = (object)array('course'=>3);
5142          $DB->insert_record($tablename, $data);
5143          $transaction2 = $DB->start_delegated_transaction();
5144          $data = (object)array('course'=>4);
5145          $DB->insert_record($tablename, $data);
5146          try {
5147              $transaction1->allow_commit();
5148              $this->fail('wrong order of commits must throw exception');
5149          } catch (moodle_exception $e) {
5150              $this->assertInstanceOf('dml_transaction_exception', $e);
5151          }
5152          try {
5153              $transaction2->allow_commit();
5154              $this->fail('first wrong commit forces rollback');
5155          } catch (moodle_exception $e) {
5156              $this->assertInstanceOf('dml_transaction_exception', $e);
5157          }
5158          // This is done in default exception handler usually.
5159          $this->assertTrue($DB->is_transaction_started());
5160          $this->assertEquals(2, $DB->count_records($tablename)); // Not rolled back yet.
5161          $DB->force_transaction_rollback();
5162          $this->assertEquals(0, $DB->count_records($tablename));
5163          $DB->delete_records($tablename);
5164  
5165          // Wrong order of nested rollbacks.
5166          $transaction1 = $DB->start_delegated_transaction();
5167          $data = (object)array('course'=>3);
5168          $DB->insert_record($tablename, $data);
5169          $transaction2 = $DB->start_delegated_transaction();
5170          $data = (object)array('course'=>4);
5171          $DB->insert_record($tablename, $data);
5172          try {
5173              // This first rollback should prevent all other rollbacks.
5174              $transaction1->rollback(new Exception('test'));
5175          } catch (Exception $e) {
5176              $this->assertEquals(get_class($e), 'Exception');
5177          }
5178          try {
5179              $transaction2->rollback(new Exception('test'));
5180          } catch (Exception $e) {
5181              $this->assertEquals(get_class($e), 'Exception');
5182          }
5183          try {
5184              $transaction1->rollback(new Exception('test'));
5185          } catch (moodle_exception $e) {
5186              $this->assertInstanceOf('dml_transaction_exception', $e);
5187          }
5188          // This is done in default exception handler usually.
5189          $this->assertTrue($DB->is_transaction_started());
5190          $DB->force_transaction_rollback();
5191          $DB->delete_records($tablename);
5192  
5193          // Unknown transaction object.
5194          $transaction1 = $DB->start_delegated_transaction();
5195          $data = (object)array('course'=>3);
5196          $DB->insert_record($tablename, $data);
5197          $transaction2 = new moodle_transaction($DB);
5198          try {
5199              $transaction2->allow_commit();
5200              $this->fail('foreign transaction must fail');
5201          } catch (moodle_exception $e) {
5202              $this->assertInstanceOf('dml_transaction_exception', $e);
5203          }
5204          try {
5205              $transaction1->allow_commit();
5206              $this->fail('first wrong commit forces rollback');
5207          } catch (moodle_exception $e) {
5208              $this->assertInstanceOf('dml_transaction_exception', $e);
5209          }
5210          $DB->force_transaction_rollback();
5211          $DB->delete_records($tablename);
5212      }
5213  
5214      public function test_concurent_transactions() {
5215          // Notes about this test:
5216          // 1- MySQL needs to use one engine with transactions support (InnoDB).
5217          // 2- MSSQL needs to have enabled versioning for read committed
5218          //    transactions (ALTER DATABASE xxx SET READ_COMMITTED_SNAPSHOT ON)
5219          $DB = $this->tdb;
5220          $dbman = $DB->get_manager();
5221  
5222          $table = $this->get_test_table();
5223          $tablename = $table->getName();
5224  
5225          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
5226          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
5227          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
5228          $dbman->create_table($table);
5229  
5230          $transaction = $DB->start_delegated_transaction();
5231          $data = (object)array('course'=>1);
5232          $this->assertEquals(0, $DB->count_records($tablename));
5233          $DB->insert_record($tablename, $data);
5234          $this->assertEquals(1, $DB->count_records($tablename));
5235  
5236          // Open second connection.
5237          $cfg = $DB->export_dbconfig();
5238          if (!isset($cfg->dboptions)) {
5239              $cfg->dboptions = array();
5240          }
5241          // If we have a readonly slave situation, we need to either observe
5242          // the latency, or if the latency is not specified we need to take
5243          // the slave out because the table may not have propagated yet.
5244          if (isset($cfg->dboptions['readonly'])) {
5245              if (isset($cfg->dboptions['readonly']['latency'])) {
5246                  usleep(intval(1000000 * $cfg->dboptions['readonly']['latency']));
5247              } else {
5248                  unset($cfg->dboptions['readonly']);
5249              }
5250          }
5251          $DB2 = moodle_database::get_driver_instance($cfg->dbtype, $cfg->dblibrary);
5252          $DB2->connect($cfg->dbhost, $cfg->dbuser, $cfg->dbpass, $cfg->dbname, $cfg->prefix, $cfg->dboptions);
5253  
5254          // Second instance should not see pending inserts.
5255          $this->assertEquals(0, $DB2->count_records($tablename));
5256          $data = (object)array('course'=>2);
5257          $DB2->insert_record($tablename, $data);
5258          $this->assertEquals(1, $DB2->count_records($tablename));
5259  
5260          // First should see the changes done from second.
5261          $this->assertEquals(2, $DB->count_records($tablename));
5262  
5263          // Now commit and we should see it finally in second connections.
5264          $transaction->allow_commit();
5265          $this->assertEquals(2, $DB2->count_records($tablename));
5266  
5267          // Let's try delete all is also working on (this checks MDL-29198).
5268          // Initially both connections see all the records in the table (2).
5269          $this->assertEquals(2, $DB->count_records($tablename));
5270          $this->assertEquals(2, $DB2->count_records($tablename));
5271          $transaction = $DB->start_delegated_transaction();
5272  
5273          // Delete all from within transaction.
5274          $DB->delete_records($tablename);
5275  
5276          // Transactional $DB, sees 0 records now.
5277          $this->assertEquals(0, $DB->count_records($tablename));
5278  
5279          // Others ($DB2) get no changes yet.
5280          $this->assertEquals(2, $DB2->count_records($tablename));
5281  
5282          // Now commit and we should see changes.
5283          $transaction->allow_commit();
5284          $this->assertEquals(0, $DB2->count_records($tablename));
5285  
5286          $DB2->dispose();
5287      }
5288  
5289      public function test_session_locks() {
5290          $DB = $this->tdb;
5291          $dbman = $DB->get_manager();
5292  
5293          // Open second connection.
5294          $cfg = $DB->export_dbconfig();
5295          if (!isset($cfg->dboptions)) {
5296              $cfg->dboptions = array();
5297          }
5298          $DB2 = moodle_database::get_driver_instance($cfg->dbtype, $cfg->dblibrary);
5299          $DB2->connect($cfg->dbhost, $cfg->dbuser, $cfg->dbpass, $cfg->dbname, $cfg->prefix, $cfg->dboptions);
5300  
5301          // Testing that acquiring a lock effectively locks.
5302          // Get a session lock on connection1.
5303          $rowid = rand(100, 200);
5304          $timeout = 1;
5305          $DB->get_session_lock($rowid, $timeout);
5306  
5307          // Try to get the same session lock on connection2.
5308          try {
5309              $DB2->get_session_lock($rowid, $timeout);
5310              $DB2->release_session_lock($rowid); // Should not be executed, but here for safety.
5311              $this->fail('An Exception is missing, expected due to session lock acquired.');
5312          } catch (moodle_exception $e) {
5313              $this->assertInstanceOf('dml_sessionwait_exception', $e);
5314              $DB->release_session_lock($rowid); // Release lock on connection1.
5315          }
5316  
5317          // Testing that releasing a lock effectively frees.
5318          // Get a session lock on connection1.
5319          $rowid = rand(100, 200);
5320          $timeout = 1;
5321          $DB->get_session_lock($rowid, $timeout);
5322          // Release the lock on connection1.
5323          $DB->release_session_lock($rowid);
5324  
5325          // Get the just released lock on connection2.
5326          $DB2->get_session_lock($rowid, $timeout);
5327          // Release the lock on connection2.
5328          $DB2->release_session_lock($rowid);
5329  
5330          $DB2->dispose();
5331      }
5332  
5333      public function test_bound_param_types() {
5334          $DB = $this->tdb;
5335          $dbman = $DB->get_manager();
5336  
5337          $table = $this->get_test_table();
5338          $tablename = $table->getName();
5339  
5340          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
5341          $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null);
5342          $table->add_field('content', XMLDB_TYPE_TEXT, 'big', null, XMLDB_NOTNULL);
5343          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
5344          $dbman->create_table($table);
5345  
5346          $this->assertNotEmpty($DB->insert_record($tablename, array('name' => '1', 'content'=>'xx')));
5347          $this->assertNotEmpty($DB->insert_record($tablename, array('name' => 2, 'content'=>'yy')));
5348          $this->assertNotEmpty($DB->insert_record($tablename, array('name' => 'somestring', 'content'=>'zz')));
5349          $this->assertNotEmpty($DB->insert_record($tablename, array('name' => 'aa', 'content'=>'1')));
5350          $this->assertNotEmpty($DB->insert_record($tablename, array('name' => 'bb', 'content'=>2)));
5351          $this->assertNotEmpty($DB->insert_record($tablename, array('name' => 'cc', 'content'=>'sometext')));
5352  
5353          // Conditions in CHAR columns.
5354          $this->assertTrue($DB->record_exists($tablename, array('name'=>1)));
5355          $this->assertTrue($DB->record_exists($tablename, array('name'=>'1')));
5356          $this->assertFalse($DB->record_exists($tablename, array('name'=>111)));
5357          $this->assertNotEmpty($DB->get_record($tablename, array('name'=>1)));
5358          $this->assertNotEmpty($DB->get_record($tablename, array('name'=>'1')));
5359          $this->assertEmpty($DB->get_record($tablename, array('name'=>111)));
5360          $sqlqm = "SELECT *
5361                      FROM {{$tablename}}
5362                     WHERE name = ?";
5363          $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm, array(1)));
5364          $this->assertCount(1, $records);
5365          $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm, array('1')));
5366          $this->assertCount(1, $records);
5367          $records = $DB->get_records_sql($sqlqm, array(222));
5368          $this->assertCount(0, $records);
5369          $sqlnamed = "SELECT *
5370                         FROM {{$tablename}}
5371                        WHERE name = :name";
5372          $this->assertNotEmpty($records = $DB->get_records_sql($sqlnamed, array('name' => 2)));
5373          $this->assertCount(1, $records);
5374          $this->assertNotEmpty($records = $DB->get_records_sql($sqlnamed, array('name' => '2')));
5375          $this->assertCount(1, $records);
5376  
5377          // Conditions in TEXT columns always must be performed with the sql_compare_text
5378          // helper function on both sides of the condition.
5379          $sqlqm = "SELECT *
5380                      FROM {{$tablename}}
5381                     WHERE " . $DB->sql_compare_text('content') . " =  " . $DB->sql_compare_text('?');
5382          $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm, array('1')));
5383          $this->assertCount(1, $records);
5384          $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm, array(1)));
5385          $this->assertCount(1, $records);
5386          $sqlnamed = "SELECT *
5387                         FROM {{$tablename}}
5388                        WHERE " . $DB->sql_compare_text('content') . " =  " . $DB->sql_compare_text(':content');
5389          $this->assertNotEmpty($records = $DB->get_records_sql($sqlnamed, array('content' => 2)));
5390          $this->assertCount(1, $records);
5391          $this->assertNotEmpty($records = $DB->get_records_sql($sqlnamed, array('content' => '2')));
5392          $this->assertCount(1, $records);
5393      }
5394  
5395      public function test_bound_param_reserved() {
5396          $DB = $this->tdb;
5397          $dbman = $DB->get_manager();
5398  
5399          $table = $this->get_test_table();
5400          $tablename = $table->getName();
5401  
5402          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
5403          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
5404          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
5405          $dbman->create_table($table);
5406  
5407          $DB->insert_record($tablename, array('course' => '1'));
5408  
5409          // Make sure reserved words do not cause fatal problems in query parameters.
5410  
5411          $DB->execute("UPDATE {{$tablename}} SET course = 1 WHERE id = :select", array('select'=>1));
5412          $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE course = :select", array('select'=>1));
5413          $rs = $DB->get_recordset_sql("SELECT * FROM {{$tablename}} WHERE course = :select", array('select'=>1));
5414          $rs->close();
5415          $DB->get_fieldset_sql("SELECT id FROM {{$tablename}} WHERE course = :select", array('select'=>1));
5416          $DB->set_field_select($tablename, 'course', '1', "id = :select", array('select'=>1));
5417          $DB->delete_records_select($tablename, "id = :select", array('select'=>1));
5418  
5419          // If we get here test passed ok.
5420          $this->assertTrue(true);
5421      }
5422  
5423      public function test_limits_and_offsets() {
5424          $DB = $this->tdb;
5425          $dbman = $DB->get_manager();
5426  
5427          $table = $this->get_test_table();
5428          $tablename = $table->getName();
5429  
5430          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
5431          $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null);
5432          $table->add_field('content', XMLDB_TYPE_TEXT, 'big', null, XMLDB_NOTNULL);
5433          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
5434          $dbman->create_table($table);
5435  
5436          $this->assertNotEmpty($DB->insert_record($tablename, array('name' => 'a', 'content'=>'one')));
5437          $this->assertNotEmpty($DB->insert_record($tablename, array('name' => 'b', 'content'=>'two')));
5438          $this->assertNotEmpty($DB->insert_record($tablename, array('name' => 'c', 'content'=>'three')));
5439          $this->assertNotEmpty($DB->insert_record($tablename, array('name' => 'd', 'content'=>'four')));
5440          $this->assertNotEmpty($DB->insert_record($tablename, array('name' => 'e', 'content'=>'five')));
5441          $this->assertNotEmpty($DB->insert_record($tablename, array('name' => 'f', 'content'=>'six')));
5442  
5443          $sqlqm = "SELECT *
5444                      FROM {{$tablename}}";
5445          $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm, null, 4));
5446          $this->assertCount(2, $records);
5447          $this->assertSame('e', reset($records)->name);
5448          $this->assertSame('f', end($records)->name);
5449  
5450          $sqlqm = "SELECT *
5451                      FROM {{$tablename}}";
5452          $this->assertEmpty($records = $DB->get_records_sql($sqlqm, null, 8));
5453  
5454          $sqlqm = "SELECT *
5455                      FROM {{$tablename}}";
5456          $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm, null, 0, 4));
5457          $this->assertCount(4, $records);
5458          $this->assertSame('a', reset($records)->name);
5459          $this->assertSame('d', end($records)->name);
5460  
5461          $sqlqm = "SELECT *
5462                      FROM {{$tablename}}";
5463          $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm, null, 0, 8));
5464          $this->assertCount(6, $records);
5465          $this->assertSame('a', reset($records)->name);
5466          $this->assertSame('f', end($records)->name);
5467  
5468          $sqlqm = "SELECT *
5469                      FROM {{$tablename}}";
5470          $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm, null, 1, 4));
5471          $this->assertCount(4, $records);
5472          $this->assertSame('b', reset($records)->name);
5473          $this->assertSame('e', end($records)->name);
5474  
5475          $sqlqm = "SELECT *
5476                      FROM {{$tablename}}";
5477          $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm, null, 4, 4));
5478          $this->assertCount(2, $records);
5479          $this->assertSame('e', reset($records)->name);
5480          $this->assertSame('f', end($records)->name);
5481  
5482          $sqlqm = "SELECT t.*, t.name AS test
5483                      FROM {{$tablename}} t
5484                      ORDER BY t.id ASC";
5485          $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm, null, 4, 4));
5486          $this->assertCount(2, $records);
5487          $this->assertSame('e', reset($records)->name);
5488          $this->assertSame('f', end($records)->name);
5489  
5490          $sqlqm = "SELECT DISTINCT t.name, t.name AS test
5491                      FROM {{$tablename}} t
5492                      ORDER BY t.name DESC";
5493          $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm, null, 4, 4));
5494          $this->assertCount(2, $records);
5495          $this->assertSame('b', reset($records)->name);
5496          $this->assertSame('a', end($records)->name);
5497  
5498          $sqlqm = "SELECT 1
5499                      FROM {{$tablename}} t
5500                      WHERE t.name = 'a'";
5501          $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm, null, 0, 1));
5502          $this->assertCount(1, $records);
5503  
5504          $sqlqm = "SELECT 'constant'
5505                      FROM {{$tablename}} t
5506                      WHERE t.name = 'a'";
5507          $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm, null, 0, 8));
5508          $this->assertCount(1, $records);
5509  
5510          $this->assertNotEmpty($DB->insert_record($tablename, array('name' => 'a', 'content'=>'one')));
5511          $this->assertNotEmpty($DB->insert_record($tablename, array('name' => 'b', 'content'=>'two')));
5512          $this->assertNotEmpty($DB->insert_record($tablename, array('name' => 'c', 'content'=>'three')));
5513  
5514          $sqlqm = "SELECT t.name, COUNT(DISTINCT t2.id) AS count, 'Test' AS teststring
5515                      FROM {{$tablename}} t
5516                      LEFT JOIN (
5517                          SELECT t.id, t.name
5518                          FROM {{$tablename}} t
5519                      ) t2 ON t2.name = t.name
5520                      GROUP BY t.name
5521                      ORDER BY t.name ASC";
5522          $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm));
5523          $this->assertCount(6, $records);         // a,b,c,d,e,f.
5524          $this->assertEquals(2, reset($records)->count);  // a has 2 records now.
5525          $this->assertEquals(1, end($records)->count);    // f has 1 record still.
5526  
5527          $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm, null, 0, 2));
5528          $this->assertCount(2, $records);
5529          $this->assertEquals(2, reset($records)->count);
5530          $this->assertEquals(2, end($records)->count);
5531      }
5532  
5533      /**
5534       * Test debugging messages about invalid limit number values.
5535       */
5536      public function test_invalid_limits_debugging() {
5537          $DB = $this->tdb;
5538          $dbman = $DB->get_manager();
5539  
5540          // Setup test data.
5541          $table = $this->get_test_table();
5542          $tablename = $table->getName();
5543          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
5544          $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
5545          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
5546          $dbman->create_table($table);
5547          $DB->insert_record($tablename, array('course' => '1'));
5548  
5549          // Verify that get_records_sql throws debug notices with invalid limit params.
5550          $DB->get_records_sql("SELECT * FROM {{$tablename}}", null, 'invalid');
5551          $this->assertDebuggingCalled("Non-numeric limitfrom parameter detected: 'invalid', did you pass the correct arguments?");
5552  
5553          $DB->get_records_sql("SELECT * FROM {{$tablename}}", null, 1, 'invalid');
5554          $this->assertDebuggingCalled("Non-numeric limitnum parameter detected: 'invalid', did you pass the correct arguments?");
5555  
5556          // Verify that get_recordset_sql throws debug notices with invalid limit params.
5557          $rs = $DB->get_recordset_sql("SELECT * FROM {{$tablename}}", null, 'invalid');
5558          $this->assertDebuggingCalled("Non-numeric limitfrom parameter detected: 'invalid', did you pass the correct arguments?");
5559          $rs->close();
5560  
5561          $rs = $DB->get_recordset_sql("SELECT * FROM {{$tablename}}", null, 1, 'invalid');
5562          $this->assertDebuggingCalled("Non-numeric limitnum parameter detected: 'invalid', did you pass the correct arguments?");
5563          $rs->close();
5564  
5565          // Verify that some edge cases do no create debugging messages.
5566          // String form of integer values.
5567          $DB->get_records_sql("SELECT * FROM {{$tablename}}", null, '1');
5568          $this->assertDebuggingNotCalled();
5569          $DB->get_records_sql("SELECT * FROM {{$tablename}}", null, 1, '2');
5570          $this->assertDebuggingNotCalled();
5571          // Empty strings.
5572          $DB->get_records_sql("SELECT * FROM {{$tablename}}", null, '');
5573          $this->assertDebuggingNotCalled();
5574          $DB->get_records_sql("SELECT * FROM {{$tablename}}", null, 1, '');
5575          $this->assertDebuggingNotCalled();
5576          // Null values.
5577          $DB->get_records_sql("SELECT * FROM {{$tablename}}", null, null);
5578          $this->assertDebuggingNotCalled();
5579          $DB->get_records_sql("SELECT * FROM {{$tablename}}", null, 1, null);
5580          $this->assertDebuggingNotCalled();
5581  
5582          // Verify that empty arrays DO create debugging mesages.
5583          $DB->get_records_sql("SELECT * FROM {{$tablename}}", null, array());
5584          $this->assertDebuggingCalled("Non-numeric limitfrom parameter detected: array (\n), did you pass the correct arguments?");
5585          $DB->get_records_sql("SELECT * FROM {{$tablename}}", null, 1, array());
5586          $this->assertDebuggingCalled("Non-numeric limitnum parameter detected: array (\n), did you pass the correct arguments?");
5587  
5588          // Verify Negative number handling:
5589          // -1 is explicitly treated as 0 for historical reasons.
5590          $DB->get_records_sql("SELECT * FROM {{$tablename}}", null, -1);
5591          $this->assertDebuggingNotCalled();
5592          $DB->get_records_sql("SELECT * FROM {{$tablename}}", null, 1, -1);
5593          $this->assertDebuggingNotCalled();
5594          // Any other negative values should throw debugging messages.
5595          $DB->get_records_sql("SELECT * FROM {{$tablename}}", null, -2);
5596          $this->assertDebuggingCalled("Negative limitfrom parameter detected: -2, did you pass the correct arguments?");
5597          $DB->get_records_sql("SELECT * FROM {{$tablename}}", null, 1, -2);
5598          $this->assertDebuggingCalled("Negative limitnum parameter detected: -2, did you pass the correct arguments?");
5599      }
5600  
5601      public function test_queries_counter() {
5602  
5603          $DB = $this->tdb;
5604          $dbman = $this->tdb->get_manager();
5605  
5606          // Test database.
5607          $table = $this->get_test_table();
5608          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
5609          $table->add_field('fieldvalue', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
5610          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
5611  
5612          $dbman->create_table($table);
5613          $tablename = $table->getName();
5614  
5615          // Initial counters values.
5616          $initreads = $DB->perf_get_reads();
5617          $initwrites = $DB->perf_get_writes();
5618          $previousqueriestime = $DB->perf_get_queries_time();
5619  
5620          // Selects counts as reads.
5621  
5622          // The get_records_sql() method generates only 1 db query.
5623          $whatever = $DB->get_records_sql("SELECT * FROM {{$tablename}}");
5624          $this->assertEquals($initreads + 1, $DB->perf_get_reads());
5625  
5626          // The get_records() method generates 2 queries the first time is called
5627          // as it is fetching the table structure.
5628          $whatever = $DB->get_records($tablename, array('id' => '1'));
5629          $this->assertEquals($initreads + 3, $DB->perf_get_reads());
5630          $this->assertEquals($initwrites, $DB->perf_get_writes());
5631  
5632          // The elapsed time is counted.
5633          $lastqueriestime = $DB->perf_get_queries_time();
5634          $this->assertGreaterThanOrEqual($previousqueriestime, $lastqueriestime);
5635          $previousqueriestime = $lastqueriestime;
5636  
5637          // Only 1 now, it already fetched the table columns.
5638          $whatever = $DB->get_records($tablename);
5639          $this->assertEquals($initreads + 4, $DB->perf_get_reads());
5640  
5641          // And only 1 more from now.
5642          $whatever = $DB->get_records($tablename);
5643          $this->assertEquals($initreads + 5, $DB->perf_get_reads());
5644  
5645          // Inserts counts as writes.
5646  
5647          $rec1 = new stdClass();
5648          $rec1->fieldvalue = 11;
5649          $rec1->id = $DB->insert_record($tablename, $rec1);
5650          $this->assertEquals($initwrites + 1, $DB->perf_get_writes());
5651          $this->assertEquals($initreads + 5, $DB->perf_get_reads());
5652  
5653          // The elapsed time is counted.
5654          $lastqueriestime = $DB->perf_get_queries_time();
5655          $this->assertGreaterThanOrEqual($previousqueriestime, $lastqueriestime);
5656          $previousqueriestime = $lastqueriestime;
5657  
5658          $rec2 = new stdClass();
5659          $rec2->fieldvalue = 22;
5660          $rec2->id = $DB->insert_record($tablename, $rec2);
5661          $this->assertEquals($initwrites + 2, $DB->perf_get_writes());
5662  
5663          // Updates counts as writes.
5664  
5665          $rec1->fieldvalue = 111;
5666          $DB->update_record($tablename, $rec1);
5667          $this->assertEquals($initwrites + 3, $DB->perf_get_writes());
5668          $this->assertEquals($initreads + 5, $DB->perf_get_reads());
5669  
5670          // The elapsed time is counted.
5671          $lastqueriestime = $DB->perf_get_queries_time();
5672          $this->assertGreaterThanOrEqual($previousqueriestime, $lastqueriestime);
5673          $previousqueriestime = $lastqueriestime;
5674  
5675          // Sum of them.
5676          $totaldbqueries = $DB->perf_get_reads() + $DB->perf_get_writes();
5677          $this->assertEquals($totaldbqueries, $DB->perf_get_queries());
5678      }
5679  
5680      public function test_sql_intersect() {
5681          $DB = $this->tdb;
5682          $dbman = $this->tdb->get_manager();
5683  
5684          $tables = array();
5685          for ($i = 0; $i < 3; $i++) {
5686              $table = $this->get_test_table('i'.$i);
5687              $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
5688              $table->add_field('ival', XMLDB_TYPE_INTEGER, '10', null, null, null, null);
5689              $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, '0');
5690              $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
5691              $dbman->create_table($table);
5692              $tables[$i] = $table;
5693          }
5694          $DB->insert_record($tables[0]->getName(), array('ival' => 1, 'name' => 'One'), false);
5695          $DB->insert_record($tables[0]->getName(), array('ival' => 2, 'name' => 'Two'), false);
5696          $DB->insert_record($tables[0]->getName(), array('ival' => 3, 'name' => 'Three'), false);
5697          $DB->insert_record($tables[0]->getName(), array('ival' => 4, 'name' => 'Four'), false);
5698  
5699          $DB->insert_record($tables[1]->getName(), array('ival' => 1, 'name' => 'One'), false);
5700          $DB->insert_record($tables[1]->getName(), array('ival' => 2, 'name' => 'Two'), false);
5701          $DB->insert_record($tables[1]->getName(), array('ival' => 3, 'name' => 'Three'), false);
5702  
5703          $DB->insert_record($tables[2]->getName(), array('ival' => 1, 'name' => 'One'), false);
5704          $DB->insert_record($tables[2]->getName(), array('ival' => 2, 'name' => 'Two'), false);
5705          $DB->insert_record($tables[2]->getName(), array('ival' => 5, 'name' => 'Five'), false);
5706  
5707          // Intersection on the int column.
5708          $params = array('excludename' => 'Two');
5709          $sql1 = 'SELECT ival FROM {'.$tables[0]->getName().'}';
5710          $sql2 = 'SELECT ival FROM {'.$tables[1]->getName().'} WHERE name <> :excludename';
5711          $sql3 = 'SELECT ival FROM {'.$tables[2]->getName().'}';
5712  
5713          $sql = $DB->sql_intersect(array($sql1), 'ival') . ' ORDER BY ival';
5714          $this->assertEquals(array(1, 2, 3, 4), $DB->get_fieldset_sql($sql, $params));
5715  
5716          $sql = $DB->sql_intersect(array($sql1, $sql2), 'ival') . ' ORDER BY ival';
5717          $this->assertEquals(array(1, 3), $DB->get_fieldset_sql($sql, $params));
5718  
5719          $sql = $DB->sql_intersect(array($sql1, $sql2, $sql3), 'ival') . ' ORDER BY ival';
5720          $this->assertEquals(array(1),
5721              $DB->get_fieldset_sql($sql, $params));
5722  
5723          // Intersection on the char column.
5724          $params = array('excludeival' => 2);
5725          $sql1 = 'SELECT name FROM {'.$tables[0]->getName().'}';
5726          $sql2 = 'SELECT name FROM {'.$tables[1]->getName().'} WHERE ival <> :excludeival';
5727          $sql3 = 'SELECT name FROM {'.$tables[2]->getName().'}';
5728  
5729          $sql = $DB->sql_intersect(array($sql1), 'name') . ' ORDER BY name';
5730          $this->assertEquals(array('Four', 'One', 'Three', 'Two'), $DB->get_fieldset_sql($sql, $params));
5731  
5732          $sql = $DB->sql_intersect(array($sql1, $sql2), 'name') . ' ORDER BY name';
5733          $this->assertEquals(array('One', 'Three'), $DB->get_fieldset_sql($sql, $params));
5734  
5735          $sql = $DB->sql_intersect(array($sql1, $sql2, $sql3), 'name') . ' ORDER BY name';
5736          $this->assertEquals(array('One'), $DB->get_fieldset_sql($sql, $params));
5737  
5738          // Intersection on the several columns.
5739          $params = array('excludename' => 'Two');
5740          $sql1 = 'SELECT ival, name FROM {'.$tables[0]->getName().'}';
5741          $sql2 = 'SELECT ival, name FROM {'.$tables[1]->getName().'} WHERE name <> :excludename';
5742          $sql3 = 'SELECT ival, name FROM {'.$tables[2]->getName().'}';
5743  
5744          $sql = $DB->sql_intersect(array($sql1), 'ival, name') . ' ORDER BY ival';
5745          $this->assertEquals(array(1 => 'One', 2 => 'Two', 3 => 'Three', 4 => 'Four'),
5746              $DB->get_records_sql_menu($sql, $params));
5747  
5748          $sql = $DB->sql_intersect(array($sql1, $sql2), 'ival, name') . ' ORDER BY ival';
5749          $this->assertEquals(array(1 => 'One', 3 => 'Three'),
5750              $DB->get_records_sql_menu($sql, $params));
5751  
5752          $sql = $DB->sql_intersect(array($sql1, $sql2, $sql3), 'ival, name') . ' ORDER BY ival';
5753          $this->assertEquals(array(1 => 'One'),
5754              $DB->get_records_sql_menu($sql, $params));
5755  
5756          // Drop temporary tables.
5757          foreach ($tables as $table) {
5758              $dbman->drop_table($table);
5759          }
5760      }
5761  
5762      /**
5763       * Test that the database has full utf8 support (4 bytes).
5764       */
5765      public function test_four_byte_character_insertion() {
5766          $DB = $this->tdb;
5767  
5768          if ($DB->get_dbfamily() === 'mysql' && strpos($DB->get_dbcollation(), 'utf8_') === 0) {
5769              $this->markTestSkipped($DB->get_name() .
5770                      ' does not support 4 byte characters with only a utf8 collation.
5771                      Please change to utf8mb4 for full utf8 support.');
5772          }
5773  
5774          $dbman = $this->tdb->get_manager();
5775  
5776          $table = $this->get_test_table();
5777          $tablename = $table->getName();
5778  
5779          $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
5780          $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null);
5781          $table->add_field('content', XMLDB_TYPE_TEXT, 'big', null, XMLDB_NOTNULL);
5782          $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
5783          $dbman->create_table($table);
5784  
5785          $data = array(
5786              'name' => 'Name with a four byte character 𠮟る',
5787              'content' => 'Content with a four byte emoji 📝 memo.'
5788          );
5789  
5790          $insertid = $DB->insert_record($tablename, $data);
5791          $result = $DB->get_record($tablename, array('id' => $insertid));
5792          $this->assertEquals($data['name'], $result->name);
5793          $this->assertEquals($data['content'], $result->content);
5794  
5795          $dbman->drop_table($table);
5796      }
5797  }
5798  
5799  /**
5800   * This class is not a proper subclass of moodle_database. It is
5801   * intended to be used only in unit tests, in order to gain access to the
5802   * protected methods of moodle_database, and unit test them.
5803   */
5804  class moodle_database_for_testing extends moodle_database {
5805      protected $prefix = 'mdl_';
5806  
5807      public function public_fix_table_names($sql) {
5808          return $this->fix_table_names($sql);
5809      }
5810  
5811      public function driver_installed() {}
5812      public function get_dbfamily() {}
5813      protected function get_dbtype() {}
5814      protected function get_dblibrary() {}
5815      public function get_name() {}
5816      public function get_configuration_help() {}
5817      public function connect($dbhost, $dbuser, $dbpass, $dbname, $prefix, array $dboptions=null) {}
5818      public function get_server_info() {}
5819      protected function allowed_param_types() {}
5820      public function get_last_error() {}
5821      public function get_tables($usecache=true) {}
5822      public function get_indexes($table) {}
5823      protected function fetch_columns(string $table): array {
5824          return [];
5825      }
5826      protected function normalise_value($column, $value) {}
5827      public function set_debug($state) {}
5828      public function get_debug() {}
5829      public function change_database_structure($sql, $tablenames = null) {}
5830      public function execute($sql, array $params=null) {}
5831      public function get_recordset_sql($sql, array $params=null, $limitfrom=0, $limitnum=0) {}
5832      public function get_records_sql($sql, array $params=null, $limitfrom=0, $limitnum=0) {}
5833      public function get_fieldset_sql($sql, array $params=null) {}
5834      public function insert_record_raw($table, $params, $returnid=true, $bulk=false, $customsequence=false) {}
5835      public function insert_record($table, $dataobject, $returnid=true, $bulk=false) {}
5836      public function import_record($table, $dataobject) {}
5837      public function update_record_raw($table, $params, $bulk=false) {}
5838      public function update_record($table, $dataobject, $bulk=false) {}
5839      public function set_field_select($table, $newfield, $newvalue, $select, array $params=null) {}
5840      public function delete_records_select($table, $select, array $params=null) {}
5841      public function sql_concat() {}
5842      public function sql_concat_join($separator="' '", $elements=array()) {}
5843      public function sql_substr($expr, $start, $length=false) {}
5844      public function begin_transaction() {}
5845      public function commit_transaction() {}
5846      public function rollback_transaction() {}
5847  }
5848  
5849  
5850  /**
5851   * Dumb test class with toString() returning 1.
5852   */
5853  class dml_test_object_one {
5854      public function __toString() {
5855          return 1;
5856      }
5857  }