Proyectos de Subversion Moodle

Rev

Rev 11 | | Comparar con el anterior | Ultima modificación | Ver Log |

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