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