Failed Conditions
Pull Request — develop (#3348)
by Sergei
126:17 queued 61:12
created

DataAccessTest::substringExpressionProvider()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 20
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 16
dl 0
loc 20
rs 9.7333
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
namespace Doctrine\Tests\DBAL\Functional;
4
5
use DateTime;
6
use Doctrine\DBAL\Connection;
7
use Doctrine\DBAL\DBALException;
8
use Doctrine\DBAL\Driver\Mysqli\Driver as MySQLiDriver;
9
use Doctrine\DBAL\Driver\OCI8\Driver as Oci8Driver;
10
use Doctrine\DBAL\Driver\PDOConnection;
11
use Doctrine\DBAL\Driver\PDOOracle\Driver as PDOOracleDriver;
12
use Doctrine\DBAL\Driver\SQLSrv\Driver as SQLSrvDriver;
13
use Doctrine\DBAL\FetchMode;
14
use Doctrine\DBAL\ParameterType;
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\Type;
20
use Doctrine\Tests\DbalFunctionalTestCase;
21
use InvalidArgumentException;
22
use PDO;
23
use const CASE_LOWER;
24
use const PHP_EOL;
25
use function array_change_key_case;
26
use function array_filter;
27
use function array_keys;
28
use function count;
29
use function date;
30
use function implode;
31
use function is_numeric;
32
use function json_encode;
33
use function property_exists;
34
use function sprintf;
35
use function strtotime;
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');
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($sql, [1, $datetime], [ParameterType::STRING, Type::DATETIME]);
230
231
        self::assertCount(1, $data);
232
233
        $row = $data[0];
234
        self::assertCount(2, $row);
235
236
        $row = array_change_key_case($row, CASE_LOWER);
237
        self::assertEquals(1, $row['test_int']);
238
        self::assertStringStartsWith($datetimeString, $row['test_datetime']);
239
    }
240
241
    /**
242
     * @group DBAL-209
243
     */
244
    public function testFetchAllWithMissingTypes() : void
245
    {
246
        if ($this->connection->getDriver() instanceof MySQLiDriver ||
247
            $this->connection->getDriver() instanceof SQLSrvDriver) {
248
            $this->markTestSkipped('mysqli and sqlsrv actually supports this');
249
        }
250
251
        $datetimeString = '2010-01-01 10:10:10';
252
        $datetime       = new DateTime($datetimeString);
253
        $sql            = 'SELECT test_int, test_datetime FROM fetch_table WHERE test_int = ? AND test_datetime = ?';
254
255
        $this->expectException(DBALException::class);
256
257
        $this->connection->fetchAll($sql, [1, $datetime]);
258
    }
259
260
    public function testFetchBoth() : void
261
    {
262
        $sql = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?';
263
        $row = $this->connection->executeQuery($sql, [1, 'foo'])->fetch(FetchMode::MIXED);
264
265
        self::assertNotFalse($row);
266
267
        $row = array_change_key_case($row, CASE_LOWER);
268
269
        self::assertEquals(1, $row['test_int']);
270
        self::assertEquals('foo', $row['test_string']);
271
        self::assertEquals(1, $row[0]);
272
        self::assertEquals('foo', $row[1]);
273
    }
274
275
    public function testFetchNoResult() : void
276
    {
277
        self::assertFalse(
278
            $this->connection->executeQuery('SELECT test_int FROM fetch_table WHERE test_int = ?', [-1])->fetch()
279
        );
280
    }
281
282
    public function testFetchAssoc() : void
283
    {
284
        $sql = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?';
285
        $row = $this->connection->fetchAssoc($sql, [1, 'foo']);
286
287
        self::assertNotFalse($row);
288
289
        $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

289
        $row = array_change_key_case(/** @scrutinizer ignore-type */ $row, CASE_LOWER);
Loading history...
290
291
        self::assertEquals(1, $row['test_int']);
292
        self::assertEquals('foo', $row['test_string']);
293
    }
294
295
    public function testFetchAssocWithTypes() : void
296
    {
297
        $datetimeString = '2010-01-01 10:10:10';
298
        $datetime       = new DateTime($datetimeString);
299
300
        $sql = 'SELECT test_int, test_datetime FROM fetch_table WHERE test_int = ? AND test_datetime = ?';
301
        $row = $this->connection->fetchAssoc($sql, [1, $datetime], [ParameterType::STRING, Type::DATETIME]);
302
303
        self::assertNotFalse($row);
304
305
        $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

305
        $row = array_change_key_case(/** @scrutinizer ignore-type */ $row, CASE_LOWER);
Loading history...
306
307
        self::assertEquals(1, $row['test_int']);
308
        self::assertStringStartsWith($datetimeString, $row['test_datetime']);
309
    }
