Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 401 and 402] [Versions 402 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 /** 3880 * Test DML libraries sql_cast_to_char method 3881 * 3882 * @covers ::sql_cast_to_char 3883 */ 3884 public function test_cast_to_char(): void { 3885 $DB = $this->tdb; 3886 $dbman = $DB->get_manager(); 3887 3888 $tableone = $this->get_test_table('one'); 3889 $tableone->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); 3890 $tableone->add_field('intfield', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); 3891 $tableone->add_field('details', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); 3892 $tableone->add_key('primary', XMLDB_KEY_PRIMARY, ['id']); 3893 $dbman->create_table($tableone); 3894 3895 $tableonename = $tableone->getName(); 3896 $DB->insert_record($tableonename, (object) ['intfield' => 10, 'details' => 'uno']); 3897 $DB->insert_record($tableonename, (object) ['intfield' => 20, 'details' => 'dos']); 3898 3899 $tabletwo = $this->get_test_table('two'); 3900 $tabletwo->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); 3901 $tabletwo->add_field('charfield', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); 3902 $tabletwo->add_key('primary', XMLDB_KEY_PRIMARY, ['id']); 3903 $dbman->create_table($tabletwo); 3904 3905 $tabletwoname = $tabletwo->getName(); 3906 $DB->insert_record($tabletwoname, (object) ['charfield' => '10']); 3907 3908 // Test by joining a char field to a cast int field (mixing types not supported across databases). 3909 $sql = "SELECT t1.details 3910 FROM {{$tableonename}} t1 3911 JOIN {{$tabletwoname}} t2 ON t2.charfield = " . $DB->sql_cast_to_char('t1.intfield'); 3912 3913 $fieldset = $DB->get_fieldset_sql($sql); 3914 $this->assertEquals(['uno'], $fieldset); 3915 } 3916 3917 public function test_cast_char2int() { 3918 $DB = $this->tdb; 3919 $dbman = $DB->get_manager(); 3920 3921 $table1 = $this->get_test_table("1"); 3922 $tablename1 = $table1->getName(); 3923 3924 $table1->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); 3925 $table1->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null); 3926 $table1->add_field('nametext', XMLDB_TYPE_TEXT, 'small', null, null, null, null); 3927 $table1->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); 3928 $dbman->create_table($table1); 3929 3930 $DB->insert_record($tablename1, array('name'=>'0100', 'nametext'=>'0200')); 3931 $DB->insert_record($tablename1, array('name'=>'10', 'nametext'=>'20')); 3932 3933 $table2 = $this->get_test_table("2"); 3934 $tablename2 = $table2->getName(); 3935 $table2->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); 3936 $table2->add_field('res', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); 3937 $table2->add_field('restext', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); 3938 $table2->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); 3939 $dbman->create_table($table2); 3940 3941 $DB->insert_record($tablename2, array('res'=>100, 'restext'=>200)); 3942 3943 // Casting varchar field. 3944 $sql = "SELECT * 3945 FROM {".$tablename1."} t1 3946 JOIN {".$tablename2."} t2 ON ".$DB->sql_cast_char2int("t1.name")." = t2.res "; 3947 $records = $DB->get_records_sql($sql); 3948 $this->assertCount(1, $records); 3949 // Also test them in order clauses. 3950 $sql = "SELECT * FROM {{$tablename1}} ORDER BY ".$DB->sql_cast_char2int('name'); 3951 $records = $DB->get_records_sql($sql); 3952 $this->assertCount(2, $records); 3953 $this->assertSame('10', reset($records)->name); 3954 $this->assertSame('0100', next($records)->name); 3955 3956 // Casting text field. 3957 $sql = "SELECT * 3958 FROM {".$tablename1."} t1 3959 JOIN {".$tablename2."} t2 ON ".$DB->sql_cast_char2int("t1.nametext", true)." = t2.restext "; 3960 $records = $DB->get_records_sql($sql); 3961 $this->assertCount(1, $records); 3962 // Also test them in order clauses. 3963 $sql = "SELECT * FROM {{$tablename1}} ORDER BY ".$DB->sql_cast_char2int('nametext', true); 3964 $records = $DB->get_records_sql($sql); 3965 $this->assertCount(2, $records); 3966 $this->assertSame('20', reset($records)->nametext); 3967 $this->assertSame('0200', next($records)->nametext); 3968 } 3969 3970 public function test_cast_char2real() { 3971 $DB = $this->tdb; 3972 $dbman = $DB->get_manager(); 3973 3974 $table = $this->get_test_table(); 3975 $tablename = $table->getName(); 3976 3977 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); 3978 $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null); 3979 $table->add_field('nametext', XMLDB_TYPE_TEXT, 'small', null, null, null, null); 3980 $table->add_field('res', XMLDB_TYPE_NUMBER, '12, 7', null, null, null, null); 3981 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); 3982 $dbman->create_table($table); 3983 3984 $DB->insert_record($tablename, array('name'=>'10.10', 'nametext'=>'10.10', 'res'=>5.1)); 3985 $DB->insert_record($tablename, array('name'=>'91.10', 'nametext'=>'91.10', 'res'=>666)); 3986 $DB->insert_record($tablename, array('name'=>'011.13333333', 'nametext'=>'011.13333333', 'res'=>10.1)); 3987 3988 // Casting varchar field. 3989 $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_cast_char2real('name')." > res"; 3990 $records = $DB->get_records_sql($sql); 3991 $this->assertCount(2, $records); 3992 // Also test them in order clauses. 3993 $sql = "SELECT * FROM {{$tablename}} ORDER BY ".$DB->sql_cast_char2real('name'); 3994 $records = $DB->get_records_sql($sql); 3995 $this->assertCount(3, $records); 3996 $this->assertSame('10.10', reset($records)->name); 3997 $this->assertSame('011.13333333', next($records)->name); 3998 $this->assertSame('91.10', next($records)->name); 3999 // And verify we can operate with them without too much problem with at least 6 decimals scale accuracy. 4000 $sql = "SELECT AVG(" . $DB->sql_cast_char2real('name') . ") FROM {{$tablename}}"; 4001 $this->assertEqualsWithDelta(37.44444443333333, (float)$DB->get_field_sql($sql), 1.0E-6); 4002 4003 // Casting text field. 4004 $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_cast_char2real('nametext', true)." > res"; 4005 $records = $DB->get_records_sql($sql); 4006 $this->assertCount(2, $records); 4007 // Also test them in order clauses. 4008 $sql = "SELECT * FROM {{$tablename}} ORDER BY ".$DB->sql_cast_char2real('nametext', true); 4009 $records = $DB->get_records_sql($sql); 4010 $this->assertCount(3, $records); 4011 $this->assertSame('10.10', reset($records)->nametext); 4012 $this->assertSame('011.13333333', next($records)->nametext); 4013 $this->assertSame('91.10', next($records)->nametext); 4014 // And verify we can operate with them without too much problem with at least 6 decimals scale accuracy. 4015 $sql = "SELECT AVG(" . $DB->sql_cast_char2real('nametext', true) . ") FROM {{$tablename}}"; 4016 $this->assertEqualsWithDelta(37.44444443333333, (float)$DB->get_field_sql($sql), 1.0E-6); 4017 4018 // Check it works with values passed as param. 4019 $sql = "SELECT name FROM {{$tablename}} WHERE FLOOR(res - " . $DB->sql_cast_char2real(':param') . ") = 0"; 4020 $this->assertEquals('011.13333333', $DB->get_field_sql($sql, array('param' => '10.09999'))); 4021 4022 // And also, although not recommended, with directly passed values. 4023 $sql = "SELECT name FROM {{$tablename}} WHERE FLOOR(res - " . $DB->sql_cast_char2real('10.09999') . ") = 0"; 4024 $this->assertEquals('011.13333333', $DB->get_field_sql($sql)); 4025 } 4026 4027 public function test_sql_compare_text() { 4028 $DB = $this->tdb; 4029 $dbman = $DB->get_manager(); 4030 4031 $table = $this->get_test_table(); 4032 $tablename = $table->getName(); 4033 4034 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); 4035 $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null); 4036 $table->add_field('description', XMLDB_TYPE_TEXT, 'big', null, null, null, null); 4037 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); 4038 $dbman->create_table($table); 4039 4040 $DB->insert_record($tablename, array('name'=>'abcd', 'description'=>'abcd')); 4041 $DB->insert_record($tablename, array('name'=>'abcdef', 'description'=>'bbcdef')); 4042 $DB->insert_record($tablename, array('name'=>'aaaa', 'description'=>'aaaacccccccccccccccccc')); 4043 $DB->insert_record($tablename, array('name'=>'xxxx', 'description'=>'123456789a123456789b123456789c123456789d')); 4044 4045 // Only some supported databases truncate TEXT fields for comparisons, currently MSSQL and Oracle. 4046 $dbtruncatestextfields = ($DB->get_dbfamily() == 'mssql' || $DB->get_dbfamily() == 'oracle'); 4047 4048 if ($dbtruncatestextfields) { 4049 // Ensure truncation behaves as expected. 4050 4051 $sql = "SELECT " . $DB->sql_compare_text('description') . " AS field FROM {{$tablename}} WHERE name = ?"; 4052 $description = $DB->get_field_sql($sql, array('xxxx')); 4053 4054 // Should truncate to 32 chars (the default). 4055 $this->assertEquals('123456789a123456789b123456789c12', $description); 4056 4057 $sql = "SELECT " . $DB->sql_compare_text('description', 35) . " AS field FROM {{$tablename}} WHERE name = ?"; 4058 $description = $DB->get_field_sql($sql, array('xxxx')); 4059 4060 // Should truncate to the specified number of chars. 4061 $this->assertEquals('123456789a123456789b123456789c12345', $description); 4062 } 4063 4064 // Ensure text field comparison is successful. 4065 $sql = "SELECT * FROM {{$tablename}} WHERE name = ".$DB->sql_compare_text('description'); 4066 $records = $DB->get_records_sql($sql); 4067 $this->assertCount(1, $records); 4068 4069 $sql = "SELECT * FROM {{$tablename}} WHERE name = ".$DB->sql_compare_text('description', 4); 4070 $records = $DB->get_records_sql($sql); 4071 if ($dbtruncatestextfields) { 4072 // Should truncate description to 4 characters before comparing. 4073 $this->assertCount(2, $records); 4074 } else { 4075 // Should leave untruncated, so one less match. 4076 $this->assertCount(1, $records); 4077 } 4078 4079 // Now test the function with really big content and params. 4080 $clob = file_get_contents(__DIR__ . '/fixtures/clob.txt'); 4081 $DB->insert_record($tablename, array('name' => 'zzzz', 'description' => $clob)); 4082 $sql = "SELECT * FROM {{$tablename}} 4083 WHERE " . $DB->sql_compare_text('description') . " = " . $DB->sql_compare_text(':clob'); 4084 $records = $DB->get_records_sql($sql, array('clob' => $clob)); 4085 $this->assertCount(1, $records); 4086 $record = reset($records); 4087 $this->assertSame($clob, $record->description); 4088 } 4089 4090 public function test_unique_index_collation_trouble() { 4091 // Note: this is a work in progress, we should probably move this to ddl test. 4092 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_key('primary', XMLDB_KEY_PRIMARY, array('id')); 4102 $table->add_index('name', XMLDB_INDEX_UNIQUE, array('name')); 4103 $dbman->create_table($table); 4104 4105 $DB->insert_record($tablename, array('name'=>'aaa')); 4106 4107 try { 4108 $DB->insert_record($tablename, array('name'=>'AAA')); 4109 } catch (\moodle_exception $e) { 4110 // TODO: ignore case insensitive uniqueness problems for now. 4111 // $this->fail("Unique index is case sensitive - this may cause problems in some tables"); 4112 } 4113 4114 try { 4115 $DB->insert_record($tablename, array('name'=>'aäa')); 4116 $DB->insert_record($tablename, array('name'=>'aáa')); 4117 $this->assertTrue(true); 4118 } catch (\moodle_exception $e) { 4119 $family = $DB->get_dbfamily(); 4120 if ($family === 'mysql' or $family === 'mssql') { 4121 $this->fail("Unique index is accent insensitive, this may cause problems for non-ascii languages. This is usually caused by accent insensitive default collation."); 4122 } else { 4123 // This should not happen, PostgreSQL and Oracle do not support accent insensitive uniqueness. 4124 $this->fail("Unique index is accent insensitive, this may cause problems for non-ascii languages."); 4125 } 4126 throw($e); 4127 } 4128 } 4129 4130 public function test_sql_equal() { 4131 $DB = $this->tdb; 4132 $dbman = $DB->get_manager(); 4133 4134 $table = $this->get_test_table(); 4135 $tablename = $table->getName(); 4136 4137 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); 4138 $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null); 4139 $table->add_field('name2', XMLDB_TYPE_CHAR, '255', null, null, null, null); 4140 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); 4141 $dbman->create_table($table); 4142 4143 $DB->insert_record($tablename, array('name' => 'one', 'name2' => 'one')); 4144 $DB->insert_record($tablename, array('name' => 'ONE', 'name2' => 'ONE')); 4145 $DB->insert_record($tablename, array('name' => 'two', 'name2' => 'TWO')); 4146 $DB->insert_record($tablename, array('name' => 'öne', 'name2' => 'one')); 4147 $DB->insert_record($tablename, array('name' => 'öne', 'name2' => 'ÖNE')); 4148 4149 // Case sensitive and accent sensitive (equal and not equal). 4150 $sql = "SELECT * FROM {{$tablename}} WHERE " . $DB->sql_equal('name', '?', true, true, false); 4151 $records = $DB->get_records_sql($sql, array('one')); 4152 $this->assertCount(1, $records); 4153 $sql = "SELECT * FROM {{$tablename}} WHERE " . $DB->sql_equal('name', ':name', true, true, true); 4154 $records = $DB->get_records_sql($sql, array('name' => 'one')); 4155 $this->assertCount(4, $records); 4156 // And with column comparison instead of params. 4157 $sql = "SELECT * FROM {{$tablename}} WHERE " . $DB->sql_equal('name', 'name2', true, true, false); 4158 $records = $DB->get_records_sql($sql); 4159 $this->assertCount(2, $records); 4160 4161 // Case insensitive and accent sensitive (equal and not equal). 4162 $sql = "SELECT * FROM {{$tablename}} WHERE " . $DB->sql_equal('name', '?', false, true, false); 4163 $records = $DB->get_records_sql($sql, array('one')); 4164 $this->assertCount(2, $records); 4165 $sql = "SELECT * FROM {{$tablename}} WHERE " . $DB->sql_equal('name', ':name', false, true, true); 4166 $records = $DB->get_records_sql($sql, array('name' => 'one')); 4167 $this->assertCount(3, $records); 4168 // And with column comparison instead of params. 4169 $sql = "SELECT * FROM {{$tablename}} WHERE " . $DB->sql_equal('name', 'name2', false, true, false); 4170 $records = $DB->get_records_sql($sql); 4171 $this->assertCount(4, $records); 4172 4173 // TODO: Accent insensitive is not cross-db, only some drivers support it, so just verify the queries work. 4174 $sql = "SELECT * FROM {{$tablename}} WHERE " . $DB->sql_equal('name', '?', true, false); 4175 $records = $DB->get_records_sql($sql, array('one')); 4176 $this->assertGreaterThanOrEqual(1, count($records)); // At very least, there is 1 record with CS/AI "one". 4177 $sql = "SELECT * FROM {{$tablename}} WHERE " . $DB->sql_equal('name', '?', false, false); 4178 $records = $DB->get_records_sql($sql, array('one')); 4179 $this->assertGreaterThanOrEqual(2, count($records)); // At very least, there are 2 records with CI/AI "one". 4180 // And with column comparison instead of params. 4181 $sql = "SELECT * FROM {{$tablename}} WHERE " . $DB->sql_equal('name', 'name2', false, false); 4182 $records = $DB->get_records_sql($sql); 4183 $this->assertGreaterThanOrEqual(4, count($records)); // At very least, there are 4 records with CI/AI names matching. 4184 } 4185 4186 public function test_sql_like() { 4187 $DB = $this->tdb; 4188 $dbman = $DB->get_manager(); 4189 4190 $table = $this->get_test_table(); 4191 $tablename = $table->getName(); 4192 4193 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); 4194 $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null); 4195 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); 4196 $dbman->create_table($table); 4197 4198 $DB->insert_record($tablename, array('name'=>'SuperDuperRecord')); 4199 $DB->insert_record($tablename, array('name'=>'Nodupor')); 4200 $DB->insert_record($tablename, array('name'=>'ouch')); 4201 $DB->insert_record($tablename, array('name'=>'ouc_')); 4202 $DB->insert_record($tablename, array('name'=>'ouc%')); 4203 $DB->insert_record($tablename, array('name'=>'aui')); 4204 $DB->insert_record($tablename, array('name'=>'aüi')); 4205 $DB->insert_record($tablename, array('name'=>'aÜi')); 4206 4207 $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?', false); 4208 $records = $DB->get_records_sql($sql, array("%dup_r%")); 4209 $this->assertCount(2, $records); 4210 4211 $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?', true); 4212 $records = $DB->get_records_sql($sql, array("%dup%")); 4213 $this->assertCount(1, $records); 4214 4215 $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?'); // Defaults. 4216 $records = $DB->get_records_sql($sql, array("%dup%")); 4217 $this->assertCount(1, $records); 4218 4219 $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?', true); 4220 $records = $DB->get_records_sql($sql, array("ouc\\_")); 4221 $this->assertCount(1, $records); 4222 4223 $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?', true, true, false, '|'); 4224 $records = $DB->get_records_sql($sql, array($DB->sql_like_escape("ouc%", '|'))); 4225 $this->assertCount(1, $records); 4226 4227 $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?', true, true); 4228 $records = $DB->get_records_sql($sql, array('aui')); 4229 $this->assertCount(1, $records); 4230 4231 // Test LIKE under unusual collations. 4232 $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?', false, false); 4233 $records = $DB->get_records_sql($sql, array("%dup_r%")); 4234 $this->assertCount(2, $records); 4235 4236 $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?', true, true, true); // NOT LIKE. 4237 $records = $DB->get_records_sql($sql, array("%o%")); 4238 $this->assertCount(3, $records); 4239 4240 $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?', false, true, true); // NOT ILIKE. 4241 $records = $DB->get_records_sql($sql, array("%D%")); 4242 $this->assertCount(6, $records); 4243 4244 // Verify usual escaping characters work fine. 4245 $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?', true, true, false, '\\'); 4246 $records = $DB->get_records_sql($sql, array("ouc\\_")); 4247 $this->assertCount(1, $records); 4248 $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?', true, true, false, '|'); 4249 $records = $DB->get_records_sql($sql, array("ouc|%")); 4250 $this->assertCount(1, $records); 4251 4252 // TODO: we do not require accent insensitivness yet, just make sure it does not throw errors. 4253 $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?', true, false); 4254 $records = $DB->get_records_sql($sql, array('aui')); 4255 // $this->assertEquals(2, count($records), 'Accent insensitive LIKE searches may not be supported in all databases, this is not a problem.'); 4256 $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?', false, false); 4257 $records = $DB->get_records_sql($sql, array('aui')); 4258 // $this->assertEquals(3, count($records), 'Accent insensitive LIKE searches may not be supported in all databases, this is not a problem.'); 4259 } 4260 4261 /** 4262 * Test DML libraries sql_like_escape method 4263 */ 4264 public function test_sql_like_escape(): void { 4265 $DB = $this->tdb; 4266 $dbman = $DB->get_manager(); 4267 4268 $table = $this->get_test_table(); 4269 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); 4270 $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null); 4271 $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']); 4272 $dbman->create_table($table); 4273 4274 $tablename = $table->getName(); 4275 4276 // Two of the records contain LIKE characters (%_), plus square brackets supported only by SQL Server (and '^-' which 4277 // should be ignored by SQL Server given they only have meaning inside square brackets). 4278 $DB->insert_record($tablename, (object) ['name' => 'lionel']); 4279 $DB->insert_record($tablename, (object) ['name' => 'lionel%_^-[0]']); 4280 $DB->insert_record($tablename, (object) ['name' => 'rick']); 4281 $DB->insert_record($tablename, (object) ['name' => 'rick%_^-[0]']); 4282 4283 $select = $DB->sql_like('name', ':namelike'); 4284 $params = ['namelike' => '%' . $DB->sql_like_escape('%_^-[0]')]; 4285 4286 // All drivers should return our two records containing wildcard characters. 4287 $this->assertEqualsCanonicalizing([ 4288 'lionel%_^-[0]', 4289 'rick%_^-[0]', 4290 ], $DB->get_fieldset_select($tablename, 'name', $select, $params)); 4291 4292 // Test for unbalanced brackets. 4293 $select = $DB->sql_like('name', ':namelike'); 4294 $params = ['namelike' => '%' . $DB->sql_like_escape('[') . '%']; 4295 4296 $this->assertEqualsCanonicalizing([ 4297 'lionel%_^-[0]', 4298 'rick%_^-[0]', 4299 ], $DB->get_fieldset_select($tablename, 'name', $select, $params)); 4300 } 4301 4302 public function test_coalesce() { 4303 $DB = $this->tdb; 4304 4305 // Testing not-null occurrences, return 1st. 4306 $sql = "SELECT COALESCE('returnthis', 'orthis', 'orwhynotthis') AS test" . $DB->sql_null_from_clause(); 4307 $this->assertSame('returnthis', $DB->get_field_sql($sql, array())); 4308 $sql = "SELECT COALESCE(:paramvalue, 'orthis', 'orwhynotthis') AS test" . $DB->sql_null_from_clause(); 4309 $this->assertSame('returnthis', $DB->get_field_sql($sql, array('paramvalue' => 'returnthis'))); 4310 4311 // Testing null occurrences, return 2nd. 4312 $sql = "SELECT COALESCE(null, 'returnthis', 'orthis') AS test" . $DB->sql_null_from_clause(); 4313 $this->assertSame('returnthis', $DB->get_field_sql($sql, array())); 4314 $sql = "SELECT COALESCE(:paramvalue, 'returnthis', 'orthis') AS test" . $DB->sql_null_from_clause(); 4315 $this->assertSame('returnthis', $DB->get_field_sql($sql, array('paramvalue' => null))); 4316 $sql = "SELECT COALESCE(null, :paramvalue, 'orthis') AS test" . $DB->sql_null_from_clause(); 4317 $this->assertSame('returnthis', $DB->get_field_sql($sql, array('paramvalue' => 'returnthis'))); 4318 4319 // Testing null occurrences, return 3rd. 4320 $sql = "SELECT COALESCE(null, null, 'returnthis') AS test" . $DB->sql_null_from_clause(); 4321 $this->assertSame('returnthis', $DB->get_field_sql($sql, array())); 4322 $sql = "SELECT COALESCE(null, :paramvalue, 'returnthis') AS test" . $DB->sql_null_from_clause(); 4323 $this->assertSame('returnthis', $DB->get_field_sql($sql, array('paramvalue' => null))); 4324 $sql = "SELECT COALESCE(null, null, :paramvalue) AS test" . $DB->sql_null_from_clause(); 4325 $this->assertSame('returnthis', $DB->get_field_sql($sql, array('paramvalue' => 'returnthis'))); 4326 4327 // Testing all null occurrences, return null. 4328 // Note: under mssql, if all elements are nulls, at least one must be a "typed" null, hence 4329 // we cannot test this in a cross-db way easily, so next 2 tests are using 4330 // different queries depending of the DB family. 4331 $customnull = $DB->get_dbfamily() == 'mssql' ? 'CAST(null AS varchar)' : 'null'; 4332 $sql = "SELECT COALESCE(null, null, " . $customnull . ") AS test" . $DB->sql_null_from_clause(); 4333 $this->assertNull($DB->get_field_sql($sql, array())); 4334 $sql = "SELECT COALESCE(null, :paramvalue, " . $customnull . ") AS test" . $DB->sql_null_from_clause(); 4335 $this->assertNull($DB->get_field_sql($sql, array('paramvalue' => null))); 4336 4337 // Check there are not problems with whitespace strings. 4338 $sql = "SELECT COALESCE(null, :paramvalue, null) AS test" . $DB->sql_null_from_clause(); 4339 $this->assertSame('', $DB->get_field_sql($sql, array('paramvalue' => ''))); 4340 } 4341 4342 public function test_sql_concat() { 4343 $DB = $this->tdb; 4344 $dbman = $DB->get_manager(); 4345 4346 // Testing all sort of values. 4347 $sql = "SELECT ".$DB->sql_concat("?", "?", "?")." AS fullname ". $DB->sql_null_from_clause(); 4348 // String, some unicode chars. 4349 $params = array('name', 'áéíóú', 'name3'); 4350 $this->assertSame('nameáéíóúname3', $DB->get_field_sql($sql, $params)); 4351 // String, spaces and numbers. 4352 $params = array('name', ' ', 12345); 4353 $this->assertSame('name 12345', $DB->get_field_sql($sql, $params)); 4354 // Float, empty and strings. 4355 $params = array(123.45, '', 'test'); 4356 $this->assertSame('123.45test', $DB->get_field_sql($sql, $params)); 4357 // Only integers. 4358 $params = array(12, 34, 56); 4359 $this->assertSame('123456', $DB->get_field_sql($sql, $params)); 4360 // Float, null and strings. 4361 $params = array(123.45, null, 'test'); 4362 $this->assertNull($DB->get_field_sql($sql, $params)); // Concatenate null with anything result = null. 4363 4364 // Testing fieldnames + values and also integer fieldnames. 4365 $table = $this->get_test_table(); 4366 $tablename = $table->getName(); 4367 4368 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); 4369 $table->add_field('charshort', XMLDB_TYPE_CHAR, '255'); 4370 $table->add_field('charlong', XMLDB_TYPE_CHAR, '1333'); 4371 $table->add_field('description', XMLDB_TYPE_TEXT, 'big'); 4372 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); 4373 $dbman->create_table($table); 4374 4375 // Regarding 1300 length - all drivers except Oracle support larger values (2K+), but this hits a limit on Oracle. 4376 $DB->insert_record($tablename, [ 4377 'charshort' => 'áéíóú', 4378 'charlong' => str_repeat('A', 512), 4379 'description' => str_repeat('X', 1300), 4380 ]); 4381 $DB->insert_record($tablename, [ 4382 'charshort' => 'dxxx', 4383 'charlong' => str_repeat('B', 512), 4384 'description' => str_repeat('Y', 1300), 4385 ]); 4386 $DB->insert_record($tablename, [ 4387 'charshort' => 'bcde', 4388 'charlong' => str_repeat('C', 512), 4389 'description' => str_repeat('Z', 1300), 4390 ]); 4391 4392 // Char (short) fieldnames and values. 4393 $fieldsql = $DB->sql_concat('charshort', "'harcoded'", '?', '?'); 4394 $this->assertEqualsCanonicalizing([ 4395 'áéíóúharcoded123.45test', 4396 'dxxxharcoded123.45test', 4397 'bcdeharcoded123.45test', 4398 ], $DB->get_fieldset_select($tablename, $fieldsql, '', [123.45, 'test'])); 4399 4400 // Char (long) fieldnames and values. 4401 $fieldsql = $DB->sql_concat('charlong', "'harcoded'", '?', '?'); 4402 $this->assertEqualsCanonicalizing([ 4403 str_repeat('A', 512) . 'harcoded123.45test', 4404 str_repeat('B', 512) . 'harcoded123.45test', 4405 str_repeat('C', 512) . 'harcoded123.45test', 4406 ], $DB->get_fieldset_select($tablename, $fieldsql, '', [123.45, 'test'])); 4407 4408 // Text fieldnames and values. 4409 $fieldsql = $DB->sql_concat('description', "'harcoded'", '?', '?'); 4410 $this->assertEqualsCanonicalizing([ 4411 str_repeat('X', 1300) . 'harcoded123.45test', 4412 str_repeat('Y', 1300) . 'harcoded123.45test', 4413 str_repeat('Z', 1300) . 'harcoded123.45test', 4414 ], $DB->get_fieldset_select($tablename, $fieldsql, '', [123.45, 'test'])); 4415 4416 // Integer fieldnames and values. 4417 $fieldsql = $DB->sql_concat('id', "'harcoded'", '?', '?'); 4418 $this->assertEqualsCanonicalizing([ 4419 '1harcoded123.45test', 4420 '2harcoded123.45test', 4421 '3harcoded123.45test', 4422 ], $DB->get_fieldset_select($tablename, $fieldsql, '', [123.45, 'test'])); 4423 4424 // All integer fieldnames. 4425 $fieldsql = $DB->sql_concat('id', 'id', 'id'); 4426 $this->assertEqualsCanonicalizing([ 4427 '111', 4428 '222', 4429 '333', 4430 ], $DB->get_fieldset_select($tablename, $fieldsql, '')); 4431 4432 } 4433 4434 public function sql_concat_join_provider() { 4435 return array( 4436 // All strings. 4437 array( 4438 "' '", 4439 array("'name'", "'name2'", "'name3'"), 4440 array(), 4441 'name name2 name3', 4442 ), 4443 // All strings using placeholders 4444 array( 4445 "' '", 4446 array("?", "?", "?"), 4447 array('name', 'name2', 'name3'), 4448 'name name2 name3', 4449 ), 4450 // All integers. 4451 array( 4452 "' '", 4453 array(1, 2, 3), 4454 array(), 4455 '1 2 3', 4456 ), 4457 // All integers using placeholders 4458 array( 4459 "' '", 4460 array("?", "?", "?"), 4461 array(1, 2, 3), 4462 '1 2 3', 4463 ), 4464 // Mix of strings and integers. 4465 array( 4466 "' '", 4467 array(1, "'2'", 3), 4468 array(), 4469 '1 2 3', 4470 ), 4471 // Mix of strings and integers using placeholders. 4472 array( 4473 "' '", 4474 array(1, '2', 3), 4475 array(), 4476 '1 2 3', 4477 ), 4478 ); 4479 } 4480 4481 /** 4482 * @dataProvider sql_concat_join_provider 4483 * @param string $concat The string to use when concatanating. 4484 * @param array $fields The fields to concatanate 4485 * @param array $params Any parameters to provide to the query 4486 * @param @string $expected The expected result 4487 */ 4488 public function test_concat_join($concat, $fields, $params, $expected) { 4489 $DB = $this->tdb; 4490 $sql = "SELECT " . $DB->sql_concat_join($concat, $fields) . " AS result" . $DB->sql_null_from_clause(); 4491 $result = $DB->get_field_sql($sql, $params); 4492 $this->assertEquals($expected, $result); 4493 } 4494 4495 /** 4496 * Test DML libraries sql_group_contact method 4497 */ 4498 public function test_group_concat(): void { 4499 $DB = $this->tdb; 4500 $dbman = $DB->get_manager(); 4501 4502 $table = $this->get_test_table(); 4503 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); 4504 $table->add_field('intfield', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); 4505 $table->add_field('charfield', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); 4506 $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']); 4507 $dbman->create_table($table); 4508 4509 $tablename = $table->getName(); 4510 $DB->insert_record($tablename, (object) ['intfield' => 10, 'charfield' => 'uno']); 4511 $DB->insert_record($tablename, (object) ['intfield' => 20, 'charfield' => 'dos']); 4512 $DB->insert_record($tablename, (object) ['intfield' => 20, 'charfield' => 'tres']); 4513 $DB->insert_record($tablename, (object) ['intfield' => 30, 'charfield' => 'tres']); 4514 4515 // Test charfield => concatenated intfield ASC. 4516 $fieldsql = $DB->sql_group_concat('intfield', ', ', 'intfield ASC'); 4517 $sql = "SELECT charfield, {$fieldsql} AS falias 4518 FROM {{$tablename}} 4519 GROUP BY charfield"; 4520 4521 $this->assertEquals([ 4522 'dos' => '20', 4523 'tres' => '20, 30', 4524 'uno' => '10', 4525 ], $DB->get_records_sql_menu($sql)); 4526 4527 // Test charfield => concatenated intfield DESC. 4528 $fieldsql = $DB->sql_group_concat('intfield', ', ', 'intfield DESC'); 4529 $sql = "SELECT charfield, {$fieldsql} AS falias 4530 FROM {{$tablename}} 4531 GROUP BY charfield"; 4532 4533 $this->assertEquals([ 4534 'dos' => '20', 4535 'tres' => '30, 20', 4536 'uno' => '10', 4537 ], $DB->get_records_sql_menu($sql)); 4538 4539 // Test intfield => concatenated charfield ASC. 4540 $fieldsql = $DB->sql_group_concat('charfield', ', ', 'charfield ASC'); 4541 $sql = "SELECT intfield, {$fieldsql} AS falias 4542 FROM {{$tablename}} 4543 GROUP BY intfield"; 4544 4545 $this->assertEquals([ 4546 10 => 'uno', 4547 20 => 'dos, tres', 4548 30 => 'tres', 4549 ], $DB->get_records_sql_menu($sql)); 4550 4551 // Test intfield => concatenated charfield DESC. 4552 $fieldsql = $DB->sql_group_concat('charfield', ', ', 'charfield DESC'); 4553 $sql = "SELECT intfield, {$fieldsql} AS falias 4554 FROM {{$tablename}} 4555 GROUP BY intfield"; 4556 4557 $this->assertEquals([ 4558 10 => 'uno', 4559 20 => 'tres, dos', 4560 30 => 'tres', 4561 ], $DB->get_records_sql_menu($sql)); 4562 4563 // Assert expressions with parameters can also be used. 4564 $fieldexpr = $DB->sql_concat(':greeting', 'charfield'); 4565 $fieldsql = $DB->sql_group_concat($fieldexpr, ', ', 'charfield ASC'); 4566 $sql = "SELECT intfield, {$fieldsql} AS falias 4567 FROM {{$tablename}} 4568 GROUP BY intfield"; 4569 $this->assertEquals([ 4570 10 => 'Hola uno', 4571 20 => 'Hola dos, Hola tres', 4572 30 => 'Hola tres', 4573 ], $DB->get_records_sql_menu($sql, ['greeting' => 'Hola '])); 4574 } 4575 4576 /** 4577 * Test DML libraries sql_group_contact method joining tables, aggregating data from each 4578 */ 4579 public function test_group_concat_join_tables(): void { 4580 $DB = $this->tdb; 4581 $dbman = $DB->get_manager(); 4582 4583 $tableparent = $this->get_test_table('parent'); 4584 $tableparent->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); 4585 $tableparent->add_field('name', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); 4586 $tableparent->add_key('primary', XMLDB_KEY_PRIMARY, ['id']); 4587 $dbman->create_table($tableparent); 4588 4589 $tablechild = $this->get_test_table('child'); 4590 $tablechild->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); 4591 $tablechild->add_field('parentid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); 4592 $tablechild->add_field('name', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); 4593 $tablechild->add_key('primary', XMLDB_KEY_PRIMARY, ['id']); 4594 $tablechild->add_key('parentid', XMLDB_KEY_FOREIGN, ['parentid'], $tableparent->getName(), ['id']); 4595 $dbman->create_table($tablechild); 4596 4597 $tableparentname = $tableparent->getName(); 4598 $tablechildname = $tablechild->getName(); 4599 4600 $parentone = $DB->insert_record($tableparentname, (object) ['name' => 'Alice']); 4601 $DB->insert_record($tablechildname, (object) ['parentid' => $parentone, 'name' => 'Eve']); 4602 $DB->insert_record($tablechildname, (object) ['parentid' => $parentone, 'name' => 'Charlie']); 4603 4604 $parenttwo = $DB->insert_record($tableparentname, (object) ['name' => 'Bob']); 4605 $DB->insert_record($tablechildname, (object) ['parentid' => $parenttwo, 'name' => 'Dan']); 4606 $DB->insert_record($tablechildname, (object) ['parentid' => $parenttwo, 'name' => 'Grace']); 4607 4608 $tableparentalias = 'p'; 4609 $tablechildalias = 'c'; 4610 4611 $fieldsql = $DB->sql_group_concat("{$tablechildalias}.name", ', ', "{$tablechildalias}.name ASC"); 4612 4613 $sql = "SELECT {$tableparentalias}.name, {$fieldsql} AS falias 4614 FROM {{$tableparentname}} {$tableparentalias} 4615 JOIN {{$tablechildname}} {$tablechildalias} ON {$tablechildalias}.parentid = {$tableparentalias}.id 4616 GROUP BY {$tableparentalias}.name"; 4617 4618 $this->assertEqualsCanonicalizing([ 4619 (object) [ 4620 'name' => 'Alice', 4621 'falias' => 'Charlie, Eve', 4622 ], 4623 (object) [ 4624 'name' => 'Bob', 4625 'falias' => 'Dan, Grace', 4626 ], 4627 ], $DB->get_records_sql($sql)); 4628 } 4629 4630 public function test_sql_fullname() { 4631 $DB = $this->tdb; 4632 $sql = "SELECT ".$DB->sql_fullname(':first', ':last')." AS fullname ".$DB->sql_null_from_clause(); 4633 $params = array('first'=>'Firstname', 'last'=>'Surname'); 4634 $this->assertEquals("Firstname Surname", $DB->get_field_sql($sql, $params)); 4635 } 4636 4637 public function test_sql_order_by_text() { 4638 $DB = $this->tdb; 4639 $dbman = $DB->get_manager(); 4640 4641 $table = $this->get_test_table(); 4642 $tablename = $table->getName(); 4643 4644 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); 4645 $table->add_field('description', XMLDB_TYPE_TEXT, 'big', null, null, null, null); 4646 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); 4647 $dbman->create_table($table); 4648 4649 $DB->insert_record($tablename, array('description'=>'abcd')); 4650 $DB->insert_record($tablename, array('description'=>'dxxx')); 4651 $DB->insert_record($tablename, array('description'=>'bcde')); 4652 4653 $sql = "SELECT * FROM {{$tablename}} ORDER BY ".$DB->sql_order_by_text('description'); 4654 $records = $DB->get_records_sql($sql); 4655 $first = array_shift($records); 4656 $this->assertEquals(1, $first->id); 4657 $second = array_shift($records); 4658 $this->assertEquals(3, $second->id); 4659 $last = array_shift($records); 4660 $this->assertEquals(2, $last->id); 4661 } 4662 4663 /** 4664 * Test DML libraries sql_order_by_null method 4665 */ 4666 public function test_sql_order_by_null(): void { 4667 $DB = $this->tdb; 4668 $dbman = $DB->get_manager(); 4669 4670 $table = $this->get_test_table(); 4671 $tablename = $table->getName(); 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_key('primary', XMLDB_KEY_PRIMARY, array('id')); 4676 $dbman->create_table($table); 4677 4678 $DB->insert_record($tablename, array('name' => 'aaaa')); 4679 $DB->insert_record($tablename, array('name' => 'bbbb')); 4680 $DB->insert_record($tablename, array('name' => '')); 4681 $DB->insert_record($tablename, array('name' => null)); 4682 4683 $sql = "SELECT * FROM {{$tablename}} ORDER BY ".$DB->sql_order_by_null('name'); 4684 $records = $DB->get_records_sql($sql); 4685 $this->assertEquals(null, array_shift($records)->name); 4686 $this->assertEquals('', array_shift($records)->name); 4687 $this->assertEquals('aaaa', array_shift($records)->name); 4688 $this->assertEquals('bbbb', array_shift($records)->name); 4689 4690 $sql = "SELECT * FROM {{$tablename}} ORDER BY ".$DB->sql_order_by_null('name', SORT_DESC); 4691 $records = $DB->get_records_sql($sql); 4692 $this->assertEquals('bbbb', array_shift($records)->name); 4693 $this->assertEquals('aaaa', array_shift($records)->name); 4694 $this->assertEquals('', array_shift($records)->name); 4695 $this->assertEquals(null, array_shift($records)->name); 4696 } 4697 4698 public function test_sql_substring() { 4699 $DB = $this->tdb; 4700 $dbman = $DB->get_manager(); 4701 4702 $table = $this->get_test_table(); 4703 $tablename = $table->getName(); 4704 4705 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); 4706 $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null); 4707 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); 4708 $dbman->create_table($table); 4709 4710 $string = 'abcdefghij'; 4711 4712 $DB->insert_record($tablename, array('name'=>$string)); 4713 4714 $sql = "SELECT id, ".$DB->sql_substr("name", 5)." AS name FROM {{$tablename}}"; 4715 $record = $DB->get_record_sql($sql); 4716 $this->assertEquals(substr($string, 5-1), $record->name); 4717 4718 $sql = "SELECT id, ".$DB->sql_substr("name", 5, 2)." AS name FROM {{$tablename}}"; 4719 $record = $DB->get_record_sql($sql); 4720 $this->assertEquals(substr($string, 5-1, 2), $record->name); 4721 4722 try { 4723 // Silence php warning. 4724 @$DB->sql_substr("name"); 4725 $this->fail("Expecting an exception, none occurred"); 4726 } catch (\moodle_exception $e) { 4727 $this->assertInstanceOf('coding_exception', $e); 4728 } catch (\Error $error) { 4729 // PHP 7.1 throws Error even earlier. 4730 $this->assertMatchesRegularExpression('/Too few arguments to function/', $error->getMessage()); 4731 } 4732 4733 // Cover the function using placeholders in all positions. 4734 $start = 4; 4735 $length = 2; 4736 // 1st param (target). 4737 $sql = "SELECT id, ".$DB->sql_substr(":param1", $start)." AS name FROM {{$tablename}}"; 4738 $record = $DB->get_record_sql($sql, array('param1' => $string)); 4739 $this->assertEquals(substr($string, $start - 1), $record->name); // PHP's substr is 0-based. 4740 // 2nd param (start). 4741 $sql = "SELECT id, ".$DB->sql_substr("name", ":param1")." AS name FROM {{$tablename}}"; 4742 $record = $DB->get_record_sql($sql, array('param1' => $start)); 4743 $this->assertEquals(substr($string, $start - 1), $record->name); // PHP's substr is 0-based. 4744 // 3rd param (length). 4745 $sql = "SELECT id, ".$DB->sql_substr("name", $start, ":param1")." AS name FROM {{$tablename}}"; 4746 $record = $DB->get_record_sql($sql, array('param1' => $length)); 4747 $this->assertEquals(substr($string, $start - 1, $length), $record->name); // PHP's substr is 0-based. 4748 // All together. 4749 $sql = "SELECT id, ".$DB->sql_substr(":param1", ":param2", ":param3")." AS name FROM {{$tablename}}"; 4750 $record = $DB->get_record_sql($sql, array('param1' => $string, 'param2' => $start, 'param3' => $length)); 4751 $this->assertEquals(substr($string, $start - 1, $length), $record->name); // PHP's substr is 0-based. 4752 4753 // Try also with some expression passed. 4754 $sql = "SELECT id, ".$DB->sql_substr("name", "(:param1 + 1) - 1")." AS name FROM {{$tablename}}"; 4755 $record = $DB->get_record_sql($sql, array('param1' => $start)); 4756 $this->assertEquals(substr($string, $start - 1), $record->name); // PHP's substr is 0-based. 4757 } 4758 4759 public function test_sql_length() { 4760 $DB = $this->tdb; 4761 $this->assertEquals($DB->get_field_sql( 4762 "SELECT ".$DB->sql_length("'aeiou'").$DB->sql_null_from_clause()), 5); 4763 $this->assertEquals($DB->get_field_sql( 4764 "SELECT ".$DB->sql_length("'áéíóú'").$DB->sql_null_from_clause()), 5); 4765 } 4766 4767 public function test_sql_position() { 4768 $DB = $this->tdb; 4769 $this->assertEquals($DB->get_field_sql( 4770 "SELECT ".$DB->sql_position("'ood'", "'Moodle'").$DB->sql_null_from_clause()), 2); 4771 $this->assertEquals($DB->get_field_sql( 4772 "SELECT ".$DB->sql_position("'Oracle'", "'Moodle'").$DB->sql_null_from_clause()), 0); 4773 } 4774 4775 public function test_sql_empty() { 4776 $DB = $this->tdb; 4777 $dbman = $DB->get_manager(); 4778 4779 $table = $this->get_test_table(); 4780 $tablename = $table->getName(); 4781 4782 $this->assertSame('', $DB->sql_empty()); // Since 2.5 the hack is applied automatically to all bound params. 4783 $this->assertDebuggingCalled(); 4784 4785 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); 4786 $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null); 4787 $table->add_field('namenotnull', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, 'default value'); 4788 $table->add_field('namenotnullnodeflt', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); 4789 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); 4790 $dbman->create_table($table); 4791 4792 $DB->insert_record($tablename, array('name'=>'', 'namenotnull'=>'')); 4793 $DB->insert_record($tablename, array('name'=>null)); 4794 $DB->insert_record($tablename, array('name'=>'lalala')); 4795 $DB->insert_record($tablename, array('name'=>0)); 4796 4797 $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE name = ?", array('')); 4798 $this->assertCount(1, $records); 4799 $record = reset($records); 4800 $this->assertSame('', $record->name); 4801 4802 $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE namenotnull = ?", array('')); 4803 $this->assertCount(1, $records); 4804 $record = reset($records); 4805 $this->assertSame('', $record->namenotnull); 4806 4807 $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE namenotnullnodeflt = ?", array('')); 4808 $this->assertCount(4, $records); 4809 $record = reset($records); 4810 $this->assertSame('', $record->namenotnullnodeflt); 4811 } 4812 4813 public function test_sql_isempty() { 4814 $DB = $this->tdb; 4815 $dbman = $DB->get_manager(); 4816 4817 $table = $this->get_test_table(); 4818 $tablename = $table->getName(); 4819 4820 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); 4821 $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); 4822 $table->add_field('namenull', XMLDB_TYPE_CHAR, '255', null, null, null, null); 4823 $table->add_field('description', XMLDB_TYPE_TEXT, 'big', null, XMLDB_NOTNULL, null, null); 4824 $table->add_field('descriptionnull', XMLDB_TYPE_TEXT, 'big', null, null, null, null); 4825 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); 4826 $dbman->create_table($table); 4827 4828 $DB->insert_record($tablename, array('name'=>'', 'namenull'=>'', 'description'=>'', 'descriptionnull'=>'')); 4829 $DB->insert_record($tablename, array('name'=>'??', 'namenull'=>null, 'description'=>'??', 'descriptionnull'=>null)); 4830 $DB->insert_record($tablename, array('name'=>'la', 'namenull'=>'la', 'description'=>'la', 'descriptionnull'=>'lalala')); 4831 $DB->insert_record($tablename, array('name'=>0, 'namenull'=>0, 'description'=>0, 'descriptionnull'=>0)); 4832 4833 $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE ".$DB->sql_isempty($tablename, 'name', false, false)); 4834 $this->assertCount(1, $records); 4835 $record = reset($records); 4836 $this->assertSame('', $record->name); 4837 4838 $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE ".$DB->sql_isempty($tablename, 'namenull', true, false)); 4839 $this->assertCount(1, $records); 4840 $record = reset($records); 4841 $this->assertSame('', $record->namenull); 4842 4843 $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE ".$DB->sql_isempty($tablename, 'description', false, true)); 4844 $this->assertCount(1, $records); 4845 $record = reset($records); 4846 $this->assertSame('', $record->description); 4847 4848 $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE ".$DB->sql_isempty($tablename, 'descriptionnull', true, true)); 4849 $this->assertCount(1, $records); 4850 $record = reset($records); 4851 $this->assertSame('', $record->descriptionnull); 4852 } 4853 4854 public function test_sql_isnotempty() { 4855 $DB = $this->tdb; 4856 $dbman = $DB->get_manager(); 4857 4858 $table = $this->get_test_table(); 4859 $tablename = $table->getName(); 4860 4861 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); 4862 $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); 4863 $table->add_field('namenull', XMLDB_TYPE_CHAR, '255', null, null, null, null); 4864 $table->add_field('description', XMLDB_TYPE_TEXT, 'big', null, XMLDB_NOTNULL, null, null); 4865 $table->add_field('descriptionnull', XMLDB_TYPE_TEXT, 'big', null, null, null, null); 4866 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); 4867 $dbman->create_table($table); 4868 4869 $DB->insert_record($tablename, array('name'=>'', 'namenull'=>'', 'description'=>'', 'descriptionnull'=>'')); 4870 $DB->insert_record($tablename, array('name'=>'??', 'namenull'=>null, 'description'=>'??', 'descriptionnull'=>null)); 4871 $DB->insert_record($tablename, array('name'=>'la', 'namenull'=>'la', 'description'=>'la', 'descriptionnull'=>'lalala')); 4872 $DB->insert_record($tablename, array('name'=>0, 'namenull'=>0, 'description'=>0, 'descriptionnull'=>0)); 4873 4874 $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE ".$DB->sql_isnotempty($tablename, 'name', false, false)); 4875 $this->assertCount(3, $records); 4876 $record = reset($records); 4877 $this->assertSame('??', $record->name); 4878 4879 $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE ".$DB->sql_isnotempty($tablename, 'namenull', true, false)); 4880 $this->assertCount(2, $records); // Nulls aren't comparable (so they aren't "not empty"). SQL expected behaviour. 4881 $record = reset($records); 4882 $this->assertSame('la', $record->namenull); // So 'la' is the first non-empty 'namenull' record. 4883 4884 $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE ".$DB->sql_isnotempty($tablename, 'description', false, true)); 4885 $this->assertCount(3, $records); 4886 $record = reset($records); 4887 $this->assertSame('??', $record->description); 4888 4889 $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE ".$DB->sql_isnotempty($tablename, 'descriptionnull', true, true)); 4890 $this->assertCount(2, $records); // Nulls aren't comparable (so they aren't "not empty"). SQL expected behaviour. 4891 $record = reset($records); 4892 $this->assertSame('lalala', $record->descriptionnull); // So 'lalala' is the first non-empty 'descriptionnull' record. 4893 } 4894 4895 public function test_sql_regex() { 4896 $DB = $this->tdb; 4897 $dbman = $DB->get_manager(); 4898 if (!$DB->sql_regex_supported()) { 4899 $this->markTestSkipped($DB->get_name().' does not support regular expressions'); 4900 } 4901 4902 $table = $this->get_test_table(); 4903 $tablename = $table->getName(); 4904 4905 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); 4906 $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null); 4907 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); 4908 $dbman->create_table($table); 4909 4910 $DB->insert_record($tablename, array('name'=>'LALALA')); 4911 $DB->insert_record($tablename, array('name'=>'holaaa')); 4912 $DB->insert_record($tablename, array('name'=>'aouch')); 4913 4914 // Regex /a$/i (case-insensitive). 4915 $sql = "SELECT * FROM {{$tablename}} WHERE name ".$DB->sql_regex()." ?"; 4916 $params = array('a$'); 4917 $records = $DB->get_records_sql($sql, $params); 4918 $this->assertCount(2, $records); 4919 4920 // Regex ! (not) /.a/i (case insensitive). 4921 $sql = "SELECT * FROM {{$tablename}} WHERE name ".$DB->sql_regex(false)." ?"; 4922 $params = array('.a'); 4923 $records = $DB->get_records_sql($sql, $params); 4924 $this->assertCount(1, $records); 4925 4926 // Regex /a$/ (case-sensitive). 4927 $sql = "SELECT * FROM {{$tablename}} WHERE name ".$DB->sql_regex(true, true)." ?"; 4928 $params = array('a$'); 4929 $records = $DB->get_records_sql($sql, $params); 4930 $this->assertCount(1, $records); 4931 4932 // Regex ! (not) /.a/ (case sensitive). 4933 $sql = "SELECT * FROM {{$tablename}} WHERE name ".$DB->sql_regex(false, true)." ?"; 4934 $params = array('.a'); 4935 $records = $DB->get_records_sql($sql, $params); 4936 $this->assertCount(2, $records); 4937 4938 } 4939 4940 /** 4941 * Test some complicated variations of set_field_select. 4942 */ 4943 public function test_set_field_select_complicated() { 4944 $DB = $this->tdb; 4945 $dbman = $DB->get_manager(); 4946 4947 $table = $this->get_test_table(); 4948 $tablename = $table->getName(); 4949 4950 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); 4951 $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); 4952 $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null); 4953 $table->add_field('content', XMLDB_TYPE_TEXT, 'big', null, XMLDB_NOTNULL); 4954 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); 4955 $dbman->create_table($table); 4956 4957 $DB->insert_record($tablename, array('course' => 3, 'content' => 'hello', 'name'=>'xyz')); 4958 $DB->insert_record($tablename, array('course' => 3, 'content' => 'world', 'name'=>'abc')); 4959 $DB->insert_record($tablename, array('course' => 5, 'content' => 'hello', 'name'=>'def')); 4960 $DB->insert_record($tablename, array('course' => 2, 'content' => 'universe', 'name'=>'abc')); 4961 // This SQL is a tricky case because we are selecting from the same table we are updating. 4962 $sql = 'id IN (SELECT outerq.id from (SELECT innerq.id from {' . $tablename . '} innerq WHERE course = 3) outerq)'; 4963 $DB->set_field_select($tablename, 'name', 'ghi', $sql); 4964 4965 $this->assertSame(2, $DB->count_records_select($tablename, 'name = ?', array('ghi'))); 4966 4967 } 4968 4969 /** 4970 * Test some more complex SQL syntax which moodle uses and depends on to work 4971 * useful to determine if new database libraries can be supported. 4972 */ 4973 public function test_get_records_sql_complicated() { 4974 $DB = $this->tdb; 4975 $dbman = $DB->get_manager(); 4976 4977 $table = $this->get_test_table(); 4978 $tablename = $table->getName(); 4979 4980 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); 4981 $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); 4982 $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null); 4983 $table->add_field('content', XMLDB_TYPE_TEXT, 'big', null, XMLDB_NOTNULL); 4984 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); 4985 $dbman->create_table($table); 4986 4987 $DB->insert_record($tablename, array('course' => 3, 'content' => 'hello', 'name'=>'xyz')); 4988 $DB->insert_record($tablename, array('course' => 3, 'content' => 'world', 'name'=>'abc')); 4989 $DB->insert_record($tablename, array('course' => 5, 'content' => 'hello', 'name'=>'def')); 4990 $DB->insert_record($tablename, array('course' => 2, 'content' => 'universe', 'name'=>'abc')); 4991 4992 // Test grouping by expressions in the query. MDL-26819. Note that there are 4 ways: 4993 // - By column position (GROUP by 1) - Not supported by mssql & oracle 4994 // - By column name (GROUP by course) - Supported by all, but leading to wrong results 4995 // - By column alias (GROUP by casecol) - Not supported by mssql & oracle 4996 // - By complete expression (GROUP BY CASE ...) - 100% cross-db, this test checks it 4997 $sql = "SELECT (CASE WHEN course = 3 THEN 1 ELSE 0 END) AS casecol, 4998 COUNT(1) AS countrecs, 4999 MAX(name) AS maxname 5000 FROM {{$tablename}} 5001 GROUP BY CASE WHEN course = 3 THEN 1 ELSE 0 END 5002 ORDER BY casecol DESC"; 5003 $result = array( 5004 1 => (object)array('casecol' => 1, 'countrecs' => 2, 'maxname' => 'xyz'), 5005 0 => (object)array('casecol' => 0, 'countrecs' => 2, 'maxname' => 'def')); 5006 $records = $DB->get_records_sql($sql, null); 5007 $this->assertEquals($result, $records); 5008 5009 // Another grouping by CASE expression just to ensure it works ok for multiple WHEN. 5010 $sql = "SELECT CASE name 5011 WHEN 'xyz' THEN 'last' 5012 WHEN 'def' THEN 'mid' 5013 WHEN 'abc' THEN 'first' 5014 END AS casecol, 5015 COUNT(1) AS countrecs, 5016 MAX(name) AS maxname 5017 FROM {{$tablename}} 5018 GROUP BY CASE name 5019 WHEN 'xyz' THEN 'last' 5020 WHEN 'def' THEN 'mid' 5021 WHEN 'abc' THEN 'first' 5022 END 5023 ORDER BY casecol DESC"; 5024 $result = array( 5025 'mid' => (object)array('casecol' => 'mid', 'countrecs' => 1, 'maxname' => 'def'), 5026 'last' => (object)array('casecol' => 'last', 'countrecs' => 1, 'maxname' => 'xyz'), 5027 'first'=> (object)array('casecol' => 'first', 'countrecs' => 2, 'maxname' => 'abc')); 5028 $records = $DB->get_records_sql($sql, null); 5029 $this->assertEquals($result, $records); 5030 5031 // Test CASE expressions in the ORDER BY clause - used by MDL-34657. 5032 $sql = "SELECT id, course, name 5033 FROM {{$tablename}} 5034 ORDER BY CASE WHEN (course = 5 OR name = 'xyz') THEN 0 ELSE 1 END, name, course"; 5035 // First, records matching the course = 5 OR name = 'xyz', then the rest. Each. 5036 // group ordered by name and course. 5037 $result = array( 5038 3 => (object)array('id' => 3, 'course' => 5, 'name' => 'def'), 5039 1 => (object)array('id' => 1, 'course' => 3, 'name' => 'xyz'), 5040 4 => (object)array('id' => 4, 'course' => 2, 'name' => 'abc'), 5041 2 => (object)array('id' => 2, 'course' => 3, 'name' => 'abc')); 5042 $records = $DB->get_records_sql($sql, null); 5043 $this->assertEquals($result, $records); 5044 // Verify also array keys, order is important in this test. 5045 $this->assertEquals(array_keys($result), array_keys($records)); 5046 5047 // Test limits in queries with DISTINCT/ALL clauses and multiple whitespace. MDL-25268. 5048 $sql = "SELECT DISTINCT course 5049 FROM {{$tablename}} 5050 ORDER BY course"; 5051 // Only limitfrom. 5052 $records = $DB->get_records_sql($sql, null, 1); 5053 $this->assertCount(2, $records); 5054 $this->assertEquals(3, reset($records)->course); 5055 $this->assertEquals(5, next($records)->course); 5056 // Only limitnum. 5057 $records = $DB->get_records_sql($sql, null, 0, 2); 5058 $this->assertCount(2, $records); 5059 $this->assertEquals(2, reset($records)->course); 5060 $this->assertEquals(3, next($records)->course); 5061 // Both limitfrom and limitnum. 5062 $records = $DB->get_records_sql($sql, null, 2, 2); 5063 $this->assertCount(1, $records); 5064 $this->assertEquals(5, reset($records)->course); 5065 5066 // We have sql like this in moodle, this syntax breaks on older versions of sqlite for example.. 5067 $sql = "SELECT a.id AS id, a.course AS course 5068 FROM {{$tablename}} a 5069 JOIN (SELECT * FROM {{$tablename}}) b ON a.id = b.id 5070 WHERE a.course = ?"; 5071 5072 $records = $DB->get_records_sql($sql, array(3)); 5073 $this->assertCount(2, $records); 5074 $this->assertEquals(1, reset($records)->id); 5075 $this->assertEquals(2, next($records)->id); 5076 5077 // Do NOT try embedding sql_xxxx() helper functions in conditions array of count_records(), they don't break params/binding! 5078 $count = $DB->count_records_select($tablename, "course = :course AND ".$DB->sql_compare_text('content')." = :content", array('course' => 3, 'content' => 'hello')); 5079 $this->assertEquals(1, $count); 5080 5081 // Test int x string comparison. 5082 $sql = "SELECT * 5083 FROM {{$tablename}} c 5084 WHERE name = ?"; 5085 $this->assertCount(0, $DB->get_records_sql($sql, array(10))); 5086 $this->assertCount(0, $DB->get_records_sql($sql, array("10"))); 5087 $DB->insert_record($tablename, array('course' => 7, 'content' => 'xx', 'name'=>'1')); 5088 $DB->insert_record($tablename, array('course' => 7, 'content' => 'yy', 'name'=>'2')); 5089 $this->assertCount(1, $DB->get_records_sql($sql, array(1))); 5090 $this->assertCount(1, $DB->get_records_sql($sql, array("1"))); 5091 $this->assertCount(0, $DB->get_records_sql($sql, array(10))); 5092 $this->assertCount(0, $DB->get_records_sql($sql, array("10"))); 5093 $DB->insert_record($tablename, array('course' => 7, 'content' => 'xx', 'name'=>'1abc')); 5094 $this->assertCount(1, $DB->get_records_sql($sql, array(1))); 5095 $this->assertCount(1, $DB->get_records_sql($sql, array("1"))); 5096 5097 // Test get_in_or_equal() with a big number of elements. Note that ideally 5098 // we should be detecting and warning about any use over, say, 200 elements 5099 // And recommend to change code to use subqueries and/or chunks instead. 5100 $currentcount = $DB->count_records($tablename); 5101 $numelements = 10000; // Verify that we can handle 10000 elements (crazy!) 5102 $values = range(1, $numelements); 5103 5104 list($insql, $inparams) = $DB->get_in_or_equal($values, SQL_PARAMS_QM); // With QM params. 5105 $sql = "SELECT * 5106 FROM {{$tablename}} 5107 WHERE id $insql"; 5108 $results = $DB->get_records_sql($sql, $inparams); 5109 $this->assertCount($currentcount, $results); 5110 5111 list($insql, $inparams) = $DB->get_in_or_equal($values, SQL_PARAMS_NAMED); // With NAMED params. 5112 $sql = "SELECT * 5113 FROM {{$tablename}} 5114 WHERE id $insql"; 5115 $results = $DB->get_records_sql($sql, $inparams); 5116 $this->assertCount($currentcount, $results); 5117 } 5118 5119 public function test_replace_all_text() { 5120 $DB = $this->tdb; 5121 $dbman = $DB->get_manager(); 5122 5123 if (!$DB->replace_all_text_supported()) { 5124 $this->markTestSkipped($DB->get_name().' does not support replacing of texts'); 5125 } 5126 5127 $table = $this->get_test_table(); 5128 $tablename = $table->getName(); 5129 5130 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); 5131 $table->add_field('name', XMLDB_TYPE_CHAR, '20', null, null); 5132 $table->add_field('intro', XMLDB_TYPE_TEXT, 'big', null, null); 5133 // Add a CHAR field named using a word reserved for all the supported DB servers. 5134 $table->add_field('where', XMLDB_TYPE_CHAR, '20', null, null, null, 'localhost'); 5135 // Add a TEXT field named using a word reserved for all the supported DB servers. 5136 $table->add_field('from', XMLDB_TYPE_TEXT, 'big', null, null); 5137 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); 5138 $dbman->create_table($table); 5139 5140 $fromfield = $dbman->generator->getEncQuoted('from'); 5141 $DB->execute("INSERT INTO {".$tablename."} (name,intro,$fromfield) VALUES (NULL,NULL,'localhost')"); 5142 $DB->execute("INSERT INTO {".$tablename."} (name,intro,$fromfield) VALUES ('','','localhost')"); 5143 $DB->execute("INSERT INTO {".$tablename."} (name,intro,$fromfield) VALUES ('xxyy','vvzz','localhost')"); 5144 $DB->execute("INSERT INTO {".$tablename."} (name,intro,$fromfield) VALUES ('aa bb aa bb','cc dd cc aa','localhost')"); 5145 $DB->execute("INSERT INTO {".$tablename."} (name,intro,$fromfield) VALUES ('kkllll','kkllll','localhost')"); 5146 5147 $expected = $DB->get_records($tablename, array(), 'id ASC'); 5148 $idx = 1; 5149 $id1 = $id2 = $id3 = $id4 = $id5 = 0; 5150 foreach (array_keys($expected) as $identifier) { 5151 ${"id$idx"} = (string)$identifier; 5152 $idx++; 5153 } 5154 5155 $columns = $DB->get_columns($tablename); 5156 5157 // Replace should work even with columns named using a reserved word. 5158 $this->assertEquals('C', $columns['where']->meta_type); 5159 $this->assertEquals('localhost', $expected[$id1]->where); 5160 $this->assertEquals('localhost', $expected[$id2]->where); 5161 $this->assertEquals('localhost', $expected[$id3]->where); 5162 $this->assertEquals('localhost', $expected[$id4]->where); 5163 $this->assertEquals('localhost', $expected[$id5]->where); 5164 $DB->replace_all_text($tablename, $columns['where'], 'localhost', '::1'); 5165 $result = $DB->get_records($tablename, array(), 'id ASC'); 5166 $expected[$id1]->where = '::1'; 5167 $expected[$id2]->where = '::1'; 5168 $expected[$id3]->where = '::1'; 5169 $expected[$id4]->where = '::1'; 5170 $expected[$id5]->where = '::1'; 5171 $this->assertEquals($expected, $result); 5172 $this->assertEquals('X', $columns['from']->meta_type); 5173 $DB->replace_all_text($tablename, $columns['from'], 'localhost', '127.0.0.1'); 5174 $result = $DB->get_records($tablename, array(), 'id ASC'); 5175 $expected[$id1]->from = '127.0.0.1'; 5176 $expected[$id2]->from = '127.0.0.1'; 5177 $expected[$id3]->from = '127.0.0.1'; 5178 $expected[$id4]->from = '127.0.0.1'; 5179 $expected[$id5]->from = '127.0.0.1'; 5180 $this->assertEquals($expected, $result); 5181 5182 $DB->replace_all_text($tablename, $columns['name'], 'aa', 'o'); 5183 $result = $DB->get_records($tablename, array(), 'id ASC'); 5184 $expected[$id4]->name = 'o bb o bb'; 5185 $this->assertEquals($expected, $result); 5186 5187 $DB->replace_all_text($tablename, $columns['intro'], 'aa', 'o'); 5188 $result = $DB->get_records($tablename, array(), 'id ASC'); 5189 $expected[$id4]->intro = 'cc dd cc o'; 5190 $this->assertEquals($expected, $result); 5191 5192 $DB->replace_all_text($tablename, $columns['name'], '_', '*'); 5193 $DB->replace_all_text($tablename, $columns['name'], '?', '*'); 5194 $DB->replace_all_text($tablename, $columns['name'], '%', '*'); 5195 $DB->replace_all_text($tablename, $columns['intro'], '_', '*'); 5196 $DB->replace_all_text($tablename, $columns['intro'], '?', '*'); 5197 $DB->replace_all_text($tablename, $columns['intro'], '%', '*'); 5198 $result = $DB->get_records($tablename, array(), 'id ASC'); 5199 $this->assertEquals($expected, $result); 5200 5201 $long = '1234567890123456789'; 5202 $DB->replace_all_text($tablename, $columns['name'], 'kk', $long); 5203 $result = $DB->get_records($tablename, array(), 'id ASC'); 5204 $expected[$id5]->name = \core_text::substr($long.'llll', 0, 20); 5205 $this->assertEquals($expected, $result); 5206 5207 $DB->replace_all_text($tablename, $columns['intro'], 'kk', $long); 5208 $result = $DB->get_records($tablename, array(), 'id ASC'); 5209 $expected[$id5]->intro = $long.'llll'; 5210 $this->assertEquals($expected, $result); 5211 } 5212 5213 public function test_onelevel_commit() { 5214 $DB = $this->tdb; 5215 $dbman = $DB->get_manager(); 5216 5217 $table = $this->get_test_table(); 5218 $tablename = $table->getName(); 5219 5220 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); 5221 $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); 5222 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); 5223 $dbman->create_table($table); 5224 5225 $transaction = $DB->start_delegated_transaction(); 5226 $data = (object)array('course'=>3); 5227 $this->assertEquals(0, $DB->count_records($tablename)); 5228 $DB->insert_record($tablename, $data); 5229 $this->assertEquals(1, $DB->count_records($tablename)); 5230 $transaction->allow_commit(); 5231 $this->assertEquals(1, $DB->count_records($tablename)); 5232 } 5233 5234 public function test_transaction_ignore_error_trouble() { 5235 $DB = $this->tdb; 5236 $dbman = $DB->get_manager(); 5237 5238 $table = $this->get_test_table(); 5239 $tablename = $table->getName(); 5240 5241 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); 5242 $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); 5243 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); 5244 $table->add_index('course', XMLDB_INDEX_UNIQUE, array('course')); 5245 $dbman->create_table($table); 5246 5247 // Test error on SQL_QUERY_INSERT. 5248 $transaction = $DB->start_delegated_transaction(); 5249 $this->assertEquals(0, $DB->count_records($tablename)); 5250 $DB->insert_record($tablename, (object)array('course'=>1)); 5251 $this->assertEquals(1, $DB->count_records($tablename)); 5252 try { 5253 $DB->insert_record($tablename, (object)array('course'=>1)); 5254 } catch (\Exception $e) { 5255 // This must be ignored and it must not roll back the whole transaction. 5256 } 5257 $DB->insert_record($tablename, (object)array('course'=>2)); 5258 $this->assertEquals(2, $DB->count_records($tablename)); 5259 $transaction->allow_commit(); 5260 $this->assertEquals(2, $DB->count_records($tablename)); 5261 $this->assertFalse($DB->is_transaction_started()); 5262 5263 // Test error on SQL_QUERY_SELECT. 5264 $DB->delete_records($tablename); 5265 $transaction = $DB->start_delegated_transaction(); 5266 $this->assertEquals(0, $DB->count_records($tablename)); 5267 $DB->insert_record($tablename, (object)array('course'=>1)); 5268 $this->assertEquals(1, $DB->count_records($tablename)); 5269 try { 5270 $DB->get_records_sql('s e l e c t'); 5271 } catch (\moodle_exception $e) { 5272 // This must be ignored and it must not roll back the whole transaction. 5273 } 5274 $DB->insert_record($tablename, (object)array('course'=>2)); 5275 $this->assertEquals(2, $DB->count_records($tablename)); 5276 $transaction->allow_commit(); 5277 $this->assertEquals(2, $DB->count_records($tablename)); 5278 $this->assertFalse($DB->is_transaction_started()); 5279 5280 // Test error on structure SQL_QUERY_UPDATE. 5281 $DB->delete_records($tablename); 5282 $transaction = $DB->start_delegated_transaction(); 5283 $this->assertEquals(0, $DB->count_records($tablename)); 5284 $DB->insert_record($tablename, (object)array('course'=>1)); 5285 $this->assertEquals(1, $DB->count_records($tablename)); 5286 try { 5287 $DB->execute('xxxx'); 5288 } catch (\moodle_exception $e) { 5289 // This must be ignored and it must not roll back the whole transaction. 5290 } 5291 $DB->insert_record($tablename, (object)array('course'=>2)); 5292 $this->assertEquals(2, $DB->count_records($tablename)); 5293 $transaction->allow_commit(); 5294 $this->assertEquals(2, $DB->count_records($tablename)); 5295 $this->assertFalse($DB->is_transaction_started()); 5296 5297 // Test error on structure SQL_QUERY_STRUCTURE. 5298 $DB->delete_records($tablename); 5299 $transaction = $DB->start_delegated_transaction(); 5300 $this->assertEquals(0, $DB->count_records($tablename)); 5301 $DB->insert_record($tablename, (object)array('course'=>1)); 5302 $this->assertEquals(1, $DB->count_records($tablename)); 5303 try { 5304 $DB->change_database_structure('xxxx'); 5305 } catch (\moodle_exception $e) { 5306 // This must be ignored and it must not roll back the whole transaction. 5307 } 5308 $DB->insert_record($tablename, (object)array('course'=>2)); 5309 $this->assertEquals(2, $DB->count_records($tablename)); 5310 $transaction->allow_commit(); 5311 $this->assertEquals(2, $DB->count_records($tablename)); 5312 $this->assertFalse($DB->is_transaction_started()); 5313 5314 // NOTE: SQL_QUERY_STRUCTURE is intentionally not tested here because it should never fail. 5315 } 5316 5317 public function test_onelevel_rollback() { 5318 $DB = $this->tdb; 5319 $dbman = $DB->get_manager(); 5320 5321 $table = $this->get_test_table(); 5322 $tablename = $table->getName(); 5323 5324 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); 5325 $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); 5326 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); 5327 $dbman->create_table($table); 5328 5329 // This might in fact encourage ppl to migrate from myisam to innodb. 5330 5331 $transaction = $DB->start_delegated_transaction(); 5332 $data = (object)array('course'=>3); 5333 $this->assertEquals(0, $DB->count_records($tablename)); 5334 $DB->insert_record($tablename, $data); 5335 $this->assertEquals(1, $DB->count_records($tablename)); 5336 try { 5337 $transaction->rollback(new \Exception('test')); 5338 $this->fail('transaction rollback must rethrow exception'); 5339 } catch (\Exception $e) { 5340 // Ignored. 5341 } 5342 $this->assertEquals(0, $DB->count_records($tablename)); 5343 } 5344 5345 public function test_nested_transactions() { 5346 $DB = $this->tdb; 5347 $dbman = $DB->get_manager(); 5348 5349 $table = $this->get_test_table(); 5350 $tablename = $table->getName(); 5351 5352 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); 5353 $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); 5354 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); 5355 $dbman->create_table($table); 5356 5357 // Two level commit. 5358 $this->assertFalse($DB->is_transaction_started()); 5359 $transaction1 = $DB->start_delegated_transaction(); 5360 $this->assertTrue($DB->is_transaction_started()); 5361 $data = (object)array('course'=>3); 5362 $DB->insert_record($tablename, $data); 5363 $transaction2 = $DB->start_delegated_transaction(); 5364 $data = (object)array('course'=>4); 5365 $DB->insert_record($tablename, $data); 5366 $transaction2->allow_commit(); 5367 $this->assertTrue($DB->is_transaction_started()); 5368 $transaction1->allow_commit(); 5369 $this->assertFalse($DB->is_transaction_started()); 5370 $this->assertEquals(2, $DB->count_records($tablename)); 5371 5372 $DB->delete_records($tablename); 5373 5374 // Rollback from top level. 5375 $transaction1 = $DB->start_delegated_transaction(); 5376 $data = (object)array('course'=>3); 5377 $DB->insert_record($tablename, $data); 5378 $transaction2 = $DB->start_delegated_transaction(); 5379 $data = (object)array('course'=>4); 5380 $DB->insert_record($tablename, $data); 5381 $transaction2->allow_commit(); 5382 try { 5383 $transaction1->rollback(new \Exception('test')); 5384 $this->fail('transaction rollback must rethrow exception'); 5385 } catch (\Exception $e) { 5386 $this->assertEquals(get_class($e), 'Exception'); 5387 } 5388 $this->assertEquals(0, $DB->count_records($tablename)); 5389 5390 $DB->delete_records($tablename); 5391 5392 // Rollback from nested level. 5393 $transaction1 = $DB->start_delegated_transaction(); 5394 $data = (object)array('course'=>3); 5395 $DB->insert_record($tablename, $data); 5396 $transaction2 = $DB->start_delegated_transaction(); 5397 $data = (object)array('course'=>4); 5398 $DB->insert_record($tablename, $data); 5399 try { 5400 $transaction2->rollback(new \Exception('test')); 5401 $this->fail('transaction rollback must rethrow exception'); 5402 } catch (\Exception $e) { 5403 $this->assertEquals(get_class($e), 'Exception'); 5404 } 5405 $this->assertEquals(2, $DB->count_records($tablename)); // Not rolled back yet. 5406 try { 5407 $transaction1->allow_commit(); 5408 } catch (\moodle_exception $e) { 5409 $this->assertInstanceOf('dml_transaction_exception', $e); 5410 } 5411 $this->assertEquals(2, $DB->count_records($tablename)); // Not rolled back yet. 5412 // The forced rollback is done from the default_exception handler and similar places, 5413 // let's do it manually here. 5414 $this->assertTrue($DB->is_transaction_started()); 5415 $DB->force_transaction_rollback(); 5416 $this->assertFalse($DB->is_transaction_started()); 5417 $this->assertEquals(0, $DB->count_records($tablename)); // Finally rolled back. 5418 5419 $DB->delete_records($tablename); 5420 5421 // Test interactions of recordset and transactions - this causes problems in SQL Server. 5422 $table2 = $this->get_test_table('2'); 5423 $tablename2 = $table2->getName(); 5424 5425 $table2->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); 5426 $table2->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); 5427 $table2->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); 5428 $dbman->create_table($table2); 5429 5430 $DB->insert_record($tablename, array('course'=>1)); 5431 $DB->insert_record($tablename, array('course'=>2)); 5432 $DB->insert_record($tablename, array('course'=>3)); 5433 5434 $DB->insert_record($tablename2, array('course'=>5)); 5435 $DB->insert_record($tablename2, array('course'=>6)); 5436 $DB->insert_record($tablename2, array('course'=>7)); 5437 $DB->insert_record($tablename2, array('course'=>8)); 5438 5439 $rs1 = $DB->get_recordset($tablename); 5440 $i = 0; 5441 foreach ($rs1 as $record1) { 5442 $i++; 5443 $rs2 = $DB->get_recordset($tablename2); 5444 $j = 0; 5445 foreach ($rs2 as $record2) { 5446 $t = $DB->start_delegated_transaction(); 5447 $DB->set_field($tablename, 'course', $record1->course+1, array('id'=>$record1->id)); 5448 $DB->set_field($tablename2, 'course', $record2->course+1, array('id'=>$record2->id)); 5449 $t->allow_commit(); 5450 $j++; 5451 } 5452 $rs2->close(); 5453 $this->assertEquals(4, $j); 5454 } 5455 $rs1->close(); 5456 $this->assertEquals(3, $i); 5457 5458 // Test nested recordsets isolation without transaction. 5459 $DB->delete_records($tablename); 5460 $DB->insert_record($tablename, array('course'=>1)); 5461 $DB->insert_record($tablename, array('course'=>2)); 5462 $DB->insert_record($tablename, array('course'=>3)); 5463 5464 $DB->delete_records($tablename2); 5465 $DB->insert_record($tablename2, array('course'=>5)); 5466 $DB->insert_record($tablename2, array('course'=>6)); 5467 $DB->insert_record($tablename2, array('course'=>7)); 5468 $DB->insert_record($tablename2, array('course'=>8)); 5469 5470 $rs1 = $DB->get_recordset($tablename); 5471 $i = 0; 5472 foreach ($rs1 as $record1) { 5473 $i++; 5474 $rs2 = $DB->get_recordset($tablename2); 5475 $j = 0; 5476 foreach ($rs2 as $record2) { 5477 $DB->set_field($tablename, 'course', $record1->course+1, array('id'=>$record1->id)); 5478 $DB->set_field($tablename2, 'course', $record2->course+1, array('id'=>$record2->id)); 5479 $j++; 5480 } 5481 $rs2->close(); 5482 $this->assertEquals(4, $j); 5483 } 5484 $rs1->close(); 5485 $this->assertEquals(3, $i); 5486 } 5487 5488 public function test_transactions_forbidden() { 5489 $DB = $this->tdb; 5490 $dbman = $DB->get_manager(); 5491 5492 $table = $this->get_test_table(); 5493 $tablename = $table->getName(); 5494 5495 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); 5496 $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); 5497 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); 5498 $dbman->create_table($table); 5499 5500 $DB->transactions_forbidden(); 5501 $transaction = $DB->start_delegated_transaction(); 5502 $data = (object)array('course'=>1); 5503 $DB->insert_record($tablename, $data); 5504 try { 5505 $DB->transactions_forbidden(); 5506 } catch (\moodle_exception $e) { 5507 $this->assertInstanceOf('dml_transaction_exception', $e); 5508 } 5509 // The previous test does not force rollback. 5510 $transaction->allow_commit(); 5511 $this->assertFalse($DB->is_transaction_started()); 5512 $this->assertEquals(1, $DB->count_records($tablename)); 5513 } 5514 5515 public function test_wrong_transactions() { 5516 $DB = $this->tdb; 5517 $dbman = $DB->get_manager(); 5518 5519 $table = $this->get_test_table(); 5520 $tablename = $table->getName(); 5521 5522 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); 5523 $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); 5524 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); 5525 $dbman->create_table($table); 5526 5527 // Wrong order of nested commits. 5528 $transaction1 = $DB->start_delegated_transaction(); 5529 $data = (object)array('course'=>3); 5530 $DB->insert_record($tablename, $data); 5531 $transaction2 = $DB->start_delegated_transaction(); 5532 $data = (object)array('course'=>4); 5533 $DB->insert_record($tablename, $data); 5534 try { 5535 $transaction1->allow_commit(); 5536 $this->fail('wrong order of commits must throw exception'); 5537 } catch (\moodle_exception $e) { 5538 $this->assertInstanceOf('dml_transaction_exception', $e); 5539 } 5540 try { 5541 $transaction2->allow_commit(); 5542 $this->fail('first wrong commit forces rollback'); 5543 } catch (\moodle_exception $e) { 5544 $this->assertInstanceOf('dml_transaction_exception', $e); 5545 } 5546 // This is done in default exception handler usually. 5547 $this->assertTrue($DB->is_transaction_started()); 5548 $this->assertEquals(2, $DB->count_records($tablename)); // Not rolled back yet. 5549 $DB->force_transaction_rollback(); 5550 $this->assertEquals(0, $DB->count_records($tablename)); 5551 $DB->delete_records($tablename); 5552 5553 // Wrong order of nested rollbacks. 5554 $transaction1 = $DB->start_delegated_transaction(); 5555 $data = (object)array('course'=>3); 5556 $DB->insert_record($tablename, $data); 5557 $transaction2 = $DB->start_delegated_transaction(); 5558 $data = (object)array('course'=>4); 5559 $DB->insert_record($tablename, $data); 5560 try { 5561 // This first rollback should prevent all other rollbacks. 5562 $transaction1->rollback(new \Exception('test')); 5563 } catch (\Exception $e) { 5564 $this->assertEquals(get_class($e), 'Exception'); 5565 } 5566 try { 5567 $transaction2->rollback(new \Exception('test')); 5568 } catch (\Exception $e) { 5569 $this->assertEquals(get_class($e), 'Exception'); 5570 } 5571 try { 5572 $transaction1->rollback(new \Exception('test')); 5573 } catch (\moodle_exception $e) { 5574 $this->assertInstanceOf('dml_transaction_exception', $e); 5575 } 5576 // This is done in default exception handler usually. 5577 $this->assertTrue($DB->is_transaction_started()); 5578 $DB->force_transaction_rollback(); 5579 $DB->delete_records($tablename); 5580 5581 // Unknown transaction object. 5582 $transaction1 = $DB->start_delegated_transaction(); 5583 $data = (object)array('course'=>3); 5584 $DB->insert_record($tablename, $data); 5585 $transaction2 = new moodle_transaction($DB); 5586 try { 5587 $transaction2->allow_commit(); 5588 $this->fail('foreign transaction must fail'); 5589 } catch (\moodle_exception $e) { 5590 $this->assertInstanceOf('dml_transaction_exception', $e); 5591 } 5592 try { 5593 $transaction1->allow_commit(); 5594 $this->fail('first wrong commit forces rollback'); 5595 } catch (\moodle_exception $e) { 5596 $this->assertInstanceOf('dml_transaction_exception', $e); 5597 } 5598 $DB->force_transaction_rollback(); 5599 $DB->delete_records($tablename); 5600 } 5601 5602 public function test_concurent_transactions() { 5603 // Notes about this test: 5604 // 1- MySQL needs to use one engine with transactions support (InnoDB). 5605 // 2- MSSQL needs to have enabled versioning for read committed 5606 // transactions (ALTER DATABASE xxx SET READ_COMMITTED_SNAPSHOT ON) 5607 $DB = $this->tdb; 5608 $dbman = $DB->get_manager(); 5609 5610 $table = $this->get_test_table(); 5611 $tablename = $table->getName(); 5612 5613 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); 5614 $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); 5615 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); 5616 $dbman->create_table($table); 5617 5618 $transaction = $DB->start_delegated_transaction(); 5619 $data = (object)array('course'=>1); 5620 $this->assertEquals(0, $DB->count_records($tablename)); 5621 $DB->insert_record($tablename, $data); 5622 $this->assertEquals(1, $DB->count_records($tablename)); 5623 5624 // Open second connection. 5625 $cfg = $DB->export_dbconfig(); 5626 if (!isset($cfg->dboptions)) { 5627 $cfg->dboptions = array(); 5628 } 5629 // If we have a readonly slave situation, we need to either observe 5630 // the latency, or if the latency is not specified we need to take 5631 // the slave out because the table may not have propagated yet. 5632 if (isset($cfg->dboptions['readonly'])) { 5633 if (isset($cfg->dboptions['readonly']['latency'])) { 5634 usleep(intval(1000000 * $cfg->dboptions['readonly']['latency'])); 5635 } else { 5636 unset($cfg->dboptions['readonly']); 5637 } 5638 } 5639 $DB2 = moodle_database::get_driver_instance($cfg->dbtype, $cfg->dblibrary); 5640 $DB2->connect($cfg->dbhost, $cfg->dbuser, $cfg->dbpass, $cfg->dbname, $cfg->prefix, $cfg->dboptions); 5641 5642 // Second instance should not see pending inserts. 5643 $this->assertEquals(0, $DB2->count_records($tablename)); 5644 $data = (object)array('course'=>2); 5645 $DB2->insert_record($tablename, $data); 5646 $this->assertEquals(1, $DB2->count_records($tablename)); 5647 5648 // First should see the changes done from second. 5649 $this->assertEquals(2, $DB->count_records($tablename)); 5650 5651 // Now commit and we should see it finally in second connections. 5652 $transaction->allow_commit(); 5653 $this->assertEquals(2, $DB2->count_records($tablename)); 5654 5655 // Let's try delete all is also working on (this checks MDL-29198). 5656 // Initially both connections see all the records in the table (2). 5657 $this->assertEquals(2, $DB->count_records($tablename)); 5658 $this->assertEquals(2, $DB2->count_records($tablename)); 5659 $transaction = $DB->start_delegated_transaction(); 5660 5661 // Delete all from within transaction. 5662 $DB->delete_records($tablename); 5663 5664 // Transactional $DB, sees 0 records now. 5665 $this->assertEquals(0, $DB->count_records($tablename)); 5666 5667 // Others ($DB2) get no changes yet. 5668 $this->assertEquals(2, $DB2->count_records($tablename)); 5669 5670 // Now commit and we should see changes. 5671 $transaction->allow_commit(); 5672 $this->assertEquals(0, $DB2->count_records($tablename)); 5673 5674 $DB2->dispose(); 5675 } 5676 5677 public function test_session_locks() { 5678 $DB = $this->tdb; 5679 $dbman = $DB->get_manager(); 5680 5681 // Open second connection. 5682 $cfg = $DB->export_dbconfig(); 5683 if (!isset($cfg->dboptions)) { 5684 $cfg->dboptions = array(); 5685 } 5686 $DB2 = moodle_database::get_driver_instance($cfg->dbtype, $cfg->dblibrary); 5687 $DB2->connect($cfg->dbhost, $cfg->dbuser, $cfg->dbpass, $cfg->dbname, $cfg->prefix, $cfg->dboptions); 5688 5689 // Testing that acquiring a lock effectively locks. 5690 // Get a session lock on connection1. 5691 $rowid = rand(100, 200); 5692 $timeout = 1; 5693 $DB->get_session_lock($rowid, $timeout); 5694 5695 // Try to get the same session lock on connection2. 5696 try { 5697 $DB2->get_session_lock($rowid, $timeout); 5698 $DB2->release_session_lock($rowid); // Should not be executed, but here for safety. 5699 $this->fail('An Exception is missing, expected due to session lock acquired.'); 5700 } catch (\moodle_exception $e) { 5701 $this->assertInstanceOf('dml_sessionwait_exception', $e); 5702 $DB->release_session_lock($rowid); // Release lock on connection1. 5703 } 5704 5705 // Testing that releasing a lock effectively frees. 5706 // Get a session lock on connection1. 5707 $rowid = rand(100, 200); 5708 $timeout = 1; 5709 $DB->get_session_lock($rowid, $timeout); 5710 // Release the lock on connection1. 5711 $DB->release_session_lock($rowid); 5712 5713 // Get the just released lock on connection2. 5714 $DB2->get_session_lock($rowid, $timeout); 5715 // Release the lock on connection2. 5716 $DB2->release_session_lock($rowid); 5717 5718 $DB2->dispose(); 5719 } 5720 5721 public function test_bound_param_types() { 5722 $DB = $this->tdb; 5723 $dbman = $DB->get_manager(); 5724 5725 $table = $this->get_test_table(); 5726 $tablename = $table->getName(); 5727 5728 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); 5729 $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null); 5730 $table->add_field('content', XMLDB_TYPE_TEXT, 'big', null, XMLDB_NOTNULL); 5731 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); 5732 $dbman->create_table($table); 5733 5734 $this->assertNotEmpty($DB->insert_record($tablename, array('name' => '1', 'content'=>'xx'))); 5735 $this->assertNotEmpty($DB->insert_record($tablename, array('name' => 2, 'content'=>'yy'))); 5736 $this->assertNotEmpty($DB->insert_record($tablename, array('name' => 'somestring', 'content'=>'zz'))); 5737 $this->assertNotEmpty($DB->insert_record($tablename, array('name' => 'aa', 'content'=>'1'))); 5738 $this->assertNotEmpty($DB->insert_record($tablename, array('name' => 'bb', 'content'=>2))); 5739 $this->assertNotEmpty($DB->insert_record($tablename, array('name' => 'cc', 'content'=>'sometext'))); 5740 5741 // Conditions in CHAR columns. 5742 $this->assertTrue($DB->record_exists($tablename, array('name'=>1))); 5743 $this->assertTrue($DB->record_exists($tablename, array('name'=>'1'))); 5744 $this->assertFalse($DB->record_exists($tablename, array('name'=>111))); 5745 $this->assertNotEmpty($DB->get_record($tablename, array('name'=>1))); 5746 $this->assertNotEmpty($DB->get_record($tablename, array('name'=>'1'))); 5747 $this->assertEmpty($DB->get_record($tablename, array('name'=>111))); 5748 $sqlqm = "SELECT * 5749 FROM {{$tablename}} 5750 WHERE name = ?"; 5751 $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm, array(1))); 5752 $this->assertCount(1, $records); 5753 $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm, array('1'))); 5754 $this->assertCount(1, $records); 5755 $records = $DB->get_records_sql($sqlqm, array(222)); 5756 $this->assertCount(0, $records); 5757 $sqlnamed = "SELECT * 5758 FROM {{$tablename}} 5759 WHERE name = :name"; 5760 $this->assertNotEmpty($records = $DB->get_records_sql($sqlnamed, array('name' => 2))); 5761 $this->assertCount(1, $records); 5762 $this->assertNotEmpty($records = $DB->get_records_sql($sqlnamed, array('name' => '2'))); 5763 $this->assertCount(1, $records); 5764 5765 // Conditions in TEXT columns always must be performed with the sql_compare_text 5766 // helper function on both sides of the condition. 5767 $sqlqm = "SELECT * 5768 FROM {{$tablename}} 5769 WHERE " . $DB->sql_compare_text('content') . " = " . $DB->sql_compare_text('?'); 5770 $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm, array('1'))); 5771 $this->assertCount(1, $records); 5772 $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm, array(1))); 5773 $this->assertCount(1, $records); 5774 $sqlnamed = "SELECT * 5775 FROM {{$tablename}} 5776 WHERE " . $DB->sql_compare_text('content') . " = " . $DB->sql_compare_text(':content'); 5777 $this->assertNotEmpty($records = $DB->get_records_sql($sqlnamed, array('content' => 2))); 5778 $this->assertCount(1, $records); 5779 $this->assertNotEmpty($records = $DB->get_records_sql($sqlnamed, array('content' => '2'))); 5780 $this->assertCount(1, $records); 5781 } 5782 5783 public function test_bound_param_reserved() { 5784 $DB = $this->tdb; 5785 $dbman = $DB->get_manager(); 5786 5787 $table = $this->get_test_table(); 5788 $tablename = $table->getName(); 5789 5790 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); 5791 $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); 5792 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); 5793 $dbman->create_table($table); 5794 5795 $DB->insert_record($tablename, array('course' => '1')); 5796 5797 // Make sure reserved words do not cause fatal problems in query parameters. 5798 5799 $DB->execute("UPDATE {{$tablename}} SET course = 1 WHERE id = :select", array('select'=>1)); 5800 $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE course = :select", array('select'=>1)); 5801 $rs = $DB->get_recordset_sql("SELECT * FROM {{$tablename}} WHERE course = :select", array('select'=>1)); 5802 $rs->close(); 5803 $DB->get_fieldset_sql("SELECT id FROM {{$tablename}} WHERE course = :select", array('select'=>1)); 5804 $DB->set_field_select($tablename, 'course', '1', "id = :select", array('select'=>1)); 5805 $DB->delete_records_select($tablename, "id = :select", array('select'=>1)); 5806 5807 // If we get here test passed ok. 5808 $this->assertTrue(true); 5809 } 5810 5811 public function test_limits_and_offsets() { 5812 $DB = $this->tdb; 5813 $dbman = $DB->get_manager(); 5814 5815 $table = $this->get_test_table(); 5816 $tablename = $table->getName(); 5817 5818 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); 5819 $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null); 5820 $table->add_field('content', XMLDB_TYPE_TEXT, 'big', null, XMLDB_NOTNULL); 5821 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); 5822 $dbman->create_table($table); 5823 5824 $this->assertNotEmpty($DB->insert_record($tablename, array('name' => 'a', 'content'=>'one'))); 5825 $this->assertNotEmpty($DB->insert_record($tablename, array('name' => 'b', 'content'=>'two'))); 5826 $this->assertNotEmpty($DB->insert_record($tablename, array('name' => 'c', 'content'=>'three'))); 5827 $this->assertNotEmpty($DB->insert_record($tablename, array('name' => 'd', 'content'=>'four'))); 5828 $this->assertNotEmpty($DB->insert_record($tablename, array('name' => 'e', 'content'=>'five'))); 5829 $this->assertNotEmpty($DB->insert_record($tablename, array('name' => 'f', 'content'=>'six'))); 5830 5831 $sqlqm = "SELECT * 5832 FROM {{$tablename}}"; 5833 $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm, null, 4)); 5834 $this->assertCount(2, $records); 5835 $this->assertSame('e', reset($records)->name); 5836 $this->assertSame('f', end($records)->name); 5837 5838 $sqlqm = "SELECT * 5839 FROM {{$tablename}}"; 5840 $this->assertEmpty($records = $DB->get_records_sql($sqlqm, null, 8)); 5841 5842 $sqlqm = "SELECT * 5843 FROM {{$tablename}}"; 5844 $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm, null, 0, 4)); 5845 $this->assertCount(4, $records); 5846 $this->assertSame('a', reset($records)->name); 5847 $this->assertSame('d', end($records)->name); 5848 5849 $sqlqm = "SELECT * 5850 FROM {{$tablename}}"; 5851 $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm, null, 0, 8)); 5852 $this->assertCount(6, $records); 5853 $this->assertSame('a', reset($records)->name); 5854 $this->assertSame('f', end($records)->name); 5855 5856 $sqlqm = "SELECT * 5857 FROM {{$tablename}}"; 5858 $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm, null, 1, 4)); 5859 $this->assertCount(4, $records); 5860 $this->assertSame('b', reset($records)->name); 5861 $this->assertSame('e', end($records)->name); 5862 5863 $sqlqm = "SELECT * 5864 FROM {{$tablename}}"; 5865 $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm, null, 4, 4)); 5866 $this->assertCount(2, $records); 5867 $this->assertSame('e', reset($records)->name); 5868 $this->assertSame('f', end($records)->name); 5869 5870 $sqlqm = "SELECT t.*, t.name AS test 5871 FROM {{$tablename}} t 5872 ORDER BY t.id ASC"; 5873 $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm, null, 4, 4)); 5874 $this->assertCount(2, $records); 5875 $this->assertSame('e', reset($records)->name); 5876 $this->assertSame('f', end($records)->name); 5877 5878 $sqlqm = "SELECT DISTINCT t.name, t.name AS test 5879 FROM {{$tablename}} t 5880 ORDER BY t.name DESC"; 5881 $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm, null, 4, 4)); 5882 $this->assertCount(2, $records); 5883 $this->assertSame('b', reset($records)->name); 5884 $this->assertSame('a', end($records)->name); 5885 5886 $sqlqm = "SELECT 1 5887 FROM {{$tablename}} t 5888 WHERE t.name = 'a'"; 5889 $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm, null, 0, 1)); 5890 $this->assertCount(1, $records); 5891 5892 $sqlqm = "SELECT 'constant' 5893 FROM {{$tablename}} t 5894 WHERE t.name = 'a'"; 5895 $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm, null, 0, 8)); 5896 $this->assertCount(1, $records); 5897 5898 $this->assertNotEmpty($DB->insert_record($tablename, array('name' => 'a', 'content'=>'one'))); 5899 $this->assertNotEmpty($DB->insert_record($tablename, array('name' => 'b', 'content'=>'two'))); 5900 $this->assertNotEmpty($DB->insert_record($tablename, array('name' => 'c', 'content'=>'three'))); 5901 5902 $sqlqm = "SELECT t.name, COUNT(DISTINCT t2.id) AS count, 'Test' AS teststring 5903 FROM {{$tablename}} t 5904 LEFT JOIN ( 5905 SELECT t.id, t.name 5906 FROM {{$tablename}} t 5907 ) t2 ON t2.name = t.name 5908 GROUP BY t.name 5909 ORDER BY t.name ASC"; 5910 $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm)); 5911 $this->assertCount(6, $records); // a,b,c,d,e,f. 5912 $this->assertEquals(2, reset($records)->count); // a has 2 records now. 5913 $this->assertEquals(1, end($records)->count); // f has 1 record still. 5914 5915 $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm, null, 0, 2)); 5916 $this->assertCount(2, $records); 5917 $this->assertEquals(2, reset($records)->count); 5918 $this->assertEquals(2, end($records)->count); 5919 } 5920 5921 /** 5922 * Test debugging messages about invalid limit number values. 5923 */ 5924 public function test_invalid_limits_debugging() { 5925 $DB = $this->tdb; 5926 $dbman = $DB->get_manager(); 5927 5928 // Setup test data. 5929 $table = $this->get_test_table(); 5930 $tablename = $table->getName(); 5931 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); 5932 $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); 5933 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); 5934 $dbman->create_table($table); 5935 $DB->insert_record($tablename, array('course' => '1')); 5936 5937 // Verify that get_records_sql throws debug notices with invalid limit params. 5938 $DB->get_records_sql("SELECT * FROM {{$tablename}}", null, 'invalid'); 5939 $this->assertDebuggingCalled("Non-numeric limitfrom parameter detected: 'invalid', did you pass the correct arguments?"); 5940 5941 $DB->get_records_sql("SELECT * FROM {{$tablename}}", null, 1, 'invalid'); 5942 $this->assertDebuggingCalled("Non-numeric limitnum parameter detected: 'invalid', did you pass the correct arguments?"); 5943 5944 // Verify that get_recordset_sql throws debug notices with invalid limit params. 5945 $rs = $DB->get_recordset_sql("SELECT * FROM {{$tablename}}", null, 'invalid'); 5946 $this->assertDebuggingCalled("Non-numeric limitfrom parameter detected: 'invalid', did you pass the correct arguments?"); 5947 $rs->close(); 5948 5949 $rs = $DB->get_recordset_sql("SELECT * FROM {{$tablename}}", null, 1, 'invalid'); 5950 $this->assertDebuggingCalled("Non-numeric limitnum parameter detected: 'invalid', did you pass the correct arguments?"); 5951 $rs->close(); 5952 5953 // Verify that some edge cases do no create debugging messages. 5954 // String form of integer values. 5955 $DB->get_records_sql("SELECT * FROM {{$tablename}}", null, '1'); 5956 $this->assertDebuggingNotCalled(); 5957 $DB->get_records_sql("SELECT * FROM {{$tablename}}", null, 1, '2'); 5958 $this->assertDebuggingNotCalled(); 5959 // Empty strings. 5960 $DB->get_records_sql("SELECT * FROM {{$tablename}}", null, ''); 5961 $this->assertDebuggingNotCalled(); 5962 $DB->get_records_sql("SELECT * FROM {{$tablename}}", null, 1, ''); 5963 $this->assertDebuggingNotCalled(); 5964 // Null values. 5965 $DB->get_records_sql("SELECT * FROM {{$tablename}}", null, null); 5966 $this->assertDebuggingNotCalled(); 5967 $DB->get_records_sql("SELECT * FROM {{$tablename}}", null, 1, null); 5968 $this->assertDebuggingNotCalled(); 5969 5970 // Verify that empty arrays DO create debugging mesages. 5971 $DB->get_records_sql("SELECT * FROM {{$tablename}}", null, array()); 5972 $this->assertDebuggingCalled("Non-numeric limitfrom parameter detected: array (\n), did you pass the correct arguments?"); 5973 $DB->get_records_sql("SELECT * FROM {{$tablename}}", null, 1, array()); 5974 $this->assertDebuggingCalled("Non-numeric limitnum parameter detected: array (\n), did you pass the correct arguments?"); 5975 5976 // Verify Negative number handling: 5977 // -1 is explicitly treated as 0 for historical reasons. 5978 $DB->get_records_sql("SELECT * FROM {{$tablename}}", null, -1); 5979 $this->assertDebuggingNotCalled(); 5980 $DB->get_records_sql("SELECT * FROM {{$tablename}}", null, 1, -1); 5981 $this->assertDebuggingNotCalled(); 5982 // Any other negative values should throw debugging messages. 5983 $DB->get_records_sql("SELECT * FROM {{$tablename}}", null, -2); 5984 $this->assertDebuggingCalled("Negative limitfrom parameter detected: -2, did you pass the correct arguments?"); 5985 $DB->get_records_sql("SELECT * FROM {{$tablename}}", null, 1, -2); 5986 $this->assertDebuggingCalled("Negative limitnum parameter detected: -2, did you pass the correct arguments?"); 5987 } 5988 5989 public function test_queries_counter() { 5990 5991 $DB = $this->tdb; 5992 $dbman = $this->tdb->get_manager(); 5993 5994 // Test database. 5995 $table = $this->get_test_table(); 5996 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); 5997 $table->add_field('fieldvalue', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); 5998 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); 5999 6000 $dbman->create_table($table); 6001 $tablename = $table->getName(); 6002 6003 // Initial counters values. 6004 $initreads = $DB->perf_get_reads(); 6005 $initwrites = $DB->perf_get_writes(); 6006 $previousqueriestime = $DB->perf_get_queries_time(); 6007 6008 // Selects counts as reads. 6009 6010 // The get_records_sql() method generates only 1 db query. 6011 $whatever = $DB->get_records_sql("SELECT * FROM {{$tablename}}"); 6012 $this->assertEquals($initreads + 1, $DB->perf_get_reads()); 6013 6014 // The get_records() method generates 2 queries the first time is called 6015 // as it is fetching the table structure. 6016 $whatever = $DB->get_records($tablename, array('id' => '1')); 6017 $this->assertEquals($initreads + 3, $DB->perf_get_reads()); 6018 $this->assertEquals($initwrites, $DB->perf_get_writes()); 6019 6020 // The elapsed time is counted. 6021 $lastqueriestime = $DB->perf_get_queries_time(); 6022 $this->assertGreaterThanOrEqual($previousqueriestime, $lastqueriestime); 6023 $previousqueriestime = $lastqueriestime; 6024 6025 // Only 1 now, it already fetched the table columns. 6026 $whatever = $DB->get_records($tablename); 6027 $this->assertEquals($initreads + 4, $DB->perf_get_reads()); 6028 6029 // And only 1 more from now. 6030 $whatever = $DB->get_records($tablename); 6031 $this->assertEquals($initreads + 5, $DB->perf_get_reads()); 6032 6033 // Inserts counts as writes. 6034 6035 $rec1 = new \stdClass(); 6036 $rec1->fieldvalue = 11; 6037 $rec1->id = $DB->insert_record($tablename, $rec1); 6038 $this->assertEquals($initwrites + 1, $DB->perf_get_writes()); 6039 $this->assertEquals($initreads + 5, $DB->perf_get_reads()); 6040 6041 // The elapsed time is counted. 6042 $lastqueriestime = $DB->perf_get_queries_time(); 6043 $this->assertGreaterThanOrEqual($previousqueriestime, $lastqueriestime); 6044 $previousqueriestime = $lastqueriestime; 6045 6046 $rec2 = new \stdClass(); 6047 $rec2->fieldvalue = 22; 6048 $rec2->id = $DB->insert_record($tablename, $rec2); 6049 $this->assertEquals($initwrites + 2, $DB->perf_get_writes()); 6050 6051 // Updates counts as writes. 6052 6053 $rec1->fieldvalue = 111; 6054 $DB->update_record($tablename, $rec1); 6055 $this->assertEquals($initwrites + 3, $DB->perf_get_writes()); 6056 $this->assertEquals($initreads + 5, $DB->perf_get_reads()); 6057 6058 // The elapsed time is counted. 6059 $lastqueriestime = $DB->perf_get_queries_time(); 6060 $this->assertGreaterThanOrEqual($previousqueriestime, $lastqueriestime); 6061 $previousqueriestime = $lastqueriestime; 6062 6063 // Sum of them. 6064 $totaldbqueries = $DB->perf_get_reads() + $DB->perf_get_writes(); 6065 $this->assertEquals($totaldbqueries, $DB->perf_get_queries()); 6066 } 6067 6068 public function test_sql_intersect() { 6069 $DB = $this->tdb; 6070 $dbman = $this->tdb->get_manager(); 6071 6072 $tables = array(); 6073 for ($i = 0; $i < 3; $i++) { 6074 $table = $this->get_test_table('i'.$i); 6075 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); 6076 $table->add_field('ival', XMLDB_TYPE_INTEGER, '10', null, null, null, null); 6077 $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, '0'); 6078 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); 6079 $dbman->create_table($table); 6080 $tables[$i] = $table; 6081 } 6082 $DB->insert_record($tables[0]->getName(), array('ival' => 1, 'name' => 'One'), false); 6083 $DB->insert_record($tables[0]->getName(), array('ival' => 2, 'name' => 'Two'), false); 6084 $DB->insert_record($tables[0]->getName(), array('ival' => 3, 'name' => 'Three'), false); 6085 $DB->insert_record($tables[0]->getName(), array('ival' => 4, 'name' => 'Four'), false); 6086 6087 $DB->insert_record($tables[1]->getName(), array('ival' => 1, 'name' => 'One'), false); 6088 $DB->insert_record($tables[1]->getName(), array('ival' => 2, 'name' => 'Two'), false); 6089 $DB->insert_record($tables[1]->getName(), array('ival' => 3, 'name' => 'Three'), false); 6090 6091 $DB->insert_record($tables[2]->getName(), array('ival' => 1, 'name' => 'One'), false); 6092 $DB->insert_record($tables[2]->getName(), array('ival' => 2, 'name' => 'Two'), false); 6093 $DB->insert_record($tables[2]->getName(), array('ival' => 5, 'name' => 'Five'), false); 6094 6095 // Intersection on the int column. 6096 $params = array('excludename' => 'Two'); 6097 $sql1 = 'SELECT ival FROM {'.$tables[0]->getName().'}'; 6098 $sql2 = 'SELECT ival FROM {'.$tables[1]->getName().'} WHERE name <> :excludename'; 6099 $sql3 = 'SELECT ival FROM {'.$tables[2]->getName().'}'; 6100 6101 $sql = $DB->sql_intersect(array($sql1), 'ival') . ' ORDER BY ival'; 6102 $this->assertEquals(array(1, 2, 3, 4), $DB->get_fieldset_sql($sql, $params)); 6103 6104 $sql = $DB->sql_intersect(array($sql1, $sql2), 'ival') . ' ORDER BY ival'; 6105 $this->assertEquals(array(1, 3), $DB->get_fieldset_sql($sql, $params)); 6106 6107 $sql = $DB->sql_intersect(array($sql1, $sql2, $sql3), 'ival') . ' ORDER BY ival'; 6108 $this->assertEquals(array(1), 6109 $DB->get_fieldset_sql($sql, $params)); 6110 6111 // Intersection on the char column. 6112 $params = array('excludeival' => 2); 6113 $sql1 = 'SELECT name FROM {'.$tables[0]->getName().'}'; 6114 $sql2 = 'SELECT name FROM {'.$tables[1]->getName().'} WHERE ival <> :excludeival'; 6115 $sql3 = 'SELECT name FROM {'.$tables[2]->getName().'}'; 6116 6117 $sql = $DB->sql_intersect(array($sql1), 'name') . ' ORDER BY name'; 6118 $this->assertEquals(array('Four', 'One', 'Three', 'Two'), $DB->get_fieldset_sql($sql, $params)); 6119 6120 $sql = $DB->sql_intersect(array($sql1, $sql2), 'name') . ' ORDER BY name'; 6121 $this->assertEquals(array('One', 'Three'), $DB->get_fieldset_sql($sql, $params)); 6122 6123 $sql = $DB->sql_intersect(array($sql1, $sql2, $sql3), 'name') . ' ORDER BY name'; 6124 $this->assertEquals(array('One'), $DB->get_fieldset_sql($sql, $params)); 6125 6126 // Intersection on the several columns. 6127 $params = array('excludename' => 'Two'); 6128 $sql1 = 'SELECT ival, name FROM {'.$tables[0]->getName().'}'; 6129 $sql2 = 'SELECT ival, name FROM {'.$tables[1]->getName().'} WHERE name <> :excludename'; 6130 $sql3 = 'SELECT ival, name FROM {'.$tables[2]->getName().'}'; 6131 6132 $sql = $DB->sql_intersect(array($sql1), 'ival, name') . ' ORDER BY ival'; 6133 $this->assertEquals(array(1 => 'One', 2 => 'Two', 3 => 'Three', 4 => 'Four'), 6134 $DB->get_records_sql_menu($sql, $params)); 6135 6136 $sql = $DB->sql_intersect(array($sql1, $sql2), 'ival, name') . ' ORDER BY ival'; 6137 $this->assertEquals(array(1 => 'One', 3 => 'Three'), 6138 $DB->get_records_sql_menu($sql, $params)); 6139 6140 $sql = $DB->sql_intersect(array($sql1, $sql2, $sql3), 'ival, name') . ' ORDER BY ival'; 6141 $this->assertEquals(array(1 => 'One'), 6142 $DB->get_records_sql_menu($sql, $params)); 6143 6144 // Drop temporary tables. 6145 foreach ($tables as $table) { 6146 $dbman->drop_table($table); 6147 } 6148 } 6149 6150 /** 6151 * Test that the database has full utf8 support (4 bytes). 6152 */ 6153 public function test_four_byte_character_insertion() { 6154 $DB = $this->tdb; 6155 6156 if ($DB->get_dbfamily() === 'mysql' && strpos($DB->get_dbcollation(), 'utf8_') === 0) { 6157 $this->markTestSkipped($DB->get_name() . 6158 ' does not support 4 byte characters with only a utf8 collation. 6159 Please change to utf8mb4 for full utf8 support.'); 6160 } 6161 6162 $dbman = $this->tdb->get_manager(); 6163 6164 $table = $this->get_test_table(); 6165 $tablename = $table->getName(); 6166 6167 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); 6168 $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null); 6169 $table->add_field('content', XMLDB_TYPE_TEXT, 'big', null, XMLDB_NOTNULL); 6170 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); 6171 $dbman->create_table($table); 6172 6173 $data = array( 6174 'name' => 'Name with a four byte character 𠮟る', 6175 'content' => 'Content with a four byte emoji 📝 memo.' 6176 ); 6177 6178 $insertid = $DB->insert_record($tablename, $data); 6179 $result = $DB->get_record($tablename, array('id' => $insertid)); 6180 $this->assertEquals($data['name'], $result->name); 6181 $this->assertEquals($data['content'], $result->content); 6182 6183 $dbman->drop_table($table); 6184 } 6185 6186 /** 6187 * Mock the methods used by {@see \mysqli_native_moodle_database::get_server_info()}. 6188 * 6189 * Mocking allows to test it without the need of an actual MySQL-ish running DB server. 6190 * 6191 * @param string $mysqliserverinfo A string representing the server info as provided by the MySQLi extension. 6192 * @param string $versionfromdb A string representing the result of VERSION function. 6193 * @param bool $cfgversionfromdb A boolean representing !empty($CFG->dboptions['versionfromdb']). 6194 * @param string $expecteddbversion A string representing the expected DB version. 6195 * @see \mysqli_native_moodle_database::get_server_info() 6196 * @covers \mysqli_native_moodle_database::get_server_info 6197 * @dataProvider get_server_info_mysql_provider 6198 */ 6199 public function test_get_server_info_mysql( 6200 string $mysqliserverinfo, string $versionfromdb, bool $cfgversionfromdb, string $expecteddbversion) { 6201 // Avoid to run MySQL-ish related tests when running tests on other DB families. 6202 $DB = $this->tdb; 6203 if ($DB->get_dbfamily() != 'mysql') { 6204 $this->markTestSkipped("Not MySQL family"); 6205 } 6206 6207 // Mock the methods used by get_server_info() to simulate different MySQL-ish DB servers. 6208 $methods = [ 6209 'get_mysqli_server_info', 6210 'get_version_from_db', 6211 'should_db_version_be_read_from_db', 6212 ]; 6213 $mysqlinativemoodledatabase = $this->getMockBuilder('\mysqli_native_moodle_database') 6214 ->onlyMethods($methods) 6215 ->getMock(); 6216 $mysqlinativemoodledatabase->method('get_mysqli_server_info')->willReturn($mysqliserverinfo); 6217 $mysqlinativemoodledatabase->method('get_version_from_db')->willReturn($versionfromdb); 6218 $mysqlinativemoodledatabase->method('should_db_version_be_read_from_db')->willReturn($cfgversionfromdb); 6219 6220 ['description' => $description, 'version' => $version] = $mysqlinativemoodledatabase->get_server_info(); 6221 $this->assertEquals($mysqliserverinfo, $description); 6222 $this->assertEquals($expecteddbversion, $version); 6223 } 6224 6225 /** 6226 * Data provider to test {@see \mysqli_native_moodle_database::get_server_info} when mocking 6227 * the results of a connection to the DB server. 6228 * 6229 * The set of the data is represented by the following array items: 6230 * - a string representing the server info as provided by the MySQLi extension 6231 * - a string representing the result of VERSION function 6232 * - a boolean representing !empty($CFG->dboptions['versionfromdb']) 6233 * - a string representing the expected DB version 6234 * 6235 * @return array[] 6236 * @see \mysqli_native_moodle_database::get_server_info 6237 */ 6238 public function get_server_info_mysql_provider() { 6239 return [ 6240 'MySQL 5.7.39 - MySQLi version' => [ 6241 '5.7.39-log', 6242 '', 6243 false, 6244 '5.7.39' 6245 ], 6246 'MySQL 5.7.40 - MySQLi version' => [ 6247 '5.7.40', 6248 '', 6249 false, 6250 '5.7.40' 6251 ], 6252 'MySQL 8.0.31 - MySQLi version' => [ 6253 '8.0.31', 6254 '', 6255 false, 6256 '8.0.31' 6257 ], 6258 'MariaDB 10.4.26 (https://moodle.org/mod/forum/discuss.php?d=441156#p1774957) - MySQLi version' => [ 6259 '10.4.26-MariaDB-1:10.4.26+mariadb~deb10', 6260 '', 6261 false, 6262 '10.4.26' 6263 ], 6264 'MariaDB 10.4.27 - MySQLi version' => [ 6265 '5.5.5-10.4.27-MariaDB', 6266 '', 6267 false, 6268 '10.4.27' 6269 ], 6270 'MariaDB 10.4.27 - DB version' => [ 6271 '', 6272 '10.4.27-MariaDB', 6273 true, 6274 '10.4.27' 6275 ], 6276 'MariaDB 10.7.7 - MySQLi version' => [ 6277 '10.7.7-MariaDB-1:10.7.7+maria~ubu2004', 6278 '', 6279 false, 6280 '10.7.7' 6281 ], 6282 'MariaDB 10.7.7 - DB version' => [ 6283 '', 6284 '10.7.7-MariaDB-1:10.7.7+maria~ubu2004', 6285 true, 6286 '10.7.7' 6287 ], 6288 'MariaDB 10.2.32 on Azure via gateway - MySQLi version' => [ 6289 '5.6.42.0', 6290 '10.2.32-MariaDB', 6291 false, 6292 '5.6.42.0' 6293 ], 6294 'MariaDB 10.2.32 on Azure via gateway - DB version' => [ 6295 '5.6.42.0', 6296 '10.2.32-MariaDB', 6297 true, 6298 '10.2.32' 6299 ], 6300 'MariaDB 10.3.23 on Azure via gateway - DB version' => [ 6301 '5.6.47.0', 6302 '10.3.23-MariaDB', 6303 true, 6304 '10.3.23' 6305 ], 6306 ]; 6307 } 6308 6309 /** 6310 * Test {@see \mysqli_native_moodle_database::get_server_info()} with the actual DB Server. 6311 * @see \mysqli_native_moodle_database::get_server_info 6312 * @covers \mysqli_native_moodle_database::get_server_info 6313 */ 6314 public function test_get_server_info_dbfamily_mysql() { 6315 $DB = $this->tdb; 6316 if ($DB->get_dbfamily() != 'mysql') { 6317 $this->markTestSkipped("Not MySQL family"); 6318 } 6319 6320 $cfg = $DB->export_dbconfig(); 6321 if (!isset($cfg->dboptions)) { 6322 $cfg->dboptions = []; 6323 } 6324 // By default, DB Server version is read from the PHP client. 6325 $this->assertTrue(empty($cfg->dboptions['versionfromdb'])); 6326 $rc = new \ReflectionClass(\mysqli_native_moodle_database::class); 6327 $rcm = $rc->getMethod('should_db_version_be_read_from_db'); 6328 $rcm->setAccessible(true); 6329 $this->assertFalse($rcm->invokeArgs($DB, [])); 6330 6331 ['description' => $description, 'version' => $version] = $DB->get_server_info(); 6332 // MariaDB RPL_VERSION_HACK sanity check: "5.5.5" has never been released! 6333 $this->assertNotSame('5.5.5', $version, 6334 "Found invalid DB server version i.e. RPL_VERSION_HACK: '{$version}' ({$description})."); 6335 // DB version format is: "X.Y.Z". 6336 $this->assertMatchesRegularExpression('/^\d+\.\d+\.\d+$/', $version, 6337 "Found invalid DB server version format: '{$version}' ({$description})."); 6338 6339 // Alter the DB options to force the read from DB and check for the same assertions above. 6340 $cfg->dboptions['versionfromdb'] = true; 6341 // Open a new DB connection with the forced setting. 6342 $db2 = moodle_database::get_driver_instance($cfg->dbtype, $cfg->dblibrary); 6343 $db2->connect($cfg->dbhost, $cfg->dbuser, $cfg->dbpass, $cfg->dbname, $cfg->prefix, $cfg->dboptions); 6344 $cfg2 = $db2->export_dbconfig(); 6345 $cfg = null; 6346 $this->assertNotEmpty($cfg2->dboptions); 6347 $this->assertFalse(empty($cfg2->dboptions['versionfromdb']), 'Invalid test state!'); 6348 $this->assertTrue($rcm->invokeArgs($db2, []), 'Invalid test state!'); 6349 ['description' => $description, 'version' => $version] = $db2->get_server_info(); 6350 $this->assertNotSame('5.5.5', $version, 6351 "Found invalid DB server version when reading version from DB i.e. RPL_VERSION_HACK: '{$version}' ({$description})."); 6352 $this->assertMatchesRegularExpression('/^\d+\.\d+\.\d+$/', $version, 6353 "Found invalid DB server version format when reading version from DB: '{$version}' ({$description})."); 6354 $db2->dispose(); 6355 } 6356 } 6357 6358 /** 6359 * This class is not a proper subclass of moodle_database. It is 6360 * intended to be used only in unit tests, in order to gain access to the 6361 * protected methods of moodle_database, and unit test them. 6362 */ 6363 class moodle_database_for_testing extends moodle_database { 6364 protected $prefix = 'mdl_'; 6365 6366 public function public_fix_table_names($sql) { 6367 return $this->fix_table_names($sql); 6368 } 6369 6370 public function driver_installed() {} 6371 public function get_dbfamily() {} 6372 protected function get_dbtype() {} 6373 protected function get_dblibrary() {} 6374 public function get_name() {} 6375 public function get_configuration_help() {} 6376 public function connect($dbhost, $dbuser, $dbpass, $dbname, $prefix, array $dboptions=null) {} 6377 public function get_server_info() {} 6378 protected function allowed_param_types() {} 6379 public function get_last_error() {} 6380 public function get_tables($usecache=true) {} 6381 public function get_indexes($table) {} 6382 protected function fetch_columns(string $table): array { 6383 return []; 6384 } 6385 protected function normalise_value($column, $value) {} 6386 public function set_debug($state) {} 6387 public function get_debug() {} 6388 public function change_database_structure($sql, $tablenames = null) {} 6389 public function execute($sql, array $params=null) {} 6390 public function get_recordset_sql($sql, array $params=null, $limitfrom=0, $limitnum=0) {} 6391 public function get_records_sql($sql, array $params=null, $limitfrom=0, $limitnum=0) {} 6392 public function get_fieldset_sql($sql, array $params=null) {} 6393 public function insert_record_raw($table, $params, $returnid=true, $bulk=false, $customsequence=false) {} 6394 public function insert_record($table, $dataobject, $returnid=true, $bulk=false) {} 6395 public function import_record($table, $dataobject) {} 6396 public function update_record_raw($table, $params, $bulk=false) {} 6397 public function update_record($table, $dataobject, $bulk=false) {} 6398 public function set_field_select($table, $newfield, $newvalue, $select, array $params=null) {} 6399 public function delete_records_select($table, $select, array $params=null) {} 6400 public function sql_concat() {} 6401 public function sql_concat_join($separator="' '", $elements=array()) {} 6402 public function sql_group_concat(string $field, string $separator = ', ', string $sort = ''): string { 6403 return ''; 6404 } 6405 public function sql_substr($expr, $start, $length=false) {} 6406 public function begin_transaction() {} 6407 public function commit_transaction() {} 6408 public function rollback_transaction() {} 6409 } 6410 6411 6412 /** 6413 * Dumb test class with toString() returning 1. 6414 */ 6415 class dml_test_object_one { 6416 public function __toString() { 6417 return 1; 6418 } 6419 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body