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