Passed
Pull Request — master (#372)
by Wilmer
03:26
created

testCreateTableColumnTypes()   B

Complexity

Conditions 8
Paths 6

Size

Total Lines 32
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
eloc 17
c 1
b 0
f 0
nc 6
nop 0
dl 0
loc 32
rs 8.4444
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Tests;
6
7
use PHPUnit\Framework\TestCase;
8
use Yiisoft\Db\Connection\ConnectionInterface;
9
use Yiisoft\Db\Expression\Expression;
10
use Yiisoft\Db\Query\Query;
11
use Yiisoft\Db\QueryBuilder\QueryBuilder;
12
use Yiisoft\Db\Schema\Schema;
13
use Yiisoft\Db\Schema\SchemaBuilderTrait;
14
use Yiisoft\Db\Tests\Support\DbHelper;
15
16
use function is_array;
17
use function str_replace;
18
use function str_starts_with;
19
use function strncmp;
20
use function substr;
21
22
abstract class AbstractQueryBuilderTest extends TestCase
23
{
24
    use QueryBuilderColumnsTypeTrait;
25
    use SchemaBuilderTrait;
26
27
    private ConnectionInterface $db;
28
29
    public function testBuildWhereExistsWithArrayParameters(): void
30
    {
31
        $db = $this->getConnection();
0 ignored issues
show
Bug introduced by
The method getConnection() does not exist on Yiisoft\Db\Tests\AbstractQueryBuilderTest. ( Ignorable by Annotation )

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

31
        /** @scrutinizer ignore-call */ 
32
        $db = $this->getConnection();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
32
33
        $expectedQuerySql = DbHelper::replaceQuotes(
34
            'SELECT [[id]] FROM [[TotalExample]] [[t]] WHERE (EXISTS (SELECT [[1]] FROM [[Website]] [[w]]'
35
            . ' WHERE (w.id = t.website_id) AND (([[w]].[[merchant_id]]=:qp0) AND ([[w]].[[user_id]]=:qp1))))'
36
            . ' AND ([[t]].[[some_column]]=:qp2)',
37
            $db->getName(),
38
        );
39
40
        $expectedQueryParams = [':qp0' => 6, ':qp1' => 210, ':qp2' => 'asd'];
41
42
        $subQuery = (new Query($db))
43
            ->select('1')
44
            ->from('Website w')
45
            ->where('w.id = t.website_id')
46
            ->andWhere(['w.merchant_id' => 6, 'w.user_id' => '210']);
47
48
        $query = (new Query($db))
49
            ->select('id')
50
            ->from('TotalExample t')
51
            ->where(['exists', $subQuery])
52
            ->andWhere(['t.some_column' => 'asd']);
53
54
        [$actualQuerySql, $queryParams] = $db->getQueryBuilder()->build($query);
55
56
        $this->assertEquals($expectedQuerySql, $actualQuerySql);
57
        $this->assertEquals($expectedQueryParams, $queryParams);
58
    }
59
60
    public function testBuildWhereExistsWithParameters(): void
61
    {
62
        $db = $this->getConnection();
63
64
        $expectedQuerySql = DbHelper::replaceQuotes(
65
            'SELECT [[id]] FROM [[TotalExample]] [[t]] WHERE (EXISTS (SELECT [[1]] FROM [[Website]] [[w]]'
66
            . ' WHERE (w.id = t.website_id) AND (w.merchant_id = :merchant_id))) AND (t.some_column = :some_value)',
67
            $db->getName(),
68
        );
69
70
        $expectedQueryParams = [':some_value' => 'asd', ':merchant_id' => 6];
71
72
        $subQuery = (new Query($db))
73
            ->select('1')
74
            ->from('Website w')
75
            ->where('w.id = t.website_id')
76
            ->andWhere('w.merchant_id = :merchant_id', [':merchant_id' => 6]);
77
78
        $query = (new Query($db))
79
            ->select('id')
80
            ->from('TotalExample t')
81
            ->where(['exists', $subQuery])
82
            ->andWhere('t.some_column = :some_value', [':some_value' => 'asd']);
83
84
        [$actualQuerySql, $queryParams] = $db->getQueryBuilder()->build($query);
85
86
        $this->assertSame($expectedQuerySql, $actualQuerySql);
87
        $this->assertSame($expectedQueryParams, $queryParams);
88
    }
89
90
    /**
91
     * This test contains three select queries connected with UNION and UNION ALL constructions.
92
     * It could be useful to use "phpunit --group=db --filter testBuildUnion" command for run it.
93
     */
94
    public function testBuildUnion(): void
95
    {
96
        $db = $this->getConnection();
97
98
        $expectedQuerySql = DbHelper::replaceQuotes(
99
            '(SELECT [[id]] FROM [[TotalExample]] [[t1]] WHERE (w > 0) AND (x < 2)) UNION ( SELECT [[id]]'
100
            . ' FROM [[TotalTotalExample]] [[t2]] WHERE w > 5 ) UNION ALL ( SELECT [[id]] FROM [[TotalTotalExample]]'
101
            . ' [[t3]] WHERE w = 3 )',
102
            $db->getName(),
103
        );
104
105
        $secondQuery = (new Query($db))
106
            ->select('id')
107
            ->from('TotalTotalExample t2')
108
            ->where('w > 5');
109
110
        $thirdQuery = (new Query($db))
111
            ->select('id')
112
            ->from('TotalTotalExample t3')
113
            ->where('w = 3');
114
115
        $query = (new Query($db))
116
            ->select('id')
117
            ->from('TotalExample t1')
118
            ->where(['and', 'w > 0', 'x < 2'])
119
            ->union($secondQuery)
120
            ->union($thirdQuery, true);
121
122
        [$actualQuerySql, $queryParams] = $db->getQueryBuilder()->build($query);
123
124
        $this->assertSame($expectedQuerySql, $actualQuerySql);
125
        $this->assertSame([], $queryParams);
126
    }
127
128
    public function testBuildWithQuery(): void
129
    {
130
        $db = $this->getConnection();
131
132
        $expectedQuerySql = DbHelper::replaceQuotes(
133
            'WITH a1 AS (SELECT [[id]] FROM [[t1]] WHERE expr = 1), a2 AS ((SELECT [[id]] FROM [[t2]]'
134
            . ' INNER JOIN [[a1]] ON t2.id = a1.id WHERE expr = 2) UNION ( SELECT [[id]] FROM [[t3]] WHERE expr = 3 ))'
135
            . ' SELECT * FROM [[a2]]',
136
            $db->getName(),
137
        );
138
139
        $with1Query = (new Query($db))
140
            ->select('id')
141
            ->from('t1')
142
            ->where('expr = 1');
143
144
        $with2Query = (new Query($db))
145
            ->select('id')
146
            ->from('t2')
147
            ->innerJoin('a1', 't2.id = a1.id')
148
            ->where('expr = 2');
149
150
        $with3Query = (new Query($db))
151
            ->select('id')
152
            ->from('t3')
153
            ->where('expr = 3');
154
155
        $query = (new Query($db))
156
            ->withQuery($with1Query, 'a1')
157
            ->withQuery($with2Query->union($with3Query), 'a2')
158
            ->from('a2');
159
160
        [$actualQuerySql, $queryParams] = $db->getQueryBuilder()->build($query);
161
162
        $this->assertSame($expectedQuerySql, $actualQuerySql);
163
        $this->assertSame([], $queryParams);
164
    }
165
166
    public function testBuildWithQueryRecursive(): void
167
    {
168
        $db = $this->getConnection();
169
170
        $expectedQuerySql = DbHelper::replaceQuotes(
171
            'WITH RECURSIVE a1 AS (SELECT [[id]] FROM [[t1]] WHERE expr = 1) SELECT * FROM [[a1]]',
172
            $db->getName(),
173
        );
174
175
        $with1Query = (new Query($db))
176
            ->select('id')
177
            ->from('t1')
178
            ->where('expr = 1');
179
180
        $query = (new Query($db))
181
            ->withQuery($with1Query, 'a1', true)
182
            ->from('a1');
183
184
        [$actualQuerySql, $queryParams] = $db->getQueryBuilder()->build($query);
185
186
        $this->assertSame($expectedQuerySql, $actualQuerySql);
187
        $this->assertSame([], $queryParams);
188
    }
189
190
    public function testCreateTableColumnTypes(): void
191
    {
192
        $this->db = $this->getConnection();
193
194
        $qb = $this->db->getQueryBuilder();
195
196
        if ($this->db->getTableSchema('column_type_table', true) !== null) {
197
            $this->db->createCommand($qb->dropTable('column_type_table'))->execute();
198
        }
199
200
        $columns = [];
201
        $i = 0;
202
203
        foreach ($this->columnTypes() as [$column, $builder, $expected]) {
204
            if (
205
                !(
206
                    strncmp($column, Schema::TYPE_PK, 2) === 0 ||
207
                    strncmp($column, Schema::TYPE_UPK, 3) === 0 ||
208
                    strncmp($column, Schema::TYPE_BIGPK, 5) === 0 ||
209
                    strncmp($column, Schema::TYPE_UBIGPK, 6) === 0 ||
210
                    str_starts_with(substr($column, -5), 'FIRST')
211
                )
212
            ) {
213
                $columns['col' . ++$i] = str_replace('CHECK (value', 'CHECK ([[col' . $i . ']]', $column);
214
            }
215
        }
216
217
        $this->db->createCommand($qb->createTable('column_type_table', $columns))->execute();
218
219
        $this->assertNotEmpty($this->db->getTableSchema('column_type_table', true));
220
221
        unset($this->db);
222
    }
223
224
    public function testComplexSelect(): void
225
    {
226
        $db = $this->getConnection();
227
228
        $expressionString = DbHelper::replaceQuotes(
229
            "case t.Status_Id when 1 then 'Acknowledge' when 2 then 'No Action' else 'Unknown Action'"
230
            . ' END as [[Next Action]]',
231
            $db->getName(),
232
        );
233
234
        $this->assertIsString($expressionString);
235
236
        $query = (new Query($db))
237
            ->select([
238
                'ID' => 't.id',
239
                'gsm.username as GSM',
240
                'part.Part',
241
                'Part Cost' => 't.Part_Cost',
242
                'st_x(location::geometry) as lon',
243
                new Expression($expressionString),
244
            ])
245
            ->from('tablename');
246
247
        [$sql, $params] = $db->getQueryBuilder()->build($query);
248
249
        $expected = DbHelper::replaceQuotes(
250
            'SELECT [[t]].[[id]] AS [[ID]], [[gsm]].[[username]] AS [[GSM]], [[part]].[[Part]], [[t]].[[Part_Cost]]'
251
            . ' AS [[Part Cost]], st_x(location::geometry) AS [[lon]], case t.Status_Id when 1 then \'Acknowledge\''
252
            . ' when 2 then \'No Action\' else \'Unknown Action\' END as [[Next Action]] FROM [[tablename]]',
253
            $db->getName(),
254
        );
255
256
        $this->assertSame($expected, $sql);
257
        $this->assertEmpty($params);
258
    }
259
260
    /**
261
     * {@see https://github.com/yiisoft/yii2/issues/10869}
262
     */
263
    public function testFromIndexHint(): void
264
    {
265
        $db = $this->getConnection();
266
267
        $query = (new Query($db))->from([new Expression('{{%user}} USE INDEX (primary)')]);
268
269
        [$sql, $params] = $db->getQueryBuilder()->build($query);
270
        $expected = DbHelper::replaceQuotes('SELECT * FROM {{%user}} USE INDEX (primary)', $db->getName());
271
272
        $this->assertSame($expected, $sql);
273
        $this->assertEmpty($params);
274
275
        $query = (new Query($db))
276
            ->from([new Expression('{{user}} {{t}} FORCE INDEX (primary) IGNORE INDEX FOR ORDER BY (i1)')])
277
            ->leftJoin(['p' => 'profile'], 'user.id = profile.user_id USE INDEX (i2)');
278
279
        [$sql, $params] = $db->getQueryBuilder()->build($query);
280
        $expected = DbHelper::replaceQuotes(
281
            'SELECT * FROM {{user}} {{t}} FORCE INDEX (primary) IGNORE INDEX FOR ORDER BY (i1)'
282
            . ' LEFT JOIN [[profile]] [[p]] ON user.id = profile.user_id USE INDEX (i2)',
283
            $db->getName(),
284
        );
285
286
        $this->assertSame($expected, $sql);
287
        $this->assertEmpty($params);
288
    }
289
290
    public function testFromSubQuery(): void
291
    {
292
        $db = $this->getConnection();
293
294
        /* query subquery */
295
        $subquery = (new Query($db))->from('user')->where('account_id = accounts.id');
296
        $query = (new Query($db))->from(['activeusers' => $subquery]);
297
298
        /* SELECT * FROM (SELECT * FROM [[user]] WHERE [[active]] = 1) [[activeusers]]; */
299
        [$sql, $params] = $db->getQueryBuilder()->build($query);
300
        $expected = DbHelper::replaceQuotes(
301
            'SELECT * FROM (SELECT * FROM [[user]] WHERE account_id = accounts.id) [[activeusers]]',
302
            $db->getName(),
303
        );
304
305
        $this->assertSame($expected, $sql);
306
        $this->assertEmpty($params);
307
308
        /* query subquery with params */
309
        $subquery = (new Query($db))->from('user')->where('account_id = :id', ['id' => 1]);
310
        $query = (new Query($db))->from(['activeusers' => $subquery])->where('abc = :abc', ['abc' => 'abc']);
311
312
        /* SELECT * FROM (SELECT * FROM [[user]] WHERE [[active]] = 1) [[activeusers]]; */
313
        [$sql, $params] = $db->getQueryBuilder()->build($query);
314
        $expected = DbHelper::replaceQuotes(
315
            'SELECT * FROM (SELECT * FROM [[user]] WHERE account_id = :id) [[activeusers]] WHERE abc = :abc',
316
            $db->getName(),
317
        );
318
319
        $this->assertSame($expected, $sql);
320
        $this->assertEquals(['id' => 1, 'abc' => 'abc'], $params);
321
322
        /* simple subquery */
323
        $subquery = '(SELECT * FROM user WHERE account_id = accounts.id)';
324
        $query = (new Query($db))->from(['activeusers' => $subquery]);
325
326
        /* SELECT * FROM (SELECT * FROM [[user]] WHERE [[active]] = 1) [[activeusers]]; */
327
        [$sql, $params] = $db->getQueryBuilder()->build($query);
328
        $expected = DbHelper::replaceQuotes(
329
            'SELECT * FROM (SELECT * FROM user WHERE account_id = accounts.id) [[activeusers]]',
330
            $db->getName(),
331
        );
332
333
        $this->assertSame($expected, $sql);
334
        $this->assertEmpty($params);
335
    }
336
337
    public function testGetColumnType(): void
338
    {
339
        $this->db = $this->getConnection();
340
341
        $qb = $this->db->getQueryBuilder();
342
343
        foreach ($this->columnTypes() as $item) {
344
            [$column, $builder, $expected] = $item;
345
346
            $driverName = $this->db->getName();
347
348
            if (isset($item[3][$driverName])) {
349
                $expectedColumnSchemaBuilder = $item[3][$driverName];
350
            } elseif (isset($item[3]) && !is_array($item[3])) {
351
                $expectedColumnSchemaBuilder = $item[3];
352
            } else {
353
                $expectedColumnSchemaBuilder = $column;
354
            }
355
356
            $this->assertSame($expectedColumnSchemaBuilder, $builder->__toString());
357
            $this->assertSame($expected, $qb->getColumnType($column));
358
            $this->assertSame($expected, $qb->getColumnType($builder));
359
        }
360
361
        unset($this->db);
362
    }
363
364
    public function testGroupBy(): void
365
    {
366
        $db = $this->getConnection();
367
368
        /* simple string */
369
        $query = (new Query($db))
370
            ->select('*')
371
            ->from('operations')
372
            ->groupBy('name, date');
373
374
        [$sql, $params] = $db->getQueryBuilder()->build($query);
375
        $expected = DbHelper::replaceQuotes('SELECT * FROM [[operations]] GROUP BY [[name]], [[date]]', $db->getName());
376
377
        $this->assertSame($expected, $sql);
378
        $this->assertEmpty($params);
379
380
        /* array syntax */
381
        $query = (new Query($db))
382
            ->select('*')
383
            ->from('operations')
384
            ->groupBy(['name', 'date']);
385
386
        [$sql, $params] = $db->getQueryBuilder()->build($query);
387
        $expected = DbHelper::replaceQuotes('SELECT * FROM [[operations]] GROUP BY [[name]], [[date]]', $db->getName());
388
389
        $this->assertSame($expected, $sql);
390
        $this->assertEmpty($params);
391
392
        /* expression */
393
        $query = (new Query($db))
394
            ->select('*')
395
            ->from('operations')
396
            ->where('account_id = accounts.id')
397
            ->groupBy(new Expression('SUBSTR(name, 0, 1), x'));
398
399
        [$sql, $params] = $db->getQueryBuilder()->build($query);
400
        $expected = DbHelper::replaceQuotes(
401
            'SELECT * FROM [[operations]] WHERE account_id = accounts.id GROUP BY SUBSTR(name, 0, 1), x',
402
            $db->getName(),
403
        );
404
405
        $this->assertSame($expected, $sql);
406
        $this->assertEmpty($params);
407
408
        /* expression with params */
409
        $query = (new Query($db))
410
            ->select('*')
411
            ->from('operations')
412
            ->groupBy(new Expression('SUBSTR(name, 0, :to), x', [':to' => 4]));
413
414
        [$sql, $params] = $db->getQueryBuilder()->build($query);
415
        $expected = DbHelper::replaceQuotes(
416
            'SELECT * FROM [[operations]] GROUP BY SUBSTR(name, 0, :to), x',
417
            $db->getName(),
418
        );
419
420
        $this->assertSame($expected, $sql);
421
        $this->assertSame([':to' => 4], $params);
422
    }
423
424
    /**
425
     * Dummy test to speed up QB's tests which rely on DB schema.
426
     */
427
    public function testInitFixtures(): void
428
    {
429
        $db = $this->getConnection();
430
431
        $this->assertInstanceOf(QueryBuilder::class, $db->getQueryBuilder());
432
    }
433
434
    /**
435
     * {@see https://github.com/yiisoft/yii2/issues/15653}
436
     */
437
    public function testIssue15653(): void
438
    {
439
        $db = $this->getConnection();
440
441
        $query = (new Query($db))->from('admin_user')->where(['is_deleted' => false]);
442
        $query->where([])->andWhere(['in', 'id', ['1', '0']]);
443
        [$sql, $params] = $db->getQueryBuilder()->build($query);
444
445
        $this->assertSame(
446
            DbHelper::replaceQuotes('SELECT * FROM [[admin_user]] WHERE [[id]] IN (:qp0, :qp1)', $db->getName()),
447
            $sql,
448
        );
449
        $this->assertSame([':qp0' => '1', ':qp1' => '0'], $params);
450
    }
451
452
    public function testOrderBy(): void
453
    {
454
        $db = $this->getConnection();
455
456
        /* simple string */
457
        $query = (new Query($db))
458
            ->select('*')
459
            ->from('operations')
460
            ->orderBy('name ASC, date DESC');
461
462
        [$sql, $params] = $db->getQueryBuilder()->build($query);
463
        $expected = DbHelper::replaceQuotes(
464
            'SELECT * FROM [[operations]] ORDER BY [[name]], [[date]] DESC',
465
            $db->getName(),
466
        );
467
468
        $this->assertSame($expected, $sql);
469
        $this->assertEmpty($params);
470
471
        /* array syntax */
472
        $query = (new Query($db))
473
            ->select('*')
474
            ->from('operations')
475
            ->orderBy(['name' => SORT_ASC, 'date' => SORT_DESC]);
476
477
        [$sql, $params] = $db->getQueryBuilder()->build($query);
478
        $expected = DbHelper::replaceQuotes(
479
            'SELECT * FROM [[operations]] ORDER BY [[name]], [[date]] DESC',
480
            $db->getName(),
481
        );
482
483
        $this->assertSame($expected, $sql);
484
        $this->assertEmpty($params);
485
486
        /* expression */
487
        $query = (new Query($db))
488
            ->select('*')
489
            ->from('operations')
490
            ->where('account_id = accounts.id')
491
            ->orderBy(new Expression('SUBSTR(name, 3, 4) DESC, x ASC'));
492
493
        [$sql, $params] = $db->getQueryBuilder()->build($query);
494
        $expected = DbHelper::replaceQuotes(
495
            'SELECT * FROM [[operations]] WHERE account_id = accounts.id ORDER BY SUBSTR(name, 3, 4) DESC, x ASC',
496
            $db->getName(),
497
        );
498
499
        $this->assertSame($expected, $sql);
500
        $this->assertEmpty($params);
501
502
        /* expression with params */
503
        $query = (new Query($db))
504
            ->select('*')
505
            ->from('operations')
506
            ->orderBy(new Expression('SUBSTR(name, 3, :to) DESC, x ASC', [':to' => 4]));
507
508
        [$sql, $params] = $db->getQueryBuilder()->build($query);
509
        $expected = DbHelper::replaceQuotes(
510
            'SELECT * FROM [[operations]] ORDER BY SUBSTR(name, 3, :to) DESC, x ASC',
511
            $db->getName(),
512
        );
513
514
        $this->assertSame($expected, $sql);
515
        $this->assertEquals([':to' => 4], $params);
516
    }
517
518
    public function testSelectExpression(): void
519
    {
520
        $db = $this->getConnection();
521
522
        $query = (new Query($db))
523
            ->select(new Expression('1 AS ab'))
524
            ->from('tablename');
525
526
        [$sql, $params] = $db->getQueryBuilder()->build($query);
527
        $expected = DbHelper::replaceQuotes('SELECT 1 AS ab FROM [[tablename]]', $db->getName());
528
529
        $this->assertSame($expected, $sql);
530
        $this->assertEmpty($params);
531
532
        $query = (new Query($db))
533
            ->select(new Expression('1 AS ab'))
534
            ->addSelect(new Expression('2 AS cd'))
535
            ->addSelect(['ef' => new Expression('3')])
536
            ->from('tablename');
537
538
        [$sql, $params] = $db->getQueryBuilder()->build($query);
539
        $expected = DbHelper::replaceQuotes('SELECT 1 AS ab, 2 AS cd, 3 AS [[ef]] FROM [[tablename]]', $db->getName());
540
541
        $this->assertSame($expected, $sql);
542
        $this->assertEmpty($params);
543
544
        $query = (new Query($db))
545
            ->select(new Expression('SUBSTR(name, 0, :len)', [':len' => 4]))
546
            ->from('tablename');
547
548
        [$sql, $params] = $db->getQueryBuilder()->build($query);
549
        $expected = DbHelper::replaceQuotes('SELECT SUBSTR(name, 0, :len) FROM [[tablename]]', $db->getName());
550
551
        $this->assertSame($expected, $sql);
552
        $this->assertSame([':len' => 4], $params);
553
    }
554
555
    public function testSelectSubQuery(): void
556
    {
557
        $db = $this->getConnection();
558
559
        $subquery = (new Query($db))
560
            ->select('COUNT(*)')
561
            ->from('operations')
562
            ->where('account_id = accounts.id');
563
564
        $query = (new Query($db))
565
            ->select('*')
566
            ->from('accounts')
567
            ->addSelect(['operations_count' => $subquery]);
568
569
        [$sql, $params] = $db->getQueryBuilder()->build($query);
570
571
        $expected = DbHelper::replaceQuotes(
572
            'SELECT *, (SELECT COUNT(*) FROM [[operations]] WHERE account_id = accounts.id) AS [[operations_count]]'
573
            . ' FROM [[accounts]]',
574
            $db->getName(),
575
        );
576
577
        $this->assertSame($expected, $sql);
578
        $this->assertEmpty($params);
579
    }
580
}
581