[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/dml/tests/ -> dml_test.php (source)

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


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1