Completed
Pull Request — master (#3359)
by Sergei
63:31
created

DataAccessTest::testFetchColumnNonExistingIndex()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

340
        $row = array_change_key_case(/** @scrutinizer ignore-type */ $row, CASE_LOWER);
Loading history...
341
342
        self::assertEquals(1, $row[0]);
343
        self::assertStringStartsWith($datetimeString, $row[1]);
344
    }
345
346
    /**
347
     * @expectedException \Doctrine\DBAL\DBALException
348
     */
349
    public function testFetchArrayWithMissingTypes()
350
    {
351
        if ($this->connection->getDriver() instanceof MySQLiDriver ||
352
            $this->connection->getDriver() instanceof SQLSrvDriver) {
353
            $this->markTestSkipped('mysqli and sqlsrv actually supports this');
354
        }
355
356
        $datetimeString = '2010-01-01 10:10:10';
357
        $datetime       = new DateTime($datetimeString);
358
        $sql            = 'SELECT test_int, test_datetime FROM fetch_table WHERE test_int = ? AND test_datetime = ?';
359
        $row            = $this->connection->fetchArray($sql, [1, $datetime]);
0 ignored issues
show
Unused Code introduced by
The assignment to $row is dead and can be removed.
Loading history...
360
    }
361
362
    public function testFetchColumn()
363
    {
364
        $sql     = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?';
365
        $testInt = $this->connection->fetchColumn($sql, [1, 'foo'], 0);
366
367
        self::assertEquals(1, $testInt);
368
369
        $sql        = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?';
370
        $testString = $this->connection->fetchColumn($sql, [1, 'foo'], 1);
371
372
        self::assertEquals('foo', $testString);
373
    }
374
375
    public function testFetchColumnWithTypes()
376
    {
377
        $datetimeString = '2010-01-01 10:10:10';
378
        $datetime       = new DateTime($datetimeString);
379
380
        $sql    = 'SELECT test_int, test_datetime FROM fetch_table WHERE test_int = ? AND test_datetime = ?';
381
        $column = $this->connection->fetchColumn($sql, [1, $datetime], 1, [ParameterType::STRING, Type::DATETIME]);
382
383
        self::assertNotFalse($column);
384
385
        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

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

498
        $row = array_change_key_case(/** @scrutinizer ignore-type */ $row, CASE_LOWER);
Loading history...
499
500
        self::assertEquals($expectedResult, $row['trimmed']);
501
    }
502
503
    public function getTrimExpressionData()