310
311
    public function testFetchAssocWithMissingTypes() : void
312
    {
313
        if ($this->connection->getDriver() instanceof MySQLiDriver ||
314
            $this->connection->getDriver() instanceof SQLSrvDriver) {
315
            $this->markTestSkipped('mysqli and sqlsrv actually supports this');
316
        }
317
318
        $datetimeString = '2010-01-01 10:10:10';
319
        $datetime       = new DateTime($datetimeString);
320
        $sql            = 'SELECT test_int, test_datetime FROM fetch_table WHERE test_int = ? AND test_datetime = ?';
321
322
        $this->expectException(DBALException::class);
323
324
        $this->connection->fetchAssoc($sql, [1, $datetime]);
325
    }
326
327
    public function testFetchArray() : void
328
    {
329
        $sql = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?';
330
        $row = $this->connection->fetchArray($sql, [1, 'foo']);
331
332
        self::assertEquals(1, $row[0]);
333
        self::assertEquals('foo', $row[1]);
334
    }
335
336
    public function testFetchArrayWithTypes() : void
337
    {
338
        $datetimeString = '2010-01-01 10:10:10';
339
        $datetime       = new DateTime($datetimeString);
340
341
        $sql = 'SELECT test_int, test_datetime FROM fetch_table WHERE test_int = ? AND test_datetime = ?';
342
        $row = $this->connection->fetchArray($sql, [1, $datetime], [ParameterType::STRING, Type::DATETIME]);
343
344
        self::assertNotFalse($row);
345
346
        $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

346
        $row = array_change_key_case(/** @scrutinizer ignore-type */ $row, CASE_LOWER);
Loading history...
347
348
        self::assertEquals(1, $row[0]);
349
        self::assertStringStartsWith($datetimeString, $row[1]);
350
    }
351
352
    public function testFetchArrayWithMissingTypes() : void
353
    {
354
        if ($this->connection->getDriver() instanceof MySQLiDriver ||
355
            $this->connection->getDriver() instanceof SQLSrvDriver) {
356
            $this->markTestSkipped('mysqli and sqlsrv actually supports this');
357
        }
358
359
        $datetimeString = '2010-01-01 10:10:10';
360
        $datetime       = new DateTime($datetimeString);
361
        $sql            = 'SELECT test_int, test_datetime FROM fetch_table WHERE test_int = ? AND test_datetime = ?';
362
363
        $this->expectException(DBALException::class);
364
365
        $this->connection->fetchArray($sql, [1, $datetime]);
366
    }
367
368
    public function testFetchColumn() : void
369
    {
370
        $sql     = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?';
371
        $testInt = $this->connection->fetchColumn($sql, [1, 'foo'], 0);
372
373
        self::assertEquals(1, $testInt);
374
375
        $sql        = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?';
376
        $testString = $this->connection->fetchColumn($sql, [1, 'foo'], 1);
377
378
        self::assertEquals('foo', $testString);
379
    }
380
381
    public function testFetchColumnWithTypes() : void
382
    {
383
        $datetimeString = '2010-01-01 10:10:10';
384
        $datetime       = new DateTime($datetimeString);
385
386
        $sql    = 'SELECT test_int, test_datetime FROM fetch_table WHERE test_int = ? AND test_datetime = ?';
387
        $column = $this->connection->fetchColumn($sql, [1, $datetime], 1, [ParameterType::STRING, Type::DATETIME]);
388
389
        self::assertNotFalse($column);
390
391
        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

391
        self::assertStringStartsWith($datetimeString, /** @scrutinizer ignore-type */ $column);
Loading history...
392
    }
393
394
    public function testFetchColumnWithMissingTypes() : void
395
    {
396
        if ($this->connection->getDriver() instanceof MySQLiDriver ||
397
            $this->connection->getDriver() instanceof SQLSrvDriver) {
398
            $this->markTestSkipped('mysqli and sqlsrv actually supports this');
399
        }
400
401
        $datetimeString = '2010-01-01 10:10:10';
402
        $datetime       = new DateTime($datetimeString);
403
        $sql            = 'SELECT test_int, test_datetime FROM fetch_table WHERE test_int = ? AND test_datetime = ?';
404
405
        $this->expectException(DBALException::class);
406
407
        $this->connection->fetchColumn($sql, [1, $datetime], 1);
408
    }
409
410
    /**
411
     * @group DDC-697
412
     */
413
    public function testExecuteQueryBindDateTimeType() : void
