Failed Conditions
Pull Request — master (#4007)
by Sergei
62:58
created

DataAccessTest::testPrepareWithFetchAll()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 11
nc 1
nop 0
dl 0
loc 16
rs 9.9
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\DBAL\Tests\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\ParameterType;
13
use Doctrine\DBAL\Platforms\AbstractPlatform;
14
use Doctrine\DBAL\Platforms\SqlitePlatform;
15
use Doctrine\DBAL\Platforms\TrimMode;
16
use Doctrine\DBAL\Schema\Table;
17
use Doctrine\DBAL\Statement;
18
use Doctrine\DBAL\Tests\FunctionalTestCase;
19
use Doctrine\DBAL\Types\Types;
20
use InvalidArgumentException;
21
use function array_change_key_case;
22
use function assert;
23
use function count;
24
use function date;
25
use function is_array;
26
use function json_encode;
27
use function sprintf;
28
use function strtotime;
29
use const CASE_LOWER;
30
31
class DataAccessTest extends FunctionalTestCase
32
{
33
    /** @var bool */
34
    private static $generated = false;
35
36
    protected function setUp() : void
37
    {
38
        parent::setUp();
39
40
        if (self::$generated !== false) {
41
            return;
42
        }
43
44
        $table = new Table('fetch_table');
45
        $table->addColumn('test_int', 'integer');
46
        $table->addColumn('test_string', 'string', ['length' => 32]);
47
        $table->addColumn('test_datetime', 'datetime', ['notnull' => false]);
48
        $table->setPrimaryKey(['test_int']);
49
50
        $sm = $this->connection->getSchemaManager();
51
        $sm->createTable($table);
52
53
        $this->connection->insert('fetch_table', ['test_int' => 1, 'test_string' => 'foo', 'test_datetime' => '2010-01-01 10:10:10']);
54
        self::$generated = true;
55
    }
56
57
    public function testPrepareWithBindValue() : void
58
    {
59
        $sql  = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?';
60
        $stmt = $this->connection->prepare($sql);
61
        self::assertInstanceOf(Statement::class, $stmt);
62
63
        $stmt->bindValue(1, 1);
64
        $stmt->bindValue(2, 'foo');
65
        $stmt->execute();
66
67
        $row = $stmt->fetchAssociative();
68
        self::assertIsArray($row);
69
        $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

69
        $row = array_change_key_case(/** @scrutinizer ignore-type */ $row, CASE_LOWER);
Loading history...
70
        self::assertEquals(['test_int' => 1, 'test_string' => 'foo'], $row);
71
    }
72
73
    public function testPrepareWithBindParam() : void
74
    {
75
        $paramInt = 1;
76
        $paramStr = 'foo';
77
78
        $sql  = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?';
79
        $stmt = $this->connection->prepare($sql);
80
        self::assertInstanceOf(Statement::class, $stmt);
81
82
        $stmt->bindParam(1, $paramInt);
83
        $stmt->bindParam(2, $paramStr);
84
        $stmt->execute();
85
86
        $row = $stmt->fetchAssociative();
87
        self::assertIsArray($row);
88
        $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

88
        $row = array_change_key_case(/** @scrutinizer ignore-type */ $row, CASE_LOWER);
Loading history...
89
        self::assertEquals(['test_int' => 1, 'test_string' => 'foo'], $row);
90
    }
91
92
    public function testPrepareWithFetchAllAssociative() : void
93
    {
94
        $paramInt = 1;
95
        $paramStr = 'foo';
96
97
        $sql  = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?';
98
        $stmt = $this->connection->prepare($sql);
99
        self::assertInstanceOf(Statement::class, $stmt);
100
101
        $stmt->bindParam(1, $paramInt);
102
        $stmt->bindParam(2, $paramStr);
103
        $stmt->execute();
104
105
        $rows    = $stmt->fetchAllAssociative();
106
        $rows[0] = array_change_key_case($rows[0], CASE_LOWER);
107
        self::assertEquals(['test_int' => 1, 'test_string' => 'foo'], $rows[0]);
108
    }
109
110
    public function testPrepareWithFetchOne() : void
111
    {
112
        $paramInt = 1;
113
        $paramStr = 'foo';
114
115
        $sql  = 'SELECT test_int FROM fetch_table WHERE test_int = ? AND test_string = ?';
116
        $stmt = $this->connection->prepare($sql);
117
        self::assertInstanceOf(Statement::class, $stmt);
118
119
        $stmt->bindParam(1, $paramInt);
120
        $stmt->bindParam(2, $paramStr);
121
        $stmt->execute();
122
123
        $column = $stmt->fetchOne();
124
        self::assertEquals(1, $column);
125
    }
126
127
    public function testPrepareWithIterator() : void
128
    {
129
        $paramInt = 1;
130
        $paramStr = 'foo';
131
132
        $sql  = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?';
133
        $stmt = $this->connection->prepare($sql);
134
        self::assertInstanceOf(Statement::class, $stmt);
135
136
        $stmt->bindParam(1, $paramInt);
137
        $stmt->bindParam(2, $paramStr);
138
        $stmt->execute();
139
140
        $rows = [];
141
142
        foreach ($stmt->iterateAssociative() as $row) {
143
            $rows[] = array_change_key_case($row, CASE_LOWER);
0 ignored issues
show
Bug introduced by
$row of type Traversable is incompatible with the type array expected by parameter $input of array_change_key_case(). ( Ignorable by Annotation )

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

143
            $rows[] = array_change_key_case(/** @scrutinizer ignore-type */ $row, CASE_LOWER);
Loading history...
144
        }
145
146
        self::assertEquals(['test_int' => 1, 'test_string' => 'foo'], $rows[0]);
147
    }
148
149
    public function testPrepareWithQuoted() : void
150
    {
151
        $table    = 'fetch_table';
152
        $paramInt = 1;
153
        $paramStr = 'foo';
154
155
        $stmt = $this->connection->prepare(sprintf(
156
            'SELECT test_int, test_string FROM %s WHERE test_int = %d AND test_string = %s',
157
            $this->connection->quoteIdentifier($table),
158
            $paramInt,
159
            $this->connection->quote($paramStr)
160
        ));
161
        self::assertInstanceOf(Statement::class, $stmt);
162
    }
163
164
    public function testPrepareWithExecuteParams() : void
165
    {
166
        $paramInt = 1;
167
        $paramStr = 'foo';
168
169
        $sql  = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?';
170
        $stmt = $this->connection->prepare($sql);
171
        self::assertInstanceOf(Statement::class, $stmt);
172
        $stmt->execute([$paramInt, $paramStr]);
173
174
        $row = $stmt->fetchAssociative();
175
        self::assertNotFalse($row);
176
        $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

176
        $row = array_change_key_case(/** @scrutinizer ignore-type */ $row, CASE_LOWER);
Loading history...
177
        self::assertEquals(['test_int' => 1, 'test_string' => 'foo'], $row);
178
    }
179
180
    public function testFetchAllAssociative() : void
181
    {
182
        $sql  = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?';
183
        $data = $this->connection->fetchAllAssociative($sql, [1, 'foo']);
184
185
        self::assertCount(1, $data);
186
187
        $row = $data[0];
188
        self::assertCount(2, $row);
189
190
        $row = array_change_key_case($row, CASE_LOWER);
191
        self::assertEquals(1, $row['test_int']);
192
        self::assertEquals('foo', $row['test_string']);
193
    }
194
195
    /**
196
     * @group DBAL-209
197
     */
198
    public function testFetchAllWithTypes() : void
199
    {
200
        $datetimeString = '2010-01-01 10:10:10';
201
        $datetime       = new DateTime($datetimeString);
202
203
        $sql  = 'SELECT test_int, test_datetime FROM fetch_table WHERE test_int = ? AND test_datetime = ?';
204
        $data = $this->connection->fetchAllAssociative(
205
            $sql,
206
            [1, $datetime],
207
            [ParameterType::STRING, Types::DATETIME_MUTABLE]
208
        );
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::assertStringStartsWith($datetimeString, $row['test_datetime']);
218
    }
219
220
    /**
221
     * @group DBAL-209
222
     */
223
    public function testFetchAllWithMissingTypes() : void
224
    {
225
        if ($this->connection->getDriver() instanceof MySQLiDriver ||
226
            $this->connection->getDriver() instanceof SQLSrvDriver) {
227
            self::markTestSkipped('mysqli and sqlsrv actually supports this');
228
        }
229
230
        $datetimeString = '2010-01-01 10:10:10';
231
        $datetime       = new DateTime($datetimeString);
232
        $sql            = 'SELECT test_int, test_datetime FROM fetch_table WHERE test_int = ? AND test_datetime = ?';
233
234
        $this->expectException(DBALException::class);
235
236
        $this->connection->fetchAllAssociative($sql, [1, $datetime]);
237
    }
238
239
    public function testFetchNoResult() : void
240
    {
241
        self::assertFalse(
242
            $this->connection->executeQuery('SELECT test_int FROM fetch_table WHERE test_int = ?', [-1])->fetchAssociative()
243
        );
244
    }
245
246
    public function testFetchAssociative() : void
247
    {
248
        $sql = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?';
249
        $row = $this->connection->fetchAssociative($sql, [1, 'foo']);
250
251
        self::assertNotFalse($row);
252
253
        $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

253
        $row = array_change_key_case(/** @scrutinizer ignore-type */ $row, CASE_LOWER);
Loading history...
254
255
        self::assertEquals(1, $row['test_int']);
256
        self::assertEquals('foo', $row['test_string']);
257
    }
258
259
    public function testFetchAssocWithTypes() : void
260
    {
261
        $datetimeString = '2010-01-01 10:10:10';
262
        $datetime       = new DateTime($datetimeString);
263
264
        $sql = 'SELECT test_int, test_datetime FROM fetch_table WHERE test_int = ? AND test_datetime = ?';
265
        $row = $this->connection->fetchAssociative(
266
            $sql,
267
            [1, $datetime],
268
            [ParameterType::STRING, Types::DATETIME_MUTABLE]
269
        );
270
271
        self::assertNotFalse($row);
272
273
        $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

273
        $row = array_change_key_case(/** @scrutinizer ignore-type */ $row, CASE_LOWER);
Loading history...
274
275
        self::assertEquals(1, $row['test_int']);
276
        self::assertStringStartsWith($datetimeString, $row['test_datetime']);
277
    }
278
279
    public function testFetchAssocWithMissingTypes() : void
280
    {
281
        if ($this->connection->getDriver() instanceof MySQLiDriver ||
282
            $this->connection->getDriver() instanceof SQLSrvDriver) {
283
            self::markTestSkipped('mysqli and sqlsrv actually supports this');
284
        }
285
286
        $datetimeString = '2010-01-01 10:10:10';
287
        $datetime       = new DateTime($datetimeString);
288
        $sql            = 'SELECT test_int, test_datetime FROM fetch_table WHERE test_int = ? AND test_datetime = ?';
289
290
        $this->expectException(DBALException::class);
291
292
        $this->connection->fetchAssociative($sql, [1, $datetime]);
293
    }
294
295
    public function testFetchArray() : void
296
    {
297
        $sql = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?';
298
        $row = $this->connection->fetchNumeric($sql, [1, 'foo']);
299
300
        self::assertIsArray($row);
301
        self::assertEquals(1, $row[0]);
302
        self::assertEquals('foo', $row[1]);
303
    }
304
305
    public function testFetchArrayWithTypes() : void
306
    {
307
        $datetimeString = '2010-01-01 10:10:10';
308
        $datetime       = new DateTime($datetimeString);
309
310
        $sql = 'SELECT test_int, test_datetime FROM fetch_table WHERE test_int = ? AND test_datetime = ?';
311
        $row = $this->connection->fetchNumeric(
312
            $sql,
313
            [1, $datetime],
314
            [ParameterType::STRING, Types::DATETIME_MUTABLE]
315
        );
316
317
        self::assertNotFalse($row);
318
319
        $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

319
        $row = array_change_key_case(/** @scrutinizer ignore-type */ $row, CASE_LOWER);
Loading history...
320
321
        self::assertEquals(1, $row[0]);
322
        self::assertStringStartsWith($datetimeString, $row[1]);
323
    }
324
325
    public function testFetchArrayWithMissingTypes() : void
326
    {
327
        if ($this->connection->getDriver() instanceof MySQLiDriver ||
328
            $this->connection->getDriver() instanceof SQLSrvDriver) {
329
            self::markTestSkipped('mysqli and sqlsrv actually supports this');
330
        }
331
332
        $datetimeString = '2010-01-01 10:10:10';
333
        $datetime       = new DateTime($datetimeString);
334
        $sql            = 'SELECT test_int, test_datetime FROM fetch_table WHERE test_int = ? AND test_datetime = ?';
335
336
        $this->expectException(DBALException::class);
337
338
        $this->connection->fetchNumeric($sql, [1, $datetime]);
339
    }
340
341
    public function testFetchOne() : void
342
    {
343
        $sql     = 'SELECT test_int FROM fetch_table WHERE test_int = ? AND test_string = ?';
344
        $testInt = $this->connection->fetchOne($sql, [1, 'foo']);
345
346
        self::assertEquals(1, $testInt);
347
348
        $sql        = 'SELECT test_string FROM fetch_table WHERE test_int = ? AND test_string = ?';
349
        $testString = $this->connection->fetchOne($sql, [1, 'foo']);
350
351
        self::assertEquals('foo', $testString);
352
    }
353
354
    public function testFetchOneWithTypes() : void
355
    {
356
        $datetimeString = '2010-01-01 10:10:10';
357
        $datetime       = new DateTime($datetimeString);
358
359
        $sql    = 'SELECT test_datetime FROM fetch_table WHERE test_int = ? AND test_datetime = ?';
360
        $column = $this->connection->fetchOne(
361
            $sql,
362
            [1, $datetime],
363
            [ParameterType::STRING, Types::DATETIME_MUTABLE]
364
        );
365
366
        self::assertIsString($column);
367
368
        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

368
        self::assertStringStartsWith($datetimeString, /** @scrutinizer ignore-type */ $column);
Loading history...
369
    }
370
371
    public function testFetchOneWithMissingTypes() : void
372
    {
373
        if ($this->connection->getDriver() instanceof MySQLiDriver ||
374
            $this->connection->getDriver() instanceof SQLSrvDriver) {
375
            self::markTestSkipped('mysqli and sqlsrv actually supports this');
376
        }
377
378
        $datetimeString = '2010-01-01 10:10:10';
379
        $datetime       = new DateTime($datetimeString);
380
        $sql            = 'SELECT test_int, test_datetime FROM fetch_table WHERE test_int = ? AND test_datetime = ?';
381
382
        $this->expectException(DBALException::class);
383
384
        $this->connection->fetchOne($sql, [1, $datetime]);
385
    }
386
387
    /**
388
     * @group DDC-697
389
     */
390
    public function testExecuteQueryBindDateTimeType() : void
391
    {
392
        $sql  = 'SELECT count(*) AS c FROM fetch_table WHERE test_datetime = ?';
393
        $stmt = $this->connection->executeQuery(
394
            $sql,
395
            [1 => new DateTime('2010-01-01 10:10:10')],
396
            [1 => Types::DATETIME_MUTABLE]
397
        );
398
399
        self::assertEquals(1, $stmt->fetchOne());
400
    }
401
402
    /**
403
     * @group DDC-697
404
     */
405
    public function testExecuteUpdateBindDateTimeType() : void
406
    {
407
        $datetime = new DateTime('2010-02-02 20:20:20');
408
409
        $sql          = 'INSERT INTO fetch_table (test_int, test_string, test_datetime) VALUES (?, ?, ?)';
410
        $affectedRows = $this->connection->executeUpdate($sql, [
411
            1 => 50,
412
            2 => 'foo',
413
            3 => $datetime,
414
        ], [
415
            1 => ParameterType::INTEGER,
416
            2 => ParameterType::STRING,
417
            3 => Types::DATETIME_MUTABLE,
418
        ]);
419
420
        self::assertEquals(1, $affectedRows);
421
        self::assertEquals(1, $this->connection->executeQuery(
422
            'SELECT count(*) AS c FROM fetch_table WHERE test_datetime = ?',
423
            [1 => $datetime],
424
            [1 => Types::DATETIME_MUTABLE]
425
        )->fetchOne());
426
    }
427
428
    /**
429
     * @group DDC-697
430
     */
431
    public function testPrepareQueryBindValueDateTimeType() : void
432
    {
433
        $sql  = 'SELECT count(*) AS c FROM fetch_table WHERE test_datetime = ?';
434
        $stmt = $this->connection->prepare($sql);
435
        $stmt->bindValue(1, new DateTime('2010-01-01 10:10:10'), Types::DATETIME_MUTABLE);
436
        $stmt->execute();
437
438
        self::assertEquals(1, $stmt->fetchOne());
439
    }
440
441
    /**
442
     * @group DBAL-78
443
     */
444
    public function testNativeArrayListSupport() : void
445
    {
446
        for ($i = 100; $i < 110; $i++) {
447
            $this->connection->insert('fetch_table', ['test_int' => $i, 'test_string' => 'foo' . $i, 'test_datetime' => '2010-01-01 10:10:10']);
448
        }
449
450
        $stmt = $this->connection->executeQuery(
451
            'SELECT test_int FROM fetch_table WHERE test_int IN (?)',
452
            [[100, 101, 102, 103, 104]],
453
            [Connection::PARAM_INT_ARRAY]
454
        );
455
456
        $data = $stmt->fetchAllNumeric();
457
        self::assertCount(5, $data);
458
        self::assertEquals([[100], [101], [102], [103], [104]], $data);
459
460
        $stmt = $this->connection->executeQuery(
461
            'SELECT test_int FROM fetch_table WHERE test_string IN (?)',
462
            [['foo100', 'foo101', 'foo102', 'foo103', 'foo104']],
463
            [Connection::PARAM_STR_ARRAY]
464
        );
465
466
        $data = $stmt->fetchAllNumeric();
467
        self::assertCount(5, $data);
468
        self::assertEquals([[100], [101], [102], [103], [104]], $data);
469
    }
470
471
    /**
472
     * @dataProvider getTrimExpressionData
473
     */
474
    public function testTrimExpression(string $value, int $position, ?string $char, string $expectedResult) : void
475
    {
476
        $sql = 'SELECT ' .
477
            $this->connection->getDatabasePlatform()->getTrimExpression($value, $position, $char) . ' AS trimmed ' .
478
            'FROM fetch_table';
479
480
        $row = $this->connection->fetchAssociative($sql);
481
        self::assertIsArray($row);
482
483
        $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

483
        $row = array_change_key_case(/** @scrutinizer ignore-type */ $row, CASE_LOWER);
Loading history...
484
485
        self::assertEquals($expectedResult, $row['trimmed']);
486
    }
487
488
    /**
489
     * @return array<int, array<int, mixed>>
490
     */
491
    public static function getTrimExpressionData() : iterable
492
    {
493
        return [
494
            ['test_string', TrimMode::UNSPECIFIED, null, 'foo'],
495
            ['test_string', TrimMode::LEADING, null, 'foo'],
496
            ['test_string', TrimMode::TRAILING, null, 'foo'],
497
            ['test_string', TrimMode::BOTH, null, 'foo'],
498
            ['test_string', TrimMode::UNSPECIFIED, "'f'", 'oo'],
499
            ['test_string', TrimMode::UNSPECIFIED, "'o'", 'f'],
500
            ['test_string', TrimMode::UNSPECIFIED, "'.'", 'foo'],
501
            ['test_string', TrimMode::LEADING, "'f'", 'oo'],
502
            ['test_string', TrimMode::LEADING, "'o'", 'foo'],
503
            ['test_string', TrimMode::LEADING, "'.'", 'foo'],
504
            ['test_string', TrimMode::TRAILING, "'f'", 'foo'],
505
            ['test_string', TrimMode::TRAILING, "'o'", 'f'],
506
            ['test_string', TrimMode::TRAILING, "'.'", 'foo'],
507
            ['test_string', TrimMode::BOTH, "'f'", 'oo'],
508
            ['test_string', TrimMode::BOTH, "'o'", 'f'],
509
            ['test_string', TrimMode::BOTH, "'.'", 'foo'],
510
            ["' foo '", TrimMode::UNSPECIFIED, null, 'foo'],
511
            ["' foo '", TrimMode::LEADING, null, 'foo '],
512
            ["' foo '", TrimMode::TRAILING, null, ' foo'],
513
            ["' foo '", TrimMode::BOTH, null, 'foo'],
514
            ["' foo '", TrimMode::UNSPECIFIED, "'f'", ' foo '],
515
            ["' foo '", TrimMode::UNSPECIFIED, "'o'", ' foo '],
516
            ["' foo '", TrimMode::UNSPECIFIED, "'.'", ' foo '],
517
            ["' foo '", TrimMode::UNSPECIFIED, "' '", 'foo'],
518
            ["' foo '", TrimMode::LEADING, "'f'", ' foo '],
519
            ["' foo '", TrimMode::LEADING, "'o'", ' foo '],
520
            ["' foo '", TrimMode::LEADING, "'.'", ' foo '],
521
            ["' foo '", TrimMode::LEADING, "' '", 'foo '],
522
            ["' foo '", TrimMode::TRAILING, "'f'", ' foo '],
523
            ["' foo '", TrimMode::TRAILING, "'o'", ' foo '],
524
            ["' foo '", TrimMode::TRAILING, "'.'", ' foo '],
525
            ["' foo '", TrimMode::TRAILING, "' '", ' foo'],
526
            ["' foo '", TrimMode::BOTH, "'f'", ' foo '],
527
            ["' foo '", TrimMode::BOTH, "'o'", ' foo '],
528
            ["' foo '", TrimMode::BOTH, "'.'", ' foo '],
529
            ["' foo '", TrimMode::BOTH, "' '", 'foo'],
530
        ];
531
    }
532
533
    public function testTrimExpressionInvalidMode() : void
534
    {
535
        $this->expectException(InvalidArgumentException::class);
536
        $this->connection->getDatabasePlatform()->getTrimExpression('Trim me!', 0xBEEF);
537
    }
538
539
    /**
540
     * @dataProvider modeProvider
541
     */
542
    public function testDateAddSeconds(callable $buildQuery, callable $bindParams) : void
543
    {
544
        $this->assertDateExpression(
545
            $buildQuery,
546
            $bindParams,
547
            static function (AbstractPlatform $platform, string $interval) : string {
548
                return $platform->getDateAddSecondsExpression('test_datetime', $interval);
549
            },
550
            1,
551
            '2010-01-01 10:10:11'
552
        );
553
    }
554
555
    /**
556
     * @dataProvider modeProvider
557
     */
558
    public function testDateSubSeconds(callable $buildQuery, callable $bindParams) : void
559
    {
560
        $this->assertDateExpression(
561
            $buildQuery,
562
            $bindParams,
563
            static function (AbstractPlatform $platform, string $interval) : string {
564
                return $platform->getDateSubSecondsExpression('test_datetime', $interval);
565
            },
566
            1,
567
            '2010-01-01 10:10:09'
568
        );
569
    }
570
571
    /**
572
     * @dataProvider modeProvider
573
     */
574
    public function testDateAddMinutes(callable $buildQuery, callable $bindParams) : void
575
    {
576
        $this->assertDateExpression(
577
            $buildQuery,
578
            $bindParams,
579
            static function (AbstractPlatform $platform, string $interval) : string {
580
                return $platform->getDateAddMinutesExpression('test_datetime', $interval);
581
            },
582
            5,
583
            '2010-01-01 10:15:10'
584
        );
585
    }
586
587
    /**
588
     * @dataProvider modeProvider
589
     */
590
    public function testDateSubMinutes(callable $buildQuery, callable $bindParams) : void
591
    {
592
        $this->assertDateExpression(
593
            $buildQuery,
594
            $bindParams,
595
            static function (AbstractPlatform $platform, string $interval) : string {
596
                return $platform->getDateSubMinutesExpression('test_datetime', $interval);
597
            },
598
            5,
599
            '2010-01-01 10:05:10'
600
        );
601
    }
602
603
    /**
604
     * @dataProvider modeProvider
605
     */
606
    public function testDateAddHours(callable $buildQuery, callable $bindParams) : void
607
    {
608
        $this->assertDateExpression(
609
            $buildQuery,
610
            $bindParams,
611
            static function (AbstractPlatform $platform, string $interval) : string {
612
                return $platform->getDateAddHourExpression('test_datetime', $interval);
613
            },
614
            3,
615
            '2010-01-01 13:10:10'
616
        );
617
    }
618
619
    /**
620
     * @dataProvider modeProvider
621
     */
622
    public function testDateSubHours(callable $buildQuery, callable $bindParams) : void
623
    {
624
        $this->assertDateExpression(
625
            $buildQuery,
626
            $bindParams,
627
            static function (AbstractPlatform $platform, string $interval) : string {
628
                return $platform->getDateSubHourExpression('test_datetime', $interval);
629
            },
630
            3,
631
            '2010-01-01 07:10:10'
632
        );
633
    }
634
635
    /**
636
     * @dataProvider modeProvider
637
     */
638
    public function testDateAddDays(callable $buildQuery, callable $bindParams) : void
639
    {
640
        $this->assertDateExpression(
641
            $buildQuery,
642
            $bindParams,
643
            static function (AbstractPlatform $platform, string $interval) : string {
644
                return $platform->getDateAddDaysExpression('test_datetime', $interval);
645
            },
646
            10,
647
            '2010-01-11 10:10:10'
648
        );
649
    }
650
651
    /**
652
     * @dataProvider modeProvider
653
     */
654
    public function testDateSubDays(callable $buildQuery, callable $bindParams) : void
655
    {
656
        $this->assertDateExpression(
657
            $buildQuery,
658
            $bindParams,
659
            static function (AbstractPlatform $platform, string $interval) : string {
660
                return $platform->getDateSubDaysExpression('test_datetime', $interval);
661
            },
662
            10,
663
            '2009-12-22 10:10:10'
664
        );
665
    }
666
667
    /**
668
     * @dataProvider modeProvider
669
     */
670
    public function testDateAddWeeks(callable $buildQuery, callable $bindParams) : void
671
    {
672
        $this->assertDateExpression(
673
            $buildQuery,
674
            $bindParams,
675
            static function (AbstractPlatform $platform, string $interval) : string {
676
                return $platform->getDateAddWeeksExpression('test_datetime', $interval);
677
            },
678
            1,
679
            '2010-01-08 10:10:10'
680
        );
681
    }
682
683
    /**
684
     * @dataProvider modeProvider
685
     */
686
    public function testDateSubWeeks(callable $buildQuery, callable $bindParams) : void
687
    {
688
        $this->assertDateExpression(
689
            $buildQuery,
690
            $bindParams,
691
            static function (AbstractPlatform $platform, string $interval) : string {
692
                return $platform->getDateSubWeeksExpression('test_datetime', $interval);
693
            },
694
            1,
695
            '2009-12-25 10:10:10'
696
        );
697
    }
698
699
    /**
700
     * @dataProvider modeProvider
701
     */
702
    public function testDateAddMonths(callable $buildQuery, callable $bindParams) : void
703
    {
704
        $this->assertDateExpression(
705
            $buildQuery,
706
            $bindParams,
707
            static function (AbstractPlatform $platform, string $interval) : string {
708
                return $platform->getDateAddMonthExpression('test_datetime', $interval);
709
            },
710
            2,
711
            '2010-03-01 10:10:10'
712
        );
713
    }
714
715
    /**
716
     * @dataProvider modeProvider
717
     */
718
    public function testDateSubMonths(callable $buildQuery, callable $bindParams) : void
719
    {
720
        $this->assertDateExpression(
721
            $buildQuery,
722
            $bindParams,
723
            static function (AbstractPlatform $platform, string $interval) : string {
724
                return $platform->getDateSubMonthExpression('test_datetime', $interval);
725
            },
726
            2,
727
            '2009-11-01 10:10:10'
728
        );
729
    }
730
731
    /**
732
     * @dataProvider modeProvider
733
     */
734
    public function testDateAddQuarters(callable $buildQuery, callable $bindParams) : void
735
    {
736
        $this->assertDateExpression(
737
            $buildQuery,
738
            $bindParams,
739
            static function (AbstractPlatform $platform, string $interval) : string {
740
                return $platform->getDateAddQuartersExpression('test_datetime', $interval);
741
            },
742
            3,
743
            '2010-10-01 10:10:10'
744
        );
745
    }
746
747
    /**
748
     * @dataProvider modeProvider
749
     */
750
    public function testDateSubQuarters(callable $buildQuery, callable $bindParams) : void
751
    {
752
        $this->assertDateExpression(
753
            $buildQuery,
754
            $bindParams,
755
            static function (AbstractPlatform $platform, string $interval) : string {
756
                return $platform->getDateSubQuartersExpression('test_datetime', $interval);
757
            },
758
            3,
759
            '2009-04-01 10:10:10'
760
        );
761
    }
762
763
    /**
764
     * @dataProvider modeProvider
765
     */
766
    public function testDateAddYears(callable $buildQuery, callable $bindParams) : void
767
    {
768
        $this->assertDateExpression(
769
            $buildQuery,
770
            $bindParams,
771
            static function (AbstractPlatform $platform, string $interval) : string {
772
                return $platform->getDateAddYearsExpression('test_datetime', $interval);
773
            },
774
            6,
775
            '2016-01-01 10:10:10'
776
        );
777
    }
778
779
    /**
780
     * @dataProvider modeProvider
781
     */
782
    public function testDateSubYears(callable $buildQuery, callable $bindParams) : void
783
    {
784
        $this->assertDateExpression(
785
            $buildQuery,
786
            $bindParams,
787
            static function (AbstractPlatform $platform, string $interval) : string {
788
                return $platform->getDateSubYearsExpression('test_datetime', $interval);
789
            },
790
            6,
791
            '2004-01-01 10:10:10'
792
        );
793
    }
794
795
    /**
796
     * @param callable $buildQuery Builds the portion of the query representing the interval value
797
     * @param callable $bindParams Binds the interval value to the statement
798
     * @param callable $expression Builds the platform-specific interval expression
799
     * @param int      $interval   Interval value
800
     * @param string   $expected   Expected value
801
     */
802
    private function assertDateExpression(callable $buildQuery, callable $bindParams, callable $expression, int $interval, string $expected) : void
803
    {
804
        $connection = $this->connection;
805
        $platform   = $connection->getDatabasePlatform();
806
807
        $query = sprintf('SELECT %s FROM fetch_table', $expression($platform, $buildQuery($interval)));
808
        $stmt  = $connection->prepare($query);
809
        $bindParams($stmt, $interval);
810
811
        $stmt->execute();
812
813
        $date = $stmt->fetchOne();
814
815
        self::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

815
        self::assertEquals($expected, date('Y-m-d H:i:s', strtotime(/** @scrutinizer ignore-type */ $date)));
Loading history...
816
    }
817
818
    /**
819
     * @return mixed[][]
820
     */
821
    public static function modeProvider() : array
822
    {
823
        return [
824
            'bind' => [
825
                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

825
                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...
826
                    return '?';
827
                },
828
                static function (Statement $stmt, int $interval) : void {
829
                    $stmt->bindParam(1, $interval, ParameterType::INTEGER);
830
                },
831
            ],
832
            'literal' => [
833
                static function (int $interval) : string {
834
                    return sprintf('%d', $interval);
835
                },
836
                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

836
                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

836
                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...
837
                },
838
            ],
839
            'expression' => [
840
                static function (int $interval) : string {
841
                    return sprintf('(0 + %d)', $interval);
842
                },
843
                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

843
                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

843
                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...
844
                },
845
            ],
846
        ];