504
    {
505
        return [
506
            ['test_string', TrimMode::UNSPECIFIED, false, 'foo'],
507
            ['test_string', TrimMode::LEADING, false, 'foo'],
508
            ['test_string', TrimMode::TRAILING, false, 'foo'],
509
            ['test_string', TrimMode::BOTH, false, 'foo'],
510
            ['test_string', TrimMode::UNSPECIFIED, "'f'", 'oo'],
511
            ['test_string', TrimMode::UNSPECIFIED, "'o'", 'f'],
512
            ['test_string', TrimMode::UNSPECIFIED, "'.'", 'foo'],
513
            ['test_string', TrimMode::LEADING, "'f'", 'oo'],
514
            ['test_string', TrimMode::LEADING, "'o'", 'foo'],
515
            ['test_string', TrimMode::LEADING, "'.'", 'foo'],
516
            ['test_string', TrimMode::TRAILING, "'f'", 'foo'],
517
            ['test_string', TrimMode::TRAILING, "'o'", 'f'],
518
            ['test_string', TrimMode::TRAILING, "'.'", 'foo'],
519
            ['test_string', TrimMode::BOTH, "'f'", 'oo'],
520
            ['test_string', TrimMode::BOTH, "'o'", 'f'],
521
            ['test_string', TrimMode::BOTH, "'.'", 'foo'],
522
            ["' foo '", TrimMode::UNSPECIFIED, false, 'foo'],
523
            ["' foo '", TrimMode::LEADING, false, 'foo '],
524
            ["' foo '", TrimMode::TRAILING, false, ' foo'],
525
            ["' foo '", TrimMode::BOTH, false, 'foo'],
526
            ["' foo '", TrimMode::UNSPECIFIED, "'f'", ' foo '],
527
            ["' foo '", TrimMode::UNSPECIFIED, "'o'", ' foo '],
528
            ["' foo '", TrimMode::UNSPECIFIED, "'.'", ' foo '],
529
            ["' foo '", TrimMode::UNSPECIFIED, "' '", 'foo'],
530
            ["' foo '", TrimMode::LEADING, "'f'", ' foo '],
531
            ["' foo '", TrimMode::LEADING, "'o'", ' foo '],
532
            ["' foo '", TrimMode::LEADING, "'.'", ' foo '],
533
            ["' foo '", TrimMode::LEADING, "' '", 'foo '],
534
            ["' foo '", TrimMode::TRAILING, "'f'", ' foo '],
535
            ["' foo '", TrimMode::TRAILING, "'o'", ' foo '],
536
            ["' foo '", TrimMode::TRAILING, "'.'", ' foo '],
537
            ["' foo '", TrimMode::TRAILING, "' '", ' foo'],
538
            ["' foo '", TrimMode::BOTH, "'f'", ' foo '],
539
            ["' foo '", TrimMode::BOTH, "'o'", ' foo '],
540
            ["' foo '", TrimMode::BOTH, "'.'", ' foo '],
541
            ["' foo '", TrimMode::BOTH, "' '", 'foo'],
542
        ];
543
    }
544
545
    /**
546
     * @group DDC-1014
547
     */
548
    public function testDateArithmetics()
549
    {
550
        $p    = $this->connection->getDatabasePlatform();
551
        $sql  = 'SELECT ';
552
        $sql .= $p->getDateAddSecondsExpression('test_datetime', 1) . ' AS add_seconds, ';
553
        $sql .= $p->getDateSubSecondsExpression('test_datetime', 1) . ' AS sub_seconds, ';
554
        $sql .= $p->getDateAddMinutesExpression('test_datetime', 5) . ' AS add_minutes, ';
555
        $sql .= $p->getDateSubMinutesExpression('test_datetime', 5) . ' AS sub_minutes, ';
556
        $sql .= $p->getDateAddHourExpression('test_datetime', 3) . ' AS add_hour, ';
557
        $sql .= $p->getDateSubHourExpression('test_datetime', 3) . ' AS sub_hour, ';
558
        $sql .= $p->getDateAddDaysExpression('test_datetime', 10) . ' AS add_days, ';
559
        $sql .= $p->getDateSubDaysExpression('test_datetime', 10) . ' AS sub_days, ';
560
        $sql .= $p->getDateAddWeeksExpression('test_datetime', 1) . ' AS add_weeks, ';
561
        $sql .= $p->getDateSubWeeksExpression('test_datetime', 1) . ' AS sub_weeks, ';
562
        $sql .= $p->getDateAddMonthExpression('test_datetime', 2) . ' AS add_month, ';
563
        $sql .= $p->getDateSubMonthExpression('test_datetime', 2) . ' AS sub_month, ';
564
        $sql .= $p->getDateAddQuartersExpression('test_datetime', 3) . ' AS add_quarters, ';
565
        $sql .= $p->getDateSubQuartersExpression('test_datetime', 3) . ' AS sub_quarters, ';
566
        $sql .= $p->getDateAddYearsExpression('test_datetime', 6) . ' AS add_years, ';
567
        $sql .= $p->getDateSubYearsExpression('test_datetime', 6) . ' AS sub_years ';
568
        $sql .= 'FROM fetch_table';
569
570
        $row = $this->connection->fetchAssoc($sql);
571
        $row = array_change_key_case($row, CASE_LOWER);
572
573
        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');
574
        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');
575
        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');
576
        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');
577
        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');
578
        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');
579
        self::assertEquals('2010-01-11', date('Y-m-d', strtotime($row['add_days'])), 'Adding date should end up on 2010-01-11');
580
        self::assertEquals('2009-12-22', date('Y-m-d', strtotime($row['sub_days'])), 'Subtracting date should end up on 2009-12-22');
581
        self::assertEquals('2010-01-08', date('Y-m-d', strtotime($row['add_weeks'])), 'Adding week should end up on 2010-01-08');
582
        self::assertEquals('2009-12-25', date('Y-m-d', strtotime($row['sub_weeks'])), 'Subtracting week should end up on 2009-12-25');
583
        self::assertEquals('2010-03-01', date('Y-m-d', strtotime($row['add_month'])), 'Adding month should end up on 2010-03-01');
584
        self::assertEquals('2009-11-01', date('Y-m-d', strtotime($row['sub_month'])), 'Subtracting month should end up on 2009-11-01');
585
        self::assertEquals('2010-10-01', date('Y-m-d', strtotime($row['add_quarters'])), 'Adding quarters should end up on 2010-04-01');
586
        self::assertEquals('2009-04-01', date('Y-m-d', strtotime($row['sub_quarters'])), 'Subtracting quarters should end up on 2009-10-01');
587
        self::assertEquals('2016-01-01', date('Y-m-d', strtotime($row['add_years'])), 'Adding years should end up on 2016-01-01');
588
        self::assertEquals('2004-01-01', date('Y-m-d', strtotime($row['sub_years'])), 'Subtracting years should end up on 2004-01-01');
589
    }