414
    {
415
        $sql  = 'SELECT count(*) AS c FROM fetch_table WHERE test_datetime = ?';
416
        $stmt = $this->connection->executeQuery(
417
            $sql,
418
            [1 => new DateTime('2010-01-01 10:10:10')],
419
            [1 => Type::DATETIME]
420
        );
421
422
        self::assertEquals(1, $stmt->fetchColumn());
423
    }
424
425
    /**
426
     * @group DDC-697
427
     */
428
    public function testExecuteUpdateBindDateTimeType() : void
429
    {
430
        $datetime = new DateTime('2010-02-02 20:20:20');
431
432
        $sql          = 'INSERT INTO fetch_table (test_int, test_string, test_datetime) VALUES (?, ?, ?)';
433
        $affectedRows = $this->connection->executeUpdate($sql, [
434
            1 => 50,
435
            2 => 'foo',
436
            3 => $datetime,
437
        ], [
438
            1 => ParameterType::INTEGER,
439
            2 => ParameterType::STRING,
440
            3 => Type::DATETIME,
441
        ]);
442
443
        self::assertEquals(1, $affectedRows);
444
        self::assertEquals(1, $this->connection->executeQuery(
445
            'SELECT count(*) AS c FROM fetch_table WHERE test_datetime = ?',
446
            [1 => $datetime],
447
            [1 => Type::DATETIME]
448
        )->fetchColumn());
449
    }
450
451
    /**
452
     * @group DDC-697
453
     */
454
    public function testPrepareQueryBindValueDateTimeType() : void
455
    {
456
        $sql  = 'SELECT count(*) AS c FROM fetch_table WHERE test_datetime = ?';
457
        $stmt = $this->connection->prepare($sql);
458
        $stmt->bindValue(1, new DateTime('2010-01-01 10:10:10'), Type::DATETIME);
459
        $stmt->execute();
460
461
        self::assertEquals(1, $stmt->fetchColumn());
462
    }
463
464
    /**
465
     * @group DBAL-78
466
     */
467
    public function testNativeArrayListSupport() : void
468
    {
469
        for ($i = 100; $i < 110; $i++) {
470
            $this->connection->insert('fetch_table', ['test_int' => $i, 'test_string' => 'foo' . $i, 'test_datetime' => '2010-01-01 10:10:10']);
471
        }
472
473
        $stmt = $this->connection->executeQuery(
474
            'SELECT test_int FROM fetch_table WHERE test_int IN (?)',
475
            [[100, 101, 102, 103, 104]],
476
            [Connection::PARAM_INT_ARRAY]
477
        );
478
479
        $data = $stmt->fetchAll(FetchMode::NUMERIC);
480
        self::assertCount(5, $data);
481
        self::assertEquals([[100], [101], [102], [103], [104]], $data);
482
483
        $stmt = $this->connection->executeQuery(
484
            'SELECT test_int FROM fetch_table WHERE test_string IN (?)',
485
            [['foo100', 'foo101', 'foo102', 'foo103', 'foo104']],
486
            [Connection::PARAM_STR_ARRAY]
487
        );
488
489
        $data = $stmt->fetchAll(FetchMode::NUMERIC);
490
        self::assertCount(5, $data);
491
        self::assertEquals([[100], [101], [102], [103], [104]], $data);
492
    }
493
494
    /**
495
     * @dataProvider getTrimExpressionData
496
     */
497
    public function testTrimExpression(string $value, int $position, ?string $char, string $expectedResult) : void
498
    {
499
        $sql = 'SELECT ' .
500
            $this->connection->getDatabasePlatform()->getTrimExpression($value, $position, $char) . ' AS trimmed ' .
501
            'FROM fetch_table';
502
503
        $row = $this->connection->fetchAssoc($sql);
504
        $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

504
        $row = array_change_key_case(/** @scrutinizer ignore-type */ $row, CASE_LOWER);
Loading history...
505
506
        self::assertEquals($expectedResult, $row['trimmed']);
507
    }
508
509
    /**
510
     * @return mixed[][]
511
     */
512
    public static function getTrimExpressionData() : iterable
