Completed
Pull Request — master (#3339)
by Sergei
22:50
created

DataAccessTest::testPrepareWithBindValue()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

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

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

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