590
591
    public function testSqliteDateArithmeticWithDynamicInterval()
592
    {
593
        $platform = $this->connection->getDatabasePlatform();
594
595
        if (! $platform instanceof SqlitePlatform) {
596
            $this->markTestSkipped('test is for sqlite only');
597
        }
598
599
        $table = new Table('fetch_table_date_math');
600
        $table->addColumn('test_date', 'date');
601
        $table->addColumn('test_days', 'integer');
602
        $table->setPrimaryKey(['test_date']);
603
604
        /** @var AbstractSchemaManager $sm */
605
        $sm = $this->connection->getSchemaManager();
606
        $sm->createTable($table);
607
608
        $this->connection->insert('fetch_table_date_math', ['test_date' => '2010-01-01', 'test_days' => 10]);
609
        $this->connection->insert('fetch_table_date_math', ['test_date' => '2010-06-01', 'test_days' => 20]);
610
611
        $sql  = 'SELECT COUNT(*) FROM fetch_table_date_math WHERE ';
612
        $sql .= $platform->getDateSubDaysExpression('test_date', 'test_days') . " < '2010-05-12'";
0 ignored issues
show
Bug introduced by
'test_days' of type string is incompatible with the type integer expected by parameter $days of Doctrine\DBAL\Platforms\...DateSubDaysExpression(). ( Ignorable by Annotation )

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

612
        $sql .= $platform->getDateSubDaysExpression('test_date', /** @scrutinizer ignore-type */ 'test_days') . " < '2010-05-12'";
Loading history...
613
614
        $rowCount = $this->connection->fetchColumn($sql, [], 0);
615
616
        $this->assertEquals(1, $rowCount);
617
    }
618
619
    public function testLocateExpression()