513
    {
514
        return [
515
            ['test_string', TrimMode::UNSPECIFIED, null, 'foo'],
516
            ['test_string', TrimMode::LEADING, null, 'foo'],
517
            ['test_string', TrimMode::TRAILING, null, 'foo'],
518
            ['test_string', TrimMode::BOTH, null, 'foo'],
519
            ['test_string', TrimMode::UNSPECIFIED, "'f'", 'oo'],
520
            ['test_string', TrimMode::UNSPECIFIED, "'o'", 'f'],
521
            ['test_string', TrimMode::UNSPECIFIED, "'.'", 'foo'],
522
            ['test_string', TrimMode::LEADING, "'f'", 'oo'],
523
            ['test_string', TrimMode::LEADING, "'o'", 'foo'],
524
            ['test_string', TrimMode::LEADING, "'.'", 'foo'],
525
            ['test_string', TrimMode::TRAILING, "'f'", 'foo'],
526
            ['test_string', TrimMode::TRAILING, "'o'", 'f'],
527
            ['test_string', TrimMode::TRAILING, "'.'", 'foo'],
528
            ['test_string', TrimMode::BOTH, "'f'", 'oo'],
529
            ['test_string', TrimMode::BOTH, "'o'", 'f'],
530
            ['test_string', TrimMode::BOTH, "'.'", 'foo'],
531
            ["' foo '", TrimMode::UNSPECIFIED, null, 'foo'],
532
            ["' foo '", TrimMode::LEADING, null, 'foo '],
533
            ["' foo '", TrimMode::TRAILING, null, ' foo'],
534
            ["' foo '", TrimMode::BOTH, null, 'foo'],
535
            ["' foo '", TrimMode::UNSPECIFIED, "'f'", ' foo '],
536
            ["' foo '", TrimMode::UNSPECIFIED, "'o'", ' foo '],
537
            ["' foo '", TrimMode::UNSPECIFIED, "'.'", ' foo '],
538
            ["' foo '", TrimMode::UNSPECIFIED, "' '", 'foo'],
539
            ["' foo '", TrimMode::LEADING, "'f'", ' foo '],
540
            ["' foo '", TrimMode::LEADING, "'o'", ' foo '],
541
            ["' foo '", TrimMode::LEADING, "'.'", ' foo '],
542
            ["' foo '", TrimMode::LEADING, "' '", 'foo '],
543
            ["' foo '", TrimMode::TRAILING, "'f'", ' foo '],
544
            ["' foo '", TrimMode::TRAILING, "'o'", ' foo '],
545
            ["' foo '", TrimMode::TRAILING, "'.'", ' foo '],
546
            ["' foo '", TrimMode::TRAILING, "' '", ' foo'],
547
            ["' foo '", TrimMode::BOTH, "'f'", ' foo '],
548
            ["' foo '", TrimMode::BOTH, "'o'", ' foo '],
549
            ["' foo '", TrimMode::BOTH, "'.'", ' foo '],
550
            ["' foo '", TrimMode::BOTH, "' '", 'foo'],
551
        ];
552
    }
553
554
    public function testTrimExpressionInvalidMode() : void
555
    {
556
        $this->expectException(InvalidArgumentException::class);
557
        $this->connection->getDatabasePlatform()->getTrimExpression('Trim me!', 0xBEEF);
558
    }
559
560
    /**
561
     * @group DDC-1014
562
     */
563
    public function testDateArithmetics() : void
