Passed
Pull Request — master (#380)
by Wilmer
04:10 queued 01:20
created

QueryBuilderTest::testCreateViewWithParams()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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