620
    {
621
        $platform = $this->connection->getDatabasePlatform();
622
623
        $sql  = 'SELECT ';
624
        $sql .= $platform->getLocateExpression('test_string', "'oo'") . ' AS locate1, ';
625
        $sql .= $platform->getLocateExpression('test_string', "'foo'") . ' AS locate2, ';
626
        $sql .= $platform->getLocateExpression('test_string', "'bar'") . ' AS locate3, ';
627
        $sql .= $platform->getLocateExpression('test_string', 'test_string') . ' AS locate4, ';
628
        $sql .= $platform->getLocateExpression("'foo'", 'test_string') . ' AS locate5, ';
629
        $sql .= $platform->getLocateExpression("'barfoobaz'", 'test_string') . ' AS locate6, ';
630
        $sql .= $platform->getLocateExpression("'bar'", 'test_string') . ' AS locate7, ';
631
        $sql .= $platform->getLocateExpression('test_string', "'oo'", 2) . ' AS locate8, ';
632
        $sql .= $platform->getLocateExpression('test_string', "'oo'", 3) . ' AS locate9 ';
633
        $sql .= 'FROM fetch_table';
634
635
        $row = $this->connection->fetchAssoc($sql);
636
        $row = array_change_key_case($row, CASE_LOWER);
637
638
        self::assertEquals(2, $row['locate1']);
639
        self::assertEquals(1, $row['locate2']);
640
        self::assertEquals(0, $row['locate3']);
641
        self::assertEquals(1, $row['locate4']);
642
        self::assertEquals(1, $row['locate5']);
643
        self::assertEquals(4, $row['locate6']);
644
        self::assertEquals(0, $row['locate7']);
645
        self::assertEquals(2, $row['locate8']);
646
        self::assertEquals(0, $row['locate9']);
647
    }
648
649
    public function testQuoteSQLInjection()
650
    {
651
        $sql  = 'SELECT * FROM fetch_table WHERE test_string = ' . $this->connection->quote("bar' OR '1'='1");
652
        $rows = $this->connection->fetchAll($sql);
653
654
        self::assertCount(0, $rows, 'no result should be returned, otherwise SQL injection is possible');
655
    }
656
657
    /**
658
     * @group DDC-1213
659
     */
660
    public function testBitComparisonExpressionSupport()
661
    {
662
        $this->connection->exec('DELETE FROM fetch_table');
663
        $platform = $this->connection->getDatabasePlatform();
664
        $bitmap   = [];
665
666
        for ($i = 2; $i < 9; $i += 2) {
667
            $bitmap[$i] = [
668
                'bit_or'    => ($i | 2),
669
                'bit_and'   => ($i & 2),
670
            ];
671
            $this->connection->insert('fetch_table', [
672
                'test_int'      => $i,
673
                'test_string'   => json_encode($bitmap[$i]),
674
                'test_datetime' => '2010-01-01 10:10:10',
675
            ]);
676
        }
677
678
        $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...
679
        $sql[] = 'test_int, ';
680
        $sql[] = 'test_string, ';
681
        $sql[] = $platform->getBitOrComparisonExpression('test_int', 2) . ' AS bit_or, ';
682
        $sql[] = $platform->getBitAndComparisonExpression('test_int', 2) . ' AS bit_and ';
683
        $sql[] = 'FROM fetch_table';
684
685
        $stmt = $this->connection->executeQuery(implode(PHP_EOL, $sql));
686
        $data = $stmt->fetchAll(FetchMode::ASSOCIATIVE);
687
688
        self::assertCount(4, $data);
689
        self::assertEquals(count($bitmap), count($data));
690
        foreach ($data as $row) {
691
            $row = array_change_key_case($row, CASE_LOWER);
692
693
            self::assertArrayHasKey('test_int', $row);
694
695
            $id = $row['test_int'];
696
697
            self::assertArrayHasKey($id, $bitmap);
698
            self::assertArrayHasKey($id, $bitmap);
699
700
            self::assertArrayHasKey('bit_or', $row);
701
            self::assertArrayHasKey('bit_and', $row);
702
703
            self::assertEquals($row['bit_or'], $bitmap[$id]['bit_or']);
704
            self::assertEquals($row['bit_and'], $bitmap[$id]['bit_and']);
705
        }
706
    }
707
708
    public function testSetDefaultFetchMode()
709
    {
710
        $stmt = $this->connection->query('SELECT * FROM fetch_table');
711
        $stmt->setFetchMode(FetchMode::NUMERIC);
712
713
        $row = array_keys($stmt->fetch());
714
        self::assertCount(0, array_filter($row, static function ($v) {
715
            return ! is_numeric($v);
716
        }), 'should be no non-numerical elements in the result.');
717
    }