564
    {
565
        $p    = $this->connection->getDatabasePlatform();
566
        $sql  = 'SELECT ';
567
        $sql .= $p->getDateAddSecondsExpression('test_datetime', 1) . ' AS add_seconds, ';
568
        $sql .= $p->getDateSubSecondsExpression('test_datetime', 1) . ' AS sub_seconds, ';
569
        $sql .= $p->getDateAddMinutesExpression('test_datetime', 5) . ' AS add_minutes, ';
570
        $sql .= $p->getDateSubMinutesExpression('test_datetime', 5) . ' AS sub_minutes, ';
571
        $sql .= $p->getDateAddHourExpression('test_datetime', 3) . ' AS add_hour, ';
572
        $sql .= $p->getDateSubHourExpression('test_datetime', 3) . ' AS sub_hour, ';
573
        $sql .= $p->getDateAddDaysExpression('test_datetime', 10) . ' AS add_days, ';
574
        $sql .= $p->getDateSubDaysExpression('test_datetime', 10) . ' AS sub_days, ';
575
        $sql .= $p->getDateAddWeeksExpression('test_datetime', 1) . ' AS add_weeks, ';
576
        $sql .= $p->getDateSubWeeksExpression('test_datetime', 1) . ' AS sub_weeks, ';
577
        $sql .= $p->getDateAddMonthExpression('test_datetime', 2) . ' AS add_month, ';
578
        $sql .= $p->getDateSubMonthExpression('test_datetime', 2) . ' AS sub_month, ';
579
        $sql .= $p->getDateAddQuartersExpression('test_datetime', 3) . ' AS add_quarters, ';
580
        $sql .= $p->getDateSubQuartersExpression('test_datetime', 3) . ' AS sub_quarters, ';
581
        $sql .= $p->getDateAddYearsExpression('test_datetime', 6) . ' AS add_years, ';
582
        $sql .= $p->getDateSubYearsExpression('test_datetime', 6) . ' AS sub_years ';
583
        $sql .= 'FROM fetch_table';
584
585
        $row = $this->connection->fetchAssoc($sql);
586
        $row = array_change_key_case($row, CASE_LOWER);
587
588
        self::assertEquals('2010-01-01 10:10:11', date('Y-m-d H:i:s', strtotime($row['add_seconds'])), 'Adding second should end up on 2010-01-01 10:10:11');
589
        self::assertEquals('2010-01-01 10:10:09', date('Y-m-d H:i:s', strtotime($row['sub_seconds'])), 'Subtracting second should end up on 2010-01-01 10:10:09');
590
        self::assertEquals('2010-01-01 10:15:10', date('Y-m-d H:i:s', strtotime($row['add_minutes'])), 'Adding minutes should end up on 2010-01-01 10:15:10');
591
        self::assertEquals('2010-01-01 10:05:10', date('Y-m-d H:i:s', strtotime($row['sub_minutes'])), 'Subtracting minutes should end up on 2010-01-01 10:05:10');
592
        self::assertEquals('2010-01-01 13:10', date('Y-m-d H:i', strtotime($row['add_hour'])), 'Adding date should end up on 2010-01-01 13:10');
593
        self::assertEquals('2010-01-01 07:10', date('Y-m-d H:i', strtotime($row['sub_hour'])), 'Subtracting date should end up on 2010-01-01 07:10');
594
        self::assertEquals('2010-01-11', date('Y-m-d', strtotime($row['add_days'])), 'Adding date should end up on 2010-01-11');
595
        self::assertEquals('2009-12-22', date('Y-m-d', strtotime($row['sub_days'])), 'Subtracting date should end up on 2009-12-22');
596
        self::assertEquals('2010-01-08', date('Y-m-d', strtotime($row['add_weeks'])), 'Adding week should end up on 2010-01-08');
597
        self::assertEquals('2009-12-25', date('Y-m-d', strtotime($row['sub_weeks'])), 'Subtracting week should end up on 2009-12-25');
598
        self::assertEquals('2010-03-01', date('Y-m-d', strtotime($row['add_month'])), 'Adding month should end up on 2010-03-01');
599
        self::assertEquals('2009-11-01', date('Y-m-d', strtotime($row['sub_month'])), 'Subtracting month should end up on 2009-11-01');
600
        self::assertEquals('2010-10-01', date('Y-m-d', strtotime($row['add_quarters'])), 'Adding quarters should end up on 2010-04-01');
601
        self::assertEquals('2009-04-01', date('Y-m-d', strtotime($row['sub_quarters'])), 'Subtracting quarters should end up on 2009-10-01');
602
        self::assertEquals('2016-01-01', date('Y-m-d', strtotime($row['add_years'])), 'Adding years should end up on 2016-01-01');
603
        self::assertEquals('2004-01-01', date('Y-m-d', strtotime($row['sub_years'])), 'Subtracting years should end up on 2004-01-01');
604
    }
605
606
    public function testSqliteDateArithmeticWithDynamicInterval() : void
607
    {
608
        $platform = $this->connection->getDatabasePlatform();
609
610
        if (! $platform instanceof SqlitePlatform) {
611
            $this->markTestSkipped('test is for sqlite only');
612
        }
613
614
        $table = new Table('fetch_table_date_math');
615
        $table->addColumn('test_date', 'date');
616
        $table->addColumn('test_days', 'integer');
617
        $table->setPrimaryKey(['test_date']);
618
619
        $sm = $this->connection->getSchemaManager();
620
        $sm->createTable($table);
621
622
        $this->connection->insert('fetch_table_date_math', ['test_date' => '2010-01-01', 'test_days' => 10]);
623
        $this->connection->insert('fetch_table_date_math', ['test_date' => '2010-06-01', 'test_days' => 20]);
624
625
        $sql  = 'SELECT COUNT(*) FROM fetch_table_date_math WHERE ';
626
        $sql .= $platform->getDateSubDaysExpression('test_date', 'test_days') . " < '2010-05-12'";
627
628
        $rowCount = $this->connection->fetchColumn($sql, [], 0);
629
630
        $this->assertEquals(1, $rowCount);
631
    }
632
633
    public function testLocateExpression() : void