847
    }
848
849
    public function testSqliteDateArithmeticWithDynamicInterval() : void
850
    {
851
        $platform = $this->connection->getDatabasePlatform();
852
853
        if (! $platform instanceof SqlitePlatform) {
854
            self::markTestSkipped('test is for sqlite only');
855
        }
856
857
        $table = new Table('fetch_table_date_math');
858
        $table->addColumn('test_date', 'date');
859
        $table->addColumn('test_days', 'integer');
860
        $table->setPrimaryKey(['test_date']);
861
862
        $sm = $this->connection->getSchemaManager();
863
        $sm->createTable($table);
864
865
        $this->connection->insert('fetch_table_date_math', ['test_date' => '2010-01-01', 'test_days' => 10]);
866
        $this->connection->insert('fetch_table_date_math', ['test_date' => '2010-06-01', 'test_days' => 20]);
867
868
        $sql  = 'SELECT COUNT(*) FROM fetch_table_date_math WHERE ';
869
        $sql .= $platform->getDateSubDaysExpression('test_date', 'test_days') . " < '2010-05-12'";
870
871
        $rowCount = $this->connection->fetchOne($sql);
872
873
        self::assertEquals(1, $rowCount);
874
    }
875
876
    public function testLocateExpression() : void
877
    {
878
        $platform = $this->connection->getDatabasePlatform();
879
880
        $sql  = 'SELECT ';
881
        $sql .= $platform->getLocateExpression('test_string', "'oo'") . ' AS locate1, ';
882
        $sql .= $platform->getLocateExpression('test_string', "'foo'") . ' AS locate2, ';
883
        $sql .= $platform->getLocateExpression('test_string', "'bar'") . ' AS locate3, ';
884
        $sql .= $platform->getLocateExpression('test_string', 'test_string') . ' AS locate4, ';
885
        $sql .= $platform->getLocateExpression("'foo'", 'test_string') . ' AS locate5, ';
886
        $sql .= $platform->getLocateExpression("'barfoobaz'", 'test_string') . ' AS locate6, ';
887
        $sql .= $platform->getLocateExpression("'bar'", 'test_string') . ' AS locate7, ';
888
        $sql .= $platform->getLocateExpression('test_string', "'oo'", '2') . ' AS locate8, ';
889
        $sql .= $platform->getLocateExpression('test_string', "'oo'", '3') . ' AS locate9 ';
890
        $sql .= 'FROM fetch_table';
891
892
        $row = $this->connection->fetchAssociative($sql);
893
        assert(is_array($row));
894
895
        $row = array_change_key_case($row, CASE_LOWER);
896
897
        self::assertEquals(2, $row['locate1']);
898
        self::assertEquals(1, $row['locate2']);
899
        self::assertEquals(0, $row['locate3']);
900
        self::assertEquals(1, $row['locate4']);
901
        self::assertEquals(1, $row['locate5']);
902
        self::assertEquals(4, $row['locate6']);
903
        self::assertEquals(0, $row['locate7']);
904
        self::assertEquals(2, $row['locate8']);
905
        self::assertEquals(0, $row['locate9']);
906
    }
