Passed
Pull Request — master (#380)
by Wilmer
16:46 queued 13:55
created

CommonCommandTest::testInsertSelectAlias()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 45
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 33
nc 1
nop 0
dl 0
loc 45
rs 9.392
c 1
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 testDropCommentFromColumn(): void
696
    {
697
        $db = $this->getConnectionwithData();
698
699
        $command = $db->createCommand();
700
        $command->addCommentOnColumn('customer', 'id', 'Primary key.')->execute();
701
        $commentOnColumn = DbHelper::getCommmentsFromColumn('customer', 'id', $db);
702
703
        $this->assertSame('Primary key.', $commentOnColumn);
704
705
        $command->dropCommentFromColumn('customer', 'id')->execute();
706
        $commentOnColumn = DbHelper::getCommmentsFromColumn('customer', 'id', $db);
707
708
        $this->assertSame([], $commentOnColumn);
709
    }
710
711
    public function testDropCommentFromTable(): void
712
    {
713
        $db = $this->getConnectionWithData();
714
715
        $command = $db->createCommand();
716
        $command->addCommentOnTable('customer', 'Customer table.')->execute();
717
        $commentOnTable = DbHelper::getCommmentsFromTable('customer', $db);
718
719
        $this->assertSame('Customer table.', $commentOnTable);
720
721
        $command->dropCommentFromTable('customer')->execute();
722
        $commentOnTable = DbHelper::getCommmentsFromTable('customer', $db);
723
724
        $this->assertSame([], $commentOnTable);
725
    }
726
727
    public function testDropDefaultValue(): void
728
    {
729
        $db = $this->getConnectionWithData();
730
731
        $command = $db->createCommand();
732
        $sql = $command->dropDefaultValue('char_col2', 'type')->getSql();
733
734
        $this->assertSame(
735
            DbHelper::replaceQuotes(
736
                <<<SQL
737
                ALTER TABLE [[type]] DROP CONSTRAINT [[char_col2]]
738
                SQL,
739
                $db->getName(),
740
            ),
741
            $sql,
742
        );
743
    }
744
745
    /**
746
     * @throws Exception
747
     * @throws InvalidConfigException
748
     * @throws Throwable
749
     */
750
    public function testDropTable(): void
751
    {
752
        $db = $this->getConnectionWithData();
753
754
        $tableName = 'type';
755
756
        $this->assertNotNull($db->getSchema()->getTableSchema($tableName));
757
758
        $db->createCommand()->dropTable($tableName)->execute();
759
760
        $this->assertNull($db->getSchema()->getTableSchema($tableName));
761
    }
762
763
    /**
764
     * @throws Exception
765
     * @throws InvalidConfigException
766
     * @throws Throwable
767
     */
768
    public function testDropView(): void
769
    {
770
        $db = $this->getConnectionWithData();
771
772
        /* since it already exists in the fixtures */
773
        $viewName = 'animal_view';
774
775
        $this->assertNotNull($db->getSchema()->getTableSchema($viewName));
776
777
        $db->createCommand()->dropView($viewName)->execute();
778
779
        $this->assertNull($db->getSchema()->getTableSchema($viewName));
780
    }
781
782
    /**
783
     * @throws Exception
784
     * @throws InvalidConfigException
785
     * @throws Throwable
786
     */
787
    public function testExecute(): void
788
    {
789
        $db = $this->getConnectionWithData();
790
791
        $command = $db->createCommand();
792
        $command->setSql(
793
            <<<SQL
794
            INSERT INTO {{customer}}([[email]], [[name]], [[address]]) VALUES ('[email protected]', 'user4', 'address4')
795
            SQL
796
        );
797
798
        $this->assertSame(1, $command->execute());
799
800
        $command = $command->setSql(
801
            <<<SQL
802
            SELECT COUNT(*) FROM {{customer}} WHERE [[name]] = 'user4'
803
            SQL
804
        );
805
806
        $this->assertEquals(1, $command->queryScalar());
807
808
        $command->setSql('bad SQL');
809
        $message = match ($db->getName()) {
810
            'sqlite' => 'SQLSTATE[HY000]: General error: 1 near "bad": syntax error',
811
            'sqlsrv' => 'SQLSTATE[42000]: [Microsoft]',
812
        };
813
814
        $this->expectException(Exception::class);
815
        $this->expectExceptionMessage($message);
816
817
        $command->execute();
818
    }
819
820
    /**
821
     * @throws Exception
822
     * @throws InvalidConfigException
823
     * @throws Throwable
824
     */
825
    public function testInsert(): void
826
    {
827
        $db = $this->getConnectionWithData();
828
829
        $db->createCommand(
830
            <<<SQL
831
            DELETE FROM {{customer}}
832
            SQL
833
        )->execute();
834
        $command = $db->createCommand();
835
        $command
836
            ->insert('{{customer}}', ['email' => '[email protected]', 'name' => 'test', 'address' => 'test address'])
837
            ->execute();
838
839
        $this->assertEquals(
840
            1,
841
            $db->createCommand(
842
                <<<SQL
843
                SELECT COUNT(*) FROM {{customer}};
844
                SQL
845
            )->queryScalar(),
846
        );
847
848
        $record = $db->createCommand(
849
            <<<SQL
850
            SELECT [[email]], [[name]], [[address]] FROM {{customer}}
851
            SQL
852
        )->queryOne();
853
854
        $this->assertSame(['email' => '[email protected]', 'name' => 'test', 'address' => 'test address'], $record);
855
    }
856
857
    /**
858
     * @throws Exception
859
     * @throws InvalidConfigException
860
     * @throws Throwable
861
     */
862
    public function testInsertExpression(): void
863
    {
864
        $db = $this->getConnectionWithData();
865
866
        $command = $db->createCommand();
867
        $command->setSql(
868
            <<<SQL
869
            DELETE FROM {{order_with_null_fk}}
870
            SQL
871
        )->execute();
872
        $expression = match ($db->getName()) {
873
            'mysql' => 'YEAR(NOW())',
874
            'pgsql' => "EXTRACT(YEAR FROM TIMESTAMP 'now')",
875
            'sqlite' => "strftime('%Y')",
876
            'sqlsrv' => 'YEAR(GETDATE())',
877
        };
878
        $command
879
            ->insert('{{order_with_null_fk}}', ['created_at' => new Expression($expression), 'total' => 1])
880
            ->execute();
881
882
        $this->assertEquals(1, $command->setSql(
883
            <<<SQL
884
            SELECT COUNT(*) FROM {{order_with_null_fk}}
885
            SQL
886
        )->queryScalar());
887
888
        $record = $command->setSql(
889
            <<<SQL
890
            SELECT [[created_at]] FROM {{order_with_null_fk}}
891
            SQL
892
        )->queryOne();
893
894
        $this->assertEquals(['created_at' => date('Y')], $record);
895
    }
896
897
    /**
898
     * @throws Exception
899
     * @throws InvalidConfigException
900
     * @throws InvalidCallException
901
     * @throws Throwable
902
     */
903
    public function testsInsertQueryAsColumnValue(): void
904
    {
905
        $db = $this->getConnectionWithData();
906
907
        $command = $db->createCommand();
908
        $time = time();
909
        $command->setSql(
910
            <<<SQL
911
            DELETE FROM {{order_with_null_fk}}
912
            SQL
913
        )->execute();
914
        $command->insert('{{order}}', ['customer_id' => 1, 'created_at' => $time, 'total' => 42])->execute();
915
916
        if ($db->getName() === 'pgsql') {
917
            $orderId = $db->getLastInsertID('public.order_id_seq');
918
        } else {
919
            $orderId = $db->getLastInsertID();
920
        }
921
922
        $columnValueQuery = $this->getQuery($db)->select('created_at')->from('{{order}}')->where(['id' => $orderId]);
923
        $command
924
            ->insert(
925
                '{{order_with_null_fk}}',
926
                ['customer_id' => $orderId, 'created_at' => $columnValueQuery, 'total' => 42]
927
            )
928
            ->execute();
929
930
        $this->assertEquals(
931
            $time,
932
            $command->setSql(
933
                <<<SQL
934
                SELECT [[created_at]] FROM {{order_with_null_fk}} WHERE [[customer_id]] = :id
935
                SQL
936
            )->bindValues([':id' => $orderId])->queryScalar(),
937
        );
938
939
        $command->setSql(
940
            <<<SQL
941
            DELETE FROM {{order_with_null_fk}}
942
            SQL
943
        )->execute();
944
        $command->setSql(
945
            <<<SQL
946
            DELETE FROM {{order}}
947
            SQL
948
        )->execute();
949
    }
950
951
    /**
952
     * Test INSERT INTO ... SELECT SQL statement.
953
     *
954
     * @throws Exception
955
     * @throws InvalidConfigException
956
     * @throws Throwable
957
     */
958
    public function testInsertSelect(): void
959
    {
960
        $db = $this->getConnectionWithData();
961
962
        $command = $db->createCommand();
963
        $command->setSql(
964
            <<<SQL
965
            DELETE FROM {{customer}}
966
            SQL
967
        )->execute();
968
        $command->insert(
969
            '{{customer}}',
970
            ['email' => '[email protected]', 'name' => 'test', 'address' => 'test address']
971
        )->execute();
972
        $query = $this->getQuery($db)
973
            ->select(['{{customer}}.[[email]] as name', '[[name]] as email', '[[address]]'])
974
            ->from('{{customer}}')
975
            ->where(['and', ['<>', 'name', 'foo'], ['status' => [0, 1, 2, 3]]]);
976
        $command->insert('{{customer}}', $query)->execute();
977
978
        $this->assertEquals(
979
            2,
980
            $command->setSql(
981
                <<<SQL
982
                SELECT COUNT(*) FROM {{customer}}
983
                SQL
984
            )->queryScalar(),
985
        );
986
987
        $record = $command->setSql(
988
            <<<SQL
989
            SELECT [[email]], [[name]], [[address]] FROM {{customer}}
990
            SQL
991
        )->queryAll();
992
993
        $this->assertSame(
994
            [
995
                ['email' => '[email protected]', 'name' => 'test', 'address' => 'test address'],
996
                ['email' => 'test', 'name' => '[email protected]', 'address' => 'test address'],
997
            ],
998
            $record,
999
        );
1000
    }
1001
1002
    /**
1003
     * Test INSERT INTO ... SELECT SQL statement with alias syntax.
1004
     *
1005
     * @throws Exception
1006
     * @throws InvalidConfigException
1007
     * @throws Throwable
1008
     */
1009
    public function testInsertSelectAlias(): void
1010
    {
1011
        $db = $this->getConnectionWithData();
1012
1013
        $command = $db->createCommand();
1014
        $command->setSql(
1015
            <<<SQL
1016
            DELETE FROM {{customer}}
1017
            SQL
1018
        )->execute();
1019
        $command->insert(
1020
            '{{customer}}',
1021
            [
1022
                'email' => '[email protected]',
1023
                'name' => 'test',
1024
                'address' => 'test address',
1025
            ]
1026
        )->execute();
1027
        $query = $this->getQuery($db)
1028
            ->select(['email' => '{{customer}}.[[email]]', 'address' => 'name', 'name' => 'address'])
1029
            ->from('{{customer}}')
1030
            ->where(['and', ['<>', 'name', 'foo'], ['status' => [0, 1, 2, 3]]]);
1031
        $command->insert('{{customer}}', $query)->execute();
1032
1033
        $this->assertEquals(
1034
            2,
1035
            $command->setSql(
1036
                <<<SQL
1037
                SELECT COUNT(*) FROM {{customer}}
1038
                SQL
1039
            )->queryScalar(),
1040
        );
1041
1042
        $record = $command->setSql(
1043
            <<<SQL
1044
            SELECT [[email]], [[name]], [[address]] FROM {{customer}}
1045
            SQL
1046
        )->queryAll();
1047
1048
        $this->assertSame(
1049
            [
1050
                ['email' => '[email protected]', 'name' => 'test', 'address' => 'test address'],
1051
                ['email' => '[email protected]', 'name' => 'test address', 'address' => 'test'],
1052
            ],
1053
            $record,
1054
        );
1055
    }
1056
1057
    /**
1058
     * Test INSERT INTO ... SELECT SQL statement with wrong query object.
1059
     *
1060
     * @dataProvider \Yiisoft\Db\Tests\Provider\CommandProvider::invalidSelectColumns()
1061
     *
1062
     * @throws Exception
1063
     * @throws Throwable
1064
     */
1065
    public function testInsertSelectFailed(array|ExpressionInterface|string $invalidSelectColumns): void
1066
    {
1067
        $db = $this->getConnection();
1068
1069
        $query = new Query($db);
1070
        $query->select($invalidSelectColumns)->from('{{customer}}');
1071
        $command = $db->createCommand();
1072
1073
        $this->expectException(InvalidArgumentException::class);
1074
        $this->expectExceptionMessage('Expected select query object with enumerated (named) parameters');
1075
1076
        $command->insert('{{customer}}', $query)->execute();
1077
    }
1078
1079
    /**
1080
     * @throws Exception
1081
     * @throws InvalidConfigException
1082
     * @throws Throwable
1083
     */
1084
    public function testInsertToBlob(): void
1085
    {
1086
        $db = $this->getConnectionWithData();
1087
1088
        $command = $db->createCommand();
1089
        $command->delete('type')->execute();
1090
        $columns = [
1091
            'int_col' => 1,
1092
            'char_col' => 'test',
1093
            'float_col' => 3.14,
1094
            'bool_col' => true,
1095
            'blob_col' => serialize(['test' => 'data', 'num' => 222]),
1096
        ];
1097
        $command->insert('type', $columns)->execute();
1098
        $result = $command->setSql(
1099
            <<<SQL
1100
            SELECT [[blob_col]] FROM {{type}}
1101
            SQL
1102
        )->queryOne();
1103
1104
        $this->assertIsArray($result);
1105
1106
        $resultBlob = is_resource($result['blob_col']) ? stream_get_contents($result['blob_col']) : $result['blob_col'];
1107
1108
        $this->assertSame($columns['blob_col'], $resultBlob);
1109
    }
1110
1111
    /**
1112
     * @throws Exception
1113
     * @throws InvalidConfigException
1114
     * @throws Throwable
1115
     */
1116
    public function testIntegrityViolation(): void
1117
    {
1118
        $db = $this->getConnectionWithData();
1119
1120
        $this->expectException(IntegrityException::class);
1121
1122
        $command = $db->createCommand(
1123
            <<<SQL
1124
            INSERT INTO {{profile}}([[id]], [[description]]) VALUES (123, 'duplicate')
1125
            SQL
1126
        );
1127
        $command->execute();
1128
        $command->execute();
1129
    }
1130
1131
    /**
1132
     * @throws Exception
1133
     * @throws InvalidCallException
1134
     * @throws InvalidConfigException
1135
     * @throws Throwable
1136
     */
1137
    public function testLastInsertId(): void
1138
    {
1139
        $db = $this->getConnectionWithData();
1140
1141
        $sql = <<<SQL
1142
        INSERT INTO {{profile}}([[description]]) VALUES ('non duplicate')
1143
        SQL;
1144
        $db->createCommand($sql)->execute();
1145
1146
        $this->assertSame('3', $db->getLastInsertID());
1147
    }
1148
1149
    /**
1150
     * Verify that {{}} are not going to be replaced in parameters.
1151
     *
1152
     * @throws Exception
1153
     * @throws InvalidConfigException
1154
     * @throws Throwable
1155
     */
1156
    public function testNoTablenameReplacement(): void
1157
    {
1158
        $db = $this->getConnectionWithData();
1159
1160
        $db
1161
            ->createCommand()
1162
            ->insert(
1163
                '{{customer}}',
1164
                ['name' => 'Some {{weird}} name', 'email' => '[email protected]', 'address' => 'Some {{%weird}} address']
1165
            )
1166
            ->execute();
1167
1168
        if ($db->getName() === 'pgsql') {
1169
            $customerId = $db->getLastInsertID('public.customer_id_seq');
1170
        } else {
1171
            $customerId = $db->getLastInsertID();
1172
        }
1173
1174
        $customer = $db->createCommand(
1175
            <<<SQL
1176
            SELECT [[name]], [[email]], [[address]] FROM {{customer}} WHERE [[id]] = :id
1177
            SQL,
1178
            [':id' => $customerId]
1179
        )->queryOne();
1180
1181
        $this->assertIsArray($customer);
1182
        $this->assertSame('Some {{weird}} name', $customer['name']);
1183
        $this->assertSame('Some {{%weird}} address', $customer['address']);
1184
1185
        $db
1186
            ->createCommand()
1187
            ->update(
1188
                '{{customer}}',
1189
                ['name' => 'Some {{updated}} name', 'address' => 'Some {{%updated}} address'],
1190
                ['id' => $customerId]
1191
            )
1192
            ->execute();
1193
        $customer = $db->createCommand(
1194
            <<<SQL
1195
            SELECT [[name]], [[email]], [[address]] FROM {{customer}} WHERE [[id]] = :id
1196
            SQL,
1197
            [':id' => $customerId]
1198
        )->queryOne();
1199
1200
        $this->assertIsArray($customer);
1201
        $this->assertSame('Some {{updated}} name', $customer['name']);
1202
        $this->assertSame('Some {{%updated}} address', $customer['address']);
1203
    }
1204
1205
    /**
1206
     * @throws Exception
1207
     * @throws InvalidConfigException
1208
     * @throws Throwable
1209
     *
1210
     * @todo check if this test is correct
1211
     */
1212
    public function testQuery(): void
1213
    {
1214
        $db = $this->getConnectionWithData();
1215
1216
        $command = $db->createCommand(
1217
            <<<SQL
1218
            SELECT * FROM {{customer}}
1219
            SQL
1220
        );
1221
1222
        $this->assertNull($command->getPdoStatement());
1223
1224
        $reader = $command->query();
1225
1226
        // check tests that the reader is a valid iterator
1227
        if ($db->getName() !== 'sqlite' && $db->getName() !== 'pgsql' && $db->getName() !== 'sqlsrv') {
1228
            $this->assertEquals(3, $reader->count());
1229
        }
1230
1231
        $this->assertNotNull($command->getPdoStatement());
1232
        $this->assertInstanceOf(DataReaderInterface::class, $reader);
1233
        $this->assertIsInt($reader->count());
1234
1235
        foreach ($reader as $row) {
1236
            $this->assertIsArray($row);
1237
            $this->assertCount(6, $row);
1238
        }
1239
1240
        $command = $db->createCommand('bad SQL');
1241
1242
        $this->expectException(Exception::class);
1243
1244
        $command->query();
1245
    }
1246
1247
    /**
1248
     * @throws Exception
1249
     * @throws InvalidConfigException
1250
     * @throws Throwable
1251
     */
1252
    public function testQueryAll(): void
1253
    {
1254
        $db = $this->getConnectionWithData();
1255
1256
        $command = $db->createCommand(
1257
            <<<SQL
1258
            SELECT * FROM {{customer}}
1259
            SQL
1260
        );
1261
        $rows = $command->queryAll();
1262
1263
        $this->assertIsArray($rows);
1264
        $this->assertCount(3, $rows);
1265
        $this->assertIsArray($rows[0]);
1266
        $this->assertCount(6, $rows[0]);
1267
1268
        $command = $db->createCommand('bad SQL');
1269
1270
        $this->expectException(Exception::class);
1271
1272
        $command->queryAll();
1273
        $command = $db->createCommand(
1274
            <<<SQL
1275
            SELECT * FROM {{customer}} where id = 100
1276
            SQL
1277
        );
1278
        $rows = $command->queryAll();
1279
1280
        $this->assertIsArray($rows);
1281
        $this->assertCount(0, $rows);
1282
        $this->assertSame([], $rows);
1283
    }
1284
1285
    /**
1286
     * @throws Exception
1287
     * @throws InvalidConfigException
1288
     * @throws Throwable
1289
     */
1290
    public function testQueryOne(): void
1291
    {
1292
        $db = $this->getConnectionWithData();
1293
1294
        $sql = <<<SQL
1295
        SELECT * FROM {{customer}} ORDER BY [[id]]
1296
        SQL;
1297
        $row = $db->createCommand($sql)->queryOne();
1298
1299
        $this->assertIsArray($row);
1300
        $this->assertEquals(1, $row['id']);
1301
        $this->assertEquals('user1', $row['name']);
1302
1303
        $command = $db->createCommand($sql);
1304
        $command->prepare();
1305
        $row = $command->queryOne();
1306
1307
        $this->assertIsArray($row);
1308
        $this->assertEquals(1, $row['id']);
1309
        $this->assertEquals('user1', $row['name']);
1310
1311
        $sql = <<<SQL
1312
        SELECT * FROM {{customer}} WHERE [[id]] = 10
1313
        SQL;
1314
        $command = $db->createCommand($sql);
1315
1316
        $this->assertNull($command->queryOne());
1317
    }
1318
1319
    /**
1320
     * @throws Exception
1321
     * @throws InvalidConfigException
1322
     * @throws Throwable
1323
     */
1324
    public function testQueryCache(): void
1325
    {
1326
        $db = $this->getConnectionWithData();
1327
1328
        $query = $this->getQuery($db)->select(['name'])->from('customer');
1329
        $command = $db->createCommand();
1330
        $update = $command->setSql(
1331
            <<<SQL
1332
            UPDATE {{customer}} SET [[name]] = :name WHERE [[id]] = :id
1333
            SQL
1334
        );
1335
1336
        $this->assertSame('user1', $query->where(['id' => 1])->scalar(), 'Asserting initial value');
1337
1338
        /* No cache */
1339
        $update->bindValues([':id' => 1, ':name' => 'user11'])->execute();
1340
1341
        $this->assertSame(
1342
            'user11',
1343
            $query->where(['id' => 1])->scalar(),
1344
            'Query reflects DB changes when caching is disabled',
1345
        );
1346
1347
        /* Connection cache */
1348
        $db->cache(
1349
            static function (ConnectionPDOInterface $db) use ($query, $update) {
1350
                self::assertSame('user2', $query->where(['id' => 2])->scalar(), 'Asserting initial value for user #2');
1351
1352
                $update->bindValues([':id' => 2, ':name' => 'user22'])->execute();
1353
1354
                self::assertSame(
1355
                    'user2',
1356
                    $query->where(['id' => 2])->scalar(),
1357
                    'Query does NOT reflect DB changes when wrapped in connection caching',
1358
                );
1359
1360
                $db->noCache(
1361
                    static function () use ($query) {
1362
                        self::assertSame(
1363
                            'user22',
1364
                            $query->where(['id' => 2])->scalar(),
1365
                            'Query reflects DB changes when wrapped in connection caching and noCache simultaneously',
1366
                        );
1367
                    }
1368
                );
1369
1370
                self::assertSame(
1371
                    'user2',
1372
                    $query->where(['id' => 2])->scalar(),
1373
                    'Cache does not get changes after getting newer data from DB in noCache block.',
1374
                );
1375
            },
1376
            10,
1377
        );
1378
1379
        $db->queryCacheEnable(false);
1380
1381
        $db->cache(
1382
            static function () use ($query, $update) {
1383
                self::assertSame(
1384
                    'user22',
1385
                    $query->where(['id' => 2])->scalar(),
1386
                    'When cache is disabled for the whole connection, Query inside cache block does not get cached',
1387
                );
1388
1389
                $update->bindValues([':id' => 2, ':name' => 'user2'])->execute();
1390
1391
                self::assertSame('user2', $query->where(['id' => 2])->scalar());
1392
            },
1393
            10,
1394
        );
1395
1396
        $db->queryCacheEnable(true);
1397
        $query->cache();
1398
1399
        $this->assertSame('user11', $query->where(['id' => 1])->scalar());
1400
1401
        $update->bindValues([':id' => 1, ':name' => 'user1'])->execute();
1402
1403
        $this->assertSame(
1404
            'user11',
1405
            $query->where(['id' => 1])->scalar(),
1406
            'When both Connection and Query have cache enabled, we get cached value',
1407
        );
1408
        $this->assertSame(
1409
            'user1',
1410
            $query->noCache()->where(['id' => 1])->scalar(),
1411
            'When Query has disabled cache, we get actual data',
1412
        );
1413
1414
        $db->cache(
1415
            static function () use ($query) {
1416
                self::assertSame('user1', $query->noCache()->where(['id' => 1])->scalar());
1417
                self::assertSame('user11', $query->cache()->where(['id' => 1])->scalar());
1418
            },
1419
            10,
1420
        );
1421
    }
1422
1423
    /**
1424
     * @throws Exception
1425
     * @throws InvalidConfigException
1426
     * @throws Throwable
1427
     */
1428
    public function testQueryColumn(): void
1429
    {
1430
        $db = $this->getConnectionWithData();
1431
1432
        $command = $db->createCommand(
1433
            <<<SQL
1434
            SELECT * FROM {{customer}}
1435
            SQL
1436
        );
1437
        $rows = $command->queryColumn();
1438
1439
        $this->assertIsArray($rows);
1440
        $this->assertCount(3, $rows);
1441
        $this->assertEquals('1', $rows[0]);
1442
1443
        $command = $db->createCommand('bad SQL');
1444
1445
        $this->expectException(Exception::class);
1446
1447
        $command->queryColumn();
1448
        $command = $db->createCommand(
1449
            <<<SQL
1450
            SELECT * FROM {{customer}} where id = 100
1451
            SQL
1452
        );
1453
        $rows = $command->queryColumn();
1454
1455
        $this->assertIsArray($rows);
1456
        $this->assertCount(0, $rows);
1457
        $this->assertSame([], $rows);
1458
    }
1459
1460
    /**
1461
     * @throws Exception
1462
     * @throws InvalidConfigException
1463
     * @throws Throwable
1464
     */
1465
    public function testQueryScalar(): void
1466
    {
1467
        $db = $this->getConnectionWithData();
1468
1469
        $sql = <<<SQL
1470
        SELECT * FROM {{customer}} ORDER BY [[id]]
1471
        SQL;
1472
1473
        $this->assertEquals(1, $db->createCommand($sql)->queryScalar());
1474
1475
        $sql = <<<SQL
1476
        SELECT [[id]] FROM {{customer}} ORDER BY [[id]]
1477
        SQL;
1478
        $command = $db->createCommand($sql);
1479
        $command->prepare();
1480
1481
        $this->assertEquals(1, $command->queryScalar());
1482
1483
        $command = $db->createCommand(
1484
            <<<SQL
1485
            SELECT [[id]] FROM {{customer}} WHERE [[id]] = 10
1486
            SQL
1487
        );
1488
1489
        $this->assertFalse($command->queryScalar());
1490
    }
1491
1492
    /**
1493
     * @throws Exception
1494
     * @throws InvalidConfigException
1495
     * @throws Throwable
1496
     */
1497
    public function testRenameTable(): void
1498
    {
1499
        $db = $this->getConnectionWithData();
1500
1501
        $fromTableName = 'type';
1502
        $toTableName = 'new_type';
1503
        $command = $db->createCommand();
1504
1505
        if ($db->getSchema()->getTableSchema($toTableName) !== null) {
1506
            $command->dropTable($toTableName)->execute();
1507
        }
1508
1509
        $this->assertNotNull($db->getSchema()->getTableSchema($fromTableName));
1510
        $this->assertNull($db->getSchema()->getTableSchema($toTableName));
1511
1512
        $command->renameTable($fromTableName, $toTableName)->execute();
1513
1514
        $this->assertNull($db->getSchema()->getTableSchema($fromTableName, true));
1515
        $this->assertNotNull($db->getSchema()->getTableSchema($toTableName, true));
1516
    }
1517
1518
    /**
1519
     * @throws Exception
1520
     * @throws InvalidConfigException
1521
     * @throws ReflectionException
1522
     * @throws Throwable
1523
     */
1524
    public function testRetryHandler(): void
1525
    {
1526
        $db = $this->getConnectionwithData();
1527
1528
        $this->assertNull($db->getTransaction());
1529
1530
        $db->createCommand(
1531
            <<<SQL
1532
            INSERT INTO {{profile}}([[description]]) VALUES('command retry')
1533
            SQL
1534
        )->execute();
1535
1536
        $this->assertNull($db->getTransaction());
1537
        $this->assertEquals(
1538
            1,
1539
            $db->createCommand(
1540
                <<<SQL
1541
                SELECT COUNT(*) FROM {{profile}} WHERE [[description]] = 'command retry'
1542
                SQL
1543
            )->queryScalar()
1544
        );
1545
1546
        $attempts = null;
1547
        $hitHandler = false;
1548
        $hitCatch = false;
1549
        $command = $db->createCommand(
1550
            <<<SQL
1551
            INSERT INTO {{profile}}([[id]], [[description]]) VALUES(1, 'command retry')
1552
            SQL
1553
        );
1554
1555
        Assert::invokeMethod(
1556
            $command,
1557
            'setRetryHandler',
1558
            [static function ($exception, $attempt) use (&$attempts, &$hitHandler) {
1559
                $attempts = $attempt;
1560
                $hitHandler = true;
1561
1562
                return $attempt <= 2;
1563
            }]
1564
        );
1565
1566
        try {
1567
            $command->execute();
1568
        } catch (Exception $e) {
1569
            $hitCatch = true;
1570
1571
            $this->assertInstanceOf(IntegrityException::class, $e);
1572
        }
1573
1574
        $this->assertNull($db->getTransaction());
1575
        $this->assertSame(3, $attempts);
1576
        $this->assertTrue($hitHandler);
1577
        $this->assertTrue($hitCatch);
1578
    }
1579
1580
    /**
1581
     * @throws Exception
1582
     * @throws InvalidConfigException
1583
     * @throws Throwable
1584
     */
1585
    public function testSerialize(): void
1586
    {
1587
        $db = $this->getConnection();
1588
1589
        $db->open();
1590
        $serialized = serialize($db);
1591
1592
        $this->assertNotNull($db->getPDO());
1593
1594
        $unserialized = unserialize($serialized);
1595
1596
        $this->assertInstanceOf(ConnectionPDOInterface::class, $unserialized);
1597
        $this->assertNull($unserialized->getPDO());
1598
        $this->assertEquals(123, $unserialized->createCommand('SELECT 123')->queryScalar());
1599
    }
1600
1601
    /**
1602
     * @throws Exception
1603
     * @throws InvalidConfigException
1604
     * @throws ReflectionException
1605
     * @throws Throwable
1606
     */
1607
    public function testTransaction(): void
1608
    {
1609
        $db = $this->getConnectionWithData();
1610
1611
        $this->assertNull($db->getTransaction());
1612
1613
        $command = $db->createCommand();
1614
        $command = $command->setSql(
1615
            <<<SQL
1616
            INSERT INTO {{profile}}([[description]]) VALUES('command transaction')
1617
            SQL
1618
        );
1619
1620
        Assert::invokeMethod($command, 'requireTransaction');
1621
1622
        $command->execute();
1623
1624
        $this->assertNull($db->getTransaction());
1625
        $this->assertEquals(
1626
            1,
1627
            $command->setSql(
1628
                <<<SQL
1629
                SELECT COUNT(*) FROM {{profile}} WHERE [[description]] = 'command transaction'
1630
                SQL
1631
            )->queryScalar(),
1632
        );
1633
    }
1634
1635
    /**
1636
     * @throws Exception
1637
     * @throws InvalidConfigException
1638
     * @throws Throwable
1639
     */
1640
    public function testTruncateTable(): void
1641
    {
1642
        $db = $this->getConnectionWithData();
1643
1644
        $command = $db->createCommand();
1645
        $rows = $command->setSql(
1646
            <<<SQL
1647
            SELECT * FROM {{animal}}
1648
            SQL
1649
        )->queryAll();
1650
1651
        $this->assertCount(2, $rows);
1652
1653
        $command->truncateTable('animal')->execute();
1654
        $rows = $command->setSql(
1655
            <<<SQL
1656
            SELECT * FROM {{animal}}
1657
            SQL
1658
        )->queryAll();
1659
1660
        $this->assertCount(0, $rows);
1661
    }
1662
1663
    /**
1664
     * @dataProvider \Yiisoft\Db\Tests\Provider\CommandProvider::update()
1665
     *
1666
     * @throws Exception
1667
     * @throws InvalidConfigException
1668
     */
1669
    public function testUpdate(
1670
        string $table,
1671
        array $columns,
1672
        array|string $conditions,
1673
        array $params,
1674
        string $expected
1675
    ): void {
1676
        $db = $this->getConnectionWithData();
1677
1678
        $command = $db->createCommand();
1679
        $sql = $command->update($table, $columns, $conditions, $params)->getSql();
1680
1681
        $this->assertSame($expected, $sql);
1682
    }
1683
1684
    /**
1685
     * @dataProvider \Yiisoft\Db\Tests\Provider\CommandProvider::upsert()
1686
     *
1687
     * @throws Exception
1688
     * @throws Throwable
1689
     */
1690
    public function testUpsert(array $firstData, array $secondData): void
1691
    {
1692
        $db = $this->getConnectionWithData();
1693
1694
        $this->assertEquals(0, $db->createCommand('SELECT COUNT(*) FROM {{T_upsert}}')->queryScalar());
1695
        $this->performAndCompareUpsertResult($db, $firstData);
1696
        $this->assertEquals(1, $db->createCommand('SELECT COUNT(*) FROM {{T_upsert}}')->queryScalar());
1697
        $this->performAndCompareUpsertResult($db, $secondData);
1698
    }
1699
}
1700