634
    {
635
        $platform = $this->connection->getDatabasePlatform();
636
637
        $sql  = 'SELECT ';
638
        $sql .= $platform->getLocateExpression('test_string', "'oo'") . ' AS locate1, ';
639
        $sql .= $platform->getLocateExpression('test_string', "'foo'") . ' AS locate2, ';
640
        $sql .= $platform->getLocateExpression('test_string', "'bar'") . ' AS locate3, ';
641
        $sql .= $platform->getLocateExpression('test_string', 'test_string') . ' AS locate4, ';
642
        $sql .= $platform->getLocateExpression("'foo'", 'test_string') . ' AS locate5, ';
643
        $sql .= $platform->getLocateExpression("'barfoobaz'", 'test_string') . ' AS locate6, ';
644
        $sql .= $platform->getLocateExpression("'bar'", 'test_string') . ' AS locate7, ';
645
        $sql .= $platform->getLocateExpression('test_string', "'oo'", 2) . ' AS locate8, ';
646
        $sql .= $platform->getLocateExpression('test_string', "'oo'", 3) . ' AS locate9 ';
647
        $sql .= 'FROM fetch_table';
648
649
        $row = $this->connection->fetchAssoc($sql);
650
        $row = array_change_key_case($row, CASE_LOWER);
651
652
        self::assertEquals(2, $row['locate1']);
653
        self::assertEquals(1, $row['locate2']);
654
        self::assertEquals(0, $row['locate3']);
655
        self::assertEquals(1, $row['locate4']);
656
        self::assertEquals(1, $row['locate5']);
657
        self::assertEquals(4, $row['locate6']);
658
        self::assertEquals(0, $row['locate7']);
659
        self::assertEquals(2, $row['locate8']);
660
        self::assertEquals(0, $row['locate9']);
661
    }
662
663
    /**
664
     * @dataProvider substringExpressionProvider
665
     */
666
    public function testSubstringExpression(string $string, string $start, ?string $length, string $expected) : void
667
    {
668
        $platform = $this->connection->getDatabasePlatform();
669
670
        $query = $platform->getDummySelectSQL(
671
            $platform->getSubstringExpression($string, $start, $length)
672
        );
673
674
        $this->assertEquals($expected, $this->connection->fetchColumn($query));
675
    }
676
677
    /**
678
     * @return mixed[][]
679
     */
680
    public static function substringExpressionProvider() : iterable
681
    {
682
        return [
683
            'start-no-length' => [
684
                "'abcdef'",
685
                '3',
686
                null,
687
                'cdef',
688
            ],
689
            'start-with-length' => [
690
                "'abcdef'",
691
                '2',
692
                '4',
693
                'bcde',
694
            ],
695
            'expressions' => [
696
                "'abcdef'",
697
                '1 + 1',
698
                '1 + 1',
699
                'bc',
700
            ],
701
        ];
702
    }
703
704
    public function testQuoteSQLInjection() : void
705
    {
706
        $sql  = 'SELECT * FROM fetch_table WHERE test_string = ' . $this->connection->quote("bar' OR '1'='1");
707
        $rows = $this->connection->fetchAll($sql);
708
709
        self::assertCount(0, $rows, 'no result should be returned, otherwise SQL injection is possible');
710
    }
711
712
    /**
713
     * @group DDC-1213
714
     */
715
    public function testBitComparisonExpressionSupport() : void
716
    {
717
        $this->connection->exec('DELETE FROM fetch_table');
718
        $platform = $this->connection->getDatabasePlatform();
719
        $bitmap   = [];
720
721
        for ($i = 2; $i < 9; $i += 2) {
722
            $bitmap[$i] = [
723
                'bit_or'    => ($i | 2),
724
                'bit_and'   => ($i & 2),
725
            ];
726
            $this->connection->insert('fetch_table', [
727
                'test_int'      => $i,
728
                'test_string'   => json_encode($bitmap[$i]),
729
                'test_datetime' => '2010-01-01 10:10:10',
730
            ]);
731
        }
732
733
        $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...
734
        $sql[] = 'test_int, ';
735
        $sql[] = 'test_string, ';
736
        $sql[] = $platform->getBitOrComparisonExpression('test_int', 2) . ' AS bit_or, ';
737
        $sql[] = $platform->getBitAndComparisonExpression('test_int', 2) . ' AS bit_and ';
738
        $sql[] = 'FROM fetch_table';
739
740
        $stmt = $this->connection->executeQuery(implode(PHP_EOL, $sql));
741
        $data = $stmt->fetchAll(FetchMode::ASSOCIATIVE);
742
743
        self::assertCount(4, $data);
744
        self::assertEquals(count($bitmap), count($data));
745
        foreach ($data as $row) {
746
            $row = array_change_key_case($row, CASE_LOWER);
747
748
            self::assertArrayHasKey('test_int', $row);
749
750
            $id = $row['test_int'];
751
752
            self::assertArrayHasKey($id, $bitmap);
753
            self::assertArrayHasKey($id, $bitmap);
754
755
            self::assertArrayHasKey('bit_or', $row);
756
            self::assertArrayHasKey('bit_and', $row);
757
758
            self::assertEquals($row['bit_or'], $bitmap[$id]['bit_or']);
759
            self::assertEquals($row['bit_and'], $bitmap[$id]['bit_and']);
760
        }
761
    }