907
908
    /**
909
     * @dataProvider substringExpressionProvider
910
     */
911
    public function testSubstringExpression(string $string, string $start, ?string $length, string $expected) : void
912
    {
913
        $platform = $this->connection->getDatabasePlatform();
914
915
        $query = $platform->getDummySelectSQL(
916
            $platform->getSubstringExpression($string, $start, $length)
917
        );
918
919
        self::assertEquals($expected, $this->connection->fetchOne($query));
920
    }
921
922
    /**
923
     * @return mixed[][]
924
     */
925
    public static function substringExpressionProvider() : iterable
926
    {
927
        return [
928
            'start-no-length' => [
929
                "'abcdef'",
930
                '3',
931
                null,
932
                'cdef',
933
            ],
934
            'start-with-length' => [
935
                "'abcdef'",
936
                '2',
937
                '4',
938
                'bcde',
939
            ],
940
            'expressions' => [
941
                "'abcdef'",
942
                '1 + 1',
943
                '1 + 1',
944
                'bc',
945
            ],
946
        ];
947
    }
948
949
    public function testQuoteSQLInjection() : void
950
    {
951
        $sql  = 'SELECT * FROM fetch_table WHERE test_string = ' . $this->connection->quote("bar' OR '1'='1");
952
        $rows = $this->connection->fetchAllAssociative($sql);
953
954
        self::assertCount(0, $rows, 'no result should be returned, otherwise SQL injection is possible');
955
    }
