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