Passed
Pull Request — master (#380)
by Wilmer
05:19 queued 02:05
created

CommonCommandTest::testBatchInsertSQL()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 20
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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