223eaeee8df1a4627429fc17481683b56c12c8f2
[moodle.git] / lib / dml / tests / dml_test.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * DML layer tests.
19  *
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
24  */
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.
33     }
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.
39      *
40      * @param string $suffix table name suffix, use if you need more test tables
41      * @return xmldb_table the table object.
42      */
43     private function get_test_table($suffix = '') {
44         $tablename = "test_table";
45         if ($suffix !== '') {
46             $tablename .= $suffix;
47         }
49         $table = new xmldb_table($tablename);
50         $table->setComment("This is a test'n drop table. You can drop it safely");
51         return new xmldb_table($tablename);
52     }
54     public function test_diagnose() {
55         $DB = $this->tdb;
56         $result = $DB->diagnose();
57         $this->assertNull($result, 'Database self diagnostics failed %s');
58     }
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);
66     }
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);
80         }
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);
105         }
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;
133         }
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;
166         }
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);
205         }
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);
214         }
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');
274     }
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));
296     }
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);
346         }
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);
356         }
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);
366         }
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);
383         }
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);
394         }
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);
411         }
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));
439     }
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');
461     }
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++;
539         }
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));
594     }
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);
614     }
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;
642         }
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]);
650     }
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('enumfield', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, 'test2');
664         $table->add_field('onenum', XMLDB_TYPE_NUMBER, '10,2', null, null, null, 200);
665         $table->add_field('onefloat', XMLDB_TYPE_FLOAT, '10,2', null, null, null, 300);
666         $table->add_field('anotherfloat', XMLDB_TYPE_FLOAT, null, null, null, null, 400);
667         $table->add_field('negativedfltint', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '-1');
668         $table->add_field('negativedfltnumber', XMLDB_TYPE_NUMBER, '10', null, XMLDB_NOTNULL, null, '-2');
669         $table->add_field('negativedfltfloat', XMLDB_TYPE_FLOAT, '10', null, XMLDB_NOTNULL, null, '-3');
670         $table->add_field('someint1', XMLDB_TYPE_INTEGER, '1', null, null, null, '0');
671         $table->add_field('someint2', XMLDB_TYPE_INTEGER, '2', null, null, null, '0');
672         $table->add_field('someint3', XMLDB_TYPE_INTEGER, '3', null, null, null, '0');
673         $table->add_field('someint4', XMLDB_TYPE_INTEGER, '4', null, null, null, '0');
674         $table->add_field('someint5', XMLDB_TYPE_INTEGER, '5', null, null, null, '0');
675         $table->add_field('someint6', XMLDB_TYPE_INTEGER, '6', null, null, null, '0');
676         $table->add_field('someint7', XMLDB_TYPE_INTEGER, '7', null, null, null, '0');
677         $table->add_field('someint8', XMLDB_TYPE_INTEGER, '8', null, null, null, '0');
678         $table->add_field('someint9', XMLDB_TYPE_INTEGER, '9', null, null, null, '0');
679         $table->add_field('someint10', XMLDB_TYPE_INTEGER, '10', null, null, null, '0');
680         $table->add_field('someint18', XMLDB_TYPE_INTEGER, '18', null, null, null, '0');
681         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
682         $dbman->create_table($table);
684         $columns = $DB->get_columns($tablename);
685         $this->assertInternalType('array', $columns);
687         $fields = $table->getFields();
688         $this->assertCount(count($columns), $fields);
690         $field = $columns['id'];
691         $this->assertSame('R', $field->meta_type);
692         $this->assertTrue($field->auto_increment);
693         $this->assertTrue($field->unique);
695         $field = $columns['course'];
696         $this->assertSame('I', $field->meta_type);
697         $this->assertFalse($field->auto_increment);
698         $this->assertTrue($field->has_default);
699         $this->assertEquals(0, $field->default_value);
700         $this->assertTrue($field->not_null);
702         for ($i=1; $i<=10; $i++) {
703             $field = $columns['someint'.$i];
704             $this->assertSame('I', $field->meta_type);
705             $this->assertGreaterThanOrEqual($i, $field->max_length);
706         }
707         $field = $columns['someint18'];
708         $this->assertSame('I', $field->meta_type);
709         $this->assertGreaterThanOrEqual(18, $field->max_length);
711         $field = $columns['name'];
712         $this->assertSame('C', $field->meta_type);
713         $this->assertFalse($field->auto_increment);
714         $this->assertEquals(255, $field->max_length);
715         $this->assertTrue($field->has_default);
716         $this->assertSame('lala', $field->default_value);
717         $this->assertFalse($field->not_null);
719         $field = $columns['description'];
720         $this->assertSame('X', $field->meta_type);
721         $this->assertFalse($field->auto_increment);
722         $this->assertFalse($field->has_default);
723         $this->assertNull($field->default_value);
724         $this->assertFalse($field->not_null);
726         $field = $columns['enumfield'];
727         $this->assertSame('C', $field->meta_type);
728         $this->assertFalse($field->auto_increment);
729         $this->assertSame('test2', $field->default_value);
730         $this->assertTrue($field->not_null);
732         $field = $columns['onenum'];
733         $this->assertSame('N', $field->meta_type);
734         $this->assertFalse($field->auto_increment);
735         $this->assertEquals(10, $field->max_length);
736         $this->assertEquals(2, $field->scale);
737         $this->assertTrue($field->has_default);
738         $this->assertEquals(200.0, $field->default_value);
739         $this->assertFalse($field->not_null);
741         $field = $columns['onefloat'];
742         $this->assertSame('N', $field->meta_type);
743         $this->assertFalse($field->auto_increment);
744         $this->assertTrue($field->has_default);
745         $this->assertEquals(300.0, $field->default_value);
746         $this->assertFalse($field->not_null);
748         $field = $columns['anotherfloat'];
749         $this->assertSame('N', $field->meta_type);
750         $this->assertFalse($field->auto_increment);
751         $this->assertTrue($field->has_default);
752         $this->assertEquals(400.0, $field->default_value);
753         $this->assertFalse($field->not_null);
755         // Test negative defaults in numerical columns.
756         $field = $columns['negativedfltint'];
757         $this->assertTrue($field->has_default);
758         $this->assertEquals(-1, $field->default_value);
760         $field = $columns['negativedfltnumber'];
761         $this->assertTrue($field->has_default);
762         $this->assertEquals(-2, $field->default_value);
764         $field = $columns['negativedfltfloat'];
765         $this->assertTrue($field->has_default);
766         $this->assertEquals(-3, $field->default_value);
768         for ($i = 0; $i < count($columns); $i++) {
769             if ($i == 0) {
770                 $next_column = reset($columns);
771                 $next_field  = reset($fields);
772             } else {
773                 $next_column = next($columns);
774                 $next_field  = next($fields);
775             }
777             $this->assertEquals($next_column->name, $next_field->getName());
778         }
780         // Test get_columns for non-existing table returns empty array. MDL-30147.
781         $columns = $DB->get_columns('xxxx');
782         $this->assertEquals(array(), $columns);
784         // Create something similar to "context_temp" with id column without sequence.
785         $dbman->drop_table($table);
786         $table = $this->get_test_table();
787         $tablename = $table->getName();
788         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
789         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
790         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
791         $dbman->create_table($table);
793         $columns = $DB->get_columns($tablename);
794         $this->assertFalse($columns['id']->auto_increment);
795     }
797     public function test_get_manager() {
798         $DB = $this->tdb;
799         $dbman = $this->tdb->get_manager();
801         $this->assertInstanceOf('database_manager', $dbman);
802     }
804     public function test_setup_is_unicodedb() {
805         $DB = $this->tdb;
806         $this->assertTrue($DB->setup_is_unicodedb());
807     }
809     public function test_set_debug() { // Tests get_debug() too.
810         $DB = $this->tdb;
811         $dbman = $this->tdb->get_manager();
813         $table = $this->get_test_table();
814         $tablename = $table->getName();
816         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
817         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
818         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
819         $dbman->create_table($table);
821         $sql = "SELECT * FROM {{$tablename}}";
823         $prevdebug = $DB->get_debug();
825         ob_start();
826         $DB->set_debug(true);
827         $this->assertTrue($DB->get_debug());
828         $DB->execute($sql);
829         $DB->set_debug(false);
830         $this->assertFalse($DB->get_debug());
831         $debuginfo = ob_get_contents();
832         ob_end_clean();
833         $this->assertFalse($debuginfo === '');
835         ob_start();
836         $DB->execute($sql);
837         $debuginfo = ob_get_contents();
838         ob_end_clean();
839         $this->assertTrue($debuginfo === '');
841         $DB->set_debug($prevdebug);
842     }
844     public function test_execute() {
845         $DB = $this->tdb;
846         $dbman = $this->tdb->get_manager();
848         $table1 = $this->get_test_table('1');
849         $tablename1 = $table1->getName();
850         $table1->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
851         $table1->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
852         $table1->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, '0');
853         $table1->add_index('course', XMLDB_INDEX_NOTUNIQUE, array('course'));
854         $table1->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
855         $dbman->create_table($table1);
857         $table2 = $this->get_test_table('2');
858         $tablename2 = $table2->getName();
859         $table2->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
860         $table2->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
861         $table2->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
862         $dbman->create_table($table2);
864         $DB->insert_record($tablename1, array('course' => 3, 'name' => 'aaa'));
865         $DB->insert_record($tablename1, array('course' => 1, 'name' => 'bbb'));
866         $DB->insert_record($tablename1, array('course' => 7, 'name' => 'ccc'));
867         $DB->insert_record($tablename1, array('course' => 3, 'name' => 'ddd'));
869         // Select results are ignored.
870         $sql = "SELECT * FROM {{$tablename1}} WHERE course = :course";
871         $this->assertTrue($DB->execute($sql, array('course'=>3)));
873         // Throw exception on error.
874         $sql = "XXUPDATE SET XSSD";
875         try {
876             $DB->execute($sql);
877             $this->fail("Expecting an exception, none occurred");
878         } catch (moodle_exception $e) {
879             $this->assertInstanceOf('dml_exception', $e);
880         }
882         // Update records.
883         $sql = "UPDATE {{$tablename1}}
884                    SET course = 6
885                  WHERE course = ?";
886         $this->assertTrue($DB->execute($sql, array('3')));
887         $this->assertEquals(2, $DB->count_records($tablename1, array('course' => 6)));
889         // Update records with subquery condition.
890         // Confirm that the option not using table aliases is cross-db.
891         $sql = "UPDATE {{$tablename1}}
892                    SET course = 0
893                  WHERE NOT EXISTS (
894                            SELECT course
895                              FROM {{$tablename2}} tbl2
896                             WHERE tbl2.course = {{$tablename1}}.course
897                               AND 1 = 0)"; // Really we don't update anything, but verify the syntax is allowed.
898         $this->assertTrue($DB->execute($sql));
900         // Insert from one into second table.
901         $sql = "INSERT INTO {{$tablename2}} (course)
903                 SELECT course
904                   FROM {{$tablename1}}";
905         $this->assertTrue($DB->execute($sql));
906         $this->assertEquals(4, $DB->count_records($tablename2));
907     }
909     public function test_get_recordset() {
910         $DB = $this->tdb;
911         $dbman = $DB->get_manager();
913         $table = $this->get_test_table();
914         $tablename = $table->getName();
916         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
917         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
918         $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, '0');
919         $table->add_field('onetext', XMLDB_TYPE_TEXT, 'big', null, null, null);
920         $table->add_index('course', XMLDB_INDEX_NOTUNIQUE, array('course'));
921         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
922         $dbman->create_table($table);
924         $data = array(array('course' => 3, 'name' => 'record1', 'onetext'=>'abc'),
925             array('course' => 3, 'name' => 'record2', 'onetext'=>'abcd'),
926             array('course' => 5, 'name' => 'record3', 'onetext'=>'abcde'));
928         foreach ($data as $key => $record) {
929             $data[$key]['id'] = $DB->insert_record($tablename, $record);
930         }
932         // Standard recordset iteration.
933         $rs = $DB->get_recordset($tablename);
934         $this->assertInstanceOf('moodle_recordset', $rs);
935         reset($data);
936         foreach ($rs as $record) {
937             $data_record = current($data);
938             foreach ($record as $k => $v) {
939                 $this->assertEquals($data_record[$k], $v);
940             }
941             next($data);
942         }
943         $rs->close();
945         // Iterator style usage.
946         $rs = $DB->get_recordset($tablename);
947         $this->assertInstanceOf('moodle_recordset', $rs);
948         reset($data);
949         while ($rs->valid()) {
950             $record = $rs->current();
951             $data_record = current($data);
952             foreach ($record as $k => $v) {
953                 $this->assertEquals($data_record[$k], $v);
954             }
955             next($data);
956             $rs->next();
957         }
958         $rs->close();
960         // Make sure rewind is ignored.
961         $rs = $DB->get_recordset($tablename);
962         $this->assertInstanceOf('moodle_recordset', $rs);
963         reset($data);
964         $i = 0;
965         foreach ($rs as $record) {
966             $i++;
967             $rs->rewind();
968             if ($i > 10) {
969                 $this->fail('revind not ignored in recordsets');
970                 break;
971             }
972             $data_record = current($data);
973             foreach ($record as $k => $v) {
974                 $this->assertEquals($data_record[$k], $v);
975             }
976             next($data);
977         }
978         $rs->close();
980         // Test for exception throwing on text conditions being compared. (MDL-24863, unwanted auto conversion of param to int).
981         $conditions = array('onetext' => '1');
982         try {
983             $rs = $DB->get_recordset($tablename, $conditions);
984             $this->fail('An Exception is missing, expected due to equating of text fields');
985         } catch (moodle_exception $e) {
986             $this->assertInstanceOf('dml_exception', $e);
987             $this->assertSame('textconditionsnotallowed', $e->errorcode);
988         }
990         // Test nested iteration.
991         $rs1 = $DB->get_recordset($tablename);
992         $i = 0;
993         foreach ($rs1 as $record1) {
994             $rs2 = $DB->get_recordset($tablename);
995             $i++;
996             $j = 0;
997             foreach ($rs2 as $record2) {
998                 $j++;
999             }
1000             $rs2->close();
1001             $this->assertCount($j, $data);
1002         }
1003         $rs1->close();
1004         $this->assertCount($i, $data);
1006         // Notes:
1007         //  * limits are tested in test_get_recordset_sql()
1008         //  * where_clause() is used internally and is tested in test_get_records()
1009     }
1011     public function test_get_recordset_static() {
1012         $DB = $this->tdb;
1013         $dbman = $DB->get_manager();
1015         $table = $this->get_test_table();
1016         $tablename = $table->getName();
1018         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1019         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1020         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1021         $dbman->create_table($table);
1023         $DB->insert_record($tablename, array('course' => 1));
1024         $DB->insert_record($tablename, array('course' => 2));
1025         $DB->insert_record($tablename, array('course' => 3));
1026         $DB->insert_record($tablename, array('course' => 4));
1028         $rs = $DB->get_recordset($tablename, array(), 'id');
1030         $DB->set_field($tablename, 'course', 666, array('course'=>1));
1031         $DB->delete_records($tablename, array('course'=>2));
1033         $i = 0;
1034         foreach ($rs as $record) {
1035             $i++;
1036             $this->assertEquals($i, $record->course);
1037         }
1038         $rs->close();
1039         $this->assertEquals(4, $i);
1041         // Now repeat with limits because it may use different code.
1042         $DB->delete_records($tablename, array());
1044         $DB->insert_record($tablename, array('course' => 1));
1045         $DB->insert_record($tablename, array('course' => 2));
1046         $DB->insert_record($tablename, array('course' => 3));
1047         $DB->insert_record($tablename, array('course' => 4));
1049         $rs = $DB->get_recordset($tablename, array(), 'id', '*', 0, 3);
1051         $DB->set_field($tablename, 'course', 666, array('course'=>1));
1052         $DB->delete_records($tablename, array('course'=>2));
1054         $i = 0;
1055         foreach ($rs as $record) {
1056             $i++;
1057             $this->assertEquals($i, $record->course);
1058         }
1059         $rs->close();
1060         $this->assertEquals(3, $i);
1061     }
1063     public function test_get_recordset_iterator_keys() {
1064         $DB = $this->tdb;
1065         $dbman = $DB->get_manager();
1067         $table = $this->get_test_table();
1068         $tablename = $table->getName();
1070         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1071         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1072         $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, '0');
1073         $table->add_index('course', XMLDB_INDEX_NOTUNIQUE, array('course'));
1074         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1075         $dbman->create_table($table);
1077         $data = array(array('course' => 3, 'name' => 'record1'),
1078             array('course' => 3, 'name' => 'record2'),
1079             array('course' => 5, 'name' => 'record3'));
1080         foreach ($data as $key => $record) {
1081             $data[$key]['id'] = $DB->insert_record($tablename, $record);
1082         }
1084         // Test repeated numeric keys are returned ok.
1085         $rs = $DB->get_recordset($tablename, null, null, 'course, name, id');
1087         reset($data);
1088         $count = 0;
1089         foreach ($rs as $key => $record) {
1090             $data_record = current($data);
1091             $this->assertEquals($data_record['course'], $key);
1092             next($data);
1093             $count++;
1094         }
1095         $rs->close();
1096         $this->assertEquals(3, $count);
1098         // Test string keys are returned ok.
1099         $rs = $DB->get_recordset($tablename, null, null, 'name, course, id');
1101         reset($data);
1102         $count = 0;
1103         foreach ($rs as $key => $record) {
1104             $data_record = current($data);
1105             $this->assertEquals($data_record['name'], $key);
1106             next($data);
1107             $count++;
1108         }
1109         $rs->close();
1110         $this->assertEquals(3, $count);
1112         // Test numeric not starting in 1 keys are returned ok.
1113         $rs = $DB->get_recordset($tablename, null, 'id DESC', 'id, course, name');
1115         $data = array_reverse($data);
1116         reset($data);
1117         $count = 0;
1118         foreach ($rs as $key => $record) {
1119             $data_record = current($data);
1120             $this->assertEquals($data_record['id'], $key);
1121             next($data);
1122             $count++;
1123         }
1124         $rs->close();
1125         $this->assertEquals(3, $count);
1126     }
1128     public function test_get_recordset_list() {
1129         $DB = $this->tdb;
1130         $dbman = $DB->get_manager();
1132         $table = $this->get_test_table();
1133         $tablename = $table->getName();
1135         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1136         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, null, null, '0');
1137         $table->add_index('course', XMLDB_INDEX_NOTUNIQUE, array('course'));
1138         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1139         $dbman->create_table($table);
1141         $DB->insert_record($tablename, array('course' => 3));
1142         $DB->insert_record($tablename, array('course' => 3));
1143         $DB->insert_record($tablename, array('course' => 5));
1144         $DB->insert_record($tablename, array('course' => 2));
1145         $DB->insert_record($tablename, array('course' => null));
1146         $DB->insert_record($tablename, array('course' => 1));
1147         $DB->insert_record($tablename, array('course' => 0));
1149         $rs = $DB->get_recordset_list($tablename, 'course', array(3, 2));
1150         $counter = 0;
1151         foreach ($rs as $record) {
1152             $counter++;
1153         }
1154         $this->assertEquals(3, $counter);
1155         $rs->close();
1157         $rs = $DB->get_recordset_list($tablename, 'course', array(3));
1158         $counter = 0;
1159         foreach ($rs as $record) {
1160             $counter++;
1161         }
1162         $this->assertEquals(2, $counter);
1163         $rs->close();
1165         $rs = $DB->get_recordset_list($tablename, 'course', array(null));
1166         $counter = 0;
1167         foreach ($rs as $record) {
1168             $counter++;
1169         }
1170         $this->assertEquals(1, $counter);
1171         $rs->close();
1173         $rs = $DB->get_recordset_list($tablename, 'course', array(6, null));
1174         $counter = 0;
1175         foreach ($rs as $record) {
1176             $counter++;
1177         }
1178         $this->assertEquals(1, $counter);
1179         $rs->close();
1181         $rs = $DB->get_recordset_list($tablename, 'course', array(null, 5, 5, 5));
1182         $counter = 0;
1183         foreach ($rs as $record) {
1184             $counter++;
1185         }
1186         $this->assertEquals(2, $counter);
1187         $rs->close();
1189         $rs = $DB->get_recordset_list($tablename, 'course', array(true));
1190         $counter = 0;
1191         foreach ($rs as $record) {
1192             $counter++;
1193         }
1194         $this->assertEquals(1, $counter);
1195         $rs->close();
1197         $rs = $DB->get_recordset_list($tablename, 'course', array(false));
1198         $counter = 0;
1199         foreach ($rs as $record) {
1200             $counter++;
1201         }
1202         $this->assertEquals(1, $counter);
1203         $rs->close();
1205         $rs = $DB->get_recordset_list($tablename, 'course', array()); // Must return 0 rows without conditions. MDL-17645.
1207         $counter = 0;
1208         foreach ($rs as $record) {
1209             $counter++;
1210         }
1211         $rs->close();
1212         $this->assertEquals(0, $counter);
1214         // Notes:
1215         //  * limits are tested in test_get_recordset_sql()
1216         //  * where_clause() is used internally and is tested in test_get_records()
1217     }
1219     public function test_get_recordset_select() {
1220         $DB = $this->tdb;
1221         $dbman = $DB->get_manager();
1223         $table = $this->get_test_table();
1224         $tablename = $table->getName();
1226         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1227         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1228         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1229         $dbman->create_table($table);
1231         $DB->insert_record($tablename, array('course' => 3));
1232         $DB->insert_record($tablename, array('course' => 3));
1233         $DB->insert_record($tablename, array('course' => 5));
1234         $DB->insert_record($tablename, array('course' => 2));
1236         $rs = $DB->get_recordset_select($tablename, '');
1237         $counter = 0;
1238         foreach ($rs as $record) {
1239             $counter++;
1240         }
1241         $rs->close();
1242         $this->assertEquals(4, $counter);
1244         $this->assertNotEmpty($rs = $DB->get_recordset_select($tablename, 'course = 3'));
1245         $counter = 0;
1246         foreach ($rs as $record) {
1247             $counter++;
1248         }
1249         $rs->close();
1250         $this->assertEquals(2, $counter);
1252         // Notes:
1253         //  * limits are tested in test_get_recordset_sql()
1254     }
1256     public function test_get_recordset_sql() {
1257         $DB = $this->tdb;
1258         $dbman = $DB->get_manager();
1260         $table = $this->get_test_table();
1261         $tablename = $table->getName();
1263         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1264         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1265         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1266         $dbman->create_table($table);
1268         $inskey1 = $DB->insert_record($tablename, array('course' => 3));
1269         $inskey2 = $DB->insert_record($tablename, array('course' => 5));
1270         $inskey3 = $DB->insert_record($tablename, array('course' => 4));
1271         $inskey4 = $DB->insert_record($tablename, array('course' => 3));
1272         $inskey5 = $DB->insert_record($tablename, array('course' => 2));
1273         $inskey6 = $DB->insert_record($tablename, array('course' => 1));
1274         $inskey7 = $DB->insert_record($tablename, array('course' => 0));
1276         $rs = $DB->get_recordset_sql("SELECT * FROM {{$tablename}} WHERE course = ?", array(3));
1277         $counter = 0;
1278         foreach ($rs as $record) {
1279             $counter++;
1280         }
1281         $rs->close();
1282         $this->assertEquals(2, $counter);
1284         // Limits - only need to test this case, the rest have been tested by test_get_records_sql()
1285         // only limitfrom = skips that number of records.
1286         $rs = $DB->get_recordset_sql("SELECT * FROM {{$tablename}} ORDER BY id", null, 2, 0);
1287         $records = array();
1288         foreach ($rs as $key => $record) {
1289             $records[$key] = $record;
1290         }
1291         $rs->close();
1292         $this->assertCount(5, $records);
1293         $this->assertEquals($inskey3, reset($records)->id);
1294         $this->assertEquals($inskey7, end($records)->id);
1296         // Note: fetching nulls, empties, LOBs already tested by test_insert_record() no needed here.
1297     }
1299     public function test_export_table_recordset() {
1300         $DB = $this->tdb;
1301         $dbman = $DB->get_manager();
1303         $table = $this->get_test_table();
1304         $tablename = $table->getName();
1306         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1307         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1308         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1309         $dbman->create_table($table);
1311         $ids = array();
1312         $ids[] = $DB->insert_record($tablename, array('course' => 3));
1313         $ids[] = $DB->insert_record($tablename, array('course' => 5));
1314         $ids[] = $DB->insert_record($tablename, array('course' => 4));
1315         $ids[] = $DB->insert_record($tablename, array('course' => 3));
1316         $ids[] = $DB->insert_record($tablename, array('course' => 2));
1317         $ids[] = $DB->insert_record($tablename, array('course' => 1));
1318         $ids[] = $DB->insert_record($tablename, array('course' => 0));
1320         $rs = $DB->export_table_recordset($tablename);
1321         $rids = array();
1322         foreach ($rs as $record) {
1323             $rids[] = $record->id;
1324         }
1325         $rs->close();
1326         $this->assertEquals($ids, $rids, '', 0, 0, true);
1327     }
1329     public function test_get_records() {
1330         $DB = $this->tdb;
1331         $dbman = $DB->get_manager();
1333         $table = $this->get_test_table();
1334         $tablename = $table->getName();
1336         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1337         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1338         $table->add_field('onetext', XMLDB_TYPE_TEXT, 'big', null, null, null);
1339         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1340         $dbman->create_table($table);
1342         $DB->insert_record($tablename, array('course' => 3));
1343         $DB->insert_record($tablename, array('course' => 3));
1344         $DB->insert_record($tablename, array('course' => 5));
1345         $DB->insert_record($tablename, array('course' => 2));
1347         // All records.
1348         $records = $DB->get_records($tablename);
1349         $this->assertCount(4, $records);
1350         $this->assertEquals(3, $records[1]->course);
1351         $this->assertEquals(3, $records[2]->course);
1352         $this->assertEquals(5, $records[3]->course);
1353         $this->assertEquals(2, $records[4]->course);
1355         // Records matching certain conditions.
1356         $records = $DB->get_records($tablename, array('course' => 3));
1357         $this->assertCount(2, $records);
1358         $this->assertEquals(3, $records[1]->course);
1359         $this->assertEquals(3, $records[2]->course);
1361         // All records sorted by course.
1362         $records = $DB->get_records($tablename, null, 'course');
1363         $this->assertCount(4, $records);
1364         $current_record = reset($records);
1365         $this->assertEquals(4, $current_record->id);
1366         $current_record = next($records);
1367         $this->assertEquals(1, $current_record->id);
1368         $current_record = next($records);
1369         $this->assertEquals(2, $current_record->id);
1370         $current_record = next($records);
1371         $this->assertEquals(3, $current_record->id);
1373         // All records, but get only one field.
1374         $records = $DB->get_records($tablename, null, '', 'id');
1375         $this->assertFalse(isset($records[1]->course));
1376         $this->assertTrue(isset($records[1]->id));
1377         $this->assertCount(4, $records);
1379         // Booleans into params.
1380         $records = $DB->get_records($tablename, array('course' => true));
1381         $this->assertCount(0, $records);
1382         $records = $DB->get_records($tablename, array('course' => false));
1383         $this->assertCount(0, $records);
1385         // Test for exception throwing on text conditions being compared. (MDL-24863, unwanted auto conversion of param to int).
1386         $conditions = array('onetext' => '1');
1387         try {
1388             $records = $DB->get_records($tablename, $conditions);
1389             if (debugging()) {
1390                 // Only in debug mode - hopefully all devs test code in debug mode...
1391                 $this->fail('An Exception is missing, expected due to equating of text fields');
1392             }
1393         } catch (moodle_exception $e) {
1394             $this->assertInstanceOf('dml_exception', $e);
1395             $this->assertSame('textconditionsnotallowed', $e->errorcode);
1396         }
1398         // Test get_records passing non-existing table.
1399         // with params.
1400         try {
1401             $records = $DB->get_records('xxxx', array('id' => 0));
1402             $this->fail('An Exception is missing, expected due to query against non-existing table');
1403         } catch (moodle_exception $e) {
1404             $this->assertInstanceOf('dml_exception', $e);
1405             if (debugging()) {
1406                 // Information for developers only, normal users get general error message.
1407                 $this->assertSame('ddltablenotexist', $e->errorcode);
1408             }
1409         }
1410         // And without params.
1411         try {
1412             $records = $DB->get_records('xxxx', array());
1413             $this->fail('An Exception is missing, expected due to query against non-existing table');
1414         } catch (moodle_exception $e) {
1415             $this->assertInstanceOf('dml_exception', $e);
1416             if (debugging()) {
1417                 // Information for developers only, normal users get general error message.
1418                 $this->assertSame('ddltablenotexist', $e->errorcode);
1419             }
1420         }
1422         // Test get_records passing non-existing column.
1423         try {
1424             $records = $DB->get_records($tablename, array('xxxx' => 0));
1425             $this->fail('An Exception is missing, expected due to query against non-existing column');
1426         } catch (moodle_exception $e) {
1427             $this->assertInstanceOf('dml_exception', $e);
1428             if (debugging()) {
1429                 // Information for developers only, normal users get general error message.
1430                 $this->assertSame('ddlfieldnotexist', $e->errorcode);
1431             }
1432         }
1434         // Note: delegate limits testing to test_get_records_sql().
1435     }
1437     public function test_get_records_list() {
1438         $DB = $this->tdb;
1439         $dbman = $DB->get_manager();
1441         $table = $this->get_test_table();
1442         $tablename = $table->getName();
1444         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1445         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1446         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1447         $dbman->create_table($table);
1449         $DB->insert_record($tablename, array('course' => 3));
1450         $DB->insert_record($tablename, array('course' => 3));
1451         $DB->insert_record($tablename, array('course' => 5));
1452         $DB->insert_record($tablename, array('course' => 2));
1454         $records = $DB->get_records_list($tablename, 'course', array(3, 2));
1455         $this->assertInternalType('array', $records);
1456         $this->assertCount(3, $records);
1457         $this->assertEquals(1, reset($records)->id);
1458         $this->assertEquals(2, next($records)->id);
1459         $this->assertEquals(4, next($records)->id);
1461         $this->assertSame(array(), $records = $DB->get_records_list($tablename, 'course', array())); // Must return 0 rows without conditions. MDL-17645.
1462         $this->assertCount(0, $records);
1464         // Note: delegate limits testing to test_get_records_sql().
1465     }
1467     public function test_get_records_sql() {
1468         $DB = $this->tdb;
1469         $dbman = $DB->get_manager();
1471         $table = $this->get_test_table();
1472         $tablename = $table->getName();
1474         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1475         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1476         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1477         $dbman->create_table($table);
1479         $inskey1 = $DB->insert_record($tablename, array('course' => 3));
1480         $inskey2 = $DB->insert_record($tablename, array('course' => 5));
1481         $inskey3 = $DB->insert_record($tablename, array('course' => 4));
1482         $inskey4 = $DB->insert_record($tablename, array('course' => 3));
1483         $inskey5 = $DB->insert_record($tablename, array('course' => 2));
1484         $inskey6 = $DB->insert_record($tablename, array('course' => 1));
1485         $inskey7 = $DB->insert_record($tablename, array('course' => 0));
1487         $table2 = $this->get_test_table("2");
1488         $tablename2 = $table2->getName();
1489         $table2->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1490         $table2->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1491         $table2->add_field('nametext', XMLDB_TYPE_TEXT, 'small', null, null, null, null);
1492         $table2->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1493         $dbman->create_table($table2);
1495         $DB->insert_record($tablename2, array('course'=>3, 'nametext'=>'badabing'));
1496         $DB->insert_record($tablename2, array('course'=>4, 'nametext'=>'badabang'));
1497         $DB->insert_record($tablename2, array('course'=>5, 'nametext'=>'badabung'));
1498         $DB->insert_record($tablename2, array('course'=>6, 'nametext'=>'badabong'));
1500         $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE course = ?", array(3));
1501         $this->assertCount(2, $records);
1502         $this->assertEquals($inskey1, reset($records)->id);
1503         $this->assertEquals($inskey4, next($records)->id);
1505         // Awful test, requires debug enabled and sent to browser. Let's do that and restore after test.
1506         $records = $DB->get_records_sql("SELECT course AS id, course AS course FROM {{$tablename}}", null);
1507         $this->assertDebuggingCalled();
1508         $this->assertCount(6, $records);
1509         set_debugging(DEBUG_MINIMAL);
1510         $records = $DB->get_records_sql("SELECT course AS id, course AS course FROM {{$tablename}}", null);
1511         $this->assertDebuggingNotCalled();
1512         $this->assertCount(6, $records);
1513         set_debugging(DEBUG_DEVELOPER);
1515         // Negative limits = no limits.
1516         $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} ORDER BY id", null, -1, -1);
1517         $this->assertCount(7, $records);
1519         // Zero limits = no limits.
1520         $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} ORDER BY id", null, 0, 0);
1521         $this->assertCount(7, $records);
1523         // Only limitfrom = skips that number of records.
1524         $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} ORDER BY id", null, 2, 0);
1525         $this->assertCount(5, $records);
1526         $this->assertEquals($inskey3, reset($records)->id);
1527         $this->assertEquals($inskey7, end($records)->id);
1529         // Only limitnum = fetches that number of records.
1530         $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} ORDER BY id", null, 0, 3);
1531         $this->assertCount(3, $records);
1532         $this->assertEquals($inskey1, reset($records)->id);
1533         $this->assertEquals($inskey3, end($records)->id);
1535         // Both limitfrom and limitnum = skips limitfrom records and fetches limitnum ones.
1536         $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} ORDER BY id", null, 3, 2);
1537         $this->assertCount(2, $records);
1538         $this->assertEquals($inskey4, reset($records)->id);
1539         $this->assertEquals($inskey5, end($records)->id);
1541         // Both limitfrom and limitnum in query having subqueris.
1542         // Note the subquery skips records with course = 0 and 3.
1543         $sql = "SELECT * FROM {{$tablename}}
1544                  WHERE course NOT IN (
1545                      SELECT course FROM {{$tablename}}
1546                       WHERE course IN (0, 3))
1547                 ORDER BY course";
1548         $records = $DB->get_records_sql($sql, null, 0, 2); // Skip 0, get 2.
1549         $this->assertCount(2, $records);
1550         $this->assertEquals($inskey6, reset($records)->id);
1551         $this->assertEquals($inskey5, end($records)->id);
1552         $records = $DB->get_records_sql($sql, null, 2, 2); // Skip 2, get 2.
1553         $this->assertCount(2, $records);
1554         $this->assertEquals($inskey3, reset($records)->id);
1555         $this->assertEquals($inskey2, end($records)->id);
1557         // Test 2 tables with aliases and limits with order bys.
1558         $sql = "SELECT t1.id, t1.course AS cid, t2.nametext
1559                   FROM {{$tablename}} t1, {{$tablename2}} t2
1560                  WHERE t2.course=t1.course
1561               ORDER BY t1.course, ". $DB->sql_compare_text('t2.nametext');
1562         $records = $DB->get_records_sql($sql, null, 2, 2); // Skip courses 3 and 6, get 4 and 5.
1563         $this->assertCount(2, $records);
1564         $this->assertSame('5', end($records)->cid);
1565         $this->assertSame('4', reset($records)->cid);
1567         // Test 2 tables with aliases and limits with the highest INT limit works.
1568         $records = $DB->get_records_sql($sql, null, 2, PHP_INT_MAX); // Skip course {3,6}, get {4,5}.
1569         $this->assertCount(2, $records);
1570         $this->assertSame('5', end($records)->cid);
1571         $this->assertSame('4', reset($records)->cid);
1573         // Test 2 tables with aliases and limits with order bys (limit which is highest INT number).
1574         $records = $DB->get_records_sql($sql, null, PHP_INT_MAX, 2); // Skip all courses.
1575         $this->assertCount(0, $records);
1577         // Test 2 tables with aliases and limits with order bys (limit which s highest INT number).
1578         $records = $DB->get_records_sql($sql, null, PHP_INT_MAX, PHP_INT_MAX); // Skip all courses.
1579         $this->assertCount(0, $records);
1581         // TODO: Test limits in queries having DISTINCT clauses.
1583         // Note: fetching nulls, empties, LOBs already tested by test_update_record() no needed here.
1584     }
1586     public function test_get_records_menu() {
1587         $DB = $this->tdb;
1588         $dbman = $DB->get_manager();
1590         $table = $this->get_test_table();
1591         $tablename = $table->getName();
1593         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1594         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1595         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1596         $dbman->create_table($table);
1598         $DB->insert_record($tablename, array('course' => 3));
1599         $DB->insert_record($tablename, array('course' => 3));
1600         $DB->insert_record($tablename, array('course' => 5));
1601         $DB->insert_record($tablename, array('course' => 2));
1603         $records = $DB->get_records_menu($tablename, array('course' => 3));
1604         $this->assertInternalType('array', $records);
1605         $this->assertCount(2, $records);
1606         $this->assertNotEmpty($records[1]);
1607         $this->assertNotEmpty($records[2]);
1608         $this->assertEquals(3, $records[1]);
1609         $this->assertEquals(3, $records[2]);
1611         // Note: delegate limits testing to test_get_records_sql().
1612     }
1614     public function test_get_records_select_menu() {
1615         $DB = $this->tdb;
1616         $dbman = $DB->get_manager();
1618         $table = $this->get_test_table();
1619         $tablename = $table->getName();
1621         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1622         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1623         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1624         $dbman->create_table($table);
1626         $DB->insert_record($tablename, array('course' => 3));
1627         $DB->insert_record($tablename, array('course' => 2));
1628         $DB->insert_record($tablename, array('course' => 3));
1629         $DB->insert_record($tablename, array('course' => 5));
1631         $records = $DB->get_records_select_menu($tablename, "course > ?", array(2));
1632         $this->assertInternalType('array', $records);
1634         $this->assertCount(3, $records);
1635         $this->assertArrayHasKey(1, $records);
1636         $this->assertArrayNotHasKey(2, $records);
1637         $this->assertArrayHasKey(3, $records);
1638         $this->assertArrayHasKey(4, $records);
1639         $this->assertSame('3', $records[1]);
1640         $this->assertSame('3', $records[3]);
1641         $this->assertSame('5', $records[4]);
1643         // Note: delegate limits testing to test_get_records_sql().
1644     }
1646     public function test_get_records_sql_menu() {
1647         $DB = $this->tdb;
1648         $dbman = $DB->get_manager();
1650         $table = $this->get_test_table();
1651         $tablename = $table->getName();
1653         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1654         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1655         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1656         $dbman->create_table($table);
1658         $DB->insert_record($tablename, array('course' => 3));
1659         $DB->insert_record($tablename, array('course' => 2));
1660         $DB->insert_record($tablename, array('course' => 3));
1661         $DB->insert_record($tablename, array('course' => 5));
1663         $records = $DB->get_records_sql_menu("SELECT * FROM {{$tablename}} WHERE course > ?", array(2));
1664         $this->assertInternalType('array', $records);
1666         $this->assertCount(3, $records);
1667         $this->assertArrayHasKey(1, $records);
1668         $this->assertArrayNotHasKey(2, $records);
1669         $this->assertArrayHasKey(3, $records);
1670         $this->assertArrayHasKey(4, $records);
1671         $this->assertSame('3', $records[1]);
1672         $this->assertSame('3', $records[3]);
1673         $this->assertSame('5', $records[4]);
1675         // Note: delegate limits testing to test_get_records_sql().
1676     }
1678     public function test_get_record() {
1679         $DB = $this->tdb;
1680         $dbman = $DB->get_manager();
1682         $table = $this->get_test_table();
1683         $tablename = $table->getName();
1685         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1686         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1687         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1688         $dbman->create_table($table);
1690         $DB->insert_record($tablename, array('course' => 3));
1691         $DB->insert_record($tablename, array('course' => 2));
1693         $record = $DB->get_record($tablename, array('id' => 2));
1694         $this->assertInstanceOf('stdClass', $record);
1696         $this->assertEquals(2, $record->course);
1697         $this->assertEquals(2, $record->id);
1698     }
1701     public function test_get_record_select() {
1702         $DB = $this->tdb;
1703         $dbman = $DB->get_manager();
1705         $table = $this->get_test_table();
1706         $tablename = $table->getName();
1708         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1709         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1710         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1711         $dbman->create_table($table);
1713         $DB->insert_record($tablename, array('course' => 3));
1714         $DB->insert_record($tablename, array('course' => 2));
1716         $record = $DB->get_record_select($tablename, "id = ?", array(2));
1717         $this->assertInstanceOf('stdClass', $record);
1719         $this->assertEquals(2, $record->course);
1721         // Note: delegates limit testing to test_get_records_sql().
1722     }
1724     public function test_get_record_sql() {
1725         $DB = $this->tdb;
1726         $dbman = $DB->get_manager();
1728         $table = $this->get_test_table();
1729         $tablename = $table->getName();
1731         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1732         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1733         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1734         $dbman->create_table($table);
1736         $DB->insert_record($tablename, array('course' => 3));
1737         $DB->insert_record($tablename, array('course' => 2));
1739         // Standard use.
1740         $record = $DB->get_record_sql("SELECT * FROM {{$tablename}} WHERE id = ?", array(2));
1741         $this->assertInstanceOf('stdClass', $record);
1742         $this->assertEquals(2, $record->course);
1743         $this->assertEquals(2, $record->id);
1745         // Backwards compatibility with $ignoremultiple.
1746         $this->assertFalse((bool)IGNORE_MISSING);
1747         $this->assertTrue((bool)IGNORE_MULTIPLE);
1749         // Record not found - ignore.
1750         $this->assertFalse($DB->get_record_sql("SELECT * FROM {{$tablename}} WHERE id = ?", array(666), IGNORE_MISSING));
1751         $this->assertFalse($DB->get_record_sql("SELECT * FROM {{$tablename}} WHERE id = ?", array(666), IGNORE_MULTIPLE));
1753         // Record not found error.
1754         try {
1755             $DB->get_record_sql("SELECT * FROM {{$tablename}} WHERE id = ?", array(666), MUST_EXIST);
1756             $this->fail("Exception expected");
1757         } catch (dml_missing_record_exception $e) {
1758             $this->assertTrue(true);
1759         }
1761         $this->assertNotEmpty($DB->get_record_sql("SELECT * FROM {{$tablename}}", array(), IGNORE_MISSING));
1762         $this->assertDebuggingCalled();
1763         set_debugging(DEBUG_MINIMAL);
1764         $this->assertNotEmpty($DB->get_record_sql("SELECT * FROM {{$tablename}}", array(), IGNORE_MISSING));
1765         $this->assertDebuggingNotCalled();
1766         set_debugging(DEBUG_DEVELOPER);
1768         // Multiple matches ignored.
1769         $this->assertNotEmpty($DB->get_record_sql("SELECT * FROM {{$tablename}}", array(), IGNORE_MULTIPLE));
1771         // Multiple found error.
1772         try {
1773             $DB->get_record_sql("SELECT * FROM {{$tablename}}", array(), MUST_EXIST);
1774             $this->fail("Exception expected");
1775         } catch (dml_multiple_records_exception $e) {
1776             $this->assertTrue(true);
1777         }
1778     }
1780     public function test_get_field() {
1781         $DB = $this->tdb;
1782         $dbman = $DB->get_manager();
1784         $table = $this->get_test_table();
1785         $tablename = $table->getName();
1787         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1788         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1789         $table->add_field('onetext', XMLDB_TYPE_TEXT, 'big', null, null, null);
1790         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1791         $dbman->create_table($table);
1793         $id1 = $DB->insert_record($tablename, array('course' => 3));
1794         $DB->insert_record($tablename, array('course' => 5));
1795         $DB->insert_record($tablename, array('course' => 5));
1797         $this->assertEquals(3, $DB->get_field($tablename, 'course', array('id' => $id1)));
1798         $this->assertEquals(3, $DB->get_field($tablename, 'course', array('course' => 3)));
1800         $this->assertFalse($DB->get_field($tablename, 'course', array('course' => 11), IGNORE_MISSING));
1801         try {
1802             $DB->get_field($tablename, 'course', array('course' => 4), MUST_EXIST);
1803             $this->fail('Exception expected due to missing record');
1804         } catch (dml_exception $ex) {
1805             $this->assertTrue(true);
1806         }
1808         $this->assertEquals(5, $DB->get_field($tablename, 'course', array('course' => 5), IGNORE_MULTIPLE));
1809         $this->assertDebuggingNotCalled();
1811         $this->assertEquals(5, $DB->get_field($tablename, 'course', array('course' => 5), IGNORE_MISSING));
1812         $this->assertDebuggingCalled();
1814         // Test for exception throwing on text conditions being compared. (MDL-24863, unwanted auto conversion of param to int).
1815         $conditions = array('onetext' => '1');
1816         try {
1817             $DB->get_field($tablename, 'course', $conditions);
1818             if (debugging()) {
1819                 // Only in debug mode - hopefully all devs test code in debug mode...
1820                 $this->fail('An Exception is missing, expected due to equating of text fields');
1821             }
1822         } catch (moodle_exception $e) {
1823             $this->assertInstanceOf('dml_exception', $e);
1824             $this->assertSame('textconditionsnotallowed', $e->errorcode);
1825         }
1826     }
1828     public function test_get_field_select() {
1829         $DB = $this->tdb;
1830         $dbman = $DB->get_manager();
1832         $table = $this->get_test_table();
1833         $tablename = $table->getName();
1835         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1836         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1837         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1838         $dbman->create_table($table);
1840         $DB->insert_record($tablename, array('course' => 3));
1842         $this->assertEquals(3, $DB->get_field_select($tablename, 'course', "id = ?", array(1)));
1843     }
1845     public function test_get_field_sql() {
1846         $DB = $this->tdb;
1847         $dbman = $DB->get_manager();
1849         $table = $this->get_test_table();
1850         $tablename = $table->getName();
1852         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1853         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1854         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1855         $dbman->create_table($table);
1857         $DB->insert_record($tablename, array('course' => 3));
1859         $this->assertEquals(3, $DB->get_field_sql("SELECT course FROM {{$tablename}} WHERE id = ?", array(1)));
1860     }
1862     public function test_get_fieldset_select() {
1863         $DB = $this->tdb;
1864         $dbman = $DB->get_manager();
1866         $table = $this->get_test_table();
1867         $tablename = $table->getName();
1869         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1870         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1871         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1872         $dbman->create_table($table);
1874         $DB->insert_record($tablename, array('course' => 1));
1875         $DB->insert_record($tablename, array('course' => 3));
1876         $DB->insert_record($tablename, array('course' => 2));
1877         $DB->insert_record($tablename, array('course' => 6));
1879         $fieldset = $DB->get_fieldset_select($tablename, 'course', "course > ?", array(1));
1880         $this->assertInternalType('array', $fieldset);
1882         $this->assertCount(3, $fieldset);
1883         $this->assertEquals(3, $fieldset[0]);
1884         $this->assertEquals(2, $fieldset[1]);
1885         $this->assertEquals(6, $fieldset[2]);
1886     }
1888     public function test_get_fieldset_sql() {
1889         $DB = $this->tdb;
1890         $dbman = $DB->get_manager();
1892         $table = $this->get_test_table();
1893         $tablename = $table->getName();
1895         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1896         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1897         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1898         $dbman->create_table($table);
1900         $DB->insert_record($tablename, array('course' => 1));
1901         $DB->insert_record($tablename, array('course' => 3));
1902         $DB->insert_record($tablename, array('course' => 2));
1903         $DB->insert_record($tablename, array('course' => 6));
1905         $fieldset = $DB->get_fieldset_sql("SELECT * FROM {{$tablename}} WHERE course > ?", array(1));
1906         $this->assertInternalType('array', $fieldset);
1908         $this->assertCount(3, $fieldset);
1909         $this->assertEquals(2, $fieldset[0]);
1910         $this->assertEquals(3, $fieldset[1]);
1911         $this->assertEquals(4, $fieldset[2]);
1912     }
1914     public function test_insert_record_raw() {
1915         $DB = $this->tdb;
1916         $dbman = $DB->get_manager();
1918         $table = $this->get_test_table();
1919         $tablename = $table->getName();
1921         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1922         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1923         $table->add_field('onechar', XMLDB_TYPE_CHAR, '100', null, null, null, 'onestring');
1924         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1925         $dbman->create_table($table);
1927         $record = (object)array('course' => 1, 'onechar' => 'xx');
1928         $before = clone($record);
1929         $result = $DB->insert_record_raw($tablename, $record);
1930         $this->assertSame(1, $result);
1931         $this->assertEquals($record, $before);
1933         $record = $DB->get_record($tablename, array('course' => 1));
1934         $this->assertInstanceOf('stdClass', $record);
1935         $this->assertSame('xx', $record->onechar);
1937         $result = $DB->insert_record_raw($tablename, array('course' => 2, 'onechar' => 'yy'), false);
1938         $this->assertTrue($result);
1940         // Note: bulk not implemented yet.
1941         $DB->insert_record_raw($tablename, array('course' => 3, 'onechar' => 'zz'), true, true);
1942         $record = $DB->get_record($tablename, array('course' => 3));
1943         $this->assertInstanceOf('stdClass', $record);
1944         $this->assertSame('zz', $record->onechar);
1946         // Custom sequence (id) - returnid is ignored.
1947         $result = $DB->insert_record_raw($tablename, array('id' => 10, 'course' => 3, 'onechar' => 'bb'), true, false, true);
1948         $this->assertTrue($result);
1949         $record = $DB->get_record($tablename, array('id' => 10));
1950         $this->assertInstanceOf('stdClass', $record);
1951         $this->assertSame('bb', $record->onechar);
1953         // Custom sequence - missing id error.
1954         try {
1955             $DB->insert_record_raw($tablename, array('course' => 3, 'onechar' => 'bb'), true, false, true);
1956             $this->fail('Exception expected due to missing record');
1957         } catch (coding_exception $ex) {
1958             $this->assertTrue(true);
1959         }
1961         // Wrong column error.
1962         try {
1963             $DB->insert_record_raw($tablename, array('xxxxx' => 3, 'onechar' => 'bb'));
1964             $this->fail('Exception expected due to invalid column');
1965         } catch (dml_exception $ex) {
1966             $this->assertTrue(true);
1967         }
1969         // Create something similar to "context_temp" with id column without sequence.
1970         $dbman->drop_table($table);
1971         $table = $this->get_test_table();
1972         $tablename = $table->getName();
1973         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
1974         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1975         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1976         $dbman->create_table($table);
1978         $record = (object)array('id'=>5, 'course' => 1);
1979         $DB->insert_record_raw($tablename, $record, false, false, true);
1980         $record = $DB->get_record($tablename, array());
1981         $this->assertEquals(5, $record->id);
1982     }
1984     public function test_insert_record() {
1985         // All the information in this test is fetched from DB by get_recordset() so we
1986         // have such method properly tested against nulls, empties and friends...
1988         $DB = $this->tdb;
1989         $dbman = $DB->get_manager();
1991         $table = $this->get_test_table();
1992         $tablename = $table->getName();
1994         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1995         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
1996         $table->add_field('oneint', XMLDB_TYPE_INTEGER, '10', null, null, null, 100);
1997         $table->add_field('onenum', XMLDB_TYPE_NUMBER, '10,2', null, null, null, 200);
1998         $table->add_field('onechar', XMLDB_TYPE_CHAR, '100', null, null, null, 'onestring');
1999         $table->add_field('onetext', XMLDB_TYPE_TEXT, 'big', null, null, null);
2000         $table->add_field('onebinary', XMLDB_TYPE_BINARY, 'big', null, null, null);
2001         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
2002         $dbman->create_table($table);
2004         $this->assertSame(1, $DB->insert_record($tablename, array('course' => 1), true));
2005         $record = $DB->get_record($tablename, array('course' => 1));
2006         $this->assertEquals(1, $record->id);
2007         $this->assertEquals(100, $record->oneint); // Just check column defaults have been applied.
2008         $this->assertEquals(200, $record->onenum);
2009         $this->assertSame('onestring', $record->onechar);
2010         $this->assertNull($record->onetext);
2011         $this->assertNull($record->onebinary);
2013         // Without returning id, bulk not implemented.
2014         $result = $this->assertTrue($DB->insert_record($tablename, array('course' => 99), false, true));
2015         $record = $DB->get_record($tablename, array('course' => 99));
2016         $this->assertEquals(2, $record->id);
2017         $this->assertEquals(99, $record->course);
2019         // Check nulls are set properly for all types.
2020         $record = new stdClass();
2021         $record->oneint = null;
2022         $record->onenum = null;
2023         $record->onechar = null;
2024         $record->onetext = null;
2025         $record->onebinary = null;
2026         $recid = $DB->insert_record($tablename, $record);
2027         $record = $DB->get_record($tablename, array('id' => $recid));
2028         $this->assertEquals(0, $record->course);
2029         $this->assertNull($record->oneint);
2030         $this->assertNull($record->onenum);
2031         $this->assertNull($record->onechar);
2032         $this->assertNull($record->onetext);
2033         $this->assertNull($record->onebinary);
2035         // Check zeros are set properly for all types.
2036         $record = new stdClass();
2037         $record->oneint = 0;
2038         $record->onenum = 0;
2039         $recid = $DB->insert_record($tablename, $record);
2040         $record = $DB->get_record($tablename, array('id' => $recid));
2041         $this->assertEquals(0, $record->oneint);
2042         $this->assertEquals(0, $record->onenum);
2044         // Check booleans are set properly for all types.
2045         $record = new stdClass();
2046         $record->oneint = true; // Trues.
2047         $record->onenum = true;
2048         $record->onechar = true;
2049         $record->onetext = true;
2050         $recid = $DB->insert_record($tablename, $record);
2051         $record = $DB->get_record($tablename, array('id' => $recid));
2052         $this->assertEquals(1, $record->oneint);
2053         $this->assertEquals(1, $record->onenum);
2054         $this->assertEquals(1, $record->onechar);
2055         $this->assertEquals(1, $record->onetext);
2057         $record = new stdClass();
2058         $record->oneint = false; // Falses.
2059         $record->onenum = false;
2060         $record->onechar = false;
2061         $record->onetext = false;
2062         $recid = $DB->insert_record($tablename, $record);
2063         $record = $DB->get_record($tablename, array('id' => $recid));
2064         $this->assertEquals(0, $record->oneint);
2065         $this->assertEquals(0, $record->onenum);
2066         $this->assertEquals(0, $record->onechar);
2067         $this->assertEquals(0, $record->onetext);
2069         // Check string data causes exception in numeric types.
2070         $record = new stdClass();
2071         $record->oneint = 'onestring';
2072         $record->onenum = 0;
2073         try {
2074             $DB->insert_record($tablename, $record);
2075             $this->fail("Expecting an exception, none occurred");
2076         } catch (moodle_exception $e) {
2077             $this->assertInstanceOf('dml_exception', $e);
2078         }
2079         $record = new stdClass();
2080         $record->oneint = 0;
2081         $record->onenum = 'onestring';
2082         try {
2083             $DB->insert_record($tablename, $record);
2084             $this->fail("Expecting an exception, none occurred");
2085         } catch (moodle_exception $e) {
2086             $this->assertInstanceOf('dml_exception', $e);
2087         }
2089         // Check empty string data is stored as 0 in numeric datatypes.
2090         $record = new stdClass();
2091         $record->oneint = ''; // Empty string.
2092         $record->onenum = 0;
2093         $recid = $DB->insert_record($tablename, $record);
2094         $record = $DB->get_record($tablename, array('id' => $recid));
2095         $this->assertTrue(is_numeric($record->oneint) && $record->oneint == 0);
2097         $record = new stdClass();
2098         $record->oneint = 0;
2099         $record->onenum = ''; // Empty string.
2100         $recid = $DB->insert_record($tablename, $record);
2101         $record = $DB->get_record($tablename, array('id' => $recid));
2102         $this->assertTrue(is_numeric($record->onenum) && $record->onenum == 0);
2104         // Check empty strings are set properly in string types.
2105         $record = new stdClass();
2106         $record->oneint = 0;
2107         $record->onenum = 0;
2108         $record->onechar = '';
2109         $record->onetext = '';
2110         $recid = $DB->insert_record($tablename, $record);
2111         $record = $DB->get_record($tablename, array('id' => $recid));
2112         $this->assertTrue($record->onechar === '');
2113         $this->assertTrue($record->onetext === '');
2115         // Check operation ((210.10 + 39.92) - 150.02) against numeric types.
2116         $record = new stdClass();
2117         $record->oneint = ((210.10 + 39.92) - 150.02);
2118         $record->onenum = ((210.10 + 39.92) - 150.02);
2119         $recid = $DB->insert_record($tablename, $record);
2120         $record = $DB->get_record($tablename, array('id' => $recid));
2121         $this->assertEquals(100, $record->oneint);
2122         $this->assertEquals(100, $record->onenum);
2124         // Check various quotes/backslashes combinations in string types.
2125         $teststrings = array(
2126             'backslashes and quotes alone (even): "" \'\' \\\\',
2127             'backslashes and quotes alone (odd): """ \'\'\' \\\\\\',
2128             'backslashes and quotes sequences (even): \\"\\" \\\'\\\'',
2129             'backslashes and quotes sequences (odd): \\"\\"\\" \\\'\\\'\\\'');
2130         foreach ($teststrings as $teststring) {
2131             $record = new stdClass();
2132             $record->onechar = $teststring;
2133             $record->onetext = $teststring;
2134             $recid = $DB->insert_record($tablename, $record);
2135             $record = $DB->get_record($tablename, array('id' => $recid));
2136             $this->assertEquals($teststring, $record->onechar);
2137             $this->assertEquals($teststring, $record->onetext);
2138         }
2140         // Check LOBs in text/binary columns.
2141         $clob = file_get_contents(__DIR__ . '/fixtures/clob.txt');
2142         $blob = file_get_contents(__DIR__ . '/fixtures/randombinary');
2143         $record = new stdClass();
2144         $record->onetext = $clob;
2145         $record->onebinary = $blob;
2146         $recid = $DB->insert_record($tablename, $record);
2147         $rs = $DB->get_recordset($tablename, array('id' => $recid));
2148         $record = $rs->current();
2149         $rs->close();
2150         $this->assertEquals($clob, $record->onetext, 'Test CLOB insert (full contents output disabled)');
2151         $this->assertEquals($blob, $record->onebinary, 'Test BLOB insert (full contents output disabled)');
2153         // And "small" LOBs too, just in case.
2154         $newclob = substr($clob, 0, 500);
2155         $newblob = substr($blob, 0, 250);
2156         $record = new stdClass();
2157         $record->onetext = $newclob;
2158         $record->onebinary = $newblob;
2159         $recid = $DB->insert_record($tablename, $record);
2160         $rs = $DB->get_recordset($tablename, array('id' => $recid));
2161         $record = $rs->current();
2162         $rs->close();
2163         $this->assertEquals($newclob, $record->onetext, 'Test "small" CLOB insert (full contents output disabled)');
2164         $this->assertEquals($newblob, $record->onebinary, 'Test "small" BLOB insert (full contents output disabled)');
2165         $this->assertEquals(false, $rs->key()); // Ensure recordset key() method to be working ok after closing.
2167         // And "diagnostic" LOBs too, just in case.
2168         $newclob = '\'"\\;/ěščřžýáíé';
2169         $newblob = '\'"\\;/ěščřžýáíé';
2170         $record = new stdClass();
2171         $record->onetext = $newclob;
2172         $record->onebinary = $newblob;
2173         $recid = $DB->insert_record($tablename, $record);
2174         $rs = $DB->get_recordset($tablename, array('id' => $recid));
2175         $record = $rs->current();
2176         $rs->close();
2177         $this->assertSame($newclob, $record->onetext);
2178         $this->assertSame($newblob, $record->onebinary);
2179         $this->assertEquals(false, $rs->key()); // Ensure recordset key() method to be working ok after closing.
2181         // Test data is not modified.
2182         $record = new stdClass();
2183         $record->id     = -1; // Has to be ignored.
2184         $record->course = 3;
2185         $record->lalala = 'lalal'; // Unused.
2186         $before = clone($record);
2187         $DB->insert_record($tablename, $record);
2188         $this->assertEquals($record, $before);
2190         // Make sure the id is always increasing and never reuses the same id.
2191         $id1 = $DB->insert_record($tablename, array('course' => 3));
2192         $id2 = $DB->insert_record($tablename, array('course' => 3));
2193         $this->assertTrue($id1 < $id2);
2194         $DB->delete_records($tablename, array('id'=>$id2));
2195         $id3 = $DB->insert_record($tablename, array('course' => 3));
2196         $this->assertTrue($id2 < $id3);
2197         $DB->delete_records($tablename, array());
2198         $id4 = $DB->insert_record($tablename, array('course' => 3));
2199         $this->assertTrue($id3 < $id4);
2201         // Test saving a float in a CHAR column, and reading it back.
2202         $id = $DB->insert_record($tablename, array('onechar' => 1.0));
2203         $this->assertEquals(1.0, $DB->get_field($tablename, 'onechar', array('id' => $id)));
2204         $id = $DB->insert_record($tablename, array('onechar' => 1e20));
2205         $this->assertEquals(1e20, $DB->get_field($tablename, 'onechar', array('id' => $id)));
2206         $id = $DB->insert_record($tablename, array('onechar' => 1e-4));
2207         $this->assertEquals(1e-4, $DB->get_field($tablename, 'onechar', array('id' => $id)));
2208         $id = $DB->insert_record($tablename, array('onechar' => 1e-5));
2209         $this->assertEquals(1e-5, $DB->get_field($tablename, 'onechar', array('id' => $id)));
2210         $id = $DB->insert_record($tablename, array('onechar' => 1e-300));
2211         $this->assertEquals(1e-300, $DB->get_field($tablename, 'onechar', array('id' => $id)));
2212         $id = $DB->insert_record($tablename, array('onechar' => 1e300));
2213         $this->assertEquals(1e300, $DB->get_field($tablename, 'onechar', array('id' => $id)));
2215         // Test saving a float in a TEXT column, and reading it back.
2216         $id = $DB->insert_record($tablename, array('onetext' => 1.0));
2217         $this->assertEquals(1.0, $DB->get_field($tablename, 'onetext', array('id' => $id)));
2218         $id = $DB->insert_record($tablename, array('onetext' => 1e20));
2219         $this->assertEquals(1e20, $DB->get_field($tablename, 'onetext', array('id' => $id)));
2220         $id = $DB->insert_record($tablename, array('onetext' => 1e-4));
2221         $this->assertEquals(1e-4, $DB->get_field($tablename, 'onetext', array('id' => $id)));
2222         $id = $DB->insert_record($tablename, array('onetext' => 1e-5));
2223         $this->assertEquals(1e-5, $DB->get_field($tablename, 'onetext', array('id' => $id)));
2224         $id = $DB->insert_record($tablename, array('onetext' => 1e-300));
2225         $this->assertEquals(1e-300, $DB->get_field($tablename, 'onetext', array('id' => $id)));
2226         $id = $DB->insert_record($tablename, array('onetext' => 1e300));
2227         $this->assertEquals(1e300, $DB->get_field($tablename, 'onetext', array('id' => $id)));
2229         // Test that inserting data violating one unique key leads to error.
2230         // Empty the table completely.
2231         $this->assertTrue($DB->delete_records($tablename));
2233         // Add one unique constraint (index).
2234         $key = new xmldb_key('testuk', XMLDB_KEY_UNIQUE, array('course', 'oneint'));
2235         $dbman->add_key($table, $key);
2237         // Let's insert one record violating the constraint multiple times.
2238         $record = (object)array('course' => 1, 'oneint' => 1);
2239         $this->assertTrue($DB->insert_record($tablename, $record, false)); // Insert 1st. No problem expected.
2241         // Re-insert same record, not returning id. dml_exception expected.
2242         try {
2243             $DB->insert_record($tablename, $record, false);
2244             $this->fail("Expecting an exception, none occurred");
2245         } catch (moodle_exception $e) {
2246             $this->assertInstanceOf('dml_exception', $e);
2247         }
2249         // Re-insert same record, returning id. dml_exception expected.
2250         try {
2251             $DB->insert_record($tablename, $record, true);
2252             $this->fail("Expecting an exception, none occurred");
2253         } catch (moodle_exception $e) {
2254             $this->assertInstanceOf('dml_exception', $e);
2255         }
2257         // Try to insert a record into a non-existent table. dml_exception expected.
2258         try {
2259             $DB->insert_record('nonexistenttable', $record, true);
2260             $this->fail("Expecting an exception, none occurred");
2261         } catch (exception $e) {
2262             $this->assertTrue($e instanceof dml_exception);
2263         }
2264     }
2266     public function test_insert_records() {
2267         $DB = $this->tdb;
2268         $dbman = $DB->get_manager();
2270         $table = $this->get_test_table();
2271         $tablename = $table->getName();
2273         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
2274         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
2275         $table->add_field('oneint', XMLDB_TYPE_INTEGER, '10', null, null, null, 100);
2276         $table->add_field('onenum', XMLDB_TYPE_NUMBER, '10,2', null, null, null, 200);
2277         $table->add_field('onechar', XMLDB_TYPE_CHAR, '100', null, null, null, 'onestring');
2278         $table->add_field('onetext', XMLDB_TYPE_TEXT, 'big', null, null, null);
2279         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
2280         $dbman->create_table($table);
2282         $this->assertCount(0, $DB->get_records($tablename));
2284         $record = new stdClass();
2285         $record->id = '1';
2286         $record->course = '1';
2287         $record->oneint = null;
2288         $record->onenum = '1.00';
2289         $record->onechar = 'a';
2290         $record->onetext = 'aaa';
2292         $expected = array();
2293         $records = array();
2294         for ($i = 1; $i <= 2000; $i++) { // This may take a while, it should be higher than defaults in DML drivers.
2295             $rec = clone($record);
2296             $rec->id = (string)$i;
2297             $rec->oneint = (string)$i;
2298             $expected[$i] = $rec;
2299             $rec = clone($rec);
2300             unset($rec->id);
2301             $records[$i] = $rec;
2302         }
2304         $DB->insert_records($tablename, $records);
2305         $stored = $DB->get_records($tablename, array(), 'id ASC');
2306         $this->assertEquals($expected, $stored);
2308         // Test there can be some extra properties including id.
2309         $count = $DB->count_records($tablename);
2310         $rec1 = (array)$record;
2311         $rec1['xxx'] = 1;
2312         $rec2 = (array)$record;
2313         $rec2['xxx'] = 2;
2315         $records = array($rec1, $rec2);
2316         $DB->insert_records($tablename, $records);
2317         $this->assertEquals($count + 2, $DB->count_records($tablename));
2319         // Test not all properties are necessary.
2320         $rec1 = (array)$record;
2321         unset($rec1['course']);
2322         $rec2 = (array)$record;
2323         unset($rec2['course']);
2325         $records = array($rec1, $rec2);
2326         $DB->insert_records($tablename, $records);
2328         // Make sure no changes in data object structure are tolerated.
2329         $rec1 = (array)$record;
2330         unset($rec1['id']);
2331         $rec2 = (array)$record;
2332         unset($rec2['id']);
2334         $records = array($rec1, $rec2);
2335         $DB->insert_records($tablename, $records);
2337         $rec2['xx'] = '1';
2338         $records = array($rec1, $rec2);
2339         try {
2340             $DB->insert_records($tablename, $records);
2341             $this->fail('coding_exception expected when insert_records receives different object data structures');
2342         } catch (moodle_exception $e) {
2343             $this->assertInstanceOf('coding_exception', $e);
2344         }
2346         unset($rec2['xx']);
2347         unset($rec2['course']);
2348         $rec2['course'] = '1';
2349         $records = array($rec1, $rec2);
2350         try {
2351             $DB->insert_records($tablename, $records);
2352             $this->fail('coding_exception expected when insert_records receives different object data structures');
2353         } catch (moodle_exception $e) {
2354             $this->assertInstanceOf('coding_exception', $e);
2355         }
2357         $records = 1;
2358         try {
2359             $DB->insert_records($tablename, $records);
2360             $this->fail('coding_exception expected when insert_records receives non-traversable data');
2361         } catch (moodle_exception $e) {
2362             $this->assertInstanceOf('coding_exception', $e);
2363         }
2365         $records = array(1);
2366         try {
2367             $DB->insert_records($tablename, $records);
2368             $this->fail('coding_exception expected when insert_records receives non-objet record');
2369         } catch (moodle_exception $e) {
2370             $this->assertInstanceOf('coding_exception', $e);
2371         }
2372     }
2374     public function test_import_record() {
2375         // All the information in this test is fetched from DB by get_recordset() so we
2376         // have such method properly tested against nulls, empties and friends...
2378         $DB = $this->tdb;
2379         $dbman = $DB->get_manager();
2381         $table = $this->get_test_table();
2382         $tablename = $table->getName();
2384         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
2385         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
2386         $table->add_field('oneint', XMLDB_TYPE_INTEGER, '10', null, null, null, 100);
2387         $table->add_field('onenum', XMLDB_TYPE_NUMBER, '10,2', null, null, null, 200);
2388         $table->add_field('onechar', XMLDB_TYPE_CHAR, '100', null, null, null, 'onestring');
2389         $table->add_field('onetext', XMLDB_TYPE_TEXT, 'big', null, null, null);
2390         $table->add_field('onebinary', XMLDB_TYPE_BINARY, 'big', null, null, null);
2391         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
2392         $dbman->create_table($table);
2394         $this->assertSame(1, $DB->insert_record($tablename, array('course' => 1), true));
2395         $record = $DB->get_record($tablename, array('course' => 1));
2396         $this->assertEquals(1, $record->id);
2397         $this->assertEquals(100, $record->oneint); // Just check column defaults have been applied.
2398         $this->assertEquals(200, $record->onenum);
2399         $this->assertSame('onestring', $record->onechar);
2400         $this->assertNull($record->onetext);
2401         $this->assertNull($record->onebinary);
2403         // Ignore extra columns.
2404         $record = (object)array('id'=>13, 'course'=>2, 'xxxx'=>788778);
2405         $before = clone($record);
2406         $this->assertTrue($DB->import_record($tablename, $record));
2407         $this->assertEquals($record, $before);
2408         $records = $DB->get_records($tablename);
2409         $this->assertEquals(2, $records[13]->course);
2411         // Check nulls are set properly for all types.
2412         $record = new stdClass();
2413         $record->id = 20;
2414         $record->oneint = null;
2415         $record->onenum = null;
2416         $record->onechar = null;
2417         $record->onetext = null;
2418         $record->onebinary = null;
2419         $this->assertTrue($DB->import_record($tablename, $record));
2420         $record = $DB->get_record($tablename, array('id' => 20));
2421         $this->assertEquals(0, $record->course);
2422         $this->assertNull($record->oneint);
2423         $this->assertNull($record->onenum);
2424         $this->assertNull($record->onechar);
2425         $this->assertNull($record->onetext);
2426         $this->assertNull($record->onebinary);
2428         // Check zeros are set properly for all types.
2429         $record = new stdClass();
2430         $record->id = 23;
2431         $record->oneint = 0;
2432         $record->onenum = 0;
2433         $this->assertTrue($DB->import_record($tablename, $record));
2434         $record = $DB->get_record($tablename, array('id' => 23));
2435         $this->assertEquals(0, $record->oneint);
2436         $this->assertEquals(0, $record->onenum);
2438         // Check string data causes exception in numeric types.
2439         $record = new stdClass();
2440         $record->id = 32;
2441         $record->oneint = 'onestring';
2442         $record->onenum = 0;
2443         try {
2444             $DB->import_record($tablename, $record);
2445             $this->fail("Expecting an exception, none occurred");
2446         } catch (moodle_exception $e) {
2447             $this->assertInstanceOf('dml_exception', $e);
2448         }
2449         $record = new stdClass();
2450         $record->id = 35;
2451         $record->oneint = 0;
2452         $record->onenum = 'onestring';
2453         try {
2454             $DB->import_record($tablename, $record);
2455             $this->fail("Expecting an exception, none occurred");
2456         } catch (moodle_exception $e) {
2457             $this->assertInstanceOf('dml_exception', $e);
2458         }
2460         // Check empty strings are set properly in string types.
2461         $record = new stdClass();
2462         $record->id = 44;
2463         $record->oneint = 0;
2464         $record->onenum = 0;
2465         $record->onechar = '';
2466         $record->onetext = '';
2467         $this->assertTrue($DB->import_record($tablename, $record));
2468         $record = $DB->get_record($tablename, array('id' => 44));
2469         $this->assertTrue($record->onechar === '');
2470         $this->assertTrue($record->onetext === '');
2472         // Check operation ((210.10 + 39.92) - 150.02) against numeric types.
2473         $record = new stdClass();
2474         $record->id = 47;
2475         $record->oneint = ((210.10 + 39.92) - 150.02);
2476         $record->onenum = ((210.10 + 39.92) - 150.02);
2477         $this->assertTrue($DB->import_record($tablename, $record));
2478         $record = $DB->get_record($tablename, array('id' => 47));
2479         $this->assertEquals(100, $record->oneint);
2480         $this->assertEquals(100, $record->onenum);
2482         // Check various quotes/backslashes combinations in string types.
2483         $i = 50;
2484         $teststrings = array(
2485             'backslashes and quotes alone (even): "" \'\' \\\\',
2486             'backslashes and quotes alone (odd): """ \'\'\' \\\\\\',
2487             'backslashes and quotes sequences (even): \\"\\" \\\'\\\'',
2488             'backslashes and quotes sequences (odd): \\"\\"\\" \\\'\\\'\\\'');
2489         foreach ($teststrings as $teststring) {
2490             $record = new stdClass();
2491             $record->id = $i;
2492             $record->onechar = $teststring;
2493             $record->onetext = $teststring;
2494             $this->assertTrue($DB->import_record($tablename, $record));
2495             $record = $DB->get_record($tablename, array('id' => $i));
2496             $this->assertEquals($teststring, $record->onechar);
2497             $this->assertEquals($teststring, $record->onetext);
2498             $i = $i + 3;
2499         }
2501         // Check LOBs in text/binary columns.
2502         $clob = file_get_contents(__DIR__ . '/fixtures/clob.txt');
2503         $record = new stdClass();
2504         $record->id = 70;
2505         $record->onetext = $clob;
2506         $record->onebinary = '';
2507         $this->assertTrue($DB->import_record($tablename, $record));
2508         $rs = $DB->get_recordset($tablename, array('id' => 70));
2509         $record = $rs->current();
2510         $rs->close();
2511         $this->assertEquals($clob, $record->onetext, 'Test CLOB insert (full contents output disabled)');
2513         $blob = file_get_contents(__DIR__ . '/fixtures/randombinary');
2514         $record = new stdClass();
2515         $record->id = 71;
2516         $record->onetext = '';
2517         $record->onebinary = $blob;
2518         $this->assertTrue($DB->import_record($tablename, $record));
2519         $rs = $DB->get_recordset($tablename, array('id' => 71));
2520         $record = $rs->current();
2521         $rs->close();
2522         $this->assertEquals($blob, $record->onebinary, 'Test BLOB insert (full contents output disabled)');
2524         // And "small" LOBs too, just in case.
2525         $newclob = substr($clob, 0, 500);
2526         $newblob = substr($blob, 0, 250);
2527         $record = new stdClass();
2528         $record->id = 73;
2529         $record->onetext = $newclob;
2530         $record->onebinary = $newblob;
2531         $this->assertTrue($DB->import_record($tablename, $record));
2532         $rs = $DB->get_recordset($tablename, array('id' => 73));
2533         $record = $rs->current();
2534         $rs->close();
2535         $this->assertEquals($newclob, $record->onetext, 'Test "small" CLOB insert (full contents output disabled)');
2536         $this->assertEquals($newblob, $record->onebinary, 'Test "small" BLOB insert (full contents output disabled)');
2537         $this->assertEquals(false, $rs->key()); // Ensure recordset key() method to be working ok after closing.
2538     }
2540     public function test_update_record_raw() {
2541         $DB = $this->tdb;
2542         $dbman = $DB->get_manager();
2544         $table = $this->get_test_table();
2545         $tablename = $table->getName();
2547         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
2548         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
2549         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
2550         $dbman->create_table($table);
2552         $DB->insert_record($tablename, array('course' => 1));
2553         $DB->insert_record($tablename, array('course' => 3));
2555         $record = $DB->get_record($tablename, array('course' => 1));
2556         $record->course = 2;
2557         $this->assertTrue($DB->update_record_raw($tablename, $record));
2558         $this->assertEquals(0, $DB->count_records($tablename, array('course' => 1)));
2559         $this->assertEquals(1, $DB->count_records($tablename, array('course' => 2)));
2560         $this->assertEquals(1, $DB->count_records($tablename, array('course' => 3)));
2562         $record = $DB->get_record($tablename, array('course' => 3));
2563         $record->xxxxx = 2;
2564         try {
2565             $DB->update_record_raw($tablename, $record);
2566             $this->fail("Expecting an exception, none occurred");
2567         } catch (moodle_exception $e) {
2568             $this->assertInstanceOf('moodle_exception', $e);
2569         }
2571         $record = $DB->get_record($tablename, array('course' => 3));
2572         unset($record->id);
2573         try {
2574             $DB->update_record_raw($tablename, $record);
2575             $this->fail("Expecting an exception, none occurred");
2576         } catch (moodle_exception $e) {
2577             $this->assertInstanceOf('coding_exception', $e);
2578         }
2579     }
2581     public function test_update_record() {
2583         // All the information in this test is fetched from DB by get_record() so we
2584         // have such method properly tested against nulls, empties and friends...
2586         $DB = $this->tdb;
2587         $dbman = $DB->get_manager();
2589         $table = $this->get_test_table();
2590         $tablename = $table->getName();
2592         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
2593         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
2594         $table->add_field('oneint', XMLDB_TYPE_INTEGER, '10', null, null, null, 100);
2595         $table->add_field('onenum', XMLDB_TYPE_NUMBER, '10,2', null, null, null, 200);
2596         $table->add_field('onechar', XMLDB_TYPE_CHAR, '100', null, null, null, 'onestring');
2597         $table->add_field('onetext', XMLDB_TYPE_TEXT, 'big', null, null, null);
2598         $table->add_field('onebinary', XMLDB_TYPE_BINARY, 'big', null, null, null);
2599         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
2600         $dbman->create_table($table);
2602         $DB->insert_record($tablename, array('course' => 1));
2603         $record = $DB->get_record($tablename, array('course' => 1));
2604         $record->course = 2;
2606         $this->assertTrue($DB->update_record($tablename, $record));
2607         $this->assertFalse($record = $DB->get_record($tablename, array('course' => 1)));
2608         $this->assertNotEmpty($record = $DB->get_record($tablename, array('course' => 2)));
2609         $this->assertEquals(100, $record->oneint); // Just check column defaults have been applied.
2610         $this->assertEquals(200, $record->onenum);
2611         $this->assertSame('onestring', $record->onechar);
2612         $this->assertNull($record->onetext);
2613         $this->assertNull($record->onebinary);
2615         // Check nulls are set properly for all types.
2616         $record->oneint = null;
2617         $record->onenum = null;
2618         $record->onechar = null;
2619         $record->onetext = null;
2620         $record->onebinary = null;
2621         $DB->update_record($tablename, $record);
2622         $record = $DB->get_record($tablename, array('course' => 2));
2623         $this->assertNull($record->oneint);
2624         $this->assertNull($record->onenum);
2625         $this->assertNull($record->onechar);
2626         $this->assertNull($record->onetext);
2627         $this->assertNull($record->onebinary);
2629         // Check zeros are set properly for all types.
2630         $record->oneint = 0;
2631         $record->onenum = 0;
2632         $DB->update_record($tablename, $record);
2633         $record = $DB->get_record($tablename, array('course' => 2));
2634         $this->assertEquals(0, $record->oneint);
2635         $this->assertEquals(0, $record->onenum);
2637         // Check booleans are set properly for all types.
2638         $record->oneint = true; // Trues.
2639         $record->onenum = true;
2640         $record->onechar = true;
2641         $record->onetext = true;
2642         $DB->update_record($tablename, $record);
2643         $record = $DB->get_record($tablename, array('course' => 2));
2644         $this->assertEquals(1, $record->oneint);
2645         $this->assertEquals(1, $record->onenum);
2646         $this->assertEquals(1, $record->onechar);
2647         $this->assertEquals(1, $record->onetext);
2649         $record->oneint = false; // Falses.
2650         $record->onenum = false;
2651         $record->onechar = false;
2652         $record->onetext = false;
2653         $DB->update_record($tablename, $record);
2654         $record = $DB->get_record($tablename, array('course' => 2));
2655         $this->assertEquals(0, $record->oneint);
2656         $this->assertEquals(0, $record->onenum);
2657         $this->assertEquals(0, $record->onechar);
2658         $this->assertEquals(0, $record->onetext);
2660         // Check string data causes exception in numeric types.
2661         $record->oneint = 'onestring';
2662         $record->onenum = 0;
2663         try {
2664             $DB->update_record($tablename, $record);
2665             $this->fail("Expecting an exception, none occurred");
2666         } catch (moodle_exception $e) {
2667             $this->assertInstanceOf('dml_exception', $e);
2668         }
2669         $record->oneint = 0;
2670         $record->onenum = 'onestring';
2671         try {
2672             $DB->update_record($tablename, $record);
2673             $this->fail("Expecting an exception, none occurred");
2674         } catch (moodle_exception $e) {
2675             $this->assertInstanceOf('dml_exception', $e);
2676         }
2678         // Check empty string data is stored as 0 in numeric datatypes.
2679         $record->oneint = ''; // Empty string.
2680         $record->onenum = 0;
2681         $DB->update_record($tablename, $record);
2682         $record = $DB->get_record($tablename, array('course' => 2));
2683         $this->assertTrue(is_numeric($record->oneint) && $record->oneint == 0);
2685         $record->oneint = 0;
2686         $record->onenum = ''; // Empty string.
2687         $DB->update_record($tablename, $record);
2688         $record = $DB->get_record($tablename, array('course' => 2));
2689         $this->assertTrue(is_numeric($record->onenum) && $record->onenum == 0);
2691         // Check empty strings are set properly in string types.
2692         $record->oneint = 0;
2693         $record->onenum = 0;
2694         $record->onechar = '';
2695         $record->onetext = '';
2696         $DB->update_record($tablename, $record);
2697         $record = $DB->get_record($tablename, array('course' => 2));
2698         $this->assertTrue($record->onechar === '');
2699         $this->assertTrue($record->onetext === '');
2701         // Check operation ((210.10 + 39.92) - 150.02) against numeric types.
2702         $record->oneint = ((210.10 + 39.92) - 150.02);
2703         $record->onenum = ((210.10 + 39.92) - 150.02);
2704         $DB->update_record($tablename, $record);
2705         $record = $DB->get_record($tablename, array('course' => 2));
2706         $this->assertEquals(100, $record->oneint);
2707         $this->assertEquals(100, $record->onenum);
2709         // Check various quotes/backslashes combinations in string types.
2710         $teststrings = array(
2711             'backslashes and quotes alone (even): "" \'\' \\\\',
2712             'backslashes and quotes alone (odd): """ \'\'\' \\\\\\',
2713             'backslashes and quotes sequences (even): \\"\\" \\\'\\\'',
2714             'backslashes and quotes sequences (odd): \\"\\"\\" \\\'\\\'\\\'');
2715         foreach ($teststrings as $teststring) {
2716             $record->onechar = $teststring;
2717             $record->onetext = $teststring;
2718             $DB->update_record($tablename, $record);
2719             $record = $DB->get_record($tablename, array('course' => 2));
2720             $this->assertEquals($teststring, $record->onechar);
2721             $this->assertEquals($teststring, $record->onetext);
2722         }
2724         // Check LOBs in text/binary columns.
2725         $clob = file_get_contents(__DIR__ . '/fixtures/clob.txt');
2726         $blob = file_get_contents(__DIR__ . '/fixtures/randombinary');
2727         $record->onetext = $clob;
2728         $record->onebinary = $blob;
2729         $DB->update_record($tablename, $record);
2730         $record = $DB->get_record($tablename, array('course' => 2));
2731         $this->assertEquals($clob, $record->onetext, 'Test CLOB update (full contents output disabled)');
2732         $this->assertEquals($blob, $record->onebinary, 'Test BLOB update (full contents output disabled)');
2734         // And "small" LOBs too, just in case.
2735         $newclob = substr($clob, 0, 500);
2736         $newblob = substr($blob, 0, 250);
2737         $record->onetext = $newclob;
2738         $record->onebinary = $newblob;
2739         $DB->update_record($tablename, $record);
2740         $record = $DB->get_record($tablename, array('course' => 2));
2741         $this->assertEquals($newclob, $record->onetext, 'Test "small" CLOB update (full contents output disabled)');
2742         $this->assertEquals($newblob, $record->onebinary, 'Test "small" BLOB update (full contents output disabled)');
2744         // Test saving a float in a CHAR column, and reading it back.
2745         $id = $DB->insert_record($tablename, array('onechar' => 'X'));
2746         $DB->update_record($tablename, array('id' => $id, 'onechar' => 1.0));
2747         $this->assertEquals(1.0, $DB->get_field($tablename, 'onechar', array('id' => $id)));
2748         $DB->update_record($tablename, array('id' => $id, 'onechar' => 1e20));
2749         $this->assertEquals(1e20, $DB->get_field($tablename, 'onechar', array('id' => $id)));
2750         $DB->update_record($tablename, array('id' => $id, 'onechar' => 1e-4));
2751         $this->assertEquals(1e-4, $DB->get_field($tablename, 'onechar', array('id' => $id)));
2752         $DB->update_record($tablename, array('id' => $id, 'onechar' => 1e-5));
2753         $this->assertEquals(1e-5, $DB->get_field($tablename, 'onechar', array('id' => $id)));
2754         $DB->update_record($tablename, array('id' => $id, 'onechar' => 1e-300));
2755         $this->assertEquals(1e-300, $DB->get_field($tablename, 'onechar', array('id' => $id)));
2756         $DB->update_record($tablename, array('id' => $id, 'onechar' => 1e300));
2757         $this->assertEquals(1e300, $DB->get_field($tablename, 'onechar', array('id' => $id)));
2759         // Test saving a float in a TEXT column, and reading it back.
2760         $id = $DB->insert_record($tablename, array('onetext' => 'X'));
2761         $DB->update_record($tablename, array('id' => $id, 'onetext' => 1.0));
2762         $this->assertEquals(1.0, $DB->get_field($tablename, 'onetext', array('id' => $id)));
2763         $DB->update_record($tablename, array('id' => $id, 'onetext' => 1e20));
2764         $this->assertEquals(1e20, $DB->get_field($tablename, 'onetext', array('id' => $id)));
2765         $DB->update_record($tablename, array('id' => $id, 'onetext' => 1e-4));
2766         $this->assertEquals(1e-4, $DB->get_field($tablename, 'onetext', array('id' => $id)));
2767         $DB->update_record($tablename, array('id' => $id, 'onetext' => 1e-5));
2768         $this->assertEquals(1e-5, $DB->get_field($tablename, 'onetext', array('id' => $id)));
2769         $DB->update_record($tablename, array('id' => $id, 'onetext' => 1e-300));
2770         $this->assertEquals(1e-300, $DB->get_field($tablename, 'onetext', array('id' => $id)));
2771         $DB->update_record($tablename, array('id' => $id, 'onetext' => 1e300));
2772         $this->assertEquals(1e300, $DB->get_field($tablename, 'onetext', array('id' => $id)));
2773     }
2775     public function test_set_field() {
2776         $DB = $this->tdb;
2777         $dbman = $DB->get_manager();
2779         $table = $this->get_test_table();
2780         $tablename = $table->getName();
2782         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
2783         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
2784         $table->add_field('onechar', XMLDB_TYPE_CHAR, '100', null, null, null);
2785         $table->add_field('onetext', XMLDB_TYPE_TEXT, 'big', null, null, null);
2786         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
2787         $dbman->create_table($table);
2789         // Simple set_field.
2790         $id1 = $DB->insert_record($tablename, array('course' => 1));
2791         $id2 = $DB->insert_record($tablename, array('course' => 1));
2792         $id3 = $DB->insert_record($tablename, array('course' => 3));
2793         $this->assertTrue($DB->set_field($tablename, 'course', 2, array('id' => $id1)));
2794         $this->assertEquals(2, $DB->get_field($tablename, 'course', array('id' => $id1)));
2795         $this->assertEquals(1, $DB->get_field($tablename, 'course', array('id' => $id2)));
2796         $this->assertEquals(3, $DB->get_field($tablename, 'course', array('id' => $id3)));
2797         $DB->delete_records($tablename, array());
2799         // Multiple fields affected.
2800         $id1 = $DB->insert_record($tablename, array('course' => 1));
2801         $id2 = $DB->insert_record($tablename, array('course' => 1));
2802         $id3 = $DB->insert_record($tablename, array('course' => 3));
2803         $DB->set_field($tablename, 'course', '5', array('course' => 1));
2804         $this->assertEquals(5, $DB->get_field($tablename, 'course', array('id' => $id1)));
2805         $this->assertEquals(5, $DB->get_field($tablename, 'course', array('id' => $id2)));
2806         $this->assertEquals(3, $DB->get_field($tablename, 'course', array('id' => $id3)));
2807         $DB->delete_records($tablename, array());
2809         // No field affected.
2810         $id1 = $DB->insert_record($tablename, array('course' => 1));
2811         $id2 = $DB->insert_record($tablename, array('course' => 1));
2812         $id3 = $DB->insert_record($tablename, array('course' => 3));
2813         $DB->set_field($tablename, 'course', '5', array('course' => 0));
2814         $this->assertEquals(1, $DB->get_field($tablename, 'course', array('id' => $id1)));
2815         $this->assertEquals(1, $DB->get_field($tablename, 'course', array('id' => $id2)));
2816         $this->assertEquals(3, $DB->get_field($tablename, 'course', array('id' => $id3)));
2817         $DB->delete_records($tablename, array());
2819         // All fields - no condition.
2820         $id1 = $DB->insert_record($tablename, array('course' => 1));
2821         $id2 = $DB->insert_record($tablename, array('course' => 1));
2822         $id3 = $DB->insert_record($tablename, array('course' => 3));
2823         $DB->set_field($tablename, 'course', 5, array());
2824         $this->assertEquals(5, $DB->get_field($tablename, 'course', array('id' => $id1)));
2825         $this->assertEquals(5, $DB->get_field($tablename, 'course', array('id' => $id2)));
2826         $this->assertEquals(5, $DB->get_field($tablename, 'course', array('id' => $id3)));
2828         // Test for exception throwing on text conditions being compared. (MDL-24863, unwanted auto conversion of param to int).
2829         $conditions = array('onetext' => '1');
2830         try {
2831             $DB->set_field($tablename, 'onechar', 'frog', $conditions);
2832             if (debugging()) {
2833                 // Only in debug mode - hopefully all devs test code in debug mode...
2834                 $this->fail('An Exception is missing, expected due to equating of text fields');
2835             }
2836         } catch (moodle_exception $e) {
2837             $this->assertInstanceOf('dml_exception', $e);
2838             $this->assertSame('textconditionsnotallowed', $e->errorcode);
2839         }
2841         // Test saving a float in a CHAR column, and reading it back.
2842         $id = $DB->insert_record($tablename, array('onechar' => 'X'));
2843         $DB->set_field($tablename, 'onechar', 1.0, array('id' => $id));
2844         $this->assertEquals(1.0, $DB->get_field($tablename, 'onechar', array('id' => $id)));
2845         $DB->set_field($tablename, 'onechar', 1e20, array('id' => $id));
2846         $this->assertEquals(1e20, $DB->get_field($tablename, 'onechar', array('id' => $id)));
2847         $DB->set_field($tablename, 'onechar', 1e-4, array('id' => $id));
2848         $this->assertEquals(1e-4, $DB->get_field($tablename, 'onechar', array('id' => $id)));
2849         $DB->set_field($tablename, 'onechar', 1e-5, array('id' => $id));
2850         $this->assertEquals(1e-5, $DB->get_field($tablename, 'onechar', array('id' => $id)));
2851         $DB->set_field($tablename, 'onechar', 1e-300, array('id' => $id));
2852         $this->assertEquals(1e-300, $DB->get_field($tablename, 'onechar', array('id' => $id)));
2853         $DB->set_field($tablename, 'onechar', 1e300, array('id' => $id));
2854         $this->assertEquals(1e300, $DB->get_field($tablename, 'onechar', array('id' => $id)));
2856         // Test saving a float in a TEXT column, and reading it back.
2857         $id = $DB->insert_record($tablename, array('onetext' => 'X'));
2858         $DB->set_field($tablename, 'onetext', 1.0, array('id' => $id));
2859         $this->assertEquals(1.0, $DB->get_field($tablename, 'onetext', array('id' => $id)));
2860         $DB->set_field($tablename, 'onetext', 1e20, array('id' => $id));
2861         $this->assertEquals(1e20, $DB->get_field($tablename, 'onetext', array('id' => $id)));
2862         $DB->set_field($tablename, 'onetext', 1e-4, array('id' => $id));
2863         $this->assertEquals(1e-4, $DB->get_field($tablename, 'onetext', array('id' => $id)));
2864         $DB->set_field($tablename, 'onetext', 1e-5, array('id' => $id));
2865         $this->assertEquals(1e-5, $DB->get_field($tablename, 'onetext', array('id' => $id)));
2866         $DB->set_field($tablename, 'onetext', 1e-300, array('id' => $id));
2867         $this->assertEquals(1e-300, $DB->get_field($tablename, 'onetext', array('id' => $id)));
2868         $DB->set_field($tablename, 'onetext', 1e300, array('id' => $id));
2869         $this->assertEquals(1e300, $DB->get_field($tablename, 'onetext', array('id' => $id)));
2871         // Note: All the nulls, booleans, empties, quoted and backslashes tests
2872         // go to set_field_select() because set_field() is just one wrapper over it.
2873     }
2875     public function test_set_field_select() {
2877         // All the information in this test is fetched from DB by get_field() so we
2878         // have such method properly tested against nulls, empties and friends...
2880         $DB = $this->tdb;
2881         $dbman = $DB->get_manager();
2883         $table = $this->get_test_table();
2884         $tablename = $table->getName();
2886         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
2887         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
2888         $table->add_field('oneint', XMLDB_TYPE_INTEGER, '10', null, null, null);
2889         $table->add_field('onenum', XMLDB_TYPE_NUMBER, '10,2', null, null, null);
2890         $table->add_field('onechar', XMLDB_TYPE_CHAR, '100', null, null, null);
2891         $table->add_field('onetext', XMLDB_TYPE_TEXT, 'big', null, null, null);
2892         $table->add_field('onebinary', XMLDB_TYPE_BINARY, 'big', null, null, null);
2893         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
2894         $dbman->create_table($table);
2896         $DB->insert_record($tablename, array('course' => 1));
2898         $this->assertTrue($DB->set_field_select($tablename, 'course', 2, 'id = ?', array(1)));
2899         $this->assertEquals(2, $DB->get_field($tablename, 'course', array('id' => 1)));
2901         // Check nulls are set properly for all types.
2902         $DB->set_field_select($tablename, 'oneint', null, 'id = ?', array(1)); // Trues.
2903         $DB->set_field_select($tablename, 'onenum', null, 'id = ?', array(1));
2904         $DB->set_field_select($tablename, 'onechar', null, 'id = ?', array(1));
2905         $DB->set_field_select($tablename, 'onetext', null, 'id = ?', array(1));
2906         $DB->set_field_select($tablename, 'onebinary', null, 'id = ?', array(1));
2907         $this->assertNull($DB->get_field($tablename, 'oneint', array('id' => 1)));
2908         $this->assertNull($DB->get_field($tablename, 'onenum', array('id' => 1)));
2909         $this->assertNull($DB->get_field($tablename, 'onechar', array('id' => 1)));
2910         $this->assertNull($DB->get_field($tablename, 'onetext', array('id' => 1)));
2911         $this->assertNull($DB->get_field($tablename, 'onebinary', array('id' => 1)));
2913         // Check zeros are set properly for all types.
2914         $DB->set_field_select($tablename, 'oneint', 0, 'id = ?', array(1));
2915         $DB->set_field_select($tablename, 'onenum', 0, 'id = ?', array(1));
2916         $this->assertEquals(0, $DB->get_field($tablename, 'oneint', array('id' => 1)));
2917         $this->assertEquals(0, $DB->get_field($tablename, 'onenum', array('id' => 1)));
2919         // Check booleans are set properly for all types.
2920         $DB->set_field_select($tablename, 'oneint', true, 'id = ?', array(1)); // Trues.
2921         $DB->set_field_select($tablename, 'onenum', true, 'id = ?', array(1));
2922         $DB->set_field_select($tablename, 'onechar', true, 'id = ?', array(1));
2923         $DB->set_field_select($tablename, 'onetext', true, 'id = ?', array(1));
2924         $this->assertEquals(1, $DB->get_field($tablename, 'oneint', array('id' => 1)));
2925         $this->assertEquals(1, $DB->get_field($tablename, 'onenum', array('id' => 1)));
2926         $this->assertEquals(1, $DB->get_field($tablename, 'onechar', array('id' => 1)));
2927         $this->assertEquals(1, $DB->get_field($tablename, 'onetext', array('id' => 1)));
2929         $DB->set_field_select($tablename, 'oneint', false, 'id = ?', array(1)); // Falses.
2930         $DB->set_field_select($tablename, 'onenum', false, 'id = ?', array(1));
2931         $DB->set_field_select($tablename, 'onechar', false, 'id = ?', array(1));
2932         $DB->set_field_select($tablename, 'onetext', false, 'id = ?', array(1));
2933         $this->assertEquals(0, $DB->get_field($tablename, 'oneint', array('id' => 1)));
2934         $this->assertEquals(0, $DB->get_field($tablename, 'onenum', array('id' => 1)));
2935         $this->assertEquals(0, $DB->get_field($tablename, 'onechar', array('id' => 1)));
2936         $this->assertEquals(0, $DB->get_field($tablename, 'onetext', array('id' => 1)));
2938         // Check string data causes exception in numeric types.
2939         try {
2940             $DB->set_field_select($tablename, 'oneint', 'onestring', 'id = ?', array(1));
2941             $this->fail("Expecting an exception, none occurred");
2942         } catch (moodle_exception $e) {
2943             $this->assertInstanceOf('dml_exception', $e);
2944         }
2945         try {
2946             $DB->set_field_select($tablename, 'onenum', 'onestring', 'id = ?', array(1));
2947             $this->fail("Expecting an exception, none occurred");
2948         } catch (moodle_exception $e) {
2949             $this->assertInstanceOf('dml_exception', $e);
2950         }
2952         // Check empty string data is stored as 0 in numeric datatypes.
2953         $DB->set_field_select($tablename, 'oneint', '', 'id = ?', array(1));
2954         $field = $DB->get_field($tablename, 'oneint', array('id' => 1));
2955         $this->assertTrue(is_numeric($field) && $field == 0);
2957         $DB->set_field_select($tablename, 'onenum', '', 'id = ?', array(1));
2958         $field = $DB->get_field($tablename, 'onenum', array('id' => 1));
2959         $this->assertTrue(is_numeric($field) && $field == 0);
2961         // Check empty strings are set properly in string types.
2962         $DB->set_field_select($tablename, 'onechar', '', 'id = ?', array(1));
2963         $DB->set_field_select($tablename, 'onetext', '', 'id = ?', array(1));
2964         $this->assertTrue($DB->get_field($tablename, 'onechar', array('id' => 1)) === '');
2965         $this->assertTrue($DB->get_field($tablename, 'onetext', array('id' => 1)) === '');
2967         // Check operation ((210.10 + 39.92) - 150.02) against numeric types.
2968         $DB->set_field_select($tablename, 'oneint', ((210.10 + 39.92) - 150.02), 'id = ?', array(1));
2969         $DB->set_field_select($tablename, 'onenum', ((210.10 + 39.92) - 150.02), 'id = ?', array(1));
2970         $this->assertEquals(100, $DB->get_field($tablename, 'oneint', array('id' => 1)));
2971         $this->assertEquals(100, $DB->get_field($tablename, 'onenum', array('id' => 1)));
2973         // Check various quotes/backslashes combinations in string types.
2974         $teststrings = array(
2975             'backslashes and quotes alone (even): "" \'\' \\\\',
2976             'backslashes and quotes alone (odd): """ \'\'\' \\\\\\',
2977             'backslashes and quotes sequences (even): \\"\\" \\\'\\\'',
2978             'backslashes and quotes sequences (odd): \\"\\"\\" \\\'\\\'\\\'');
2979         foreach ($teststrings as $teststring) {
2980             $DB->set_field_select($tablename, 'onechar', $teststring, 'id = ?', array(1));
2981             $DB->set_field_select($tablename, 'onetext', $teststring, 'id = ?', array(1));
2982             $this->assertEquals($teststring, $DB->get_field($tablename, 'onechar', array('id' => 1)));
2983             $this->assertEquals($teststring, $DB->get_field($tablename, 'onetext', array('id' => 1)));
2984         }
2986         // Check LOBs in text/binary columns.
2987         $clob = file_get_contents(__DIR__ . '/fixtures/clob.txt');
2988         $blob = file_get_contents(__DIR__ . '/fixtures/randombinary');
2989         $DB->set_field_select($tablename, 'onetext', $clob, 'id = ?', array(1));
2990         $DB->set_field_select($tablename, 'onebinary', $blob, 'id = ?', array(1));
2991         $this->assertEquals($clob, $DB->get_field($tablename, 'onetext', array('id' => 1)), 'Test CLOB set_field (full contents output disabled)');
2992         $this->assertEquals($blob, $DB->get_field($tablename, 'onebinary', array('id' => 1)), 'Test BLOB set_field (full contents output disabled)');
2994         // And "small" LOBs too, just in case.
2995         $newclob = substr($clob, 0, 500);
2996         $newblob = substr($blob, 0, 250);
2997         $DB->set_field_select($tablename, 'onetext', $newclob, 'id = ?', array(1));
2998         $DB->set_field_select($tablename, 'onebinary', $newblob, 'id = ?', array(1));
2999         $this->assertEquals($newclob, $DB->get_field($tablename, 'onetext', array('id' => 1)), 'Test "small" CLOB set_field (full contents output disabled)');
3000         $this->assertEquals($newblob, $DB->get_field($tablename, 'onebinary', array('id' => 1)), 'Test "small" BLOB set_field (full contents output disabled)');
3002         // This is the failure from MDL-24863. This was giving an error on MSSQL,
3003         // which converts the '1' to an integer, which cannot then be compared with
3004         // onetext cast to a varchar. This should be fixed and working now.
3005         $newchar = 'frog';
3006         // Test for exception throwing on text conditions being compared. (MDL-24863, unwanted auto conversion of param to int).
3007         $params = array('onetext' => '1');
3008         try {
3009             $DB->set_field_select($tablename, 'onechar', $newchar, $DB->sql_compare_text('onetext') . ' = ?', $params);
3010             $this->assertTrue(true, 'No exceptions thrown with numerical text param comparison for text field.');
3011         } catch (dml_exception $e) {
3012             $this->assertFalse(true, 'We have an unexpected exception.');
3013             throw $e;
3014         }
3015     }
3017     public function test_count_records() {
3018         $DB = $this->tdb;
3020         $dbman = $DB->get_manager();
3022         $table = $this->get_test_table();
3023         $tablename = $table->getName();
3025         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3026         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
3027         $table->add_field('onetext', XMLDB_TYPE_TEXT, 'big', null, null, null);
3028         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3029         $dbman->create_table($table);
3031         $this->assertSame(0, $DB->count_records($tablename));
3033         $DB->insert_record($tablename, array('course' => 3));
3034         $DB->insert_record($tablename, array('course' => 4));
3035         $DB->insert_record($tablename, array('course' => 5));
3037         $this->assertSame(3, $DB->count_records($tablename));
3039         // Test for exception throwing on text conditions being compared. (MDL-24863, unwanted auto conversion of param to int).
3040         $conditions = array('onetext' => '1');
3041         try {
3042             $DB->count_records($tablename, $conditions);
3043             if (debugging()) {
3044                 // Only in debug mode - hopefully all devs test code in debug mode...
3045                 $this->fail('An Exception is missing, expected due to equating of text fields');
3046             }
3047         } catch (moodle_exception $e) {
3048             $this->assertInstanceOf('dml_exception', $e);
3049             $this->assertSame('textconditionsnotallowed', $e->errorcode);
3050         }
3051     }
3053     public function test_count_records_select() {
3054         $DB = $this->tdb;
3056         $dbman = $DB->get_manager();
3058         $table = $this->get_test_table();
3059         $tablename = $table->getName();
3061         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3062         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
3063         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3064         $dbman->create_table($table);
3066         $this->assertSame(0, $DB->count_records($tablename));
3068         $DB->insert_record($tablename, array('course' => 3));
3069         $DB->insert_record($tablename, array('course' => 4));
3070         $DB->insert_record($tablename, array('course' => 5));
3072         $this->assertSame(2, $DB->count_records_select($tablename, 'course > ?', array(3)));
3073     }
3075     public function test_count_records_sql() {
3076         $DB = $this->tdb;
3077         $dbman = $DB->get_manager();
3079         $table = $this->get_test_table();
3080         $tablename = $table->getName();
3082         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3083         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
3084         $table->add_field('onechar', XMLDB_TYPE_CHAR, '100', null, null, null);
3085         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3086         $dbman->create_table($table);
3088         $this->assertSame(0, $DB->count_records($tablename));
3090         $DB->insert_record($tablename, array('course' => 3, 'onechar' => 'a'));
3091         $DB->insert_record($tablename, array('course' => 4, 'onechar' => 'b'));
3092         $DB->insert_record($tablename, array('course' => 5, 'onechar' => 'c'));
3094         $this->assertSame(2, $DB->count_records_sql("SELECT COUNT(*) FROM {{$tablename}} WHERE course > ?", array(3)));
3096         // Test invalid use.
3097         try {
3098             $DB->count_records_sql("SELECT onechar FROM {{$tablename}} WHERE course = ?", array(3));
3099             $this->fail('Exception expected when non-number field used in count_records_sql');
3100         } catch (moodle_exception $e) {
3101             $this->assertInstanceOf('coding_exception', $e);
3102         }
3104         try {
3105             $DB->count_records_sql("SELECT course FROM {{$tablename}} WHERE 1 = 2");
3106             $this->fail('Exception expected when non-number field used in count_records_sql');
3107         } catch (moodle_exception $e) {
3108             $this->assertInstanceOf('coding_exception', $e);
3109         }
3110     }
3112     public function test_record_exists() {
3113         $DB = $this->tdb;
3114         $dbman = $DB->get_manager();
3116         $table = $this->get_test_table();
3117         $tablename = $table->getName();
3119         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3120         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
3121         $table->add_field('onetext', XMLDB_TYPE_TEXT, 'big', null, null, null);
3122         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3123         $dbman->create_table($table);
3125         $this->assertEquals(0, $DB->count_records($tablename));
3127         $this->assertFalse($DB->record_exists($tablename, array('course' => 3)));
3128         $DB->insert_record($tablename, array('course' => 3));
3130         $this->assertTrue($DB->record_exists($tablename, array('course' => 3)));
3132         // Test for exception throwing on text conditions being compared. (MDL-24863, unwanted auto conversion of param to int).
3133         $conditions = array('onetext' => '1');
3134         try {
3135             $DB->record_exists($tablename, $conditions);
3136             if (debugging()) {
3137                 // Only in debug mode - hopefully all devs test code in debug mode...
3138                 $this->fail('An Exception is missing, expected due to equating of text fields');
3139             }
3140         } catch (moodle_exception $e) {
3141             $this->assertInstanceOf('dml_exception', $e);
3142             $this->assertSame('textconditionsnotallowed', $e->errorcode);
3143         }
3144     }
3146     public function test_record_exists_select() {
3147         $DB = $this->tdb;
3148         $dbman = $DB->get_manager();
3150         $table = $this->get_test_table();
3151         $tablename = $table->getName();
3153         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3154         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
3155         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3156         $dbman->create_table($table);
3158         $this->assertEquals(0, $DB->count_records($tablename));
3160         $this->assertFalse($DB->record_exists_select($tablename, "course = ?", array(3)));
3161         $DB->insert_record($tablename, array('course' => 3));
3163         $this->assertTrue($DB->record_exists_select($tablename, "course = ?", array(3)));
3164     }
3166     public function test_record_exists_sql() {
3167         $DB = $this->tdb;
3168         $dbman = $DB->get_manager();
3170         $table = $this->get_test_table();
3171         $tablename = $table->getName();
3173         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3174         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
3175         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3176         $dbman->create_table($table);
3178         $this->assertEquals(0, $DB->count_records($tablename));
3180         $this->assertFalse($DB->record_exists_sql("SELECT * FROM {{$tablename}} WHERE course = ?", array(3)));
3181         $DB->insert_record($tablename, array('course' => 3));
3183         $this->assertTrue($DB->record_exists_sql("SELECT * FROM {{$tablename}} WHERE course = ?", array(3)));
3184     }
3186     public function test_recordset_locks_delete() {
3187         $DB = $this->tdb;
3188         $dbman = $DB->get_manager();
3190         // Setup.
3191         $table = $this->get_test_table();
3192         $tablename = $table->getName();
3194         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3195         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
3196         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3197         $dbman->create_table($table);
3199         $DB->insert_record($tablename, array('course' => 1));
3200         $DB->insert_record($tablename, array('course' => 2));
3201         $DB->insert_record($tablename, array('course' => 3));
3202         $DB->insert_record($tablename, array('course' => 4));
3203         $DB->insert_record($tablename, array('course' => 5));
3204         $DB->insert_record($tablename, array('course' => 6));
3206         // Test against db write locking while on an open recordset.
3207         $rs = $DB->get_recordset($tablename, array(), null, 'course', 2, 2); // Get courses = {3,4}.
3208         foreach ($rs as $record) {
3209             $cid = $record->course;
3210             $DB->delete_records($tablename, array('course' => $cid));
3211             $this->assertFalse($DB->record_exists($tablename, array('course' => $cid)));
3212         }
3213         $rs->close();
3215         $this->assertEquals(4, $DB->count_records($tablename, array()));
3216     }
3218     public function test_recordset_locks_update() {
3219         $DB = $this->tdb;
3220         $dbman = $DB->get_manager();
3222         // Setup.
3223         $table = $this->get_test_table();
3224         $tablename = $table->getName();
3226         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3227         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
3228         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3229         $dbman->create_table($table);
3231         $DB->insert_record($tablename, array('course' => 1));
3232         $DB->insert_record($tablename, array('course' => 2));
3233         $DB->insert_record($tablename, array('course' => 3));
3234         $DB->insert_record($tablename, array('course' => 4));
3235         $DB->insert_record($tablename, array('course' => 5));
3236         $DB->insert_record($tablename, array('course' => 6));
3238         // Test against db write locking while on an open recordset.
3239         $rs = $DB->get_recordset($tablename, array(), null, 'course', 2, 2); // Get courses = {3,4}.
3240         foreach ($rs as $record) {
3241             $cid = $record->course;
3242             $DB->set_field($tablename, 'course', 10, array('course' => $cid));
3243             $this->assertFalse($DB->record_exists($tablename, array('course' => $cid)));
3244         }
3245         $rs->close();
3247         $this->assertEquals(2, $DB->count_records($tablename, array('course' => 10)));
3248     }
3250     public function test_delete_records() {
3251         $DB = $this->tdb;
3252         $dbman = $DB->get_manager();
3254         $table = $this->get_test_table();
3255         $tablename = $table->getName();
3257         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3258         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
3259         $table->add_field('onetext', XMLDB_TYPE_TEXT, 'big', null, null, null);
3260         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3261         $dbman->create_table($table);
3263         $DB->insert_record($tablename, array('course' => 3));
3264         $DB->insert_record($tablename, array('course' => 2));
3265         $DB->insert_record($tablename, array('course' => 2));
3267         // Delete all records.
3268         $this->assertTrue($DB->delete_records($tablename));
3269         $this->assertEquals(0, $DB->count_records($tablename));
3271         // Delete subset of records.
3272         $DB->insert_record($tablename, array('course' => 3));
3273         $DB->insert_record($tablename, array('course' => 2));
3274         $DB->insert_record($tablename, array('course' => 2));
3276         $this->assertTrue($DB->delete_records($tablename, array('course' => 2)));
3277         $this->assertEquals(1, $DB->count_records($tablename));
3279         // Delete all.
3280         $this->assertTrue($DB->delete_records($tablename, array()));
3281         $this->assertEquals(0, $DB->count_records($tablename));
3283         // Test for exception throwing on text conditions being compared. (MDL-24863, unwanted auto conversion of param to int).
3284         $conditions = array('onetext'=>'1');
3285         try {
3286             $DB->delete_records($tablename, $conditions);
3287             if (debugging()) {
3288                 // Only in debug mode - hopefully all devs test code in debug mode...
3289                 $this->fail('An Exception is missing, expected due to equating of text fields');
3290             }
3291         } catch (moodle_exception $e) {
3292             $this->assertInstanceOf('dml_exception', $e);
3293             $this->assertSame('textconditionsnotallowed', $e->errorcode);
3294         }
3296         // Test for exception throwing on text conditions being compared. (MDL-24863, unwanted auto conversion of param to int).
3297         $conditions = array('onetext' => 1);
3298         try {
3299             $DB->delete_records($tablename, $conditions);
3300             if (debugging()) {
3301                 // Only in debug mode - hopefully all devs test code in debug mode...
3302                 $this->fail('An Exception is missing, expected due to equating of text fields');
3303             }
3304         } catch (moodle_exception $e) {
3305             $this->assertInstanceOf('dml_exception', $e);
3306             $this->assertSame('textconditionsnotallowed', $e->errorcode);
3307         }
3308     }
3310     public function test_delete_records_select() {
3311         $DB = $this->tdb;
3312         $dbman = $DB->get_manager();
3314         $table = $this->get_test_table();
3315         $tablename = $table->getName();
3317         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3318         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
3319         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3320         $dbman->create_table($table);
3322         $DB->insert_record($tablename, array('course' => 3));
3323         $DB->insert_record($tablename, array('course' => 2));
3324         $DB->insert_record($tablename, array('course' => 2));
3326         $this->assertTrue($DB->delete_records_select($tablename, 'course = ?', array(2)));
3327         $this->assertEquals(1, $DB->count_records($tablename));
3328     }
3330     public function test_delete_records_list() {
3331         $DB = $this->tdb;
3332         $dbman = $DB->get_manager();
3334         $table = $this->get_test_table();
3335         $tablename = $table->getName();
3337         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3338         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
3339         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3340         $dbman->create_table($table);
3342         $DB->insert_record($tablename, array('course' => 1));
3343         $DB->insert_record($tablename, array('course' => 2));
3344         $DB->insert_record($tablename, array('course' => 3));
3346         $this->assertTrue($DB->delete_records_list($tablename, 'course', array(2, 3)));
3347         $this->assertEquals(1, $DB->count_records($tablename));
3349         $this->assertTrue($DB->delete_records_list($tablename, 'course', array())); // Must delete 0 rows without conditions. MDL-17645.
3350         $this->assertEquals(1, $DB->count_records($tablename));
3351     }
3353     public function test_object_params() {
3354         $DB = $this->tdb;
3355         $dbman = $DB->get_manager();
3357         $table = $this->get_test_table();
3358         $tablename = $table->getName();
3359         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3360         $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
3361         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3362         $dbman->create_table($table);
3364         $o = new stdClass(); // Objects without __toString - never worked.
3365         try {
3366             $DB->fix_sql_params("SELECT {{$tablename}} WHERE course = ? ", array($o));
3367             $this->fail('coding_exception expected');
3368         } catch (moodle_exception $e) {
3369             $this->assertInstanceOf('coding_exception', $e);
3370         }
3372         // Objects with __toString() forbidden everywhere since 2.3.
3373         $o = new dml_test_object_one();
3374         try {
3375             $DB->fix_sql_params("SELECT {{$tablename}} WHERE course = ? ", array($o));
3376             $this->fail('coding_exception expected');
3377         } catch (moodle_exception $e) {
3378             $this->assertInstanceOf('coding_exception', $e);
3379         }
3381         try {
3382             $DB->execute("SELECT {{$tablename}} WHERE course = ? ", array($o));
3383             $this->fail('coding_exception expected');
3384         } catch (moodle_exception $e) {
3385             $this->assertInstanceOf('coding_exception', $e);
3386         }
3388         try {
3389             $DB->get_recordset_sql("SELECT {{$tablename}} WHERE course = ? ", array($o));
3390             $this->fail('coding_exception expected');
3391         } catch (moodle_exception $e) {
3392             $this->assertInstanceOf('coding_exception', $e);
3393         }
3395         try {
3396             $DB->get_records_sql("SELECT {{$tablename}} WHERE course = ? ", array($o));
3397             $this->fail('coding_exception expected');
3398         } catch (moodle_exception $e) {
3399             $this->assertInstanceOf('coding_exception', $e);
3400         }
3402         try {
3403             $record = new stdClass();
3404             $record->course = $o;
3405             $DB->insert_record_raw($tablename, $record);
3406             $this->fail('coding_exception expected');
3407         } catch (moodle_exception $e) {
3408             $this->assertInstanceOf('coding_exception', $e);
3409         }
3411         try {
3412             $record = new stdClass();
3413             $record->course = $o;
3414             $DB->insert_record($tablename, $record);
3415             $this->fail('coding_exception expected');
3416         } catch (moodle_exception $e) {
3417             $this->assertInstanceOf('coding_exception', $e);
3418         }
3420         try {
3421             $record = new stdClass();
3422             $record->course = $o;
3423             $DB->import_record($tablename, $record);
3424             $this->fail('coding_exception expected');
3425         } catch (moodle_exception $e) {
3426             $this->assertInstanceOf('coding_exception', $e);
3427         }
3429         try {
3430             $record = new stdClass();
3431             $record->id = 1;
3432             $record->course = $o;
3433             $DB->update_record_raw($tablename, $record);
3434             $this->fail('coding_exception expected');
3435         } catch (moodle_exception $e) {
3436             $this->assertInstanceOf('coding_exception', $e);
3437         }
3439         try {
3440             $record = new stdClass();
3441             $record->id = 1;
3442             $record->course = $o;
3443             $DB->update_record($tablename, $record);
3444             $this->fail('coding_exception expected');
3445         } catch (moodle_exception $e) {
3446             $this->assertInstanceOf('coding_exception', $e);
3447         }
3449         try {
3450             $DB->set_field_select($tablename, 'course', 1, "course = ? ", array($o));
3451             $this->fail('coding_exception expected');
3452         } catch (moodle_exception $e) {
3453             $this->assertInstanceOf('coding_exception', $e);
3454         }
3456         try {
3457             $DB->delete_records_select($tablename, "course = ? ", array($o));
3458             $this->fail('coding_exception expected');
3459         } catch (moodle_exception $e) {
3460             $this->assertInstanceOf('coding_exception', $e);
3461         }
3462     }
3464     public function test_sql_null_from_clause() {
3465         $DB = $this->tdb;
3466         $sql = "SELECT 1 AS id ".$DB->sql_null_from_clause();
3467         $this->assertEquals(1, $DB->get_field_sql($sql));
3468     }
3470     public function test_sql_bitand() {
3471         $DB = $this->tdb;
3472         $dbman = $DB->get_manager();
3474         $table = $this->get_test_table();
3475         $tablename = $table->getName();
3477         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3478         $table->add_field('col1', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
3479         $table->add_field('col2', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
3480         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3481         $dbman->create_table($table);
3483         $DB->insert_record($tablename, array('col1' => 3, 'col2' => 10));
3485         $sql = "SELECT ".$DB->sql_bitand(10, 3)." AS res ".$DB->sql_null_from_clause();
3486         $this->assertEquals(2, $DB->get_field_sql($sql));
3488         $sql = "SELECT id, ".$DB->sql_bitand('col1', 'col2')." AS res FROM {{$tablename}}";
3489         $result = $DB->get_records_sql($sql);
3490         $this->assertCount(1, $result);
3491         $this->assertEquals(2, reset($result)->res);
3493         $sql = "SELECT id, ".$DB->sql_bitand('col1', '?')." AS res FROM {{$tablename}}";
3494         $result = $DB->get_records_sql($sql, array(10));
3495         $this->assertCount(1, $result);
3496         $this->assertEquals(2, reset($result)->res);
3497     }
3499     public function test_sql_bitnot() {
3500         $DB = $this->tdb;
3502         $not = $DB->sql_bitnot(2);
3503         $notlimited = $DB->sql_bitand($not, 7); // Might be positive or negative number which can not fit into PHP INT!
3505         $sql = "SELECT $notlimited AS res ".$DB->sql_null_from_clause();
3506         $this->assertEquals(5, $DB->get_field_sql($sql));
3507     }
3509     public function test_sql_bitor() {
3510         $DB = $this->tdb;
3511         $dbman = $DB->get_manager();
3513         $table = $this->get_test_table();
3514         $tablename = $table->getName();
3516         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3517         $table->add_field('col1', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
3518         $table->add_field('col2', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
3519         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3520         $dbman->create_table($table);
3522         $DB->insert_record($tablename, array('col1' => 3, 'col2' => 10));
3524         $sql = "SELECT ".$DB->sql_bitor(10, 3)." AS res ".$DB->sql_null_from_clause();
3525         $this->assertEquals(11, $DB->get_field_sql($sql));
3527         $sql = "SELECT id, ".$DB->sql_bitor('col1', 'col2')." AS res FROM {{$tablename}}";
3528         $result = $DB->get_records_sql($sql);
3529         $this->assertCount(1, $result);
3530         $this->assertEquals(11, reset($result)->res);
3532         $sql = "SELECT id, ".$DB->sql_bitor('col1', '?')." AS res FROM {{$tablename}}";
3533         $result = $DB->get_records_sql($sql, array(10));
3534         $this->assertCount(1, $result);
3535         $this->assertEquals(11, reset($result)->res);
3536     }
3538     public function test_sql_bitxor() {
3539         $DB = $this->tdb;
3540         $dbman = $DB->get_manager();
3542         $table = $this->get_test_table();
3543         $tablename = $table->getName();
3545         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3546         $table->add_field('col1', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
3547         $table->add_field('col2', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
3548         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3549         $dbman->create_table($table);
3551         $DB->insert_record($tablename, array('col1' => 3, 'col2' => 10));
3553         $sql = "SELECT ".$DB->sql_bitxor(10, 3)." AS res ".$DB->sql_null_from_clause();
3554         $this->assertEquals(9, $DB->get_field_sql($sql));
3556         $sql = "SELECT id, ".$DB->sql_bitxor('col1', 'col2')." AS res FROM {{$tablename}}";
3557         $result = $DB->get_records_sql($sql);
3558         $this->assertCount(1, $result);
3559         $this->assertEquals(9, reset($result)->res);
3561         $sql = "SELECT id, ".$DB->sql_bitxor('col1', '?')." AS res FROM {{$tablename}}";
3562         $result = $DB->get_records_sql($sql, array(10));
3563         $this->assertCount(1, $result);
3564         $this->assertEquals(9, reset($result)->res);
3565     }
3567     public function test_sql_modulo() {
3568         $DB = $this->tdb;
3569         $sql = "SELECT ".$DB->sql_modulo(10, 7)." AS res ".$DB->sql_null_from_clause();
3570         $this->assertEquals(3, $DB->get_field_sql($sql));
3571     }
3573     public function test_sql_ceil() {
3574         $DB = $this->tdb;
3575         $sql = "SELECT ".$DB->sql_ceil(665.666)." AS res ".$DB->sql_null_from_clause();
3576         $this->assertEquals(666, $DB->get_field_sql($sql));
3577     }
3579     public function test_cast_char2int() {
3580         $DB = $this->tdb;
3581         $dbman = $DB->get_manager();
3583         $table1 = $this->get_test_table("1");
3584         $tablename1 = $table1->getName();
3586         $table1->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3587         $table1->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null);
3588         $table1->add_field('nametext', XMLDB_TYPE_TEXT, 'small', null, null, null, null);
3589         $table1->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3590         $dbman->create_table($table1);
3592         $DB->insert_record($tablename1, array('name'=>'0100', 'nametext'=>'0200'));
3593         $DB->insert_record($tablename1, array('name'=>'10',   'nametext'=>'20'));
3595         $table2 = $this->get_test_table("2");
3596         $tablename2 = $table2->getName();
3597         $table2->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3598         $table2->add_field('res', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
3599         $table2->add_field('restext', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
3600         $table2->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3601         $dbman->create_table($table2);
3603         $DB->insert_record($tablename2, array('res'=>100, 'restext'=>200));
3605         // Casting varchar field.
3606         $sql = "SELECT *
3607                   FROM {".$tablename1."} t1
3608                   JOIN {".$tablename2."} t2 ON ".$DB->sql_cast_char2int("t1.name")." = t2.res ";
3609         $records = $DB->get_records_sql($sql);
3610         $this->assertCount(1, $records);
3611         // Also test them in order clauses.
3612         $sql = "SELECT * FROM {{$tablename1}} ORDER BY ".$DB->sql_cast_char2int('name');
3613         $records = $DB->get_records_sql($sql);
3614         $this->assertCount(2, $records);
3615         $this->assertSame('10', reset($records)->name);
3616         $this->assertSame('0100', next($records)->name);
3618         // Casting text field.
3619         $sql = "SELECT *
3620                   FROM {".$tablename1."} t1
3621                   JOIN {".$tablename2."} t2 ON ".$DB->sql_cast_char2int("t1.nametext", true)." = t2.restext ";
3622         $records = $DB->get_records_sql($sql);
3623         $this->assertCount(1, $records);
3624         // Also test them in order clauses.
3625         $sql = "SELECT * FROM {{$tablename1}} ORDER BY ".$DB->sql_cast_char2int('nametext', true);
3626         $records = $DB->get_records_sql($sql);
3627         $this->assertCount(2, $records);
3628         $this->assertSame('20', reset($records)->nametext);
3629         $this->assertSame('0200', next($records)->nametext);
3630     }
3632     public function test_cast_char2real() {
3633         $DB = $this->tdb;
3634         $dbman = $DB->get_manager();
3636         $table = $this->get_test_table();
3637         $tablename = $table->getName();
3639         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3640         $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null);
3641         $table->add_field('nametext', XMLDB_TYPE_TEXT, 'small', null, null, null, null);
3642         $table->add_field('res', XMLDB_TYPE_NUMBER, '12, 7', null, null, null, null);
3643         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3644         $dbman->create_table($table);
3646         $DB->insert_record($tablename, array('name'=>'10.10', 'nametext'=>'10.10', 'res'=>5.1));
3647         $DB->insert_record($tablename, array('name'=>'91.10', 'nametext'=>'91.10', 'res'=>666));
3648         $DB->insert_record($tablename, array('name'=>'011.10', 'nametext'=>'011.10', 'res'=>10.1));
3650         // Casting varchar field.
3651         $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_cast_char2real('name')." > res";
3652         $records = $DB->get_records_sql($sql);
3653         $this->assertCount(2, $records);
3654         // Also test them in order clauses.
3655         $sql = "SELECT * FROM {{$tablename}} ORDER BY ".$DB->sql_cast_char2real('name');
3656         $records = $DB->get_records_sql($sql);
3657         $this->assertCount(3, $records);
3658         $this->assertSame('10.10', reset($records)->name);
3659         $this->assertSame('011.10', next($records)->name);
3660         $this->assertSame('91.10', next($records)->name);
3662         // Casting text field.
3663         $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_cast_char2real('nametext', true)." > res";
3664         $records = $DB->get_records_sql($sql);
3665         $this->assertCount(2, $records);
3666         // Also test them in order clauses.
3667         $sql = "SELECT * FROM {{$tablename}} ORDER BY ".$DB->sql_cast_char2real('nametext', true);
3668         $records = $DB->get_records_sql($sql);
3669         $this->assertCount(3, $records);
3670         $this->assertSame('10.10', reset($records)->nametext);
3671         $this->assertSame('011.10', next($records)->nametext);
3672         $this->assertSame('91.10', next($records)->nametext);
3673     }
3675     public function test_sql_compare_text() {
3676         $DB = $this->tdb;
3677         $dbman = $DB->get_manager();
3679         $table = $this->get_test_table();
3680         $tablename = $table->getName();
3682         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3683         $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null);
3684         $table->add_field('description', XMLDB_TYPE_TEXT, 'big', null, null, null, null);
3685         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3686         $dbman->create_table($table);
3688         $DB->insert_record($tablename, array('name'=>'abcd',   'description'=>'abcd'));
3689         $DB->insert_record($tablename, array('name'=>'abcdef', 'description'=>'bbcdef'));
3690         $DB->insert_record($tablename, array('name'=>'aaaa', 'description'=>'aaaacccccccccccccccccc'));
3691         $DB->insert_record($tablename, array('name'=>'xxxx',   'description'=>'123456789a123456789b123456789c123456789d'));
3693         // Only some supported databases truncate TEXT fields for comparisons, currently MSSQL and Oracle.
3694         $dbtruncatestextfields = ($DB->get_dbfamily() == 'mssql' || $DB->get_dbfamily() == 'oracle');
3696         if ($dbtruncatestextfields) {
3697             // Ensure truncation behaves as expected.
3699             $sql = "SELECT " . $DB->sql_compare_text('description') . " AS field FROM {{$tablename}} WHERE name = ?";
3700             $description = $DB->get_field_sql($sql, array('xxxx'));
3702             // Should truncate to 32 chars (the default).
3703             $this->assertEquals('123456789a123456789b123456789c12', $description);
3705             $sql = "SELECT " . $DB->sql_compare_text('description', 35) . " AS field FROM {{$tablename}} WHERE name = ?";
3706             $description = $DB->get_field_sql($sql, array('xxxx'));
3708             // Should truncate to the specified number of chars.
3709             $this->assertEquals('123456789a123456789b123456789c12345', $description);
3710         }
3712         // Ensure text field comparison is successful.
3713         $sql = "SELECT * FROM {{$tablename}} WHERE name = ".$DB->sql_compare_text('description');
3714         $records = $DB->get_records_sql($sql);
3715         $this->assertCount(1, $records);
3717         $sql = "SELECT * FROM {{$tablename}} WHERE name = ".$DB->sql_compare_text('description', 4);
3718         $records = $DB->get_records_sql($sql);
3719         if ($dbtruncatestextfields) {
3720             // Should truncate description to 4 characters before comparing.
3721             $this->assertCount(2, $records);
3722         } else {
3723             // Should leave untruncated, so one less match.
3724             $this->assertCount(1, $records);
3725         }
3727     }
3729     public function test_unique_index_collation_trouble() {
3730         // Note: this is a work in progress, we should probably move this to ddl test.
3732         $DB = $this->tdb;
3733         $dbman = $DB->get_manager();
3735         $table = $this->get_test_table();
3736         $tablename = $table->getName();
3738         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3739         $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null);
3740         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3741         $table->add_index('name', XMLDB_INDEX_UNIQUE, array('name'));
3742         $dbman->create_table($table);
3744         $DB->insert_record($tablename, array('name'=>'aaa'));
3746         try {
3747             $DB->insert_record($tablename, array('name'=>'AAA'));
3748         } catch (moodle_exception $e) {
3749             // TODO: ignore case insensitive uniqueness problems for now.
3750             // $this->fail("Unique index is case sensitive - this may cause problems in some tables");
3751         }
3753         try {
3754             $DB->insert_record($tablename, array('name'=>'aäa'));
3755             $DB->insert_record($tablename, array('name'=>'aáa'));
3756             $this->assertTrue(true);
3757         } catch (moodle_exception $e) {
3758             $family = $DB->get_dbfamily();
3759             if ($family === 'mysql' or $family === 'mssql') {
3760                 $this->fail("Unique index is accent insensitive, this may cause problems for non-ascii languages. This is usually caused by accent insensitive default collation.");
3761             } else {
3762                 // This should not happen, PostgreSQL and Oracle do not support accent insensitive uniqueness.
3763                 $this->fail("Unique index is accent insensitive, this may cause problems for non-ascii languages.");
3764             }
3765             throw($e);
3766         }
3767     }
3769     public function test_sql_binary_equal() {
3770         $DB = $this->tdb;
3771         $dbman = $DB->get_manager();
3773         $table = $this->get_test_table();
3774         $tablename = $table->getName();
3776         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3777         $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null);
3778         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3779         $dbman->create_table($table);
3781         $DB->insert_record($tablename, array('name'=>'aaa'));
3782         $DB->insert_record($tablename, array('name'=>'aáa'));
3783         $DB->insert_record($tablename, array('name'=>'aäa'));
3784         $DB->insert_record($tablename, array('name'=>'bbb'));
3785         $DB->insert_record($tablename, array('name'=>'BBB'));
3787         $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE name = ?", array('bbb'));
3788         $this->assertEquals(1, count($records), 'SQL operator "=" is expected to be case sensitive');
3790         $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE name = ?", array('aaa'));
3791         $this->assertEquals(1, count($records), 'SQL operator "=" is expected to be accent sensitive');
3792     }
3794     public function test_sql_like() {
3795         $DB = $this->tdb;
3796         $dbman = $DB->get_manager();
3798         $table = $this->get_test_table();
3799         $tablename = $table->getName();
3801         $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
3802         $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null);
3803         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
3804         $dbman->create_table($table);
3806         $DB->insert_record($tablename, array('name'=>'SuperDuperRecord'));
3807         $DB->insert_record($tablename, array('name'=>'Nodupor'));
3808         $DB->insert_record($tablename, array('name'=>'ouch'));
3809         $DB->insert_record($tablename, array('name'=>'ouc_'));
3810         $DB->insert_record($tablename, array('name'=>'ouc%'));
3811         $DB->insert_record($tablename, array('name'=>'aui'));
3812         $DB->insert_record($tablename, array('name'=>'aüi'));
3813         $DB->insert_record($tablename, array('name'=>'aÜi'));
3815         $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?', false);
3816         $records = $DB->get_records_sql($sql, array("%dup_r%"));
3817         $this->assertCount(2, $records);
3819         $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?', true);
3820         $records = $DB->get_records_sql($sql, array("%dup%"));
3821         $this->assertCount(1, $records);
3823         $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?'); // Defaults.
3824         $records = $DB->get_records_sql($sql, array("%dup%"));
3825         $this->assertCount(1, $records);
3827         $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?', true);
3828         $records = $DB->get_records_sql($sql, array("ouc\\_"));
3829         $this->assertCount(1, $records);
3831         $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?', true, true, false, '|');
3832         $records = $DB->get_records_sql($sql, array($DB->sql_like_escape("ouc%", '|')));
3833         $this->assertCount(1, $records);
3835         $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?', true, true);
3836         $records = $DB->get_records_sql($sql, array('aui'));
3837         $this->assertCount(1, $records);
3839         $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?', true, true, true); // NOT LIKE.
3840         $records = $DB->get_records_sql($sql, array("%o%"));
3841         $this->assertCount(3, $records);
3843         $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?', false, true, true); // NOT ILIKE.
3844         $records = $DB->get_records_sql($sql, array("%D%"));
3845         $this->assertCount(6, $records);
3847         // Verify usual escaping characters work fine.
3848         $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?', true, true, false, '\\');