Passed
Pull Request — master (#399)
by Wilmer
30:07 queued 27:33
created

CommonCommandTest::testInsertSelect()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 41
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

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