Passed
Pull Request — master (#380)
by Alexander
03:00
created

testBuildSelectColumnWithoutParentheses()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 6
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Tests\QueryBuilder;
6
7
use Closure;
8
use PHPUnit\Framework\TestCase;
9
use stdClass;
10
use Yiisoft\Db\Connection\ConnectionInterface;
11
use Yiisoft\Db\Expression\Expression;
12
use Yiisoft\Db\Expression\ExpressionInterface;
13
use Yiisoft\Db\QueryBuilder\QueryBuilderInterface;
14
use Yiisoft\Db\Schema\ColumnSchemaBuilder;
15
use Yiisoft\Db\Schema\SchemaBuilderTrait;
16
use Yiisoft\Db\Tests\Support\Assert;
17
use Yiisoft\Db\Tests\Support\DbHelper;
18
use Yiisoft\Db\Tests\Support\Mock;
19
20
/**
21
 * @group db
22
 */
23
final class QueryBuilderTest extends TestCase
24
{
25
    use SchemaBuilderTrait;
26
27
    private ConnectionInterface $db;
28
    private QueryBuilderInterface $queryBuilder;
29
    private Mock $mock;
30
31
    public function setUp(): void
32
    {
33
        parent::setUp();
34
35
        $this->mock = new Mock();
36
        $this->db = $this->mock->connection();
37
        $this->queryBuilder = $this->mock->queryBuilder();
38
    }
39
40
    public function tearDown(): void
41
    {
42
        parent::tearDown();
43
44
        unset($this->queryBuilder, $this->mock);
45
    }
46
47
    /**
48
     * @dataProvider \Yiisoft\Db\Tests\Provider\QueryBuilderProvider::addDropChecks()
49
     */
50
    public function testAddDropCheck(string $sql, Closure $builder): void
51
    {
52
        $this->assertSame($this->mock->quoter()->quoteSql($sql), $builder($this->queryBuilder));
53
    }
54
55
    /**
56
     * @dataProvider \Yiisoft\Db\Tests\Provider\QueryBuilderProvider::addDropForeignKeys()
57
     */
58
    public function testAddDropForeignKey(string $sql, Closure $builder): void
59
    {
60
        $this->assertSame($this->mock->quoter()->quoteSql($sql), $builder($this->queryBuilder));
61
    }
62
63
    /**
64
     * @dataProvider \Yiisoft\Db\Tests\Provider\QueryBuilderProvider::addDropPrimaryKeys()
65
     */
66
    public function testAddDropPrimaryKey(string $sql, Closure $builder): void
67
    {
68
        $this->assertSame($this->mock->quoter()->quoteSql($sql), $builder($this->queryBuilder));
69
    }
70
71
    /**
72
     * @dataProvider \Yiisoft\Db\Tests\Provider\QueryBuilderProvider::addDropUniques()
73
     */
74
    public function testAddDropUnique(string $sql, Closure $builder): void
75
    {
76
        $this->assertSame($this->mock->quoter()->quoteSql($sql), $builder($this->queryBuilder));
77
    }
78
79
    public function testAddColumn(): void
80
    {
81
        $this->assertSame(
82
            <<<SQL
83
            ALTER TABLE `user` ADD `age` integer
84
            SQL,
85
            $this->queryBuilder->addColumn('user', 'age', 'integer')
86
        );
87
    }
88
89
    public function testsAddCommentOnColumn(): void
90
    {
91
        $this->assertSame(
92
            <<<SQL
93
            COMMENT ON COLUMN `user`.`name` IS 'This is a comment'
94
            SQL,
95
            $this->queryBuilder->addCommentOnColumn('user', 'name', 'This is a comment')
96
        );
97
    }
98
99
    public function testsAddCommentOnTable(): void
100
    {
101
        $this->assertSame(
102
            <<<SQL
103
            COMMENT ON TABLE `user` IS 'This is a comment'
104
            SQL,
105
            $this->queryBuilder->addCommentOnTable('user', 'This is a comment')
106
        );
107
    }
108
109
    /**
110
     * @dataProvider \Yiisoft\Db\Tests\Provider\QueryBuilderProvider::alterColumn()
111
     */
112
    public function testAlterColumn(
113
        string $table,
114
        string $column,
115
        ColumnSchemaBuilder|string $type,
116
        string $expected
117
    ): void {
118
        $sql = $this->queryBuilder->alterColumn($table, $column, $type);
119
        $this->assertSame($expected, $sql);
120
    }
121
122
    /**
123
     * @dataProvider \Yiisoft\Db\Tests\Provider\QueryBuilderProvider::batchInsert()
124
     */
125
    public function testBatchInsert(
126
        string $table,
127
        array $columns,
128
        array $value,
129
        string|null $expected,
130
        array $expectedParams = []
131
    ): void {
132
        $params = [];
133
        $sql = $this->queryBuilder->batchInsert($table, $columns, $value, $params);
134
135
        $this->assertSame($expected, $sql);
136
        $this->assertSame($expectedParams, $params);
137
    }
138
139
    public function testBuildColumnsWithString(): void
140
    {
141
        $columns = '(id)';
142
143
        $this->assertSame($columns, $this->queryBuilder->buildColumns($columns));
144
    }
145
146
    public function testBuildColumnsWithArray(): void
147
    {
148
        $columns = [
149
            'id',
150
            'name',
151
            'email',
152
            'address',
153
            'status',
154
        ];
155
156
        $expected = '`id`, `name`, `email`, `address`, `status`';
157
158
        $this->assertSame($expected, $this->queryBuilder->buildColumns($columns));
159
    }
160
161
    public function testBuildColumnsWithExpression(): void
162
    {
163
        $columns = [
164
            'id',
165
            'name',
166
            'email',
167
            'address',
168
            'status',
169
            new Expression('COUNT(*)'),
170
        ];
171
172
        $expected = '`id`, `name`, `email`, `address`, `status`, COUNT(*)';
173
174
        $this->assertSame($expected, $this->queryBuilder->buildColumns($columns));
175
    }
176
177
    /**
178
     * @dataProvider \Yiisoft\Db\Tests\Provider\QueryBuilderProvider::buildConditions()
179
     */
180
    public function testBuildCondition(
181
        array|ExpressionInterface|string $conditions,
182
        string $expected,
183
        array $expectedParams = []
184
    ): void {
185
        $query = $this->mock->query()->where($conditions);
186
        [$sql, $params] = $this->queryBuilder->build($query);
187
188
        $this->assertSame(
189
            'SELECT *' . (
190
                empty($expected) ? '' : ' WHERE ' . DbHelper::replaceQuotes(
191
                    $expected,
192
                    $this->mock->getDriverName(),
193
                )
194
            ),
195
            $sql,
196
        );
197
        $this->assertSame($expectedParams, $params);
198
    }
199
200
    /**
201
     * @dataProvider \Yiisoft\Db\Tests\Provider\QueryBuilderProvider::buildFilterCondition()
202
     */
203
    public function testBuildFilterCondition(array $condition, string $expected, array $expectedParams): void
204
    {
205
        $query = $this->mock->query()->filterWhere($condition);
206
        [$sql, $params] = $this->queryBuilder->build($query);
207
208
        $this->assertSame(
209
            'SELECT *' . (
210
                empty($expected) ? '' : ' WHERE ' . DbHelper::replaceQuotes(
211
                    $expected,
212
                    $this->mock->getDriverName(),
213
                )
214
            ),
215
            $sql,
216
        );
217
        $this->assertSame($expectedParams, $params);
218
    }
219
220
    /**
221
     * @dataProvider \Yiisoft\Db\Tests\Provider\QueryBuilderProvider::buildFrom()
222
     */
223
    public function testBuildFrom(string $table, string $expected): void
224
    {
225
        $params = [];
226
        $sql = $this->queryBuilder->buildFrom([$table], $params);
227
        $replacedQuotes = DbHelper::replaceQuotes($expected, $this->mock->getDriverName());
228
229
        $this->assertIsString($replacedQuotes);
230
        $this->assertSame('FROM ' . $replacedQuotes, $sql);
231
    }
232
233
    public function testBuildLimit(): void
234
    {
235
        $query = $this->mock->query()->limit(10);
236
        [$sql, $params] = $this->queryBuilder->build($query);
237
238
        $this->assertSame('SELECT * LIMIT 10', $sql);
239
        $this->assertSame([], $params);
240
    }
241
242
    public function testBuildOffset(): void
243
    {
244
        $query = $this->mock->query()->offset(10);
245
        [$sql, $params] = $this->queryBuilder->build($query);
246
247
        $this->assertSame('SELECT * OFFSET 10', $sql);
248
        $this->assertSame([], $params);
249
    }
250
251
    public function testBuildSelectColumnWithoutParentheses(): void
252
    {
253
        $params = [];
254
        $sql = $this->queryBuilder->buildSelect(['1'], $params);
255
256
        $this->assertSame('SELECT `1`', $sql);
257
    }
258
259
    public function testBuildSelectOptions(): void
260
    {
261
        $query = $this->mock->query()->selectOption('DISTINCT');
262
        [$sql, $params] = $this->queryBuilder->build($query);
263
264
        $this->assertSame('SELECT DISTINCT *', $sql);
265
        $this->assertSame([], $params);
266
    }
267
268
    /**
269
     * This test contains three select queries connected with UNION and UNION ALL constructions.
270
     * It could be useful to use "phpunit --group=db --filter testBuildUnion" command for run it.
271
     */
272
    public function testBuildUnion(): void
273
    {
274
        $expectedQuerySql = DbHelper::replaceQuotes(
275
            <<<SQL
276
            (SELECT [[id]] FROM [[TotalExample]] [[t1]] WHERE (w > 0) AND (x < 2)) UNION ( SELECT [[id]] FROM [[TotalTotalExample]] [[t2]] WHERE w > 5 ) UNION ALL ( SELECT [[id]] FROM [[TotalTotalExample]] [[t3]] WHERE w = 3 )
277
            SQL,
278
            $this->mock->getDriverName(),
279
        );
280
281
        $secondQuery = $this->mock
282
            ->query()
283
            ->select('id')
284
            ->from('TotalTotalExample t2')
285
            ->where('w > 5');
286
287
        $thirdQuery = $this->mock
288
            ->query()
289
            ->select('id')
290
            ->from('TotalTotalExample t3')
291
            ->where('w = 3');
292
293
        $query = $this->mock
294
            ->query()
295
            ->select('id')
296
            ->from('TotalExample t1')
297
            ->where(['and', 'w > 0', 'x < 2'])
298
            ->union($secondQuery)
299
            ->union($thirdQuery, true);
300
301
        [$actualQuerySql, $queryParams] = $this->queryBuilder->build($query);
302
303
        $this->assertSame($expectedQuerySql, $actualQuerySql);
304
        $this->assertSame([], $queryParams);
305
    }
306
307
    public function testBuildWithQuery(): void
308
    {
309
        $expectedQuerySql = DbHelper::replaceQuotes(
310
            <<<SQL
311
            WITH a1 AS (SELECT [[id]] FROM [[t1]] WHERE expr = 1), a2 AS ((SELECT [[id]] FROM [[t2]] INNER JOIN [[a1]] ON t2.id = a1.id WHERE expr = 2) UNION ( SELECT [[id]] FROM [[t3]] WHERE expr = 3 )) SELECT * FROM [[a2]]
312
            SQL,
313
            $this->mock->getDriverName(),
314
        );
315
316
        $with1Query = $this->mock
317
            ->query()
318
            ->select('id')
319
            ->from('t1')
320
            ->where('expr = 1');
321
322
        $with2Query = $this->mock
323
            ->query()
324
            ->select('id')
325
            ->from('t2')
326
            ->innerJoin('a1', 't2.id = a1.id')
327
            ->where('expr = 2');
328
329
        $with3Query = $this->mock
330
            ->query()
331
            ->select('id')
332
            ->from('t3')
333
            ->where('expr = 3');
334
335
        $query = $this->mock
336
            ->query()
337
            ->withQuery($with1Query, 'a1')
338
            ->withQuery($with2Query->union($with3Query), 'a2')
339
            ->from('a2');
340
341
        [$actualQuerySql, $queryParams] = $this->queryBuilder->build($query);
342
343
        $this->assertSame($expectedQuerySql, $actualQuerySql);
344
        $this->assertSame([], $queryParams);
345
    }
346
347
    public function testBuildWithQueryRecursive(): void
348
    {
349
        $expectedQuerySql = DbHelper::replaceQuotes(
350
            <<<SQL
351
            WITH RECURSIVE a1 AS (SELECT [[id]] FROM [[t1]] WHERE expr = 1) SELECT * FROM [[a1]]
352
            SQL,
353
            $this->mock->getDriverName(),
354
        );
355
356
        $with1Query = $this->mock
357
            ->query()
358
            ->select('id')
359
            ->from('t1')
360
            ->where('expr = 1');
361
362
        $query = $this->mock
363
            ->query()
364
            ->withQuery($with1Query, 'a1', true)
365
            ->from('a1');
366
367
        [$actualQuerySql, $queryParams] = $this->queryBuilder->build($query);
368
369
        $this->assertSame($expectedQuerySql, $actualQuerySql);
370
        $this->assertSame([], $queryParams);
371
    }
372
373
    /**
374
     * @dataProvider \Yiisoft\Db\Tests\Provider\QueryBuilderProvider::buildWhereExists()
375
     */
376
    public function testBuildWhereExists(string $cond, string $expectedQuerySql): void
377
    {
378
        $expectedQueryParams = [];
379
        $subQuery = $this->mock->query()->select('1')->from('Website w');
380
        $query = $this->mock->query()->select('id')->from('TotalExample t')->where([$cond, $subQuery]);
381
382
        [$actualQuerySql, $actualQueryParams] = $this->queryBuilder->build($query);
383
384
        $this->assertSame($expectedQuerySql, $actualQuerySql);
385
        $this->assertSame($expectedQueryParams, $actualQueryParams);
386
    }
387
388
    public function testBuildWhereExistsWithArrayParameters(): void
389
    {
390
        $expectedQuerySql = DbHelper::replaceQuotes(
391
            <<<SQL
392
            SELECT [[id]] FROM [[TotalExample]] [[t]] WHERE (EXISTS (SELECT [[1]] FROM [[Website]] [[w]] WHERE (w.id = t.website_id) AND (([[w]].[[merchant_id]]=:qp0) AND ([[w]].[[user_id]]=:qp1)))) AND ([[t]].[[some_column]]=:qp2)
393
            SQL,
394
            $this->mock->getDriverName(),
395
        );
396
397
        $expectedQueryParams = [':qp0' => 6, ':qp1' => 210, ':qp2' => 'asd'];
398
399
        $subQuery = $this->mock
400
            ->query()
401
            ->select('1')
402
            ->from('Website w')
403
            ->where('w.id = t.website_id')
404
            ->andWhere(['w.merchant_id' => 6, 'w.user_id' => 210]);
405
406
        $query = $this->mock
407
            ->query()
408
            ->select('id')
409
            ->from('TotalExample t')
410
            ->where(['exists', $subQuery])
411
            ->andWhere(['t.some_column' => 'asd']);
412
413
        [$actualQuerySql, $queryParams] = $this->queryBuilder->build($query);
414
415
        $this->assertSame($expectedQuerySql, $actualQuerySql);
416
        $this->assertSame($expectedQueryParams, $queryParams);
417
    }
418
419
    public function testBuildWhereExistsWithParameters(): void
420
    {
421
        $expectedQuerySql = DbHelper::replaceQuotes(
422
            <<<SQL
423
            SELECT [[id]] FROM [[TotalExample]] [[t]] WHERE (EXISTS (SELECT [[1]] FROM [[Website]] [[w]] WHERE (w.id = t.website_id) AND (w.merchant_id = :merchant_id))) AND (t.some_column = :some_value)
424
            SQL,
425
            $this->mock->getDriverName(),
426
        );
427
428
        $expectedQueryParams = [':some_value' => 'asd', ':merchant_id' => 6];
429
430
        $subQuery = $this->mock
431
            ->query()
432
            ->select('1')
433
            ->from('Website w')
434
            ->where('w.id = t.website_id')
435
            ->andWhere('w.merchant_id = :merchant_id', [':merchant_id' => 6]);
436
437
        $query = $this->mock
438
            ->query()
439
            ->select('id')
440
            ->from('TotalExample t')
441
            ->where(['exists', $subQuery])
442
            ->andWhere('t.some_column = :some_value', [':some_value' => 'asd']);
443
444
        [$actualQuerySql, $queryParams] = $this->queryBuilder->build($query);
445
446
        $this->assertSame($expectedQuerySql, $actualQuerySql);
447
        $this->assertSame($expectedQueryParams, $queryParams);
448
    }
449
450
    /**
451
     * @dataProvider \Yiisoft\Db\Tests\Provider\QueryBuilderProvider::createDropIndex()
452
     */
453
    public function testCreateDropIndex(string $sql, Closure $builder): void
454
    {
455
        $this->assertSame($this->mock->quoter()->quoteSql($sql), $builder($this->queryBuilder));
456
    }
457
458
    public function testsCreateTable(): void
459
    {
460
        $expected = DbHelper::replaceQuotes(
461
            <<<SQL
462
            CREATE TABLE [[test_table]] (
463
            \t[[id]] pk,
464
            \t[[name]] string(255) NOT NULL,
465
            \t[[email]] string(255) NOT NULL,
466
            \t[[address]] string(255) NOT NULL,
467
            \t[[status]] integer NOT NULL,
468
            \t[[profile_id]] integer NOT NULL,
469
            \t[[created_at]] timestamp NOT NULL,
470
            \t[[updated_at]] timestamp NOT NULL
471
            ) CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB
472
            SQL,
473
            $this->mock->getDriverName(),
474
        );
475
476
        $columns = [
477
            'id' => $this->primaryKey(5),
478
            'name' => $this->string(255)->notNull(),
479
            'email' => $this->string(255)->notNull(),
480
            'address' => $this->string(255)->notNull(),
481
            'status' => $this->integer()->notNull(),
482
            'profile_id' => $this->integer()->notNull(),
483
            'created_at' => $this->timestamp()->notNull(),
484
            'updated_at' => $this->timestamp()->notNull(),
485
        ];
486
487
        $options = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB';
488
489
        $sql = $this->queryBuilder->createTable('test_table', $columns, $options);
490
491
        Assert::equalsWithoutLE($expected, $sql);
492
    }
493
494
    public function testComplexSelect(): void
495
    {
496
        $expressionString = DbHelper::replaceQuotes(
497
            <<<SQL
498
            case t.Status_Id when 1 then 'Acknowledge' when 2 then 'No Action' else 'Unknown Action' END as [[Next Action]]
499
            SQL,
500
            $this->mock->getDriverName(),
501
        );
502
503
        $expected = DbHelper::replaceQuotes(
504
            <<<SQL
505
            SELECT [[t]].[[id]] AS [[ID]], [[gsm]].[[username]] AS [[GSM]], [[part]].[[Part]], [[t]].[[Part_Cost]] AS [[Part Cost]], st_x(location::geometry) AS [[lon]], case t.Status_Id when 1 then 'Acknowledge' when 2 then 'No Action' else 'Unknown Action' END as [[Next Action]] FROM [[tablename]]
506
            SQL,
507
            $this->mock->getDriverName(),
508
        );
509
510
        $this->assertIsString($expressionString);
511
512
        $query = $this->mock
513
            ->query()
514
            ->select(
515
                [
516
                    'ID' => 't.id',
517
                    'gsm.username as GSM',
518
                    'part.Part',
519
                    'Part Cost' => 't.Part_Cost',
520
                    'st_x(location::geometry) as lon',
521
                    new Expression($expressionString),
522
                ]
523
            )
524
            ->from('tablename');
525
526
        [$sql, $params] = $this->queryBuilder->build($query);
527
528
        $this->assertSame($expected, $sql);
529
        $this->assertEmpty($params);
530
    }
531
532
    /**
533
     * @dataProvider \Yiisoft\Db\Tests\Provider\QueryBuilderProvider::delete()
534
     */
535
    public function testDelete(string $table, array|string $condition, string $expectedSQL, array $expectedParams): void
536
    {
537
        $actualParams = [];
538
        $actualSQL = $this->queryBuilder->delete($table, $condition, $actualParams);
539
540
        $this->assertSame($expectedSQL, $actualSQL);
541
        $this->assertSame($expectedParams, $actualParams);
542
    }
543
544
    /**
545
     * {@see https://github.com/yiisoft/yii2/issues/10869}
546
     */
547
    public function testFromIndexHint(): void
548
    {
549
        $query = $this->mock->query()->from([new Expression('{{%user}} USE INDEX (primary)')]);
550
551
        [$sql, $params] = $this->queryBuilder->build($query);
552
553
        $expected = DbHelper::replaceQuotes(
554
            <<<SQL
555
            SELECT * FROM {{%user}} USE INDEX (primary)
556
            SQL,
557
            $this->mock->getDriverName(),
558
        );
559
560
        $this->assertSame($expected, $sql);
561
        $this->assertEmpty($params);
562
563
        $query = $this->mock
564
            ->query()
565
            ->from([new Expression('{{user}} {{t}} FORCE INDEX (primary) IGNORE INDEX FOR ORDER BY (i1)')])
566
            ->leftJoin(['p' => 'profile'], 'user.id = profile.user_id USE INDEX (i2)');
567
568
        [$sql, $params] = $this->queryBuilder->build($query);
569
570
        $expected = DbHelper::replaceQuotes(
571
            <<<SQL
572
            SELECT * FROM {{user}} {{t}} FORCE INDEX (primary) IGNORE INDEX FOR ORDER BY (i1) LEFT JOIN [[profile]] [[p]] ON user.id = profile.user_id USE INDEX (i2)
573
            SQL,
574
            $this->mock->getDriverName(),
575
        );
576
577
        $this->assertSame($expected, $sql);
578
        $this->assertEmpty($params);
579
    }
580
581
    public function testFromSubquery(): void
582
    {
583
        /* subquery */
584
        $subquery = $this->mock->query()->from('user')->where('account_id = accounts.id');
585
        $query = $this->mock->query()->from(['activeusers' => $subquery]);
586
587
        /* SELECT * FROM (SELECT * FROM [[user]] WHERE [[active]] = 1) [[activeusers]]; */
588
        [$sql, $params] = $this->queryBuilder->build($query);
589
590
        $expected = DbHelper::replaceQuotes(
591
            <<<SQL
592
            SELECT * FROM (SELECT * FROM [[user]] WHERE account_id = accounts.id) [[activeusers]]
593
            SQL,
594
            $this->mock->getDriverName(),
595
        );
596
597
        $this->assertSame($expected, $sql);
598
        $this->assertEmpty($params);
599
600
        /* subquery with params */
601
        $subquery = $this->mock->query()->from('user')->where('account_id = :id', ['id' => 1]);
602
        $query = $this->mock->query()->from(['activeusers' => $subquery])->where('abc = :abc', ['abc' => 'abc']);
603
604
        /* SELECT * FROM (SELECT * FROM [[user]] WHERE [[active]] = 1) [[activeusers]]; */
605
        [$sql, $params] = $this->queryBuilder->build($query);
606
607
        $expected = DbHelper::replaceQuotes(
608
            <<<SQL
609
            SELECT * FROM (SELECT * FROM [[user]] WHERE account_id = :id) [[activeusers]] WHERE abc = :abc
610
            SQL,
611
            $this->mock->getDriverName(),
612
        );
613
614
        $this->assertSame($expected, $sql);
615
        $this->assertSame(['abc' => 'abc', 'id' => 1], $params);
616
617
        /* simple subquery */
618
        $subquery = '(SELECT * FROM user WHERE account_id = accounts.id)';
619
        $query = $this->mock->query()->from(['activeusers' => $subquery]);
620
621
        /* SELECT * FROM (SELECT * FROM [[user]] WHERE [[active]] = 1) [[activeusers]]; */
622
        [$sql, $params] = $this->queryBuilder->build($query);
623
624
        $expected = DbHelper::replaceQuotes(
625
            <<<SQL
626
            SELECT * FROM (SELECT * FROM user WHERE account_id = accounts.id) [[activeusers]]
627
            SQL,
628
            $this->mock->getDriverName(),
629
        );
630
631
        $this->assertSame($expected, $sql);
632
        $this->assertEmpty($params);
633
    }
634
635
    public function testGroupBy(): void
636
    {
637
        /* simple string */
638
        $query = $this->mock->query()->select('*')->from('operations')->groupBy('name, date');
639
640
        [$sql, $params] = $this->queryBuilder->build($query);
641
642
        $expected = DbHelper::replaceQuotes(
643
            <<<SQL
644
            SELECT * FROM [[operations]] GROUP BY [[name]], [[date]]
645
            SQL,
646
            $this->mock->getDriverName(),
647
        );
648
649
        $this->assertSame($expected, $sql);
650
        $this->assertEmpty($params);
651
652
        /* array syntax */
653
        $query = $this->mock->query()->select('*')->from('operations')->groupBy(['name', 'date']);
654
655
        [$sql, $params] = $this->queryBuilder->build($query);
656
657
        $expected = DbHelper::replaceQuotes(
658
            <<<SQL
659
            SELECT * FROM [[operations]] GROUP BY [[name]], [[date]]
660
            SQL,
661
            $this->mock->getDriverName(),
662
        );
663
664
        $this->assertSame($expected, $sql);
665
        $this->assertEmpty($params);
666
667
        /* expression */
668
        $query = $this->mock
669
            ->query()
670
            ->select('*')
671
            ->from('operations')
672
            ->where('account_id = accounts.id')
673
            ->groupBy(new Expression('SUBSTR(name, 0, 1), x'));
674
675
        [$sql, $params] = $this->queryBuilder->build($query);
676
677
        $expected = DbHelper::replaceQuotes(
678
            <<<SQL
679
            SELECT * FROM [[operations]] WHERE account_id = accounts.id GROUP BY SUBSTR(name, 0, 1), x
680
            SQL,
681
            $this->mock->getDriverName(),
682
        );
683
684
        $this->assertSame($expected, $sql);
685
        $this->assertEmpty($params);
686
687
        /* expression with params */
688
        $query = $this->mock
689
            ->query()
690
            ->select('*')
691
            ->from('operations')
692
            ->groupBy(new Expression('SUBSTR(name, 0, :to), x', [':to' => 4]));
693
694
        [$sql, $params] = $this->queryBuilder->build($query);
695
696
        $expected = DbHelper::replaceQuotes(
697
            <<<SQL
698
            SELECT * FROM [[operations]] GROUP BY SUBSTR(name, 0, :to), x
699
            SQL,
700
            $this->mock->getDriverName(),
701
        );
702
703
        $this->assertSame($expected, $sql);
704
        $this->assertSame([':to' => 4], $params);
705
    }
706
707
    /**
708
     * {@see https://github.com/yiisoft/yii2/issues/15653}
709
     */
710
    public function testIssue15653(): void
711
    {
712
        $query = $this->mock->query()->from('admin_user')->where(['is_deleted' => false]);
713
        $query->where([])->andWhere(['in', 'id', ['1', '0']]);
714
715
        [$sql, $params] = $this->queryBuilder->build($query);
716
717
        $this->assertSame(
718
            DbHelper::replaceQuotes(
719
                <<<SQL
720
                SELECT * FROM [[admin_user]] WHERE [[id]] IN (:qp0, :qp1)
721
                SQL,
722
                $this->mock->getDriverName(),
723
            ),
724
            $sql,
725
        );
726
        $this->assertSame([':qp0' => '1', ':qp1' => '0'], $params);
727
    }
728
729
    public function testOrderBy(): void
730
    {
731
        /* simple string */
732
        $query = $this->mock->query()->select('*')->from('operations')->orderBy('name ASC, date DESC');
733
734
        [$sql, $params] = $this->queryBuilder->build($query);
735
736
        $expected = DbHelper::replaceQuotes(
737
            <<<SQL
738
            SELECT * FROM [[operations]] ORDER BY [[name]], [[date]] DESC
739
            SQL,
740
            $this->mock->getDriverName(),
741
        );
742
743
        $this->assertSame($expected, $sql);
744
        $this->assertEmpty($params);
745
746
        /* array syntax */
747
        $query = $this->mock->query()->select('*')->from('operations')->orderBy(['name' => SORT_ASC, 'date' => SORT_DESC]);
748
749
        [$sql, $params] = $this->queryBuilder->build($query);
750
751
        $expected = DbHelper::replaceQuotes(
752
            <<<SQL
753
            SELECT * FROM [[operations]] ORDER BY [[name]], [[date]] DESC
754
            SQL,
755
            $this->mock->getDriverName(),
756
        );
757
758
        $this->assertSame($expected, $sql);
759
        $this->assertEmpty($params);
760
761
        /* expression */
762
        $query = $this->mock
763
            ->query()
764
            ->select('*')
765
            ->from('operations')
766
            ->where('account_id = accounts.id')
767
            ->orderBy(new Expression('SUBSTR(name, 3, 4) DESC, x ASC'));
768
769
        [$sql, $params] = $this->queryBuilder->build($query);
770
771
        $expected = DbHelper::replaceQuotes(
772
            <<<SQL
773
            SELECT * FROM [[operations]] WHERE account_id = accounts.id ORDER BY SUBSTR(name, 3, 4) DESC, x ASC
774
            SQL,
775
            $this->mock->getDriverName(),
776
        );
777
778
        $this->assertSame($expected, $sql);
779
        $this->assertEmpty($params);
780
781
        /* expression with params */
782
        $query = $this->mock
783
            ->query()
784
            ->select('*')
785
            ->from('operations')
786
            ->orderBy(new Expression('SUBSTR(name, 3, :to) DESC, x ASC', [':to' => 4]));
787
788
        [$sql, $params] = $this->queryBuilder->build($query);
789
790
        $expected = DbHelper::replaceQuotes(
791
            <<<SQL
792
            SELECT * FROM [[operations]] ORDER BY SUBSTR(name, 3, :to) DESC, x ASC
793
            SQL,
794
            $this->mock->getDriverName(),
795
        );
796
797
        $this->assertSame($expected, $sql);
798
        $this->assertSame([':to' => 4], $params);
799
    }
800
801
    public function testRenameColumn(): void
802
    {
803
        $sql = $this->queryBuilder->renameColumn('alpha', 'string_identifier', 'string_identifier_test');
804
        $this->assertSame(
805
            <<<SQL
806
            ALTER TABLE `alpha` RENAME COLUMN `string_identifier` TO `string_identifier_test`
807
            SQL,
808
            $sql,
809
        );
810
811
        $sql = $this->queryBuilder->renameColumn('alpha', 'string_identifier_test', 'string_identifier');
812
        $this->assertSame(
813
            <<<SQL
814
            ALTER TABLE `alpha` RENAME COLUMN `string_identifier_test` TO `string_identifier`
815
            SQL,
816
            $sql,
817
        );
818
    }
819
820
    public function testRenameTable(): void
821
    {
822
        $sql = $this->queryBuilder->renameTable('table_from', 'table_to');
823
824
        $this->assertSame(
825
            <<<SQL
826
            RENAME TABLE `table_from` TO `table_to`
827
            SQL,
828
            $sql,
829
        );
830
    }
831
832
    public function testSelectExpression(): void
833
    {
834
        $query = $this->mock->query()->select(new Expression('1 AS ab'))->from('tablename');
835
836
        [$sql, $params] = $this->queryBuilder->build($query);
837
838
        $expected = DbHelper::replaceQuotes(
839
            <<<SQL
840
            SELECT 1 AS ab FROM [[tablename]]
841
            SQL,
842
            $this->mock->getDriverName(),
843
        );
844
845
        $this->assertSame($expected, $sql);
846
        $this->assertEmpty($params);
847
848
        $query = $this->mock
849
            ->query()
850
            ->select(new Expression('1 AS ab'))
851
            ->addSelect(new Expression('2 AS cd'))
852
            ->addSelect(['ef' => new Expression('3')])
853
            ->from('tablename');
854
855
        [$sql, $params] = $this->queryBuilder->build($query);
856
857
        $expected = DbHelper::replaceQuotes(
858
            <<<SQL
859
            SELECT 1 AS ab, 2 AS cd, 3 AS [[ef]] FROM [[tablename]]
860
            SQL,
861
            $this->mock->getDriverName(),
862
        );
863
864
        $this->assertSame($expected, $sql);
865
        $this->assertEmpty($params);
866
867
        $query = $this->mock
868
            ->query()
869
            ->select(new Expression('SUBSTR(name, 0, :len)', [':len' => 4]))
870
            ->from('tablename');
871
872
        [$sql, $params] = $this->queryBuilder->build($query);
873
874
        $expected = DbHelper::replaceQuotes(
875
            <<<SQL
876
            SELECT SUBSTR(name, 0, :len) FROM [[tablename]]
877
            SQL,
878
            $this->mock->getDriverName(),
879
        );
880
881
        $this->assertSame($expected, $sql);
882
        $this->assertSame([':len' => 4], $params);
883
    }
884
885
    public function testSelectExists(): void
886
    {
887
        $sql = $this->queryBuilder->selectExists('SELECT 1 FROM `table` WHERE `id` = 1');
888
889
        $this->assertSame('SELECT EXISTS(SELECT 1 FROM `table` WHERE `id` = 1)', $sql);
890
    }
891
892
    public function testSelectSubquery(): void
893
    {
894
        $expected = DbHelper::replaceQuotes(
895
            <<<SQL
896
            SELECT *, (SELECT COUNT(*) FROM [[operations]] WHERE account_id = accounts.id) AS [[operations_count]] FROM [[accounts]]
897
            SQL,
898
            $this->mock->getDriverName(),
899
        );
900
901
        $subquery = $this->mock
902
            ->query()
903
            ->select('COUNT(*)')
904
            ->from('operations')
905
            ->where('account_id = accounts.id');
906
907
        $query = $this->mock
908
            ->query()
909
            ->select('*')
910
            ->from('accounts')
911
            ->addSelect(['operations_count' => $subquery]);
912
913
        [$sql, $params] = $this->queryBuilder->build($query);
914
915
        $this->assertSame($expected, $sql);
916
        $this->assertEmpty($params);
917
    }
918
919
    public function testSetConditionClasses(): void
920
    {
921
        $this->queryBuilder->setConditionClasses(['stdClass' => stdClass::class]);
922
        $dqlBuilder = Assert::getInaccessibleProperty($this->queryBuilder, 'dqlBuilder');
923
        $conditionClasses = Assert::getInaccessibleProperty($dqlBuilder, 'conditionClasses');
924
925
        $this->assertSame(stdClass::class, $conditionClasses['stdClass']);
926
    }
927
928
    public function testSelectExpressionBuilder(): void
929
    {
930
        $this->queryBuilder->setExpressionBuilders(['stdClass' => stdClass::class]);
931
        $dqlBuilder = Assert::getInaccessibleProperty($this->queryBuilder, 'dqlBuilder');
932
        $expressionBuilders = Assert::getInaccessibleProperty($dqlBuilder, 'expressionBuilders');
933
934
        $this->assertSame(stdClass::class, $expressionBuilders['stdClass']);
935
    }
936
937
    public function testSetSeparator(): void
938
    {
939
        $this->queryBuilder->setSeparator(' ');
940
        [$sql, $params] = $this->queryBuilder->build($this->mock->query()->select('*')->from('table'));
941
942
        $this->assertSame('SELECT * FROM `table`', $sql);
943
        $this->assertEmpty($params);
944
945
        $this->queryBuilder->setSeparator("\n");
946
        [$sql, $params] = $this->queryBuilder->build($this->mock->query()->select('*')->from('table'));
947
948
        $this->assertSame(
949
            <<<SQL
950
            SELECT *
951
            FROM `table`
952
            SQL,
953
            $sql,
954
        );
955
        $this->assertEmpty($params);
956
    }
957
}
958