956
957
    /**
958
     * @group DDC-1213
959
     */
960
    public function testBitComparisonExpressionSupport() : void
961
    {
962
        $this->connection->exec('DELETE FROM fetch_table');
963
        $platform = $this->connection->getDatabasePlatform();
964
        $bitmap   = [];
965
966
        for ($i = 2; $i < 9; $i += 2) {
967
            $bitmap[$i] = [
968
                'bit_or'    => ($i | 2),
969
                'bit_and'   => ($i & 2),
970
            ];
971
            $this->connection->insert('fetch_table', [
972
                'test_int'      => $i,
973
                'test_string'   => json_encode($bitmap[$i]),
974
                'test_datetime' => '2010-01-01 10:10:10',
975
            ]);
976
        }
977
978
        $sql = 'SELECT test_int, test_string'
979
            . ', ' . $platform->getBitOrComparisonExpression('test_int', '2') . ' AS bit_or'
980
            . ', ' . $platform->getBitAndComparisonExpression('test_int', '2') . ' AS bit_and'
981
            . ' FROM fetch_table';
982
983
        $stmt = $this->connection->executeQuery($sql);
984
        $data = $stmt->fetchAllAssociative();
985
986
        self::assertCount(4, $data);
987
        self::assertEquals(count($bitmap), count($data));
988
        foreach ($data as $row) {
989
            $row = array_change_key_case($row, CASE_LOWER);
990
991
            self::assertArrayHasKey('test_int', $row);
992
993
            $id = $row['test_int'];
994
995
            self::assertArrayHasKey($id, $bitmap);
996
            self::assertArrayHasKey($id, $bitmap);
997
998
            self::assertArrayHasKey('bit_or', $row);
999
            self::assertArrayHasKey('bit_and', $row);
1000
1001
            self::assertEquals($row['bit_or'], $bitmap[$id]['bit_or']);
1002
            self::assertEquals($row['bit_and'], $bitmap[$id]['bit_and']);
1003
        }
1004
    }