718
719
    /**
720
     * @group DBAL-1091
721
     */
722
    public function testFetchAllStyleObject()
723
    {
724
        $this->setupFixture();
725
726
        $sql  = 'SELECT test_int, test_string, test_datetime FROM fetch_table';
727
        $stmt = $this->connection->prepare($sql);
728
729
        $stmt->execute();
730
731
        $results = $stmt->fetchAll(FetchMode::STANDARD_OBJECT);
732
733
        self::assertCount(1, $results);
734
        self::assertInstanceOf('stdClass', $results[0]);
735
736
        self::assertEquals(
737
            1,
738
            property_exists($results[0], 'test_int') ? $results[0]->test_int : $results[0]->TEST_INT
739
        );
740
        self::assertEquals(
741
            'foo',
742
            property_exists($results[0], 'test_string') ? $results[0]->test_string : $results[0]->TEST_STRING
743
        );
744
        self::assertStringStartsWith(
745
            '2010-01-01 10:10:10',
746
            property_exists($results[0], 'test_datetime') ? $results[0]->test_datetime : $results[0]->TEST_DATETIME
747
        );
748
    }
749
750
    /**
751
     * @group DBAL-196
752
     */
753
    public function testFetchAllSupportFetchClass()
754
    {
755
        $this->skipOci8AndMysqli();
756
        $this->setupFixture();
757
758
        $sql  = 'SELECT test_int, test_string, test_datetime FROM fetch_table';
759
        $stmt = $this->connection->prepare($sql);
760
        $stmt->execute();
761
762
        $results = $stmt->fetchAll(
763
            FetchMode::CUSTOM_OBJECT,
764
            MyFetchClass::class
765
        );
766
767
        self::assertCount(1, $results);
768
        self::assertInstanceOf(MyFetchClass::class, $results[0]);
769
770
        self::assertEquals(1, $results[0]->test_int);
771
        self::assertEquals('foo', $results[0]->test_string);
772
        self::assertStringStartsWith('2010-01-01 10:10:10', $results[0]->test_datetime);
773
    }
774
775
    /**
776
     * @group DBAL-241
777
     */
778
    public function testFetchAllStyleColumn()
779
    {
780
        $sql = 'DELETE FROM fetch_table';
781
        $this->connection->executeUpdate($sql);
782
783
        $this->connection->insert('fetch_table', ['test_int' => 1, 'test_string' => 'foo']);
784
        $this->connection->insert('fetch_table', ['test_int' => 10, 'test_string' => 'foo']);
785
786
        $sql  = 'SELECT test_int FROM fetch_table';
787
        $rows = $this->connection->query($sql)->fetchAll(FetchMode::COLUMN);
788
789
        self::assertEquals([1, 10], $rows);
790
    }
791
792
    /**
793
     * @group DBAL-214
794
     */
795
    public function testSetFetchModeClassFetchAll()
796
    {
797
        $this->skipOci8AndMysqli();
798
        $this->setupFixture();
799
800
        $sql  = 'SELECT * FROM fetch_table';
801
        $stmt = $this->connection->query($sql);
802
        $stmt->setFetchMode(FetchMode::CUSTOM_OBJECT, MyFetchClass::class);
803
804
        $results = $stmt->fetchAll();
805
806
        self::assertCount(1, $results);
807
        self::assertInstanceOf(MyFetchClass::class, $results[0]);
808
809
        self::assertEquals(1, $results[0]->test_int);
810
        self::assertEquals('foo', $results[0]->test_string);
811
        self::assertStringStartsWith('2010-01-01 10:10:10', $results[0]->test_datetime);
812
    }
813
814
    /**
815
     * @group DBAL-214
816
     */
817
    public function testSetFetchModeClassFetch()
