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