Passed
Pull Request — master (#3070)
by Sergei
07:45
created

DataAccessTest::setUp()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 19
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 12
dl 0
loc 19
rs 9.8666
c 0
b 0
f 0
cc 2
nc 2
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\Tests\DBAL\Functional;
6
7
use DateTime;
8
use Doctrine\DBAL\Connection;
9
use Doctrine\DBAL\DBALException;
10
use Doctrine\DBAL\Driver\Mysqli\Driver as MySQLiDriver;
11
use Doctrine\DBAL\Driver\SQLSrv\Driver as SQLSrvDriver;
12
use Doctrine\DBAL\FetchMode;
13
use Doctrine\DBAL\ParameterType;
14
use Doctrine\DBAL\Platforms\AbstractPlatform;
15
use Doctrine\DBAL\Platforms\SqlitePlatform;
16
use Doctrine\DBAL\Platforms\TrimMode;
17
use Doctrine\DBAL\Schema\Table;
18
use Doctrine\DBAL\Statement;
19
use Doctrine\DBAL\Types\Types;
20
use Doctrine\Tests\DbalFunctionalTestCase;
21
use InvalidArgumentException;
22
use function array_change_key_case;
23
use function array_filter;
24
use function array_keys;
25
use function assert;
26
use function count;
27
use function date;
28
use function implode;
29
use function is_array;
30
use function is_numeric;
31
use function json_encode;
32
use function sprintf;
33
use function strtotime;
34
use const CASE_LOWER;
35
use const PHP_EOL;
36
37
class DataAccessTest extends DbalFunctionalTestCase
38
{
39
    /** @var bool */
40
    private static $generated = false;
41
42
    protected function setUp() : void
43
    {
44
        parent::setUp();
45
46
        if (self::$generated !== false) {
47
            return;
48
        }
49
50
        $table = new Table('fetch_table');
51
        $table->addColumn('test_int', 'integer');
52
        $table->addColumn('test_string', 'string', ['length' => 32]);
53
        $table->addColumn('test_datetime', 'datetime', ['notnull' => false]);
54
        $table->setPrimaryKey(['test_int']);
55
56
        $sm = $this->connection->getSchemaManager();
57
        $sm->createTable($table);
58
59
        $this->connection->insert('fetch_table', ['test_int' => 1, 'test_string' => 'foo', 'test_datetime' => '2010-01-01 10:10:10']);
60
        self::$generated = true;
61
    }
62
63
    public function testPrepareWithBindValue() : void
64
    {
65
        $sql  = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?';
66
        $stmt = $this->connection->prepare($sql);
67
        self::assertInstanceOf(Statement::class, $stmt);
68
69
        $stmt->bindValue(1, 1);
70
        $stmt->bindValue(2, 'foo');
71
        $stmt->execute();
72
73
        $row = $stmt->fetch(FetchMode::ASSOCIATIVE);
74
        $row = array_change_key_case($row, CASE_LOWER);
75
        self::assertEquals(['test_int' => 1, 'test_string' => 'foo'], $row);
76
    }
77
78
    public function testPrepareWithBindParam() : void
79
    {
80
        $paramInt = 1;
81
        $paramStr = 'foo';
82
83
        $sql  = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?';
84
        $stmt = $this->connection->prepare($sql);
85
        self::assertInstanceOf(Statement::class, $stmt);
86
87
        $stmt->bindParam(1, $paramInt);
88
        $stmt->bindParam(2, $paramStr);
89
        $stmt->execute();
90
91
        $row = $stmt->fetch(FetchMode::ASSOCIATIVE);
92
        $row = array_change_key_case($row, CASE_LOWER);
93
        self::assertEquals(['test_int' => 1, 'test_string' => 'foo'], $row);
94
    }
95
96
    public function testPrepareWithFetchAll() : void
97
    {
98
        $paramInt = 1;
99
        $paramStr = 'foo';
100
101
        $sql  = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?';
102
        $stmt = $this->connection->prepare($sql);
103
        self::assertInstanceOf(Statement::class, $stmt);
104
105
        $stmt->bindParam(1, $paramInt);
106
        $stmt->bindParam(2, $paramStr);
107
        $stmt->execute();
108
109
        $rows    = $stmt->fetchAll(FetchMode::ASSOCIATIVE);
110
        $rows[0] = array_change_key_case($rows[0], CASE_LOWER);
111
        self::assertEquals(['test_int' => 1, 'test_string' => 'foo'], $rows[0]);
112
    }
113
114
    /**
115
     * @group DBAL-228
116
     */
117
    public function testPrepareWithFetchAllBoth() : void
118
    {
119
        $paramInt = 1;
120
        $paramStr = 'foo';
121
122
        $sql  = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?';
123
        $stmt = $this->connection->prepare($sql);
124
        self::assertInstanceOf(Statement::class, $stmt);
125
126
        $stmt->bindParam(1, $paramInt);
127
        $stmt->bindParam(2, $paramStr);
128
        $stmt->execute();
129
130
        $rows    = $stmt->fetchAll(FetchMode::MIXED);
131
        $rows[0] = array_change_key_case($rows[0], CASE_LOWER);
132
        self::assertEquals(['test_int' => 1, 'test_string' => 'foo', 0 => 1, 1 => 'foo'], $rows[0]);
133
    }
134
135
    public function testPrepareWithFetchColumn() : void
136
    {
137
        $paramInt = 1;
138
        $paramStr = 'foo';
139
140
        $sql  = 'SELECT test_int FROM fetch_table WHERE test_int = ? AND test_string = ?';
141
        $stmt = $this->connection->prepare($sql);
142
        self::assertInstanceOf(Statement::class, $stmt);
143
144
        $stmt->bindParam(1, $paramInt);
145
        $stmt->bindParam(2, $paramStr);
146
        $stmt->execute();
147
148
        $column = $stmt->fetchColumn();
149
        self::assertEquals(1, $column);
150
    }
151
152
    public function testPrepareWithIterator() : void
153
    {
154
        $paramInt = 1;
155
        $paramStr = 'foo';
156
157
        $sql  = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?';
158
        $stmt = $this->connection->prepare($sql);
159
        self::assertInstanceOf(Statement::class, $stmt);
160
161
        $stmt->bindParam(1, $paramInt);
162
        $stmt->bindParam(2, $paramStr);
163
        $stmt->execute();
164
165
        $rows = [];
166
        $stmt->setFetchMode(FetchMode::ASSOCIATIVE);
167
        foreach ($stmt as $row) {
168
            $rows[] = array_change_key_case($row, CASE_LOWER);
169
        }
170
171
        self::assertEquals(['test_int' => 1, 'test_string' => 'foo'], $rows[0]);
172
    }
173
174
    public function testPrepareWithQuoted() : void
175
    {
176
        $table    = 'fetch_table';
177
        $paramInt = 1;
178
        $paramStr = 'foo';
179
180
        $stmt = $this->connection->prepare(sprintf(
181
            'SELECT test_int, test_string FROM %s WHERE test_int = %d AND test_string = %s',
182
            $this->connection->quoteIdentifier($table),
183
            $paramInt,
184
            $this->connection->quote($paramStr)
185
        ));
186
        self::assertInstanceOf(Statement::class, $stmt);
187
    }
188
189
    public function testPrepareWithExecuteParams() : void
190
    {
191
        $paramInt = 1;
192
        $paramStr = 'foo';
193
194
        $sql  = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?';
195
        $stmt = $this->connection->prepare($sql);
196
        self::assertInstanceOf(Statement::class, $stmt);
197
        $stmt->execute([$paramInt, $paramStr]);
198
199
        $row = $stmt->fetch(FetchMode::ASSOCIATIVE);
200
        self::assertNotFalse($row);
201
        $row = array_change_key_case($row, CASE_LOWER);
202
        self::assertEquals(['test_int' => 1, 'test_string' => 'foo'], $row);
203
    }
204
205
    public function testFetchAll() : void
206
    {
207
        $sql  = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?';
208
        $data = $this->connection->fetchAll($sql, [1, 'foo']);
209
210
        self::assertCount(1, $data);
211
212
        $row = $data[0];
213
        self::assertCount(2, $row);
214
215
        $row = array_change_key_case($row, CASE_LOWER);
216
        self::assertEquals(1, $row['test_int']);
217
        self::assertEquals('foo', $row['test_string']);
218
    }
219
220
    /**
221
     * @group DBAL-209
222
     */
223
    public function testFetchAllWithTypes() : void
224
    {
225
        $datetimeString = '2010-01-01 10:10:10';
226
        $datetime       = new DateTime($datetimeString);
227
228
        $sql  = 'SELECT test_int, test_datetime FROM fetch_table WHERE test_int = ? AND test_datetime = ?';
229
        $data = $this->connection->fetchAll(
230
            $sql,
231
            [1, $datetime],
232
            [ParameterType::STRING, Types::DATETIME_MUTABLE]
233
        );
234
235
        self::assertCount(1, $data);
236
237
        $row = $data[0];
238
        self::assertCount(2, $row);
239
240
        $row = array_change_key_case($row, CASE_LOWER);
241
        self::assertEquals(1, $row['test_int']);
242
        self::assertStringStartsWith($datetimeString, $row['test_datetime']);
243
    }
244
245
    /**
246
     * @group DBAL-209
247
     */
248
    public function testFetchAllWithMissingTypes() : void
249
    {
250
        if ($this->connection->getDriver() instanceof MySQLiDriver ||
251
            $this->connection->getDriver() instanceof SQLSrvDriver) {
252
            $this->markTestSkipped('mysqli and sqlsrv actually supports this');
253
        }
254
255
        $datetimeString = '2010-01-01 10:10:10';
256
        $datetime       = new DateTime($datetimeString);
257
        $sql            = 'SELECT test_int, test_datetime FROM fetch_table WHERE test_int = ? AND test_datetime = ?';
258
259
        $this->expectException(DBALException::class);
260
261
        $this->connection->fetchAll($sql, [1, $datetime]);
262
    }
263
264
    public function testFetchBoth() : void
265
    {
266
        $sql = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?';
267
        $row = $this->connection->executeQuery($sql, [1, 'foo'])->fetch(FetchMode::MIXED);
268
269
        self::assertNotFalse($row);
270
271
        $row = array_change_key_case($row, CASE_LOWER);
272
273
        self::assertEquals(1, $row['test_int']);
274
        self::assertEquals('foo', $row['test_string']);
275
        self::assertEquals(1, $row[0]);
276
        self::assertEquals('foo', $row[1]);
277
    }
278
279
    public function testFetchNoResult() : void
280
    {
281
        self::assertFalse(
282
            $this->connection->executeQuery('SELECT test_int FROM fetch_table WHERE test_int = ?', [-1])->fetch()
283
        );
284
    }
285
286
    public function testFetchAssoc() : void
287
    {
288
        $sql = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?';
289
        $row = $this->connection->fetchAssoc($sql, [1, 'foo']);
290
291
        self::assertNotFalse($row);
292
293
        $row = array_change_key_case($row, CASE_LOWER);
0 ignored issues
show
Bug introduced by
It seems like $row can also be of type false; however, parameter $input of array_change_key_case() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

293
        $row = array_change_key_case(/** @scrutinizer ignore-type */ $row, CASE_LOWER);
Loading history...
294
295
        self::assertEquals(1, $row['test_int']);
296
        self::assertEquals('foo', $row['test_string']);
297
    }
298
299
    public function testFetchAssocWithTypes() : void
300
    {
301
        $datetimeString = '2010-01-01 10:10:10';
302
        $datetime       = new DateTime($datetimeString);
303
304
        $sql = 'SELECT test_int, test_datetime FROM fetch_table WHERE test_int = ? AND test_datetime = ?';
305
        $row = $this->connection->fetchAssoc(
306
            $sql,
307
            [1, $datetime],
308
            [ParameterType::STRING, Types::DATETIME_MUTABLE]
309
        );
310
311
        self::assertNotFalse($row);
312
313
        $row = array_change_key_case($row, CASE_LOWER);
0 ignored issues
show
Bug introduced by
It seems like $row can also be of type false; however, parameter $input of array_change_key_case() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

313
        $row = array_change_key_case(/** @scrutinizer ignore-type */ $row, CASE_LOWER);
Loading history...
314
315
        self::assertEquals(1, $row['test_int']);
316
        self::assertStringStartsWith($datetimeString, $row['test_datetime']);
317
    }
318
319
    public function testFetchAssocWithMissingTypes() : void
320
    {
321
        if ($this->connection->getDriver() instanceof MySQLiDriver ||
322
            $this->connection->getDriver() instanceof SQLSrvDriver) {
323
            $this->markTestSkipped('mysqli and sqlsrv actually supports this');
324
        }
325
326
        $datetimeString = '2010-01-01 10:10:10';
327
        $datetime       = new DateTime($datetimeString);
328
        $sql            = 'SELECT test_int, test_datetime FROM fetch_table WHERE test_int = ? AND test_datetime = ?';
329
330
        $this->expectException(DBALException::class);
331
332
        $this->connection->fetchAssoc($sql, [1, $datetime]);
333
    }
334
335
    public function testFetchArray() : void
336
    {
337
        $sql = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?';
338
        $row = $this->connection->fetchArray($sql, [1, 'foo']);
339
340
        self::assertEquals(1, $row[0]);
341
        self::assertEquals('foo', $row[1]);
342
    }
343
344
    public function testFetchArrayWithTypes() : void
345
    {
346
        $datetimeString = '2010-01-01 10:10:10';
347
        $datetime       = new DateTime($datetimeString);
348
349
        $sql = 'SELECT test_int, test_datetime FROM fetch_table WHERE test_int = ? AND test_datetime = ?';
350
        $row = $this->connection->fetchArray(
351
            $sql,
352
            [1, $datetime],
353
            [ParameterType::STRING, Types::DATETIME_MUTABLE]
354
        );
355
356
        self::assertNotFalse($row);
357
358
        $row = array_change_key_case($row, CASE_LOWER);
0 ignored issues
show
Bug introduced by
It seems like $row can also be of type false; however, parameter $input of array_change_key_case() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

358
        $row = array_change_key_case(/** @scrutinizer ignore-type */ $row, CASE_LOWER);
Loading history...
359
360
        self::assertEquals(1, $row[0]);
361
        self::assertStringStartsWith($datetimeString, $row[1]);
362
    }
363
364
    public function testFetchArrayWithMissingTypes() : void
365
    {
366
        if ($this->connection->getDriver() instanceof MySQLiDriver ||
367
            $this->connection->getDriver() instanceof SQLSrvDriver) {
368
            $this->markTestSkipped('mysqli and sqlsrv actually supports this');
369
        }
370
371
        $datetimeString = '2010-01-01 10:10:10';
372
        $datetime       = new DateTime($datetimeString);
373
        $sql            = 'SELECT test_int, test_datetime FROM fetch_table WHERE test_int = ? AND test_datetime = ?';
374
375
        $this->expectException(DBALException::class);
376
377
        $this->connection->fetchArray($sql, [1, $datetime]);
378
    }
379
380
    public function testFetchColumn() : void
381
    {
382
        $sql     = 'SELECT test_int FROM fetch_table WHERE test_int = ? AND test_string = ?';
383
        $testInt = $this->connection->fetchColumn($sql, [1, 'foo']);
384
385
        self::assertEquals(1, $testInt);
386
387
        $sql        = 'SELECT test_string FROM fetch_table WHERE test_int = ? AND test_string = ?';
388
        $testString = $this->connection->fetchColumn($sql, [1, 'foo']);
389
390
        self::assertEquals('foo', $testString);
391
    }
392
393
    public function testFetchColumnWithTypes() : void
394
    {
395
        $datetimeString = '2010-01-01 10:10:10';
396
        $datetime       = new DateTime($datetimeString);
397
398
        $sql    = 'SELECT test_datetime FROM fetch_table WHERE test_int = ? AND test_datetime = ?';
399
        $column = $this->connection->fetchColumn(
400
            $sql,
401
            [1, $datetime],
402
            [ParameterType::STRING, Types::DATETIME_MUTABLE]
403
        );
404
405
        self::assertIsString($column);
406
407
        self::assertStringStartsWith($datetimeString, $column);
0 ignored issues
show
Bug introduced by
It seems like $column can also be of type false; however, parameter $string of PHPUnit\Framework\Assert::assertStringStartsWith() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

407
        self::assertStringStartsWith($datetimeString, /** @scrutinizer ignore-type */ $column);
Loading history...
408
    }
409
410
    public function testFetchColumnWithMissingTypes() : void
411
    {
412
        if ($this->connection->getDriver() instanceof MySQLiDriver ||
413
            $this->connection->getDriver() instanceof SQLSrvDriver) {
414
            $this->markTestSkipped('mysqli and sqlsrv actually supports this');
415
        }
416
417
        $datetimeString = '2010-01-01 10:10:10';
418
        $datetime       = new DateTime($datetimeString);
419
        $sql            = 'SELECT test_int, test_datetime FROM fetch_table WHERE test_int = ? AND test_datetime = ?';
420
421
        $this->expectException(DBALException::class);
422
423
        $this->connection->fetchColumn($sql, [1, $datetime]);
424
    }
425
426
    /**
427
     * @group DDC-697
428
     */
429
    public function testExecuteQueryBindDateTimeType() : void
430
    {
431
        $sql  = 'SELECT count(*) AS c FROM fetch_table WHERE test_datetime = ?';
432
        $stmt = $this->connection->executeQuery(
433
            $sql,
434
            [1 => new DateTime('2010-01-01 10:10:10')],
435
            [1 => Types::DATETIME_MUTABLE]
436
        );
437
438
        self::assertEquals(1, $stmt->fetchColumn());
439
    }
440
441
    /**
442
     * @group DDC-697
443
     */
444
    public function testExecuteUpdateBindDateTimeType() : void
445
    {
446
        $datetime = new DateTime('2010-02-02 20:20:20');
447
448
        $sql          = 'INSERT INTO fetch_table (test_int, test_string, test_datetime) VALUES (?, ?, ?)';
449
        $affectedRows = $this->connection->executeUpdate($sql, [
450
            1 => 50,
451
            2 => 'foo',
452
            3 => $datetime,
453
        ], [
454
            1 => ParameterType::INTEGER,
455
            2 => ParameterType::STRING,
456
            3 => Types::DATETIME_MUTABLE,
457
        ]);
458
459
        self::assertEquals(1, $affectedRows);
460
        self::assertEquals(1, $this->connection->executeQuery(
461
            'SELECT count(*) AS c FROM fetch_table WHERE test_datetime = ?',
462
            [1 => $datetime],
463
            [1 => Types::DATETIME_MUTABLE]
464
        )->fetchColumn());
465
    }
466
467
    /**
468
     * @group DDC-697
469
     */
470
    public function testPrepareQueryBindValueDateTimeType() : void
471
    {
472
        $sql  = 'SELECT count(*) AS c FROM fetch_table WHERE test_datetime = ?';
473
        $stmt = $this->connection->prepare($sql);
474
        $stmt->bindValue(1, new DateTime('2010-01-01 10:10:10'), Types::DATETIME_MUTABLE);
475
        $stmt->execute();
476
477
        self::assertEquals(1, $stmt->fetchColumn());
478
    }
479
480
    /**
481
     * @group DBAL-78
482
     */
483
    public function testNativeArrayListSupport() : void
484
    {
485
        for ($i = 100; $i < 110; $i++) {
486
            $this->connection->insert('fetch_table', ['test_int' => $i, 'test_string' => 'foo' . $i, 'test_datetime' => '2010-01-01 10:10:10']);
487
        }
488
489
        $stmt = $this->connection->executeQuery(
490
            'SELECT test_int FROM fetch_table WHERE test_int IN (?)',
491
            [[100, 101, 102, 103, 104]],
492
            [Connection::PARAM_INT_ARRAY]
493
        );
494
495
        $data = $stmt->fetchAll(FetchMode::NUMERIC);
496
        self::assertCount(5, $data);
497
        self::assertEquals([[100], [101], [102], [103], [104]], $data);
498
499
        $stmt = $this->connection->executeQuery(
500
            'SELECT test_int FROM fetch_table WHERE test_string IN (?)',
501
            [['foo100', 'foo101', 'foo102', 'foo103', 'foo104']],
502
            [Connection::PARAM_STR_ARRAY]
503
        );
504
505
        $data = $stmt->fetchAll(FetchMode::NUMERIC);
506
        self::assertCount(5, $data);
507
        self::assertEquals([[100], [101], [102], [103], [104]], $data);
508
    }
509
510
    /**
511
     * @dataProvider getTrimExpressionData
512
     */
513
    public function testTrimExpression(string $value, int $position, ?string $char, string $expectedResult) : void
514
    {
515
        $sql = 'SELECT ' .
516
            $this->connection->getDatabasePlatform()->getTrimExpression($value, $position, $char) . ' AS trimmed ' .
517
            'FROM fetch_table';
518
519
        $row = $this->connection->fetchAssoc($sql);
520
        self::assertIsArray($row);
521
522
        $row = array_change_key_case($row, CASE_LOWER);
0 ignored issues
show
Bug introduced by
It seems like $row can also be of type false; however, parameter $input of array_change_key_case() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

522
        $row = array_change_key_case(/** @scrutinizer ignore-type */ $row, CASE_LOWER);
Loading history...
523
524
        self::assertEquals($expectedResult, $row['trimmed']);
525
    }
526
527
    /**
528
     * @return array<int, array<int, mixed>>
529
     */
530
    public static function getTrimExpressionData() : iterable
531
    {
532
        return [
533
            ['test_string', TrimMode::UNSPECIFIED, null, 'foo'],
534
            ['test_string', TrimMode::LEADING, null, 'foo'],
535
            ['test_string', TrimMode::TRAILING, null, 'foo'],
536
            ['test_string', TrimMode::BOTH, null, 'foo'],
537
            ['test_string', TrimMode::UNSPECIFIED, "'f'", 'oo'],
538
            ['test_string', TrimMode::UNSPECIFIED, "'o'", 'f'],
539
            ['test_string', TrimMode::UNSPECIFIED, "'.'", 'foo'],
540
            ['test_string', TrimMode::LEADING, "'f'", 'oo'],
541
            ['test_string', TrimMode::LEADING, "'o'", 'foo'],
542
            ['test_string', TrimMode::LEADING, "'.'", 'foo'],
543
            ['test_string', TrimMode::TRAILING, "'f'", 'foo'],
544
            ['test_string', TrimMode::TRAILING, "'o'", 'f'],
545
            ['test_string', TrimMode::TRAILING, "'.'", 'foo'],
546
            ['test_string', TrimMode::BOTH, "'f'", 'oo'],
547
            ['test_string', TrimMode::BOTH, "'o'", 'f'],
548
            ['test_string', TrimMode::BOTH, "'.'", 'foo'],
549
            ["' foo '", TrimMode::UNSPECIFIED, null, 'foo'],
550
            ["' foo '", TrimMode::LEADING, null, 'foo '],
551
            ["' foo '", TrimMode::TRAILING, null, ' foo'],
552
            ["' foo '", TrimMode::BOTH, null, 'foo'],
553
            ["' foo '", TrimMode::UNSPECIFIED, "'f'", ' foo '],
554
            ["' foo '", TrimMode::UNSPECIFIED, "'o'", ' foo '],
555
            ["' foo '", TrimMode::UNSPECIFIED, "'.'", ' foo '],
556
            ["' foo '", TrimMode::UNSPECIFIED, "' '", 'foo'],
557
            ["' foo '", TrimMode::LEADING, "'f'", ' foo '],
558
            ["' foo '", TrimMode::LEADING, "'o'", ' foo '],
559
            ["' foo '", TrimMode::LEADING, "'.'", ' foo '],
560
            ["' foo '", TrimMode::LEADING, "' '", 'foo '],
561
            ["' foo '", TrimMode::TRAILING, "'f'", ' foo '],
562
            ["' foo '", TrimMode::TRAILING, "'o'", ' foo '],
563
            ["' foo '", TrimMode::TRAILING, "'.'", ' foo '],
564
            ["' foo '", TrimMode::TRAILING, "' '", ' foo'],
565
            ["' foo '", TrimMode::BOTH, "'f'", ' foo '],
566
            ["' foo '", TrimMode::BOTH, "'o'", ' foo '],
567
            ["' foo '", TrimMode::BOTH, "'.'", ' foo '],
568
            ["' foo '", TrimMode::BOTH, "' '", 'foo'],
569
        ];
570
    }
571
572
    public function testTrimExpressionInvalidMode() : void
573
    {
574
        $this->expectException(InvalidArgumentException::class);
575
        $this->connection->getDatabasePlatform()->getTrimExpression('Trim me!', 0xBEEF);
576
    }
577
578
    /**
579
     * @dataProvider modeProvider
580
     */
581
    public function testDateAddSeconds(callable $buildQuery, callable $bindParams) : void
582
    {
583
        $this->assertDateExpression(
584
            $buildQuery,
585
            $bindParams,
586
            static function (AbstractPlatform $platform, string $interval) : string {
587
                return $platform->getDateAddSecondsExpression('test_datetime', $interval);
588
            },
589
            1,
590
            '2010-01-01 10:10:11'
591
        );
592
    }
593
594
    /**
595
     * @dataProvider modeProvider
596
     */
597
    public function testDateSubSeconds(callable $buildQuery, callable $bindParams) : void
598
    {
599
        $this->assertDateExpression(
600
            $buildQuery,
601
            $bindParams,
602
            static function (AbstractPlatform $platform, string $interval) : string {
603
                return $platform->getDateSubSecondsExpression('test_datetime', $interval);
604
            },
605
            1,
606
            '2010-01-01 10:10:09'
607
        );
608
    }
609
610
    /**
611
     * @dataProvider modeProvider
612
     */
613
    public function testDateAddMinutes(callable $buildQuery, callable $bindParams) : void
614
    {
615
        $this->assertDateExpression(
616
            $buildQuery,
617
            $bindParams,
618
            static function (AbstractPlatform $platform, string $interval) : string {
619
                return $platform->getDateAddMinutesExpression('test_datetime', $interval);
620
            },
621
            5,
622
            '2010-01-01 10:15:10'
623
        );
624
    }
625
626
    /**
627
     * @dataProvider modeProvider
628
     */
629
    public function testDateSubMinutes(callable $buildQuery, callable $bindParams) : void
630
    {
631
        $this->assertDateExpression(
632
            $buildQuery,
633
            $bindParams,
634
            static function (AbstractPlatform $platform, string $interval) : string {
635
                return $platform->getDateSubMinutesExpression('test_datetime', $interval);
636
            },
637
            5,
638
            '2010-01-01 10:05:10'
639
        );
640
    }
641
642
    /**
643
     * @dataProvider modeProvider
644
     */
645
    public function testDateAddHours(callable $buildQuery, callable $bindParams) : void
646
    {
647
        $this->assertDateExpression(
648
            $buildQuery,
649
            $bindParams,
650
            static function (AbstractPlatform $platform, string $interval) : string {
651
                return $platform->getDateAddHourExpression('test_datetime', $interval);
652
            },
653
            3,
654
            '2010-01-01 13:10:10'
655
        );
656
    }
657
658
    /**
659
     * @dataProvider modeProvider
660
     */
661
    public function testDateSubHours(callable $buildQuery, callable $bindParams) : void
662
    {
663
        $this->assertDateExpression(
664
            $buildQuery,
665
            $bindParams,
666
            static function (AbstractPlatform $platform, string $interval) : string {
667
                return $platform->getDateSubHourExpression('test_datetime', $interval);
668
            },
669
            3,
670
            '2010-01-01 07:10:10'
671
        );
672
    }
673
674
    /**
675
     * @dataProvider modeProvider
676
     */
677
    public function testDateAddDays(callable $buildQuery, callable $bindParams) : void
678
    {
679
        $this->assertDateExpression(
680
            $buildQuery,
681
            $bindParams,
682
            static function (AbstractPlatform $platform, string $interval) : string {
683
                return $platform->getDateAddDaysExpression('test_datetime', $interval);
684
            },
685
            10,
686
            '2010-01-11 10:10:10'
687
        );
688
    }
689
690
    /**
691
     * @dataProvider modeProvider
692
     */
693
    public function testDateSubDays(callable $buildQuery, callable $bindParams) : void
694
    {
695
        $this->assertDateExpression(
696
            $buildQuery,
697
            $bindParams,
698
            static function (AbstractPlatform $platform, string $interval) : string {
699
                return $platform->getDateSubDaysExpression('test_datetime', $interval);
700
            },
701
            10,
702
            '2009-12-22 10:10:10'
703
        );
704
    }
705
706
    /**
707
     * @dataProvider modeProvider
708
     */
709
    public function testDateAddWeeks(callable $buildQuery, callable $bindParams) : void
710
    {
711
        $this->assertDateExpression(
712
            $buildQuery,
713
            $bindParams,
714
            static function (AbstractPlatform $platform, string $interval) : string {
715
                return $platform->getDateAddWeeksExpression('test_datetime', $interval);
716
            },
717
            1,
718
            '2010-01-08 10:10:10'
719
        );
720
    }
721
722
    /**
723
     * @dataProvider modeProvider
724
     */
725
    public function testDateSubWeeks(callable $buildQuery, callable $bindParams) : void
726
    {
727
        $this->assertDateExpression(
728
            $buildQuery,
729
            $bindParams,
730
            static function (AbstractPlatform $platform, string $interval) : string {
731
                return $platform->getDateSubWeeksExpression('test_datetime', $interval);
732
            },
733
            1,
734
            '2009-12-25 10:10:10'
735
        );
736
    }
737
738
    /**
739
     * @dataProvider modeProvider
740
     */
741
    public function testDateAddMonths(callable $buildQuery, callable $bindParams) : void
742
    {
743
        $this->assertDateExpression(
744
            $buildQuery,
745
            $bindParams,
746
            static function (AbstractPlatform $platform, string $interval) : string {
747
                return $platform->getDateAddMonthExpression('test_datetime', $interval);
748
            },
749
            2,
750
            '2010-03-01 10:10:10'
751
        );
752
    }
753
754
    /**
755
     * @dataProvider modeProvider
756
     */
757
    public function testDateSubMonths(callable $buildQuery, callable $bindParams) : void
758
    {
759
        $this->assertDateExpression(
760
            $buildQuery,
761
            $bindParams,
762
            static function (AbstractPlatform $platform, string $interval) : string {
763
                return $platform->getDateSubMonthExpression('test_datetime', $interval);
764
            },
765
            2,
766
            '2009-11-01 10:10:10'
767
        );
768
    }
769
770
    /**
771
     * @dataProvider modeProvider
772
     */
773
    public function testDateAddQuarters(callable $buildQuery, callable $bindParams) : void
774
    {
775
        $this->assertDateExpression(
776
            $buildQuery,
777
            $bindParams,
778
            static function (AbstractPlatform $platform, string $interval) : string {
779
                return $platform->getDateAddQuartersExpression('test_datetime', $interval);
780
            },
781
            3,
782
            '2010-10-01 10:10:10'
783
        );
784
    }
785
786
    /**
787
     * @dataProvider modeProvider
788
     */
789
    public function testDateSubQuarters(callable $buildQuery, callable $bindParams) : void
790
    {
791
        $this->assertDateExpression(
792
            $buildQuery,
793
            $bindParams,
794
            static function (AbstractPlatform $platform, string $interval) : string {
795
                return $platform->getDateSubQuartersExpression('test_datetime', $interval);
796
            },
797
            3,
798
            '2009-04-01 10:10:10'
799
        );
800
    }
801
802
    /**
803
     * @dataProvider modeProvider
804
     */
805
    public function testDateAddYears(callable $buildQuery, callable $bindParams) : void
806
    {
807
        $this->assertDateExpression(
808
            $buildQuery,
809
            $bindParams,
810
            static function (AbstractPlatform $platform, string $interval) : string {
811
                return $platform->getDateAddYearsExpression('test_datetime', $interval);
812
            },
813
            6,
814
            '2016-01-01 10:10:10'
815
        );
816
    }
817
818
    /**
819
     * @dataProvider modeProvider
820
     */
821
    public function testDateSubYears(callable $buildQuery, callable $bindParams) : void
822
    {
823
        $this->assertDateExpression(
824
            $buildQuery,
825
            $bindParams,
826
            static function (AbstractPlatform $platform, string $interval) : string {
827
                return $platform->getDateSubYearsExpression('test_datetime', $interval);
828
            },
829
            6,
830
            '2004-01-01 10:10:10'
831
        );
832
    }
833
834
    /**
835
     * @param callable $buildQuery Builds the portion of the query representing the interval value
836
     * @param callable $bindParams Binds the interval value to the statement
837
     * @param callable $expression Builds the platform-specific interval expression
838
     * @param int      $interval   Interval value
839
     * @param string   $expected   Expected value
840
     */
841
    private function assertDateExpression(callable $buildQuery, callable $bindParams, callable $expression, int $interval, string $expected) : void
842
    {
843
        $connection = $this->connection;
844
        $platform   = $connection->getDatabasePlatform();
845
846
        $query = sprintf('SELECT %s FROM fetch_table', $expression($platform, $buildQuery($interval)));
847
        $stmt  = $connection->prepare($query);
848
        $bindParams($stmt, $interval);
849
850
        $stmt->execute();
851
852
        $date = $stmt->fetchColumn();
853
854
        $this->assertEquals($expected, date('Y-m-d H:i:s', strtotime($date)));
0 ignored issues
show
Bug introduced by
It seems like $date can also be of type false; however, parameter $time of strtotime() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

854
        $this->assertEquals($expected, date('Y-m-d H:i:s', strtotime(/** @scrutinizer ignore-type */ $date)));
Loading history...
855
    }
856
857
    /**
858
     * @return mixed[][]
859
     */
860
    public static function modeProvider() : array
861
    {
862
        return [
863
            'bind' => [
864
                static function (int $interval) : string {
0 ignored issues
show
Unused Code introduced by
The parameter $interval is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

864
                static function (/** @scrutinizer ignore-unused */ int $interval) : string {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
865
                    return '?';
866
                },
867
                static function (Statement $stmt, int $interval) : void {
868
                    $stmt->bindParam(1, $interval, ParameterType::INTEGER);
869
                },
870
            ],
871
            'literal' => [
872
                static function (int $interval) : string {
873
                    return sprintf('%d', $interval);
874
                },
875
                static function (Statement $stmt, int $interval) : void {
0 ignored issues
show
Unused Code introduced by
The parameter $interval is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

875
                static function (Statement $stmt, /** @scrutinizer ignore-unused */ int $interval) : void {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $stmt is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

875
                static function (/** @scrutinizer ignore-unused */ Statement $stmt, int $interval) : void {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
876
                },
877
            ],
878
            'expression' => [
879
                static function (int $interval) : string {
880
                    return sprintf('(0 + %d)', $interval);
881
                },
882
                static function (Statement $stmt, int $interval) : void {
0 ignored issues
show
Unused Code introduced by
The parameter $stmt is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

882
                static function (/** @scrutinizer ignore-unused */ Statement $stmt, int $interval) : void {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $interval is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

882
                static function (Statement $stmt, /** @scrutinizer ignore-unused */ int $interval) : void {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
883
                },
884
            ],
885
        ];
886
    }
887
888
    public function testSqliteDateArithmeticWithDynamicInterval() : void
889
    {
890
        $platform = $this->connection->getDatabasePlatform();
891
892
        if (! $platform instanceof SqlitePlatform) {
893
            $this->markTestSkipped('test is for sqlite only');
894
        }
895
896
        $table = new Table('fetch_table_date_math');
897
        $table->addColumn('test_date', 'date');
898
        $table->addColumn('test_days', 'integer');
899
        $table->setPrimaryKey(['test_date']);
900
901
        $sm = $this->connection->getSchemaManager();
902
        $sm->createTable($table);
903
904
        $this->connection->insert('fetch_table_date_math', ['test_date' => '2010-01-01', 'test_days' => 10]);
905
        $this->connection->insert('fetch_table_date_math', ['test_date' => '2010-06-01', 'test_days' => 20]);
906
907
        $sql  = 'SELECT COUNT(*) FROM fetch_table_date_math WHERE ';
908
        $sql .= $platform->getDateSubDaysExpression('test_date', 'test_days') . " < '2010-05-12'";
909
910
        $rowCount = $this->connection->fetchColumn($sql);
911
912
        $this->assertEquals(1, $rowCount);
913
    }
914
915
    public function testLocateExpression() : void
916
    {
917
        $platform = $this->connection->getDatabasePlatform();
918
919
        $sql  = 'SELECT ';
920
        $sql .= $platform->getLocateExpression('test_string', "'oo'") . ' AS locate1, ';
921
        $sql .= $platform->getLocateExpression('test_string', "'foo'") . ' AS locate2, ';
922
        $sql .= $platform->getLocateExpression('test_string', "'bar'") . ' AS locate3, ';
923
        $sql .= $platform->getLocateExpression('test_string', 'test_string') . ' AS locate4, ';
924
        $sql .= $platform->getLocateExpression("'foo'", 'test_string') . ' AS locate5, ';
925
        $sql .= $platform->getLocateExpression("'barfoobaz'", 'test_string') . ' AS locate6, ';
926
        $sql .= $platform->getLocateExpression("'bar'", 'test_string') . ' AS locate7, ';
927
        $sql .= $platform->getLocateExpression('test_string', "'oo'", '2') . ' AS locate8, ';
928
        $sql .= $platform->getLocateExpression('test_string', "'oo'", '3') . ' AS locate9 ';
929
        $sql .= 'FROM fetch_table';
930
931
        $row = $this->connection->fetchAssoc($sql);
932
        assert(is_array($row));
933
934
        $row = array_change_key_case($row, CASE_LOWER);
935
936
        self::assertEquals(2, $row['locate1']);
937
        self::assertEquals(1, $row['locate2']);
938
        self::assertEquals(0, $row['locate3']);
939
        self::assertEquals(1, $row['locate4']);
940
        self::assertEquals(1, $row['locate5']);
941
        self::assertEquals(4, $row['locate6']);
942
        self::assertEquals(0, $row['locate7']);
943
        self::assertEquals(2, $row['locate8']);
944
        self::assertEquals(0, $row['locate9']);
945
    }
946
947
    /**
948
     * @dataProvider substringExpressionProvider
949
     */
950
    public function testSubstringExpression(string $string, string $start, ?string $length, string $expected) : void
951
    {
952
        $platform = $this->connection->getDatabasePlatform();
953
954
        $query = $platform->getDummySelectSQL(
955
            $platform->getSubstringExpression($string, $start, $length)
956
        );
957
958
        $this->assertEquals($expected, $this->connection->fetchColumn($query));
959
    }
960
961
    /**
962
     * @return mixed[][]
963
     */
964
    public static function substringExpressionProvider() : iterable
965
    {
966
        return [
967
            'start-no-length' => [
968
                "'abcdef'",
969
                '3',
970
                null,
971
                'cdef',
972
            ],
973
            'start-with-length' => [
974
                "'abcdef'",
975
                '2',
976
                '4',
977
                'bcde',
978
            ],
979
            'expressions' => [
980
                "'abcdef'",
981
                '1 + 1',
982
                '1 + 1',
983
                'bc',
984
            ],
985
        ];
986
    }
987
988
    public function testQuoteSQLInjection() : void
989
    {
990
        $sql  = 'SELECT * FROM fetch_table WHERE test_string = ' . $this->connection->quote("bar' OR '1'='1");
991
        $rows = $this->connection->fetchAll($sql);
992
993
        self::assertCount(0, $rows, 'no result should be returned, otherwise SQL injection is possible');
994
    }
995
996
    /**
997
     * @group DDC-1213
998
     */
999
    public function testBitComparisonExpressionSupport() : void
1000
    {
1001
        $this->connection->exec('DELETE FROM fetch_table');
1002
        $platform = $this->connection->getDatabasePlatform();
1003
        $bitmap   = [];
1004
1005
        for ($i = 2; $i < 9; $i += 2) {
1006
            $bitmap[$i] = [
1007
                'bit_or'    => ($i | 2),
1008
                'bit_and'   => ($i & 2),
1009
            ];
1010
            $this->connection->insert('fetch_table', [
1011
                'test_int'      => $i,
1012
                'test_string'   => json_encode($bitmap[$i]),
1013
                'test_datetime' => '2010-01-01 10:10:10',
1014
            ]);
1015
        }
1016
1017
        $sql[] = 'SELECT ';
0 ignored issues
show
Comprehensibility Best Practice introduced by
$sql was never initialized. Although not strictly required by PHP, it is generally a good practice to add $sql = array(); before regardless.
Loading history...
1018
        $sql[] = 'test_int, ';
1019
        $sql[] = 'test_string, ';
1020
        $sql[] = $platform->getBitOrComparisonExpression('test_int', '2') . ' AS bit_or, ';
1021
        $sql[] = $platform->getBitAndComparisonExpression('test_int', '2') . ' AS bit_and ';
1022
        $sql[] = 'FROM fetch_table';
1023
1024
        $stmt = $this->connection->executeQuery(implode(PHP_EOL, $sql));
1025
        $data = $stmt->fetchAll(FetchMode::ASSOCIATIVE);
1026
1027
        self::assertCount(4, $data);
1028
        self::assertEquals(count($bitmap), count($data));
1029
        foreach ($data as $row) {
1030
            $row = array_change_key_case($row, CASE_LOWER);
1031
1032
            self::assertArrayHasKey('test_int', $row);
1033
1034
            $id = $row['test_int'];
1035
1036
            self::assertArrayHasKey($id, $bitmap);
1037
            self::assertArrayHasKey($id, $bitmap);
1038
1039
            self::assertArrayHasKey('bit_or', $row);
1040
            self::assertArrayHasKey('bit_and', $row);
1041
1042
            self::assertEquals($row['bit_or'], $bitmap[$id]['bit_or']);
1043
            self::assertEquals($row['bit_and'], $bitmap[$id]['bit_and']);
1044
        }
1045
    }
1046
1047
    public function testSetDefaultFetchMode() : void
1048
    {
1049
        $stmt = $this->connection->query('SELECT * FROM fetch_table');
1050
        $stmt->setFetchMode(FetchMode::NUMERIC);
1051
1052
        $row = array_keys($stmt->fetch());
1053
        self::assertCount(0, array_filter($row, static function ($v) {
1054
            return ! is_numeric($v);
1055
        }), 'should be no non-numerical elements in the result.');
1056
    }
1057
1058
    /**
1059
     * @group DBAL-241
1060
     */
1061
    public function testFetchAllStyleColumn() : void
1062
    {
1063
        $sql = 'DELETE FROM fetch_table';
1064
        $this->connection->executeUpdate($sql);
1065
1066
        $this->connection->insert('fetch_table', ['test_int' => 1, 'test_string' => 'foo']);
1067
        $this->connection->insert('fetch_table', ['test_int' => 10, 'test_string' => 'foo']);
1068
1069
        $sql  = 'SELECT test_int FROM fetch_table';
1070
        $rows = $this->connection->query($sql)->fetchAll(FetchMode::COLUMN);
1071
1072
        self::assertEquals([1, 10], $rows);
1073
    }
1074
1075
    /**
1076
     * @group DBAL-257
1077
     */
1078
    public function testEmptyFetchColumnReturnsFalse() : void
1079
    {
1080
        $this->connection->beginTransaction();
1081
        $this->connection->exec('DELETE FROM fetch_table');
1082
        self::assertFalse($this->connection->fetchColumn('SELECT test_int FROM fetch_table'));
1083
        self::assertFalse($this->connection->query('SELECT test_int FROM fetch_table')->fetchColumn());
1084
        $this->connection->rollBack();
1085
    }
1086
1087
    /**
1088
     * @group DBAL-339
1089
     */
1090
    public function testSetFetchModeOnDbalStatement() : void
1091
    {
1092
        $sql  = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?';
1093
        $stmt = $this->connection->executeQuery($sql, [1, 'foo']);
1094
        $stmt->setFetchMode(FetchMode::NUMERIC);
1095
1096
        $row = $stmt->fetch();
1097
1098
        self::assertArrayHasKey(0, $row);
1099
        self::assertArrayHasKey(1, $row);
1100
        self::assertFalse($stmt->fetch());
1101
    }
1102
1103
    /**
1104
     * @group DBAL-435
1105
     */
1106
    public function testEmptyParameters() : void
1107
    {
1108
        $sql  = 'SELECT * FROM fetch_table WHERE test_int IN (?)';
1109
        $stmt = $this->connection->executeQuery($sql, [[]], [Connection::PARAM_INT_ARRAY]);
1110
        $rows = $stmt->fetchAll();
1111
1112
        self::assertEquals([], $rows);
1113
    }
1114
1115
    /**
1116
     * @group DBAL-1028
1117
     */
1118
    public function testFetchColumnNoResult() : void
1119
    {
1120
        self::assertFalse(
1121
            $this->connection->fetchColumn('SELECT test_int FROM fetch_table WHERE test_int = ?', [-1])
1122
        );
1123
    }
1124
}
1125