818
    {
819
        $this->skipOci8AndMysqli();
820
        $this->setupFixture();
821
822
        $sql  = 'SELECT * FROM fetch_table';
823
        $stmt = $this->connection->query($sql);
824
        $stmt->setFetchMode(FetchMode::CUSTOM_OBJECT, MyFetchClass::class);
825
826
        $results = [];
827
        while ($row = $stmt->fetch()) {
828
            $results[] = $row;
829
        }
830
831
        self::assertCount(1, $results);
832
        self::assertInstanceOf(MyFetchClass::class, $results[0]);
833
834
        self::assertEquals(1, $results[0]->test_int);
835
        self::assertEquals('foo', $results[0]->test_string);
836
        self::assertStringStartsWith('2010-01-01 10:10:10', $results[0]->test_datetime);
837
    }
838
839
    /**
840
     * @group DBAL-257
841
     */
842
    public function testEmptyFetchColumnReturnsFalse()
843
    {
844
        $this->connection->beginTransaction();
845
        $this->connection->exec('DELETE FROM fetch_table');
846
        self::assertFalse($this->connection->fetchColumn('SELECT test_int FROM fetch_table'));
847
        self::assertFalse($this->connection->query('SELECT test_int FROM fetch_table')->fetchColumn());
848
        $this->connection->rollBack();
849
    }
850
851
    /**
852
     * @group DBAL-339
853
     */
854
    public function testSetFetchModeOnDbalStatement()
855
    {
856
        $sql  = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?';
857
        $stmt = $this->connection->executeQuery($sql, [1, 'foo']);
858
        $stmt->setFetchMode(FetchMode::NUMERIC);
859
860
        $row = $stmt->fetch();
861
862
        self::assertArrayHasKey(0, $row);
863
        self::assertArrayHasKey(1, $row);
864
        self::assertFalse($stmt->fetch());
865
    }
866
867
    /**
868
     * @group DBAL-435
869
     */
870
    public function testEmptyParameters()
871
    {
872
        $sql  = 'SELECT * FROM fetch_table WHERE test_int IN (?)';
873
        $stmt = $this->connection->executeQuery($sql, [[]], [Connection::PARAM_INT_ARRAY]);
874
        $rows = $stmt->fetchAll();
875
876
        self::assertEquals([], $rows);
877
    }
878
879
    /**
880
     * @group DBAL-1028
881
     */
882
    public function testFetchColumnNullValue()
883
    {
884
        $this->connection->executeUpdate(
885
            'INSERT INTO fetch_table (test_int, test_string) VALUES (?, ?)',
886
            [2, 'foo']
887
        );
888
889
        self::assertNull(
890
            $this->connection->fetchColumn('SELECT test_datetime FROM fetch_table WHERE test_int = ?', [2])
891
        );
892
    }
893
894
    /**
895
     * @group DBAL-1028
896
     */
897
    public function testFetchColumnNoResult()
898
    {
899
        self::assertFalse(
900
            $this->connection->fetchColumn('SELECT test_int FROM fetch_table WHERE test_int = ?', [-1])
901
        );
902
    }
903
904
    private function setupFixture()
905
    {
906
        $this->connection->exec('DELETE FROM fetch_table');
907
        $this->connection->insert('fetch_table', [
908
            'test_int'      => 1,
909
            'test_string'   => 'foo',
910
            'test_datetime' => '2010-01-01 10:10:10',
911
        ]);
912
    }
913
914
    private function skipOci8AndMysqli()
915
    {
916
        if (isset($GLOBALS['db_type']) && $GLOBALS['db_type'] === 'oci8') {
917
            $this->markTestSkipped('Not supported by OCI8');
918
        }
919
        if ($this->connection->getDriver()->getName() !== 'mysqli') {
920
            return;
921
        }
922
923
        $this->markTestSkipped('Mysqli driver dont support this feature.');
924
    }
925
}
926
927
class MyFetchClass
928
{
929
    /** @var int */
930
    public $test_int;
931
932
    /** @var string */
933
    public $test_string;
934
935
    /** @var string */
936
    public $test_datetime;
937
}
938