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