Passed
Pull Request — master (#380)
by Wilmer
03:09
created

CommonCommandTest   F

Complexity

Total Complexity 71

Size/Duplication

Total Lines 1707
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 884
dl 0
loc 1707
rs 2.436
c 1
b 0
f 0
wmc 71

51 Methods

Rating   Name   Duplication   Size   Complexity  
A testDropDefaultValue() 0 15 1
A testBatchInsertWithYield() 0 13 1
A testAddDropUnique() 0 28 2
A testBatchInsertDataTypesLocale() 0 55 4
A testQueryColumn() 0 30 1
A testInsert() 0 30 1
A testBatchInsert() 0 19 1
A testNoTablenameReplacement() 0 47 2
A testQueryAll() 0 31 1
A testTruncateTable() 0 21 1
A testAddCommentOnTable() 0 9 1
A testDropCommentFromTable() 0 14 1
A testColumnCase() 0 29 1
A testDropCommentFromColumn() 0 14 1
A testBindParam() 0 71 1
A testCreateTable() 0 19 2
A testBatchInsertSQL() 0 20 1
A testsInsertQueryAsColumnValue() 0 46 2
A testInsertSelectAlias() 0 45 1
A testIntegrityViolation() 0 13 1
A testCheckIntegrity() 0 7 1
A testInsertSelectFailed() 0 12 1
A testRetryHandler() 0 54 2
A testQuery() 0 33 5
A testBatchInsertFailsOld() 0 21 1
A testAddCommentOnColumn() 0 9 1
A testCheckIntegrityExecuteException() 0 17 1
B testQueryCache() 0 96 1
A testQueryScalar() 0 25 1
A testBatchInsertWithManyData() 0 19 2
A testInsertSelect() 0 41 1
A testExecute() 0 31 1
A testUpdate() 0 13 1
A testTransaction() 0 25 1
A testInsertToBlob() 0 25 2
A testDataReaderRewindException() 0 15 1
A testBindParamValue() 0 45 1
A testInsertExpression() 0 33 1
A testRenameTable() 0 19 2
A testAddDropForeignKey() 0 40 2
A testLastInsertId() 0 10 1
A testQueryOne() 0 27 1
A testCreateView() 0 27 3
A testCreateDropIndex() 0 49 2
A testBindParamsNonWhere() 0 16 1
A testDropView() 0 12 1
A testSerialize() 0 14 1
A testAlterTable() 0 21 2
A testAddDefaultValue() 0 15 1
A testDropTable() 0 11 1
A testUpsert() 0 8 1

How to fix   Complexity   

Complex Class

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

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

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

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