762
763
    public function testSetDefaultFetchMode() : void
764
    {
765
        $stmt = $this->connection->query('SELECT * FROM fetch_table');
766
        $stmt->setFetchMode(FetchMode::NUMERIC);
767
768
        $row = array_keys($stmt->fetch());
769
        self::assertCount(0, array_filter($row, static function ($v) {
770
            return ! is_numeric($v);
771
        }), 'should be no non-numerical elements in the result.');
772
    }
773
774
    /**
775
     * @group DBAL-1091
776
     */
777
    public function testFetchAllStyleObject() : void
778
    {
779
        $this->setupFixture();
780
781
        $sql  = 'SELECT test_int, test_string, test_datetime FROM fetch_table';
782
        $stmt = $this->connection->prepare($sql);
783
784
        $stmt->execute();
785
786
        $results = $stmt->fetchAll(FetchMode::STANDARD_OBJECT);
787
788
        self::assertCount(1, $results);
789
        self::assertInstanceOf('stdClass', $results[0]);
790
791
        self::assertEquals(
792
            1,
793
            property_exists($results[0], 'test_int') ? $results[0]->test_int : $results[0]->TEST_INT
794
        );
795
        self::assertEquals(
796
            'foo',
797
            property_exists($results[0], 'test_string') ? $results[0]->test_string : $results[0]->TEST_STRING
798
        );
799
        self::assertStringStartsWith(
800
            '2010-01-01 10:10:10',
801
            property_exists($results[0], 'test_datetime') ? $results[0]->test_datetime : $results[0]->TEST_DATETIME
802
        );
803
    }
804
805
    /**
806
     * @group DBAL-196
807
     */
808
    public function testFetchAllSupportFetchClass() : void
809
    {
810
        $this->beforeFetchClassTest();
811
        $this->setupFixture();
812
813
        $sql  = 'SELECT test_int, test_string, test_datetime FROM fetch_table';
814
        $stmt = $this->connection->prepare($sql);
815
        $stmt->execute();
816
817
        $results = $stmt->fetchAll(
818
            FetchMode::CUSTOM_OBJECT,
819
            MyFetchClass::class
820
        );
821
822
        self::assertCount(1, $results);
823
        self::assertInstanceOf(MyFetchClass::class, $results[0]);
824
825
        self::assertEquals(1, $results[0]->test_int);
826
        self::assertEquals('foo', $results[0]->test_string);
827
        self::assertStringStartsWith('2010-01-01 10:10:10', $results[0]->test_datetime);
828
    }
829
830
    /**
831
     * @group DBAL-241
832
     */
833
    public function testFetchAllStyleColumn() : void
834
    {
835
        $sql = 'DELETE FROM fetch_table';
836
        $this->connection->executeUpdate($sql);
837
838
        $this->connection->insert('fetch_table', ['test_int' => 1, 'test_string' => 'foo']);
839
        $this->connection->insert('fetch_table', ['test_int' => 10, 'test_string' => 'foo']);
840
841
        $sql  = 'SELECT test_int FROM fetch_table';
842
        $rows = $this->connection->query($sql)->fetchAll(FetchMode::COLUMN);
843
844
        self::assertEquals([1, 10], $rows);
845
    }
846
847
    /**
848
     * @group DBAL-214
849
     */
850
    public function testSetFetchModeClassFetchAll() : void
851
    {
852
        $this->beforeFetchClassTest();
853
        $this->setupFixture();
854
855
        $sql  = 'SELECT * FROM fetch_table';
856
        $stmt = $this->connection->query($sql);
857
        $stmt->setFetchMode(FetchMode::CUSTOM_OBJECT, MyFetchClass::class);
858
859
        $results = $stmt->fetchAll();
860
861
        self::assertCount(1, $results);
862
        self::assertInstanceOf(MyFetchClass::class, $results[0]);
863
864
        self::assertEquals(1, $results[0]->test_int);
865
        self::assertEquals('foo', $results[0]->test_string);
866
        self::assertStringStartsWith('2010-01-01 10:10:10', $results[0]->test_datetime);
867
    }
