Passed
Branch add-more-test-schema (1ebac6)
by Wilmer
02:54
created

CommonCommandTest   F

Complexity

Total Complexity 93

Size/Duplication

Total Lines 1890
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 93
eloc 897
c 1
b 0
f 0
dl 0
loc 1890
rs 1.703

How to fix   Complexity   

Complex Class

Complex classes like CommonCommandTest often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use CommonCommandTest, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Tests\Common;
6
7
use ReflectionException;
8
use Throwable;
9
use Yiisoft\Db\Driver\PDO\ConnectionPDOInterface;
10
use Yiisoft\Db\Exception\Exception;
11
use Yiisoft\Db\Exception\IntegrityException;
12
use Yiisoft\Db\Exception\InvalidArgumentException;
13
use Yiisoft\Db\Exception\InvalidCallException;
14
use Yiisoft\Db\Exception\InvalidConfigException;
15
use Yiisoft\Db\Exception\NotSupportedException;
16
use Yiisoft\Db\Expression\Expression;
17
use Yiisoft\Db\Expression\ExpressionInterface;
18
use Yiisoft\Db\Query\Data\DataReaderInterface;
19
use Yiisoft\Db\Query\Query;
20
use Yiisoft\Db\Schema\Schema;
21
use Yiisoft\Db\Tests\AbstractCommandTest;
22
use Yiisoft\Db\Tests\Support\Assert;
23
use Yiisoft\Db\Transaction\TransactionInterface;
24
25
use function call_user_func_array;
26
use function is_string;
27
use function setlocale;
28
29
abstract class CommonCommandTest extends AbstractCommandTest
30
{
31
    /**
32
     * @throws Exception
33
     * @throws InvalidConfigException
34
     * @throws Throwable
35
     */
36
    public function testAddCheck(): void
37
    {
38
        $db = $this->getConnection();
39
40
        $command = $db->createCommand();
41
        $schema = $db->getSchema();
42
43
        if ($schema->getTableSchema('{{test_ck}}') !== null) {
44
            $command->dropTable('{{test_ck}}')->execute();
45
        }
46
47
        $command->createTable('{{test_ck}}', ['int1' => 'integer'])->execute();
48
49
        $this->assertEmpty($schema->getTableChecks('{{test_ck}}', true));
50
51
        $command->addCheck('{{test_ck_constraint}}', '{{test_ck}}', '{{int1}} > 1')->execute();
52
53
        $this->assertMatchesRegularExpression(
54
            '/^.*int1.*>.*1.*$/',
55
            $schema->getTableChecks('{{test_ck}}', true)[0]->getExpression()
56
        );
57
    }
58
59
    /**
60
     * @throws Exception
61
     * @throws InvalidConfigException
62
     * @throws Throwable
63
     */
64
    public function testAddColumn(): void
65
    {
66
        $db = $this->getConnection(true);
67
68
        $command = $db->createCommand();
69
        $command->addColumn('{{customer}}', '{{city}}', Schema::TYPE_STRING)->execute();
70
71
        $this->assertTrue($db->getTableSchema('{{customer}}')->getColumn('city') !== null);
72
        $this->assertSame(Schema::TYPE_STRING, $db->getTableSchema('{{customer}}')->getColumn('city')->getType());
73
    }
74
75
    /**
76
     * @throws Exception
77
     * @throws InvalidConfigException
78
     * @throws Throwable
79
     */
80
    public function testAddCommentOnColumn(): void
81
    {
82
        $db = $this->getConnection(true);
83
84
        $tableName = '{{customer}}';
85
        $tableComment = 'Primary key.';
86
87
        $command = $db->createCommand();
88
        $schema = $db->getSchema();
89
        $command->addCommentOnColumn($tableName, 'id', $tableComment)->execute();
90
        $commentOnColumn = $schema->getTableSchema($tableName)->getColumn('id')->getComment();
91
92
        $this->assertSame($tableComment, $commentOnColumn);
93
    }
94
95
    /**
96
     * @throws Exception
97
     * @throws InvalidConfigException
98
     * @throws Throwable
99
     */
100
    public function testAddCommentOnTable(): void
101
    {
102
        $db = $this->getConnection(true);
103
104
        $tableName = '{{customer}}';
105
        $commentText = 'Customer table.';
106
107
        $command = $db->createCommand();
108
        $command->addCommentOnTable($tableName, $commentText)->execute();
109
        $commentOnTable = $db->getSchema()->getTableSchema($tableName, true)->getComment();
110
111
        $this->assertSame($commentText, $commentOnTable);
112
    }
113
114
    /**
115
     * @throws Exception
116
     * @throws InvalidConfigException
117
     * @throws Throwable
118
     */
119
    public function testAddDefaultValue(): void
120
    {
121
        $db = $this->getConnection();
122
123
        $command = $db->createCommand();
124
        $schema = $db->getSchema();
125
126
        if ($schema->getTableSchema('{{test_def}}') !== null) {
127
            $command->dropTable('{{test_def}}')->execute();
128
        }
129
130
        $command->createTable('{{test_def}}', ['int1' => Schema::TYPE_INTEGER])->execute();
131
132
        $this->assertEmpty($schema->getTableDefaultValues('{{test_def}}', true));
133
134
        $command->addDefaultValue('{{test_def_constraint}}', '{{test_def}}', 'int1', 41)->execute();
135
136
        $this->assertMatchesRegularExpression(
137
            '/^.*41.*$/',
138
            $schema->getTableDefaultValues('{{test_def}}', true)[0]->getValue(),
139
        );
140
    }
141
142
    /**
143
     * @dataProvider \Yiisoft\Db\Tests\Provider\CommandProvider::addForeignKey()
144
     *
145
     * @throws Exception
146
     * @throws InvalidConfigException
147
     * @throws Throwable
148
     */
149
    public function testAddForeignKey(
150
        string $name,
151
        string $tableName,
152
        array|string $column1,
153
        array|string $column2,
154
        string $expectedName,
155
    ): void {
156
        $db = $this->getConnection();
157
158
        $command = $db->createCommand();
159
        $schema = $db->getSchema();
160
161
        if ($schema->getTableSchema($tableName) !== null) {
162
            $command->dropTable($tableName)->execute();
163
        }
164
165
        $command->createTable(
166
            $tableName,
167
            [
168
                'int1' => 'integer not null unique',
169
                'int2' => 'integer not null unique',
170
                'int3' => 'integer not null unique',
171
                'int4' => 'integer not null unique',
172
                'unique ([[int1]], [[int2]])',
173
                'unique ([[int3]], [[int4]])',
174
            ],
175
        )->execute();
176
177
        $this->assertEmpty($schema->getTableForeignKeys($tableName, true));
178
179
        $command->addForeignKey($name, $tableName, $column1, $tableName, $column2)->execute();
180
181
        $this->assertSame($expectedName, $schema->getTableForeignKeys($tableName, true)[0]->getName());
182
183
        if (is_string($column1)) {
184
            $column1 = [$column1];
185
        }
186
187
        $this->assertSame($column1, $schema->getTableForeignKeys($tableName, true)[0]->getColumnNames());
188
189
        if (is_string($column2)) {
190
            $column2 = [$column2];
191
        }
192
193
        $this->assertSame($column2, $schema->getTableForeignKeys($tableName, true)[0]->getForeignColumnNames());
194
    }
195
196
    /**
197
     * @dataProvider \Yiisoft\Db\Tests\Provider\CommandProvider::addPrimaryKey()
198
     *
199
     * @throws Exception
200
     * @throws InvalidConfigException
201
     * @throws Throwable
202
     */
203
    public function testAddPrimaryKey(string $name, string $tableName, array|string $column): void
204
    {
205
        $db = $this->getConnection();
206
207
        $command = $db->createCommand();
208
        $schema = $db->getSchema();
209
210
        if ($schema->getTableSchema($tableName) !== null) {
211
            $command->dropTable($tableName)->execute();
212
        }
213
214
        $command->createTable($tableName, ['int1' => 'integer not null', 'int2' => 'integer not null'])->execute();
215
216
        $this->assertNull($schema->getTablePrimaryKey($tableName, true));
217
218
        $db->createCommand()->addPrimaryKey($name, $tableName, $column)->execute();
219
220
        if (is_string($column)) {
221
            $column = [$column];
222
        }
223
224
        $this->assertSame($column, $schema->getTablePrimaryKey($tableName, true)->getColumnNames());
225
    }
226
227
    /**
228
     * @dataProvider \Yiisoft\Db\Tests\Provider\CommandProvider::addUnique()
229
     *
230
     * @throws Exception
231
     * @throws InvalidConfigException
232
     * @throws Throwable
233
     */
234
    public function testAddUnique(string $name, string $tableName, array|string $column): void
235
    {
236
        $db = $this->getConnection();
237
238
        $command = $db->createCommand();
239
        $schema = $db->getSchema();
240
241
        if ($schema->getTableSchema($tableName) !== null) {
242
            $command->dropTable($tableName)->execute();
243
        }
244
245
        $command->createTable($tableName, ['int1' => 'integer not null', 'int2' => 'integer not null'])->execute();
246
247
        $this->assertEmpty($schema->getTableUniques($tableName, true));
248
249
        $command->addUnique($name, $tableName, $column)->execute();
250
251
        if (is_string($column)) {
252
            $column = [$column];
253
        }
254
255
        $this->assertSame($column, $schema->getTableUniques($tableName, true)[0]->getColumnNames());
256
    }
257
258
    /**
259
     * Make sure that `{{something}}` in values will not be encoded.
260
     *
261
     * @dataProvider \Yiisoft\Db\Tests\Provider\CommandProvider::batchInsert()
262
     *
263
     * {@see https://github.com/yiisoft/yii2/issues/11242}
264
     *
265
     * @throws Exception
266
     * @throws InvalidConfigException
267
     * @throws Throwable
268
     */
269
    public function testBatchInsert(
270
        string $table,
271
        array $columns,
272
        array $values,
273
        string $expected,
274
        array $expectedParams = [],
275
        int $insertedRow = 1
276
    ): void {
277
        $db = $this->getConnection(true);
278
279
        $command = $db->createCommand();
280
        $command->batchInsert($table, $columns, $values);
281
        $command->prepare(false);
282
283
        $this->assertSame($expected, $command->getSql());
284
        $this->assertSame($expectedParams, $command->getParams());
285
286
        $command->execute();
287
288
        $this->assertEquals($insertedRow, (new Query($db))->from($table)->count());
289
    }
290
291
    /**
292
     * Test batch insert with different data types.
293
     *
294
     * Ensure double is inserted with `.` decimal separator.
295
     *
296
     * @link https://github.com/yiisoft/yii2/issues/6526
297
     *
298
     * @throws Exception
299
     * @throws InvalidConfigException
300
     * @throws Throwable
301
     */
302
    public function testBatchInsertDataTypesLocale(): void
303
    {
304
        $locale = setlocale(LC_NUMERIC, 0);
305
306
        if ($locale === false) {
307
            $this->markTestSkipped('Your platform does not support locales.');
308
        }
309
310
        $db = $this->getConnection(true);
311
312
        $command = $db->createCommand();
313
314
        try {
315
            /* This one sets decimal mark to comma sign */
316
            setlocale(LC_NUMERIC, 'ru_RU.utf8');
317
318
            $cols = ['int_col', 'char_col', 'float_col', 'bool_col'];
319
            $data = [[1, 'A', 9.735, true], [2, 'B', -2.123, false], [3, 'C', 2.123, false]];
320
321
            /* clear data in "type" table */
322
            $command->delete('{{type}}')->execute();
323
324
            /* change, for point oracle. */
325
            if ($db->getName() === 'oci') {
326
                $command->setSql(
327
                    <<<SQL
328
                    ALTER SESSION SET NLS_NUMERIC_CHARACTERS='.,'
329
                    SQL
330
                )->execute();
331
            }
332
333
            /* batch insert on "type" table */
334
            $command->batchInsert('{{type}}', $cols, $data)->execute();
335
            $data = $command->setSql(
336
                <<<SQL
337
                SELECT [[int_col]], [[char_col]], [[float_col]], [[bool_col]] FROM {{type}} WHERE [[int_col]] IN (1,2,3) ORDER BY [[int_col]]
338
                SQL
339
            )->queryAll();
340
341
            $this->assertCount(3, $data);
342
            $this->assertEquals(1, $data[0]['int_col']);
343
            $this->assertEquals(2, $data[1]['int_col']);
344
            $this->assertEquals(3, $data[2]['int_col']);
345
346
            /* rtrim because Postgres padds the column with whitespace */
347
            $this->assertSame('A', rtrim($data[0]['char_col']));
348
            $this->assertSame('B', rtrim($data[1]['char_col']));
349
            $this->assertSame('C', rtrim($data[2]['char_col']));
350
            $this->assertEquals(9.735, $data[0]['float_col']);
351
            $this->assertEquals(-2.123, $data[1]['float_col']);
352
            $this->assertEquals(2.123, $data[2]['float_col']);
353
            $this->assertEquals(1, $data[0]['bool_col']);
354
            Assert::isOneOf($data[1]['bool_col'], ['0', false]);
355
            Assert::isOneOf($data[2]['bool_col'], ['0', false]);
356
        } catch (Exception | Throwable $e) {
357
            setlocale(LC_NUMERIC, $locale);
358
359
            throw $e;
360
        }
361
362
        setlocale(LC_NUMERIC, $locale);
363
    }
364
365
    /**
366
     * @throws Exception
367
     * @throws InvalidConfigException
368
     * @throws Throwable
369
     */
370
    public function testBatchInsertFailsOld(): void
371
    {
372
        $db = $this->getConnection(true);
373
374
        $command = $db->createCommand();
375
        $command->batchInsert(
376
            '{{customer}}',
377
            ['email', 'name', 'address'],
378
            [['[email protected]', 'test_name', 'test_address']],
379
        );
380
381
        $this->assertSame(1, $command->execute());
382
383
        $result = (new Query($db))
384
            ->select(['email', 'name', 'address'])
385
            ->from('{{customer}}')
386
            ->where(['=', '{{email}}', '[email protected]'])
387
            ->one();
388
389
        $this->assertCount(3, $result);
390
        $this->assertSame(['email' => '[email protected]', 'name' => 'test_name', 'address' => 'test_address'], $result);
391
    }
392
393
    /**
394
     * @throws Exception
395
     * @throws InvalidConfigException
396
     * @throws Throwable
397
     */
398
    public function testBatchInsertWithManyData(): void
399
    {
400
        $db = $this->getConnection(true);
401
402
        $values = [];
403
        $attemptsInsertRows = 200;
404
        $command = $db->createCommand();
405
406
        for ($i = 0; $i < $attemptsInsertRows; $i++) {
407
            $values[$i] = ['t' . $i . '@any.com', 't' . $i, 't' . $i . ' address'];
408
        }
409
410
        $command->batchInsert('{{customer}}', ['email', 'name', 'address'], $values);
411
412
        $this->assertSame($attemptsInsertRows, $command->execute());
413
414
        $insertedRowsCount = (new Query($db))->from('{{customer}}')->count();
415
416
        $this->assertGreaterThanOrEqual($attemptsInsertRows, $insertedRowsCount);
417
    }
418
419
    /**
420
     * @throws Exception
421
     * @throws InvalidConfigException
422
     * @throws Throwable
423
     */
424
    public function testBatchInsertWithYield(): void
425
    {
426
        $db = $this->getConnection(true);
427
428
        $rows = (
429
            static function () {
430
                yield ['[email protected]', 'test name', 'test address'];
431
            }
432
        )();
433
        $command = $db->createCommand();
434
        $command->batchInsert('{{customer}}', ['email', 'name', 'address'], $rows);
435
436
        $this->assertSame(1, $command->execute());
437
    }
438
439
    /**
440
     * @dataProvider \Yiisoft\Db\Tests\Provider\CommandProvider::createIndex()
441
     *
442
     * @throws Exception
443
     * @throws InvalidConfigException
444
     * @throws Throwable
445
     */
446
    public function testCreateIndex(
447
        string $name,
448
        string $tableName,
449
        array|string $column,
450
        string|null $indexType,
451
        string|null $indexMethod,
452
    ): void {
453
        $db = $this->getConnection();
454
455
        $command = $db->createCommand();
456
        $schema = $db->getSchema();
457
458
        if ($schema->getTableSchema($tableName) !== null) {
459
            $command->dropTable($tableName)->execute();
460
        }
461
462
        $command->createTable($tableName, ['int1' => 'integer not null', 'int2' => 'integer not null'])->execute();
463
464
        $this->assertEmpty($schema->getTableIndexes($tableName, true));
465
466
        $command->createIndex($name, $tableName, $column, $indexType, $indexMethod)->execute();
467
468
        if (is_string($column)) {
469
            $column = [$column];
470
        }
471
472
        $this->assertSame($column, $schema->getTableIndexes($tableName, true)[0]->getColumnNames());
473
474
        if ($indexType === 'UNIQUE') {
475
            $this->assertTrue($schema->getTableIndexes($tableName, true)[0]->isUnique());
476
        } else {
477
            $this->assertFalse($schema->getTableIndexes($tableName, true)[0]->isUnique());
478
        }
479
    }
480
481
    /**
482
     * @throws Exception
483
     * @throws InvalidConfigException
484
     * @throws Throwable
485
     */
486
    public function testCreateTable(): void
487
    {
488
        $db = $this->getConnection();
489
490
        $command = $db->createCommand();
491
        $schema = $db->getSchema();
492
493
        if ($schema->getTableSchema('{{testCreateTable}}', true) !== null) {
494
            $command->dropTable('{{testCreateTable}}')->execute();
495
        }
496
497
        $command->createTable(
498
            '{{testCreateTable}}',
499
            ['[[id]]' => Schema::TYPE_PK, '[[bar]]' => Schema::TYPE_INTEGER],
500
        )->execute();
501
        $command->insert('{{testCreateTable}}', ['[[bar]]' => 1])->execute();
502
        $records = $command->setSql(
503
            <<<SQL
504
            SELECT [[id]], [[bar]] FROM [[testCreateTable]];
505
            SQL
506
        )->queryAll();
507
508
        $this->assertEquals([['id' => 1, 'bar' => 1]], $records);
509
    }
510
511
    /**
512
     * @throws Exception
513
     * @throws InvalidConfigException
514
     * @throws Throwable
515
     */
516
    public function testCreateView(): void
517
    {
518
        $db = $this->getConnection();
519
520
        $command = $db->createCommand();
521
        $schema = $db->getSchema();
522
        $subQuery = (new Query($db))->select('{{bar}}')->from('{{testCreateViewTable}}')->where(['>', 'bar', '5']);
523
524
        if ($schema->getTableSchema('{{testCreateView}}') !== null) {
525
            $command->dropView('{{testCreateView}}')->execute();
526
        }
527
528
        if ($schema->getTableSchema('{{testCreateViewTable}}')) {
529
            $command->dropTable('{{testCreateViewTable}}')->execute();
530
        }
531
532
        $command->createTable(
533
            '{{testCreateViewTable}}',
534
            ['id' => Schema::TYPE_PK, 'bar' => Schema::TYPE_INTEGER],
535
        )->execute();
536
        $command->insert('{{testCreateViewTable}}', ['bar' => 1])->execute();
537
        $command->insert('{{testCreateViewTable}}', ['bar' => 6])->execute();
538
        $command->createView('{{testCreateView}}', $subQuery)->execute();
539
        $records = $command->setSql(
540
            <<<SQL
541
            SELECT [[bar]] FROM {{testCreateView}};
542
            SQL
543
        )->queryAll();
544
545
        $this->assertEquals([['bar' => 6]], $records);
546
547
        $command->dropView('{{testCreateView}}')->execute();
548
    }
549
550
    /**
551
     * @throws Exception
552
     * @throws InvalidConfigException
553
     * @throws Throwable
554
     */
555
    public function testDataReaderRewindException(): void
556
    {
557
        $db = $this->getConnection(true);
558
559
        $this->expectException(InvalidCallException::class);
560
        $this->expectExceptionMessage('DataReader cannot rewind. It is a forward-only reader.');
561
562
        $command = $db->createCommand();
563
        $reader = $command->setSql(
564
            <<<SQL
565
            SELECT * FROM {{customer}}
566
            SQL
567
        )->query();
568
        $reader->next();
569
        $reader->rewind();
570
    }
571
572
    /**
573
     * @throws Exception
574
     * @throws InvalidConfigException
575
     * @throws Throwable
576
     */
577
    public function testDelete(): void
578
    {
579
        $db = $this->getConnection(true);
580
581
        $command = $db->createCommand();
582
        $command->delete('{{customer}}', ['id' => 2])->execute();
583
        $chekSql = <<<SQL
584
        SELECT COUNT([[id]]) FROM [[customer]]
585
        SQL;
586
        $command->setSql($chekSql);
587
588
        $this->assertSame('2', $command->queryScalar());
589
590
        $command->delete('{{customer}}', ['id' => 3])->execute();
591
        $command->setSql($chekSql);
592
593
        $this->assertSame('1', $command->queryScalar());
594
    }
595
596
    /**
597
     * @throws Exception
598
     * @throws InvalidConfigException
599
     * @throws Throwable
600
     */
601
    public function testDropCheck(): void
602
    {
603
        $db = $this->getConnection();
604
605
        $command = $db->createCommand();
606
        $schema = $db->getSchema();
607
608
        if ($schema->getTableSchema('{{test_ck}}') !== null) {
609
            $command->dropTable('{{test_ck}}')->execute();
610
        }
611
612
        $command->createTable('{{test_ck}}', ['int1' => 'integer'])->execute();
613
614
        $this->assertEmpty($schema->getTableChecks('{{test_ck}}', true));
615
616
        $command->addCheck('{{test_ck_constraint}}', '{{test_ck}}', '[[int1]] > 1')->execute();
617
618
        $this->assertMatchesRegularExpression(
619
            '/^.*int1.*>.*1.*$/',
620
            $schema->getTableChecks('{{test_ck}}', true)[0]->getExpression(),
621
        );
622
623
        $command->dropCheck('{{test_ck_constraint}}', '{{test_ck}}')->execute();
624
625
        $this->assertEmpty($schema->getTableChecks('{{test_ck}}', true));
626
    }
627
628
    /**
629
     * @throws Exception
630
     * @throws InvalidConfigException
631
     * @throws Throwable
632
     */
633
    public function testDropColumn(): void
634
    {
635
        $db = $this->getConnection();
636
637
        $command = $db->createCommand();
638
        $schema = $db->getSchema();
639
640
        if ($schema->getTableSchema('{{testDropColumn}}', true) !== null) {
641
            $command->dropTable('{{testDropColumn}}')->execute();
642
        }
643
644
        $command->createTable(
645
            '{{testDropColumn}}',
646
            ['id' => Schema::TYPE_PK, 'bar' => Schema::TYPE_INTEGER, 'baz' => Schema::TYPE_INTEGER],
647
        )->execute();
648
        $command->dropColumn('{{testDropColumn}}', 'bar')->execute();
649
650
        $this->assertArrayNotHasKey('bar', $schema->getTableSchema('{{testDropColumn}}')->getColumns());
651
        $this->assertArrayHasKey('baz', $schema->getTableSchema('{{testDropColumn}}')->getColumns());
652
    }
653
654
    /**
655
     * @throws Exception
656
     * @throws InvalidConfigException
657
     * @throws Throwable
658
     */
659
    public function testDropCommentFromColumn(): void
660
    {
661
        $db = $this->getConnection(true);
662
663
        $tableName = '{{customer}}';
664
        $tableComment = 'Primary key.';
665
666
        $command = $db->createCommand();
667
        $schema = $db->getSchema();
668
        $command->addCommentOnColumn($tableName, 'id', $tableComment)->execute();
669
        $commentOnColumn = $schema->getTableSchema($tableName)->getColumn('id')->getComment();
670
671
        $this->assertSame($tableComment, $commentOnColumn);
672
673
        $command->dropCommentFromColumn($tableName, 'id')->execute();
674
        $commentOnColumn = $schema->getTableSchema($tableName)->getColumn('id')->getComment();
675
676
        $this->assertEmpty($commentOnColumn);
677
    }
678
679
    /**
680
     * @throws Exception
681
     * @throws InvalidConfigException
682
     * @throws Throwable
683
     */
684
    public function testDropCommentFromTable(): void
685
    {
686
        $db = $this->getConnection(true);
687
688
        $tableName = '{{customer}}';
689
        $commentText = 'Customer table.';
690
691
        $command = $db->createCommand();
692
        $command->addCommentOnTable($tableName, $commentText)->execute();
693
        $commentOnTable = $db->getSchema()->getTableSchema($tableName, true)->getComment();
694
695
        $this->assertSame($commentText, $commentOnTable);
696
697
        $command->dropCommentFromTable($tableName)->execute();
698
        $commentOnTable = $db->getSchema()->getTableSchema($tableName, true)->getComment();
699
700
        $this->assertEmpty($commentOnTable);
701
    }
702
703
    /**
704
     * @throws Exception
705
     * @throws InvalidConfigException
706
     * @throws Throwable
707
     */
708
    public function testDropDefaultValue(): void
709
    {
710
        $db = $this->getConnection();
711
712
        $command = $db->createCommand();
713
        $schema = $db->getSchema();
714
715
        if ($schema->getTableSchema('{{test_def}}') !== null) {
716
            $command->dropTable('{{test_def}}')->execute();
717
        }
718
719
        $command->createTable('{{test_def}}', ['int1' => 'integer'])->execute();
720
721
        $this->assertEmpty($schema->getTableDefaultValues('{{test_def}}', true));
722
723
        $command->addDefaultValue('{{test_def_constraint}}', '{{test_def}}', 'int1', 41)->execute();
724
725
        $this->assertMatchesRegularExpression(
726
            '/^.*41.*$/',
727
            $schema->getTableDefaultValues('{{test_def}}', true)[0]->getValue(),
728
        );
729
730
        $command->dropDefaultValue('{{test_def_constraint}}', '{{test_def}}')->execute();
731
732
        $this->assertEmpty($schema->getTableDefaultValues('{{test_def}}', true));
733
    }
734
735
    /**
736
     * @throws Exception
737
     * @throws InvalidConfigException
738
     * @throws Throwable
739
     */
740
    public function testDropForeignKey(): void
741
    {
742
        $db = $this->getConnection();
743
744
        $command = $db->createCommand();
745
        $schema = $db->getSchema();
746
747
        if ($schema->getTableSchema('{{test_fk}}') !== null) {
748
            $command->dropTable('{{test_fk}}')->execute();
749
        }
750
751
        $command->createTable('{{test_fk}}', ['id' => Schema::TYPE_PK, 'int1' => 'integer'])->execute();
752
753
        $this->assertEmpty($schema->getTableForeignKeys('{{test_fk}}', true));
754
755
        $command->addForeignKey('{{test_fk_constraint}}', '{{test_fk}}', 'int1', '{{test_fk}}', 'id')->execute();
756
757
        $this->assertNotEmpty($schema->getTableForeignKeys('{{test_fk}}', true));
758
759
        $command->dropForeignKey('{{test_fk_constraint}}', '{{test_fk}}')->execute();
760
761
        $this->assertEmpty($schema->getTableForeignKeys('{{test_fk}}', true));
762
    }
763
764
    /**
765
     * @throws Exception
766
     * @throws InvalidConfigException
767
     * @throws Throwable
768
     */
769
    public function testDropIndex(): void
770
    {
771
        $db = $this->getConnection();
772
773
        $command = $db->createCommand();
774
        $schema = $db->getSchema();
775
776
        if ($schema->getTableSchema('{{test_idx}}') !== null) {
777
            $command->dropTable('{{test_idx}}')->execute();
778
        }
779
780
        $command->createTable('{{test_idx}}', ['int1' => 'integer not null', 'int2' => 'integer not null'])->execute();
781
782
        $this->assertEmpty($schema->getTableIndexes('{[test_idx}}', true));
783
784
        $command->createIndex('{{test_idx_constraint}}', '{{test_idx}}', ['int1', 'int2'], 'UNIQUE')->execute();
785
786
        $this->assertSame(['int1', 'int2'], $schema->getTableIndexes('{{test_idx}}', true)[0]->getColumnNames());
787
        $this->assertTrue($schema->getTableIndexes('{{test_idx}}', true)[0]->isUnique());
788
789
        $command->dropIndex('{{test_idx_constraint}}', '{{test_idx}}')->execute();
790
791
        $this->assertEmpty($schema->getTableIndexes('{{test_idx}}', true));
792
    }
793
794
    /**
795
     * @throws Exception
796
     * @throws InvalidConfigException
797
     * @throws Throwable
798
     */
799
    public function testDropPrimaryKey(): void
800
    {
801
        $db = $this->getConnection();
802
803
        $command = $db->createCommand();
804
        $schema = $db->getSchema();
805
806
        if ($schema->getTableSchema('{{test_pk}}') !== null) {
807
            $command->dropTable('{{test_pk}}')->execute();
808
        }
809
810
        $command->createTable('{{test_pk}}', ['int1' => 'integer not null', 'int2' => 'integer not null'])->execute();
811
812
        $this->assertEmpty($schema->getTableSchema('{{test_pk}}', true)->getPrimaryKey());
813
814
        $command->addPrimaryKey('{{test_pk_constraint}}', '{{test_pk}}', ['int1', 'int2'])->execute();
815
816
        $this->assertSame(['int1', 'int2'], $schema->getTableSchema('{{test_pk}}', true)->getColumnNames());
817
818
        $command->dropPrimaryKey('{{test_pk_constraint}}', '{{test_pk}}')->execute();
819
820
        $this->assertEmpty($schema->getTableSchema('{{test_pk}}', true)->getPrimaryKey());
821
    }
822
823
    /**
824
     * @throws Exception
825
     * @throws InvalidConfigException
826
     * @throws Throwable
827
     */
828
    public function testDropTable(): void
829
    {
830
        $db = $this->getConnection();
831
832
        $command = $db->createCommand();
833
        $schema = $db->getSchema();
834
835
        if ($schema->getTableSchema('{{testDropTable}}') !== null) {
836
            $command->dropTable('{{testDropTable}}')->execute();
837
        }
838
839
        $command->createTable('{{testDropTable}}', ['id' => Schema::TYPE_PK, 'foo' => 'integer'])->execute();
840
841
        $this->assertNotNull($schema->getTableSchema('{{testDropTable}}', true));
842
843
        $command->dropTable('{{testDropTable}}')->execute();
844
845
        $this->assertNull($schema->getTableSchema('{{testDropTable}}', true));
846
    }
847
848
    /**
849
     * @throws Exception
850
     * @throws InvalidConfigException
851
     * @throws Throwable
852
     */
853
    public function testDropUnique(): void
854
    {
855
        $db = $this->getConnection();
856
857
        $command = $db->createCommand();
858
        $schema = $db->getSchema();
859
860
        if ($schema->getTableSchema('{{test_uq}}') !== null) {
861
            $command->dropTable('{{test_uq}}')->execute();
862
        }
863
864
        $command->createTable('{{test_uq}}', ['int1' => 'integer not null', 'int2' => 'integer not null'])->execute();
865
866
        $this->assertEmpty($schema->getTableUniques('{{test_uq}}', true));
867
868
        $command->addUnique('{{test_uq_constraint}}', '{{test_uq}}', ['int1'])->execute();
869
870
        $this->assertSame(['int1'], $schema->getTableUniques('{{test_uq}}', true)[0]->getColumnNames());
871
872
        $command->dropUnique('{{test_uq_constraint}}', '{{test_uq}}')->execute();
873
874
        $this->assertEmpty($schema->getTableUniques('{{test_uq}}', true));
875
    }
876
877
    /**
878
     * @throws Exception
879
     * @throws InvalidConfigException
880
     * @throws Throwable
881
     */
882
    public function testDropView(): void
883
    {
884
        $db = $this->getConnection(true);
885
886
        /* since it already exists in the fixtures */
887
        $viewName = '{{animal_view}}';
888
889
        $schema = $db->getSchema();
890
891
        $this->assertNotNull($schema->getTableSchema($viewName));
892
893
        $db->createCommand()->dropView($viewName)->execute();
894
895
        $this->assertNull($schema->getTableSchema($viewName));
896
    }
897
898
    /**
899
     * @throws Exception
900
     * @throws InvalidConfigException
901
     * @throws Throwable
902
     */
903
    public function testExecute(): void
904
    {
905
        $db = $this->getConnection(true);
906
907
        $command = $db->createCommand();
908
        $command->setSql(
909
            <<<SQL
910
            INSERT INTO [[customer]] ([[email]], [[name]], [[address]]) VALUES ('[email protected]', 'user4', 'address4')
911
            SQL
912
        );
913
914
        $this->assertSame(1, $command->execute());
915
916
        $command = $command->setSql(
917
            <<<SQL
918
            SELECT COUNT(*) FROM [[customer]] WHERE [[name]] = 'user4'
919
            SQL
920
        );
921
922
        $this->assertEquals(1, $command->queryScalar());
923
924
        $command->setSql('bad SQL');
925
        $message = match ($db->getName()) {
926
            'pgsql' => 'SQLSTATE[42601]',
927
            'sqlite', 'oci' => 'SQLSTATE[HY000]',
928
            default => 'SQLSTATE[42000]',
929
        };
930
931
        $this->expectException(Exception::class);
932
        $this->expectExceptionMessage($message);
933
934
        $command->execute();
935
    }
936
937
    /**
938
     * @throws Exception
939
     * @throws InvalidConfigException
940
     * @throws Throwable
941
     */
942
    public function testExecuteWithoutSql(): void
943
    {
944
        $db = $this->getConnection();
945
946
        $command = $db->createCommand();
947
        $result = $command->setSql('')->execute();
948
949
        $this->assertSame(0, $result);
950
    }
951
952
    /**
953
     * @throws Exception
954
     * @throws InvalidConfigException
955
     * @throws ReflectionException
956
     * @throws Throwable
957
     */
958
    public function testExecuteWithTransaction(): void
959
    {
960
        $db = $this->getConnection(true);
961
962
        $this->assertNull($db->getTransaction());
963
964
        $command = $db->createCommand(
965
            <<<SQL
966
            INSERT INTO {{profile}} ([[description]]) VALUES('command transaction 1')
967
            SQL,
968
        );
969
970
        Assert::invokeMethod($command, 'requireTransaction');
971
972
        $command->execute();
973
974
        $this->assertNull($db->getTransaction());
975
976
        $this->assertEquals(
977
            1,
978
            $db->createCommand(
979
                <<<SQL
980
                SELECT COUNT(*) FROM {{profile}} WHERE [[description]] = 'command transaction 1'
981
                SQL,
982
            )->queryScalar(),
983
        );
984
985
        $command = $db->createCommand(
986
            <<<SQL
987
            INSERT INTO {{profile}} ([[description]]) VALUES('command transaction 2')
988
            SQL,
989
        );
990
991
        Assert::invokeMethod($command, 'requireTransaction', [TransactionInterface::READ_UNCOMMITTED]);
992
993
        $command->execute();
994
995
        $this->assertNull($db->getTransaction());
996
997
        $this->assertEquals(
998
            1,
999
            $db->createCommand(
1000
                <<<SQL
1001
                SELECT COUNT(*) FROM {{profile}} WHERE [[description]] = 'command transaction 2'
1002
                SQL,
1003
            )->queryScalar(),
1004
        );
1005
    }
1006
1007
    /**
1008
     * @throws Exception
1009
     * @throws InvalidConfigException
1010
     * @throws Throwable
1011
     */
1012
    public function testInsert(): void
1013
    {
1014
        $db = $this->getConnection(true);
1015
1016
        $command = $db->createCommand();
1017
        $command->delete('{{customer}}')->execute();
1018
        $command->insert(
1019
            '{{customer}}',
1020
            ['[[email]]' => '[email protected]', '[[name]]' => 'test', '[[address]]' => 'test address']
1021
        )->execute();
1022
1023
        $this->assertEquals(
1024
            1,
1025
            $command->setSql(
1026
                <<<SQL
1027
                SELECT COUNT(*) FROM {{customer}}
1028
                SQL
1029
            )->queryScalar(),
1030
        );
1031
1032
        $record = $command->setSql(
1033
            <<<SQL
1034
            SELECT [[email]], [[name]], [[address]] FROM {{customer}}
1035
            SQL
1036
        )->queryOne();
1037
1038
        $this->assertSame(['email' => '[email protected]', 'name' => 'test', 'address' => 'test address'], $record);
1039
    }
1040
1041
    /**
1042
     * @throws Exception
1043
     * @throws InvalidCallException
1044
     * @throws InvalidConfigException
1045
     * @throws Throwable
1046
     */
1047
    public function testInsertEx(): void
1048
    {
1049
        $db = $this->getConnection(true);
1050
1051
        $command = $db->createCommand();
1052
1053
        $expected = match ($db->getName()) {
1054
            'pgsql' => ['id' => 4],
1055
            default => ['id' => '4'],
1056
        };
1057
1058
        $this->assertSame(
1059
            $expected,
1060
            $command->insertEx('{{customer}}', ['name' => 'test_1', 'email' => '[email protected]']),
1061
        );
1062
    }
1063
1064
    /**
1065
     * @throws Exception
1066
     * @throws InvalidConfigException
1067
     * @throws Throwable
1068
     */
1069
    public function testInsertExpression(): void
1070
    {
1071
        $db = $this->getConnection(true);
1072
1073
        $command = $db->createCommand();
1074
        $command->delete('{{order_with_null_fk}}')->execute();
1075
        $expression = match ($db->getName()) {
1076
            'mysql' => 'YEAR(NOW())',
1077
            'oci' => "TO_CHAR(SYSDATE, 'YYYY')",
1078
            'pgsql' => "EXTRACT(YEAR FROM TIMESTAMP 'now')",
1079
            'sqlite' => "strftime('%Y')",
1080
            'sqlsrv' => 'YEAR(GETDATE())',
1081
        };
1082
        $command->insert(
1083
            '{{order_with_null_fk}}',
1084
            ['created_at' => new Expression($expression), 'total' => 1],
1085
        )->execute();
1086
1087
        $this->assertEquals(
1088
            1,
1089
            $command->setSql(
1090
                <<<SQL
1091
                SELECT COUNT(*) FROM {{order_with_null_fk}}
1092
                SQL
1093
            )->queryScalar(),
1094
        );
1095
1096
        $record = $command->setSql(
1097
            <<<SQL
1098
            SELECT [[created_at]] FROM {{order_with_null_fk}}
1099
            SQL
1100
        )->queryOne();
1101
1102
        $this->assertEquals(['created_at' => date('Y')], $record);
1103
    }
1104
1105
    /**
1106
     * @throws Exception
1107
     * @throws InvalidConfigException
1108
     * @throws InvalidCallException
1109
     * @throws Throwable
1110
     */
1111
    public function testsInsertQueryAsColumnValue(): void
1112
    {
1113
        $db = $this->getConnection(true);
1114
1115
        $command = $db->createCommand();
1116
        $time = time();
1117
        $command->setSql(
1118
            <<<SQL
1119
            DELETE FROM [[order_with_null_fk]]
1120
            SQL
1121
        )->execute();
1122
        $command->insert('{{order}}', ['customer_id' => 1, 'created_at' => $time, 'total' => 42])->execute();
1123
1124
        if ($db->getName() === 'pgsql') {
1125
            $orderId = $db->getLastInsertID('public.order_id_seq');
1126
        } else {
1127
            $orderId = $db->getLastInsertID();
1128
        }
1129
1130
        $columnValueQuery = (new Query($db))->select('{{created_at}}')->from('{{order}}')->where(['id' => $orderId]);
1131
        $command->insert(
1132
            '{{order_with_null_fk}}',
1133
            ['customer_id' => $orderId, 'created_at' => $columnValueQuery, 'total' => 42],
1134
        )->execute();
1135
1136
        $this->assertEquals(
1137
            $time,
1138
            $command->setSql(
1139
                <<<SQL
1140
                SELECT [[created_at]] FROM [[order_with_null_fk]] WHERE [[customer_id]] = :id
1141
                SQL
1142
            )->bindValues([':id' => $orderId])->queryScalar(),
1143
        );
1144
1145
        $command->setSql(
1146
            <<<SQL
1147
            DELETE FROM [[order_with_null_fk]]
1148
            SQL
1149
        )->execute();
1150
        $command->setSql(
1151
            <<<SQL
1152
            DELETE FROM [[order]]
1153
            SQL
1154
        )->execute();
1155
    }
1156
1157
    /**
1158
     * @throws Exception
1159
     * @throws InvalidConfigException
1160
     * @throws Throwable
1161
     */
1162
    public function testInsertSelect(): void
1163
    {
1164
        $db = $this->getConnection(true);
1165
1166
        $command = $db->createCommand();
1167
        $command->setSql(
1168
            <<<SQL
1169
            DELETE FROM {{customer}}
1170
            SQL
1171
        )->execute();
1172
        $command->insert(
1173
            '{{customer}}',
1174
            ['email' => '[email protected]', 'name' => 'test', 'address' => 'test address']
1175
        )->execute();
1176
        $query = (new Query($db))
1177
            ->select(['{{customer}}.{{email}} as name', '{{name}} as email', '{{address}}'])
1178
            ->from('{{customer}}')
1179
            ->where(['and', ['<>', 'name', 'foo'], ['status' => [0, 1, 2, 3]]]);
1180
        $command->insert('{{customer}}', $query)->execute();
1181
1182
        $this->assertEquals(
1183
            2,
1184
            $command->setSql(
1185
                <<<SQL
1186
                SELECT COUNT(*) FROM {{customer}}
1187
                SQL
1188
            )->queryScalar(),
1189
        );
1190
1191
        $record = $command->setSql(
1192
            <<<SQL
1193
            SELECT [[email]], [[name]], [[address]] FROM {{customer}}
1194
            SQL
1195
        )->queryAll();
1196
1197
        $this->assertSame(
1198
            [
1199
                ['email' => '[email protected]', 'name' => 'test', 'address' => 'test address'],
1200
                ['email' => 'test', 'name' => '[email protected]', 'address' => 'test address'],
1201
            ],
1202
            $record,
1203
        );
1204
    }
1205
1206
    /**
1207
     * @throws Exception
1208
     * @throws InvalidConfigException
1209
     * @throws Throwable
1210
     */
1211
    public function testInsertSelectAlias(): void
1212
    {
1213
        $db = $this->getConnection(true);
1214
1215
        $command = $db->createCommand();
1216
        $command->delete('{{customer}}')->execute();
1217
        $command->insert(
1218
            '{{customer}}',
1219
            [
1220
                'email' => '[email protected]',
1221
                'name' => 'test',
1222
                'address' => 'test address',
1223
            ]
1224
        )->execute();
1225
        $query = (new Query($db))
1226
            ->select(['email' => '{{customer}}.{{email}}', 'address' => 'name', 'name' => 'address'])
1227
            ->from('{{customer}}')
1228
            ->where(['and', ['<>', 'name', 'foo'], ['status' => [0, 1, 2, 3]]]);
1229
        $command->insert('{{customer}}', $query)->execute();
1230
1231
        $this->assertEquals(
1232
            2,
1233
            $command->setSql(
1234
                <<<SQL
1235
                SELECT COUNT(*) FROM [[customer]]
1236
                SQL
1237
            )->queryScalar(),
1238
        );
1239
1240
        $record = $command->setSql(
1241
            <<<SQL
1242
            SELECT [[email]], [[name]], [[address]] FROM [[customer]]
1243
            SQL
1244
        )->queryAll();
1245
1246
        $this->assertSame(
1247
            [
1248
                ['email' => '[email protected]', 'name' => 'test', 'address' => 'test address'],
1249
                ['email' => '[email protected]', 'name' => 'test address', 'address' => 'test'],
1250
            ],
1251
            $record,
1252
        );
1253
    }
1254
1255
    /**
1256
     * Test INSERT INTO ... SELECT SQL statement with wrong query object.
1257
     *
1258
     * @dataProvider \Yiisoft\Db\Tests\Provider\CommandProvider::invalidSelectColumns()
1259
     *
1260
     * @throws Exception
1261
     * @throws Throwable
1262
     */
1263
    public function testInsertSelectFailed(array|ExpressionInterface|string $invalidSelectColumns): void
1264
    {
1265
        $db = $this->getConnection();
1266
1267
        $query = new Query($db);
1268
        $query->select($invalidSelectColumns)->from('{{customer}}');
1269
        $command = $db->createCommand();
1270
1271
        $this->expectException(InvalidArgumentException::class);
1272
        $this->expectExceptionMessage('Expected select query object with enumerated (named) parameters');
1273
1274
        $command->insert('{{customer}}', $query)->execute();
1275
    }
1276
1277
    /**
1278
     * @throws Exception
1279
     * @throws InvalidConfigException
1280
     * @throws Throwable
1281
     */
1282
    public function testInsertToBlob(): void
1283
    {
1284
        $db = $this->getConnection(true);
1285
1286
        $command = $db->createCommand();
1287
        $command->delete('{{type}}')->execute();
1288
        $columns = [
1289
            'int_col' => 1,
1290
            'char_col' => 'test',
1291
            'float_col' => 3.14,
1292
            'bool_col' => true,
1293
            'blob_col' => serialize(['test' => 'data', 'num' => 222]),
1294
        ];
1295
        $command->insert('{{type}}', $columns)->execute();
1296
        $result = $command->setSql(
1297
            <<<SQL
1298
            SELECT [[blob_col]] FROM {{type}}
1299
            SQL
1300
        )->queryOne();
1301
1302
        $this->assertIsArray($result);
1303
1304
        $resultBlob = is_resource($result['blob_col']) ? stream_get_contents($result['blob_col']) : $result['blob_col'];
1305
1306
        $this->assertSame($columns['blob_col'], $resultBlob);
1307
    }
1308
1309
    /**
1310
     * @throws Exception
1311
     * @throws InvalidConfigException
1312
     * @throws Throwable
1313
     */
1314
    public function testIntegrityViolation(): void
1315
    {
1316
        $db = $this->getConnection(true);
1317
1318
        $this->expectException(IntegrityException::class);
1319
1320
        $command = $db->createCommand(
1321
            <<<SQL
1322
            INSERT INTO [[profile]] ([[id]], [[description]]) VALUES (123, 'duplicate')
1323
            SQL
1324
        );
1325
        $command->execute();
1326
        $command->execute();
1327
    }
1328
1329
    /**
1330
     * @throws Exception
1331
     * @throws InvalidCallException
1332
     * @throws InvalidConfigException
1333
     * @throws Throwable
1334
     */
1335
    public function testNoTablenameReplacement(): void
1336
    {
1337
        $db = $this->getConnection(true);
1338
1339
        $command = $db->createCommand();
1340
        $command->insert(
1341
            '{{customer}}',
1342
            ['name' => 'Some {{weird}} name', 'email' => '[email protected]', 'address' => 'Some {{%weird}} address']
1343
        )->execute();
1344
1345
        if ($db->getName() === 'pgsql') {
1346
            $customerId = $db->getLastInsertID('public.customer_id_seq');
1347
        } else {
1348
            $customerId = $db->getLastInsertID();
1349
        }
1350
1351
        $customer = $command->setSql(
1352
            <<<SQL
1353
            SELECT [[name]], [[email]], [[address]] FROM {{customer}} WHERE [[id]]=:id
1354
            SQL,
1355
        )->bindValues([':id' => $customerId])->queryOne();
1356
1357
        $this->assertIsArray($customer);
1358
        $this->assertSame('Some {{weird}} name', $customer['name']);
1359
        $this->assertSame('Some {{%weird}} address', $customer['address']);
1360
1361
        $command->update(
1362
            '{{customer}}',
1363
            ['name' => 'Some {{updated}} name', 'address' => 'Some {{%updated}} address'],
1364
            ['id' => $customerId]
1365
        )->execute();
1366
        $customer = $command->setSql(
1367
            <<<SQL
1368
            SELECT [[name]], [[email]], [[address]] FROM {{customer}} WHERE [[id]] = :id
1369
            SQL
1370
        )->bindValues([':id' => $customerId])->queryOne();
1371
1372
        $this->assertIsArray($customer);
1373
        $this->assertSame('Some {{updated}} name', $customer['name']);
1374
        $this->assertSame('Some {{%updated}} address', $customer['address']);
1375
    }
1376
1377
    /**
1378
     * @throws Exception
1379
     * @throws InvalidConfigException
1380
     * @throws Throwable
1381
     */
1382
    public function testQuery(): void
1383
    {
1384
        $db = $this->getConnection(true);
1385
1386
        $command = $db->createCommand();
1387
        $command->setSql(
1388
            <<<SQL
1389
            SELECT * FROM [[customer]]
1390
            SQL
1391
        );
1392
1393
        $this->assertNull($command->getPdoStatement());
1394
1395
        $reader = $command->query();
1396
1397
        $this->assertNotNull($command->getPdoStatement());
1398
        $this->assertInstanceOf(DataReaderInterface::class, $reader);
1399
        $this->assertIsInt($reader->count());
1400
1401
        $expectedRow = 6;
1402
1403
        if ($db->getName() === 'oci' || $db->getName() === 'pgsql') {
1404
            $expectedRow = 7;
1405
        }
1406
1407
        foreach ($reader as $row) {
1408
            $this->assertIsArray($row);
1409
            $this->assertCount($expectedRow, $row);
1410
        }
1411
1412
        $command = $db->createCommand('bad SQL');
1413
1414
        $this->expectException(Exception::class);
1415
1416
        $command->query();
1417
    }
1418
1419
    /**
1420
     * @throws Exception
1421
     * @throws InvalidConfigException
1422
     * @throws Throwable
1423
     */
1424
    public function testQueryAll(): void
1425
    {
1426
        $db = $this->getConnection(true);
1427
1428
        $command = $db->createCommand();
1429
        $command->setSql(
1430
            <<<SQL
1431
            SELECT * FROM {{customer}}
1432
            SQL
1433
        );
1434
        $rows = $command->queryAll();
1435
        $expectedRow = 6;
1436
1437
        if ($db->getName() === 'oci' || $db->getName() === 'pgsql') {
1438
            $expectedRow = 7;
1439
        }
1440
1441
        $this->assertIsArray($rows);
1442
        $this->assertCount(3, $rows);
1443
        $this->assertIsArray($rows[0]);
1444
        $this->assertCount($expectedRow, $rows[0]);
1445
1446
        $command->setSql('bad SQL');
1447
1448
        $this->expectException(Exception::class);
1449
1450
        $command->queryAll();
1451
        $command->setSql(
1452
            <<<SQL
1453
            SELECT * FROM {{customer}} where id = 100
1454
            SQL
1455
        );
1456
        $rows = $command->queryAll();
1457
1458
        $this->assertIsArray($rows);
1459
        $this->assertCount(0, $rows);
1460
        $this->assertSame([], $rows);
1461
    }
1462
1463
    /**
1464
     * @throws Exception
1465
     * @throws InvalidConfigException
1466
     * @throws Throwable
1467
     */
1468
    public function testQueryCache(): void
1469
    {
1470
        $db = $this->getConnection(true);
1471
1472
        $query = (new Query($db))->select(['{{name}}'])->from('{{customer}}');
1473
        $command = $db->createCommand();
1474
        $update = $command->setSql(
1475
            <<<SQL
1476
            UPDATE [[customer]] SET [[name]] = :name WHERE [[id]] = :id
1477
            SQL
1478
        );
1479
1480
        $this->assertSame('user1', $query->where(['id' => 1])->scalar(), 'Asserting initial value');
1481
1482
        /* No cache */
1483
        $update->bindValues([':id' => 1, ':name' => 'user11'])->execute();
1484
1485
        $this->assertSame(
1486
            'user11',
1487
            $query->where(['id' => 1])->scalar(),
1488
            'Query reflects DB changes when caching is disabled',
1489
        );
1490
1491
        /* Connection cache */
1492
        $db->cache(
1493
            static function (ConnectionPDOInterface $db) use ($query, $update) {
1494
                self::assertSame('user2', $query->where(['id' => 2])->scalar(), 'Asserting initial value for user #2');
1495
1496
                $update->bindValues([':id' => 2, ':name' => 'user22'])->execute();
1497
1498
                self::assertSame(
1499
                    'user2',
1500
                    $query->where(['id' => 2])->scalar(),
1501
                    'Query does NOT reflect DB changes when wrapped in connection caching',
1502
                );
1503
1504
                $db->noCache(
1505
                    static function () use ($query) {
1506
                        self::assertSame(
1507
                            'user22',
1508
                            $query->where(['id' => 2])->scalar(),
1509
                            'Query reflects DB changes when wrapped in connection caching and noCache simultaneously',
1510
                        );
1511
                    }
1512
                );
1513
1514
                self::assertSame(
1515
                    'user2',
1516
                    $query->where(['id' => 2])->scalar(),
1517
                    'Cache does not get changes after getting newer data from DB in noCache block.',
1518
                );
1519
            },
1520
            10,
1521
        );
1522
1523
        $db->queryCacheEnable(false);
1524
1525
        $db->cache(
1526
            static function () use ($query, $update) {
1527
                self::assertSame(
1528
                    'user22',
1529
                    $query->where(['id' => 2])->scalar(),
1530
                    'When cache is disabled for the whole connection, Query inside cache block does not get cached',
1531
                );
1532
1533
                $update->bindValues([':id' => 2, ':name' => 'user2'])->execute();
1534
1535
                self::assertSame('user2', $query->where(['id' => 2])->scalar());
1536
            },
1537
            10,
1538
        );
1539
1540
        $db->queryCacheEnable(true);
1541
        $query->cache();
1542
1543
        $this->assertSame('user11', $query->where(['id' => 1])->scalar());
1544
1545
        $update->bindValues([':id' => 1, ':name' => 'user1'])->execute();
1546
1547
        $this->assertSame(
1548
            'user11',
1549
            $query->where(['id' => 1])->scalar(),
1550
            'When both Connection and Query have cache enabled, we get cached value',
1551
        );
1552
        $this->assertSame(
1553
            'user1',
1554
            $query->noCache()->where(['id' => 1])->scalar(),
1555
            'When Query has disabled cache, we get actual data',
1556
        );
1557
1558
        $db->cache(
1559
            static function () use ($query) {
1560
                self::assertSame('user1', $query->noCache()->where(['id' => 1])->scalar());
1561
                self::assertSame('user11', $query->cache()->where(['id' => 1])->scalar());
1562
            },
1563
            10,
1564
        );
1565
    }
1566
1567
    /**
1568
     * @throws Exception
1569
     * @throws InvalidConfigException
1570
     * @throws Throwable
1571
     */
1572
    public function testQueryColumn(): void
1573
    {
1574
        $db = $this->getConnection(true);
1575
1576
        $command = $db->createCommand();
1577
        $command->setSql(
1578
            <<<SQL
1579
            SELECT * FROM [[customer]]
1580
            SQL
1581
        );
1582
        $rows = $command->queryColumn();
1583
1584
        $this->assertIsArray($rows);
1585
        $this->assertCount(3, $rows);
1586
        $this->assertEquals('1', $rows[0]);
1587
1588
        $command->setSql('bad SQL');
1589
1590
        $this->expectException(Exception::class);
1591
1592
        $command->queryColumn();
1593
        $command->setSql(
1594
            <<<SQL
1595
            SELECT * FROM [[customer]] where id = 100
1596
            SQL
1597
        );
1598
        $rows = $command->queryColumn();
1599
1600
        $this->assertIsArray($rows);
1601
        $this->assertCount(0, $rows);
1602
        $this->assertSame([], $rows);
1603
    }
1604
1605
    /**
1606
     * @throws Exception
1607
     * @throws InvalidConfigException
1608
     * @throws Throwable
1609
     */
1610
    public function testQueryOne(): void
1611
    {
1612
        $db = $this->getConnection(true);
1613
1614
        $command = $db->createCommand();
1615
        $sql = <<<SQL
1616
        SELECT * FROM [[customer]] ORDER BY [[id]]
1617
        SQL;
1618
        $row = $command->setSql($sql)->queryOne();
1619
1620
        $this->assertIsArray($row);
1621
        $this->assertEquals(1, $row['id']);
1622
        $this->assertEquals('user1', $row['name']);
1623
1624
        $command->setSql($sql)->prepare();
1625
        $row = $command->queryOne();
1626
1627
        $this->assertIsArray($row);
1628
        $this->assertEquals(1, $row['id']);
1629
        $this->assertEquals('user1', $row['name']);
1630
1631
        $sql = <<<SQL
1632
        SELECT * FROM [[customer]] WHERE [[id]] = 10
1633
        SQL;
1634
        $command = $command->setSql($sql);
1635
1636
        $this->assertNull($command->queryOne());
1637
    }
1638
1639
    /**
1640
     * @throws Exception
1641
     * @throws InvalidConfigException
1642
     * @throws Throwable
1643
     */
1644
    public function testQueryScalar(): void
1645
    {
1646
        $db = $this->getConnection(true);
1647
1648
        $command = $db->createCommand();
1649
        $sql = <<<SQL
1650
        SELECT * FROM [[customer]] ORDER BY [[id]]
1651
        SQL;
1652
1653
        $this->assertEquals(1, $command->setSql($sql)->queryScalar());
1654
1655
        $sql = <<<SQL
1656
        SELECT [[id]] FROM [[customer]] ORDER BY [[id]]
1657
        SQL;
1658
        $command->setSql($sql)->prepare();
1659
1660
        $this->assertEquals(1, $command->queryScalar());
1661
1662
        $command = $command->setSql(
1663
            <<<SQL
1664
            SELECT [[id]] FROM [[customer]] WHERE [[id]] = 10
1665
            SQL
1666
        );
1667
1668
        $this->assertFalse($command->queryScalar());
1669
    }
1670
1671
    /**
1672
     * @throws Exception
1673
     * @throws InvalidConfigException
1674
     * @throws Throwable
1675
     */
1676
    public function testRenameColumn(): void
1677
    {
1678
        $db = $this->getConnection(true);
1679
1680
        $command = $db->createCommand();
1681
        $schema = $db->getSchema();
1682
1683
        $command->renameColumn('{{customer}}', 'address', 'address_city')->execute();
1684
1685
        $this->assertContains('address_city', $schema->getTableSchema('{{customer}}')->getColumnNames());
1686
        $this->assertNotContains('address', $schema->getTableSchema('{{customer}}')->getColumnNames());
1687
    }
1688
1689
    /**
1690
     * @throws Exception
1691
     * @throws InvalidConfigException
1692
     * @throws Throwable
1693
     */
1694
    public function testRenameTable(): void
1695
    {
1696
        $db = $this->getConnection(true);
1697
1698
        $command = $db->createCommand();
1699
        $schema = $db->getSchema();
1700
1701
        if ($schema->getTableSchema('{{new_type}}') !== null) {
1702
            $command->dropTable('{{new_type}}')->execute();
1703
        }
1704
1705
        $this->assertNotNull($schema->getTableSchema('{{type}}'));
1706
        $this->assertNull($schema->getTableSchema('{{new_type}}'));
1707
1708
        $command->renameTable('{{type}}', '{{new_type}}')->execute();
1709
1710
        $this->assertNull($schema->getTableSchema('{{type}}', true));
1711
        $this->assertNotNull($schema->getTableSchema('{{new_type}}', true));
1712
    }
1713
1714
    /**
1715
     * @throws Exception
1716
     * @throws InvalidConfigException
1717
     * @throws ReflectionException
1718
     * @throws Throwable
1719
     */
1720
    public function testSetRetryHandler(): void
1721
    {
1722
        $db = $this->getConnection(true);
1723
1724
        $command = $db->createCommand();
1725
1726
        $this->assertNull($db->getTransaction());
1727
1728
        $command->setSql(
1729
            <<<SQL
1730
            INSERT INTO [[profile]] ([[description]]) VALUES('command retry')
1731
            SQL
1732
        )->execute();
1733
1734
        $this->assertNull($db->getTransaction());
1735
        $this->assertEquals(
1736
            1,
1737
            $command->setSql(
1738
                <<<SQL
1739
                SELECT COUNT(*) FROM [[profile]] WHERE [[description]] = 'command retry'
1740
                SQL
1741
            )->queryScalar()
1742
        );
1743
1744
        $attempts = null;
1745
        $hitHandler = false;
1746
        $hitCatch = false;
1747
        $command->setSql(
1748
            <<<SQL
1749
            INSERT INTO [[profile]] ([[id]], [[description]]) VALUES(1, 'command retry')
1750
            SQL
1751
        );
1752
1753
        Assert::invokeMethod(
1754
            $command,
1755
            'setRetryHandler',
1756
            [static function ($exception, $attempt) use (&$attempts, &$hitHandler) {
1757
                $attempts = $attempt;
1758
                $hitHandler = true;
1759
1760
                return $attempt <= 2;
1761
            }]
1762
        );
1763
1764
        try {
1765
            $command->execute();
1766
        } catch (Exception $e) {
1767
            $hitCatch = true;
1768
1769
            $this->assertInstanceOf(IntegrityException::class, $e);
1770
        }
1771
1772
        $this->assertNull($db->getTransaction());
1773
        $this->assertSame(3, $attempts);
1774
        $this->assertTrue($hitHandler);
1775
        $this->assertTrue($hitCatch);
1776
    }
1777
1778
    /**
1779
     * @throws Exception
1780
     * @throws InvalidConfigException
1781
     * @throws ReflectionException
1782
     * @throws Throwable
1783
     */
1784
    public function testTransaction(): void
1785
    {
1786
        $db = $this->getConnection(true);
1787
1788
        $this->assertNull($db->getTransaction());
1789
1790
        $command = $db->createCommand();
1791
        $command = $command->setSql(
1792
            <<<SQL
1793
            INSERT INTO [[profile]] ([[description]]) VALUES('command transaction')
1794
            SQL
1795
        );
1796
1797
        Assert::invokeMethod($command, 'requireTransaction');
1798
1799
        $command->execute();
1800
1801
        $this->assertNull($db->getTransaction());
1802
        $this->assertEquals(
1803
            1,
1804
            $command->setSql(
1805
                <<<SQL
1806
                SELECT COUNT(*) FROM [[profile]] WHERE [[description]] = 'command transaction'
1807
                SQL
1808
            )->queryScalar(),
1809
        );
1810
    }
1811
1812
    /**
1813
     * @throws Exception
1814
     * @throws InvalidConfigException
1815
     * @throws Throwable
1816
     */
1817
    public function testTruncateTable(): void
1818
    {
1819
        $db = $this->getConnection(true);
1820
1821
        $command = $db->createCommand();
1822
        $rows = $command->setSql(
1823
            <<<SQL
1824
            SELECT * FROM [[animal]]
1825
            SQL
1826
        )->queryAll();
1827
1828
        $this->assertCount(2, $rows);
1829
1830
        $command->truncateTable('{{animal}}')->execute();
1831
        $rows = $command->setSql(
1832
            <<<SQL
1833
            SELECT * FROM {{animal}}
1834
            SQL
1835
        )->queryAll();
1836
1837
        $this->assertCount(0, $rows);
1838
    }
1839
1840
    /**
1841
     * @dataProvider \Yiisoft\Db\Tests\Provider\CommandProvider::update()
1842
     *
1843
     * @throws Exception
1844
     * @throws Throwable
1845
     */
1846
    public function testUpdate(
1847
        string $table,
1848
        array $columns,
1849
        array|string $conditions,
1850
        array $params,
1851
        string $expected
1852
    ): void {
1853
        $db = $this->getConnection();
1854
1855
        $command = $db->createCommand();
1856
        $sql = $command->update($table, $columns, $conditions, $params)->getSql();
1857
1858
        $this->assertSame($expected, $sql);
1859
    }
1860
1861
    /**
1862
     * @dataProvider \Yiisoft\Db\Tests\Provider\CommandProvider::upsert()
1863
     *
1864
     * @throws Exception
1865
     * @throws InvalidConfigException
1866
     * @throws NotSupportedException
1867
     * @throws Throwable
1868
     */
1869
    public function testUpsert(array $firstData, array $secondData): void
1870
    {
1871
        $db = $this->getConnection(true);
1872
1873
        $command = $db->createCommand();
1874
1875
        $this->assertEquals(
1876
            0,
1877
            $command->setSql(
1878
                <<<SQL
1879
                SELECT COUNT(*) FROM [[T_upsert]]
1880
                SQL,
1881
            )->queryScalar()
1882
        );
1883
1884
        $this->performAndCompareUpsertResult($db, $firstData);
1885
1886
        $this->assertEquals(
1887
            1,
1888
            $command->setSql(
1889
                <<<SQL
1890
                SELECT COUNT(*) FROM [[T_upsert]]
1891
                SQL,
1892
            )->queryScalar()
1893
        );
1894
1895
        $this->performAndCompareUpsertResult($db, $secondData);
1896
    }
1897
1898
    /**
1899
     * @throws Exception
1900
     * @throws InvalidConfigException
1901
     * @throws Throwable
1902
     */
1903
    protected function performAndCompareUpsertResult(ConnectionPDOInterface $db, array $data): void
1904
    {
1905
        $params = $data['params'];
1906
        $expected = $data['expected'] ?? $params[1];
1907
1908
        $command = $db->createCommand();
1909
1910
        call_user_func_array([$command, 'upsert'], $params);
1911
1912
        $command->execute();
1913
1914
        $actual = (new Query($db))
1915
            ->select(['email', 'address' => new Expression($this->upsertTestCharCast), 'status'])
1916
            ->from('{{T_upsert}}')
1917
            ->one();
1918
        $this->assertEquals($expected, $actual, $this->upsertTestCharCast);
1919
    }
1920
}
1921