1005
1006
    /**
1007
     * @group DBAL-241
1008
     */
1009
    public function testFetchAllStyleColumn() : void
1010
    {
1011
        $sql = 'DELETE FROM fetch_table';
1012
        $this->connection->executeUpdate($sql);
1013
1014
        $this->connection->insert('fetch_table', ['test_int' => 1, 'test_string' => 'foo']);
1015
        $this->connection->insert('fetch_table', ['test_int' => 10, 'test_string' => 'foo']);
1016
1017
        $sql  = 'SELECT test_int FROM fetch_table';
1018
        $rows = $this->connection->query($sql)->fetchColumn();
1019
1020
        self::assertEquals([1, 10], $rows);
1021
    }
1022
1023
    /**
1024
     * @group DBAL-257
1025
     */
1026
    public function testEmptyFetchOneReturnsFalse() : void
1027
    {
1028
        $this->connection->beginTransaction();
1029
        $this->connection->exec('DELETE FROM fetch_table');
1030
        self::assertFalse($this->connection->fetchOne('SELECT test_int FROM fetch_table'));
1031
        self::assertFalse($this->connection->query('SELECT test_int FROM fetch_table')->fetchOne());
1032
        $this->connection->rollBack();
1033
    }
1034
1035
    /**
1036
     * @group DBAL-435
1037
     */
1038
    public function testEmptyParameters() : void
1039
    {
1040
        $sql  = 'SELECT * FROM fetch_table WHERE test_int IN (?)';
1041
        $stmt = $this->connection->executeQuery($sql, [[]], [Connection::PARAM_INT_ARRAY]);
1042
        $rows = $stmt->fetchAllAssociative();
1043
1044
        self::assertEquals([], $rows);
1045
    }
1046
1047
    /**
1048
     * @group DBAL-1028
1049
     */
1050
    public function testFetchOneNoResult() : void
1051
    {
1052
        self::assertFalse(
1053
            $this->connection->fetchOne('SELECT test_int FROM fetch_table WHERE test_int = ?', [-1])
1054
        );
1055
    }
1056
}
1057