868
869
    /**
870
     * @group DBAL-214
871
     */
872
    public function testSetFetchModeClassFetch() : void
873
    {
874
        $this->beforeFetchClassTest();
875
        $this->setupFixture();
876
877
        $sql  = 'SELECT * FROM fetch_table';
878
        $stmt = $this->connection->query($sql);
879
        $stmt->setFetchMode(FetchMode::CUSTOM_OBJECT, MyFetchClass::class);
880
881
        $results = [];
882
        while ($row = $stmt->fetch()) {
883
            $results[] = $row;
884
        }
885
886
        self::assertCount(1, $results);
887
        self::assertInstanceOf(MyFetchClass::class, $results[0]);
888
889
        self::assertEquals(1, $results[0]->test_int);
890
        self::assertEquals('foo', $results[0]->test_string);
891
        self::assertStringStartsWith('2010-01-01 10:10:10', $results[0]->test_datetime);
892
    }
893
894
    /**
895
     * @group DBAL-257
896
     */
897
    public function testEmptyFetchColumnReturnsFalse() : void
898
    {
899
        $this->connection->beginTransaction();
900
        $this->connection->exec('DELETE FROM fetch_table');
901
        self::assertFalse($this->connection->fetchColumn('SELECT test_int FROM fetch_table'));
902
        self::assertFalse($this->connection->query('SELECT test_int FROM fetch_table')->fetchColumn());
903
        $this->connection->rollBack();
904
    }
905
906
    /**
907
     * @group DBAL-339
908
     */
909
    public function testSetFetchModeOnDbalStatement() : void
910
    {
911
        $sql  = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?';
912
        $stmt = $this->connection->executeQuery($sql, [1, 'foo']);
913
        $stmt->setFetchMode(FetchMode::NUMERIC);
914
915
        $row = $stmt->fetch();
916
917
        self::assertArrayHasKey(0, $row);
918
        self::assertArrayHasKey(1, $row);
919
        self::assertFalse($stmt->fetch());
920
    }
921
922
    /**
923
     * @group DBAL-435
924
     */
925
    public function testEmptyParameters() : void
926
    {
927
        $sql  = 'SELECT * FROM fetch_table WHERE test_int IN (?)';
928
        $stmt = $this->connection->executeQuery($sql, [[]], [Connection::PARAM_INT_ARRAY]);
929
        $rows = $stmt->fetchAll();
930
931
        self::assertEquals([], $rows);
932
    }
933
934
    /**
935
     * @group DBAL-1028
936
     */
937
    public function testFetchColumnNullValue() : void
938
    {
939
        $this->connection->executeUpdate(
940
            'INSERT INTO fetch_table (test_int, test_string) VALUES (?, ?)',
941
            [2, 'foo']
942
        );
943
944
        self::assertNull(
945
            $this->connection->fetchColumn('SELECT test_datetime FROM fetch_table WHERE test_int = ?', [2])
946
        );
947
    }
948
949
    /**
950
     * @group DBAL-1028
951
     */
952
    public function testFetchColumnNoResult() : void
953
    {
954
        self::assertFalse(
955
            $this->connection->fetchColumn('SELECT test_int FROM fetch_table WHERE test_int = ?', [-1])
956
        );
957
    }
958
959
    private function setupFixture() : void
960
    {
961
        $this->connection->exec('DELETE FROM fetch_table');
962
        $this->connection->insert('fetch_table', [
963
            'test_int'      => 1,
964
            'test_string'   => 'foo',
965
            'test_datetime' => '2010-01-01 10:10:10',
966
        ]);
967
    }
968
969
    private function beforeFetchClassTest() : void
970
    {
971
        $driver = $this->connection->getDriver();
972
973
        if ($driver instanceof Oci8Driver) {
974
            $this->markTestSkipped('Not supported by OCI8');
975
        }
976
977
        if ($driver instanceof MySQLiDriver) {
978
            $this->markTestSkipped('Mysqli driver dont support this feature.');
979
        }
980
981
        if (! $driver instanceof PDOOracleDriver) {
982
            return;
983
        }
984
985
        /** @var PDOConnection $connection */
986
        $connection = $this->connection
987
            ->getWrappedConnection();
988
        $connection->getWrappedConnection()->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER);
989
    }
990
}
991
992
class MyFetchClass
993
{
994
    /** @var int */
995
    public $test_int;
996
997
    /** @var string */
998
    public $test_string;
999
1000
    /** @var string */
1001
    public $test_datetime;
1002
}
1003