Passed
Pull Request — master (#372)
by Wilmer
03:25 queued 27s
created

AbstractCommandTest::testBatchInsertWithManyData()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 19
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 10
c 0
b 0
f 0
nc 2
nop 0
dl 0
loc 19
rs 9.9332
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Tests;
6
7
use PDO;
8
use PHPUnit\Framework\TestCase;
9
use Throwable;
10
use Yiisoft\Db\Command\Param;
11
use Yiisoft\Db\Command\ParamInterface;
12
use Yiisoft\Db\Connection\ConnectionInterface;
13
use Yiisoft\Db\Exception\Exception;
14
use Yiisoft\Db\Exception\IntegrityException;
15
use Yiisoft\Db\Exception\InvalidArgumentException;
16
use Yiisoft\Db\Exception\InvalidCallException;
17
use Yiisoft\Db\Exception\InvalidConfigException;
18
use Yiisoft\Db\Exception\InvalidParamException;
19
use Yiisoft\Db\Expression\Expression;
20
use Yiisoft\Db\Query\Data\DataReader;
21
use Yiisoft\Db\Query\Data\DataReaderInterface;
22
use Yiisoft\Db\Query\Query;
23
use Yiisoft\Db\QueryBuilder\QueryBuilder;
24
use Yiisoft\Db\Schema\Schema;
25
use Yiisoft\Db\Tests\Support\Assert;
26
27
use function call_user_func_array;
28
use function date;
29
use function is_array;
30
use function rtrim;
31
use function setlocale;
32
use function time;
33
34
abstract class AbstractCommandTest extends TestCase
35
{
36
    public function testAddDropForeignKey(): void
37
    {
38
        $db = $this->getConnection();
0 ignored issues
show
Bug introduced by
The method getConnection() does not exist on Yiisoft\Db\Tests\AbstractCommandTest. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

38
        /** @scrutinizer ignore-call */ 
39
        $db = $this->getConnection();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
39
40
        $tableName = 'test_fk';
41
        $name = 'test_fk_constraint';
42
43
        $schema = $db->getSchema();
44
45
        if ($schema->getTableSchema($tableName) !== null) {
46
            $db->createCommand()->dropTable($tableName)->execute();
47
        }
48
49
        $db->createCommand()->createTable(
50
            $tableName,
51
            [
52
                'int1' => 'integer not null unique',
53
                'int2' => 'integer not null unique',
54
                'int3' => 'integer not null unique',
55
                'int4' => 'integer not null unique',
56
                'unique ([[int1]], [[int2]])',
57
                'unique ([[int3]], [[int4]])',
58
            ],
59
        )->execute();
60
61
        $this->assertEmpty($schema->getTableForeignKeys($tableName, true));
62
63
        $db->createCommand()->addForeignKey($name, $tableName, ['int1'], $tableName, ['int3'])->execute();
64
65
        $this->assertSame(['int1'], $schema->getTableForeignKeys($tableName, true)[0]->getColumnNames());
66
        $this->assertSame(['int3'], $schema->getTableForeignKeys($tableName, true)[0]->getForeignColumnNames());
67
68
        $db->createCommand()->dropForeignKey($name, $tableName)->execute();
69
70
        $this->assertEmpty($schema->getTableForeignKeys($tableName, true));
71
72
        $db->createCommand()->addForeignKey(
73
            $name,
74
            $tableName,
75
            ['int1', 'int2'],
76
            $tableName,
77
            ['int3', 'int4'],
78
        )->execute();
79
80
        $this->assertSame(['int1', 'int2'], $schema->getTableForeignKeys($tableName, true)[0]->getColumnNames());
81
        $this->assertSame(['int3', 'int4'], $schema->getTableForeignKeys($tableName, true)[0]->getForeignColumnNames());
82
    }
83
84
    public function testAddDropUnique(): void
85
    {
86
        $db = $this->getConnection();
87
88
        $tableName = 'test_uq';
89
        $name = 'test_uq_constraint';
90
91
        $schema = $db->getSchema();
92
93
        if ($schema->getTableSchema($tableName) !== null) {
94
            $db->createCommand()->dropTable($tableName)->execute();
95
        }
96
97
        $db->createCommand()->createTable(
98
            $tableName,
99
            ['int1' => 'integer not null', 'int2' => 'integer not null'],
100
        )->execute();
101
102
        $this->assertEmpty($schema->getTableUniques($tableName, true));
103
104
        $db->createCommand()->addUnique($name, $tableName, ['int1'])->execute();
105
106
        $this->assertSame(['int1'], $schema->getTableUniques($tableName, true)[0]->getColumnNames());
107
108
        $db->createCommand()->dropUnique($name, $tableName)->execute();
109
110
        $this->assertEmpty($schema->getTableUniques($tableName, true));
111
112
        $db->createCommand()->addUnique($name, $tableName, ['int1', 'int2'])->execute();
113
114
        $this->assertSame(['int1', 'int2'], $schema->getTableUniques($tableName, true)[0]->getColumnNames());
115
    }
116
117
    public function testAlterTable(): void
118
    {
119
        $db = $this->getConnection();
120
121
        if ($db->getName() === 'sqlite') {
122
            $this->markTestSkipped('Sqlite does not support alterTable');
123
        }
124
125
        if ($db->getSchema()->getTableSchema('testAlterTable', true) !== null) {
126
            $db->createCommand()->dropTable('testAlterTable')->execute();
127
        }
128
129
        $db->createCommand()->createTable(
130
            'testAlterTable',
131
            ['id' => Schema::TYPE_PK, 'bar' => Schema::TYPE_INTEGER],
132
        )->execute();
133
        $db->createCommand()->insert('testAlterTable', ['bar' => 1])->execute();
134
        $db->createCommand()->alterColumn('testAlterTable', 'bar', Schema::TYPE_STRING)->execute();
135
        $db->createCommand()->insert('testAlterTable', ['bar' => 'hello'])->execute();
136
        $records = $db->createCommand('SELECT [[id]], [[bar]] FROM {{testAlterTable}}')->queryAll();
137
138
        $this->assertSame([['id' => '1', 'bar' => '1'], ['id' => '2', 'bar' => 'hello']], $records);
139
    }
140
141
    /**
142
     * @throws Exception
143
     * @throws InvalidConfigException
144
     * @throws Throwable
145
     */
146
    public function testBatchInsert(): void
147
    {
148
        $db = $this->getConnection();
149
150
        $command = $db->createCommand();
151
        $command->batchInsert(
152
            '{{customer}}',
153
            ['email', 'name', 'address'],
154
            [
155
                ['[email protected]', 't1', 't1 address'],
156
                ['[email protected]', null, false],
157
            ]
158
        );
159
160
        $this->assertSame(2, $command->execute());
161
162
        $result = (new Query($db))
163
            ->select(['email', 'name', 'address'])
164
            ->from('{{customer}}')
165
            ->where(['=', '[[email]]', '[email protected]'])
166
            ->one();
167
168
        $this->assertCount(3, $result);
169
        $this->assertSame(
170
            [
171
                'email' => '[email protected]',
172
                'name' => 't1',
173
                'address' => 't1 address',
174
            ],
175
            $result,
176
        );
177
178
        $result = (new Query($db))
179
            ->select(['email', 'name', 'address'])
180
            ->from('{{customer}}')
181
            ->where(['=', '[[email]]', '[email protected]'])
182
            ->one();
183
184
        $this->assertCount(3, $result);
185
        $this->assertSame(
186
            [
187
                'email' => '[email protected]',
188
                'name' => null,
189
                'address' => '0',
190
            ],
191
            $result,
192
        );
193
194
        /**
195
         * @link https://github.com/yiisoft/yii2/issues/11693
196
         */
197
        $command = $db->createCommand();
198
        $command->batchInsert(
199
            '{{customer}}',
200
            ['email', 'name', 'address'],
201
            []
202
        );
203
204
        $this->assertSame(0, $command->execute());
205
    }
206
207
    /**
208
     * Test batch insert with different data types.
209
     *
210
     * Ensure double is inserted with `.` decimal separator.
211
     *
212
     * @link https://github.com/yiisoft/yii2/issues/6526
213
     */
214
    public function testBatchInsertDataTypesLocale(): void
215
    {
216
        $locale = setlocale(LC_NUMERIC, 0);
217
218
        if (false === $locale) {
219
            $this->markTestSkipped('Your platform does not support locales.');
220
        }
221
222
        $db = $this->getConnection();
223
224
        try {
225
            /* This one sets decimal mark to comma sign */
226
            setlocale(LC_NUMERIC, 'ru_RU.utf8');
227
            $cols = ['int_col', 'char_col', 'float_col', 'bool_col'];
228
            $data = [
229
                [1, 'A', 9.735, true],
230
                [2, 'B', -2.123, false],
231
                [3, 'C', 2.123, false],
232
            ];
233
234
            /* clear data in "type" table */
235
            $db->createCommand()->delete('type')->execute();
236
237
            /* change, for point oracle. */
238
            if ($db->getDriver()->getDriverName() === 'oci') {
239
                $db->createCommand("ALTER SESSION SET NLS_NUMERIC_CHARACTERS='.,'")->execute();
240
            }
241
242
            /* batch insert on "type" table */
243
            $db->createCommand()->batchInsert('type', $cols, $data)->execute();
244
            $data = $db->createCommand(
245
                'SELECT [[int_col]], [[char_col]], [[float_col]], [[bool_col]] ' .
246
                'FROM {{type}} WHERE [[int_col]] IN (1,2,3) ORDER BY [[int_col]]'
247
            )->queryAll();
248
249
            $this->assertCount(3, $data);
250
            $this->assertSame('1', $data[0]['int_col']);
251
            $this->assertSame('2', $data[1]['int_col']);
252
            $this->assertSame('3', $data[2]['int_col']);
253
254
            /* rtrim because Postgres padds the column with whitespace */
255
            $this->assertSame('A', rtrim($data[0]['char_col']));
256
            $this->assertSame('B', rtrim($data[1]['char_col']));
257
            $this->assertSame('C', rtrim($data[2]['char_col']));
258
            $this->assertSame('9.735', $data[0]['float_col']);
259
            $this->assertSame('-2.123', $data[1]['float_col']);
260
            $this->assertSame('2.123', $data[2]['float_col']);
261
            $this->assertSame('1', $data[0]['bool_col']);
262
            Assert::assertIsOneOf($data[1]['bool_col'], ['0', false]);
263
            Assert::assertIsOneOf($data[2]['bool_col'], ['0', false]);
264
        } catch (Exception | Throwable $e) {
265
            setlocale(LC_NUMERIC, $locale);
266
            throw $e;
267
        }
268
269
        setlocale(LC_NUMERIC, $locale);
270
    }
271
272
    /**
273
     * @throws Exception
274
     * @throws InvalidConfigException
275
     * @throws Throwable
276
     */
277
    public function testBatchInsertFailsOld(): void
278
    {
279
        $db = $this->getConnection(true);
280
281
        $command = $db->createCommand();
282
        $command->batchInsert(
283
            '{{customer}}',
284
            ['email', 'name', 'address'],
285
            [
286
                ['[email protected]', 'test_name', 'test_address'],
287
            ]
288
        );
289
290
        $this->assertSame(1, $command->execute());
291
292
        $result = (new Query($db))
293
            ->select(['email', 'name', 'address'])
294
            ->from('{{customer}}')
295
            ->where(['=', '[[email]]', '[email protected]'])
296
            ->one();
297
298
        $this->assertCount(3, $result);
299
        $this->assertSame(
300
            [
301
                'email' => '[email protected]',
302
                'name' => 'test_name',
303
                'address' => 'test_address',
304
            ],
305
            $result,
306
        );
307
    }
308
309
    public function testBatchInsertWithManyData(): void
310
    {
311
        $values = [];
312
        $attemptsInsertRows = 200;
313
        $db = $this->getConnection();
314
315
        $command = $db->createCommand();
316
317
        for ($i = 0; $i < $attemptsInsertRows; $i++) {
318
            $values[$i] = ['t' . $i . '@any.com', 't' . $i, 't' . $i . ' address'];
319
        }
320
321
        $command->batchInsert('{{customer}}', ['email', 'name', 'address'], $values);
322
323
        $this->assertSame($attemptsInsertRows, $command->execute());
324
325
        $insertedRowsCount = (new Query($db))->from('{{customer}}')->count();
326
327
        $this->assertGreaterThanOrEqual($attemptsInsertRows, $insertedRowsCount);
328
    }
329
330
    /**
331
     * @throws Exception
332
     * @throws InvalidConfigException
333
     * @throws Throwable
334
     */
335
    public function testBatchInsertWithYield(): void
336
    {
337
        $rows = (static function () {
338
            foreach ([['[email protected]', 'test name', 'test address']] as $row) {
339
                yield $row;
340
            }
341
        })();
342
343
        $db = $this->getConnection();
344
345
        $command = $db->createCommand();
346
        $command->batchInsert('{{customer}}', ['email', 'name', 'address'], $rows);
347
348
        $this->assertSame(1, $command->execute());
349
    }
350
351
    public function testBindValues(): void
352
    {
353
        $command = $this->getConnection()->createCommand();
354
355
        $values = ['int' => 1, 'string' => 'str'];
356
        $command->bindValues($values);
357
        $bindedValues = $command->getParams(false);
358
359
        $this->assertIsArray($bindedValues);
360
        $this->assertContainsOnlyInstancesOf(ParamInterface::class, $bindedValues);
361
        $this->assertCount(2, $bindedValues);
362
363
        $param = new Param('str', 99);
364
        $command->bindValues(['param' => $param]);
365
        $bindedValues = $command->getParams(false);
366
367
        $this->assertIsArray($bindedValues);
368
        $this->assertContainsOnlyInstancesOf(ParamInterface::class, $bindedValues);
369
        $this->assertCount(3, $bindedValues);
370
        $this->assertSame($param, $bindedValues['param']);
371
        $this->assertNotEquals($param, $bindedValues['int']);
372
373
        // Replace test
374
        $command->bindValues(['int' => $param]);
375
        $bindedValues = $command->getParams(false);
376
377
        $this->assertIsArray($bindedValues);
378
        $this->assertContainsOnlyInstancesOf(ParamInterface::class, $bindedValues);
379
        $this->assertCount(3, $bindedValues);
380
        $this->assertSame($param, $bindedValues['int']);
381
    }
382
383
    public function testCreateDropIndex(): void
384
    {
385
        $db = $this->getConnection();
386
387
        $tableName = 'test_idx';
388
        $name = 'test_idx_constraint';
389
390
        $schema = $db->getSchema();
391
392
        if ($schema->getTableSchema($tableName) !== null) {
393
            $db->createCommand()->dropTable($tableName)->execute();
394
        }
395
396
        $db->createCommand()->createTable(
397
            $tableName,
398
            ['int1' => 'integer not null', 'int2' => 'integer not null'],
399
        )->execute();
400
401
        $this->assertEmpty($schema->getTableIndexes($tableName, true));
402
403
        $db->createCommand()->createIndex($name, $tableName, ['int1'])->execute();
404
405
        $this->assertSame(['int1'], $schema->getTableIndexes($tableName, true)[0]->getColumnNames());
406
        $this->assertFalse($schema->getTableIndexes($tableName, true)[0]->isUnique());
407
408
        $db->createCommand()->dropIndex($name, $tableName)->execute();
409
410
        $this->assertEmpty($schema->getTableIndexes($tableName, true));
411
412
        $db->createCommand()->createIndex($name, $tableName, ['int1', 'int2'])->execute();
413
414
        $this->assertSame(['int1', 'int2'], $schema->getTableIndexes($tableName, true)[0]->getColumnNames());
415
        $this->assertFalse($schema->getTableIndexes($tableName, true)[0]->isUnique());
416
417
        $db->createCommand()->dropIndex($name, $tableName)->execute();
418
419
        $this->assertEmpty($schema->getTableIndexes($tableName, true));
420
        $this->assertEmpty($schema->getTableIndexes($tableName, true));
421
422
        $db->createCommand()->createIndex($name, $tableName, ['int1'], QueryBuilder::INDEX_UNIQUE)->execute();
423
424
        $this->assertSame(['int1'], $schema->getTableIndexes($tableName, true)[0]->getColumnNames());
425
        $this->assertTrue($schema->getTableIndexes($tableName, true)[0]->isUnique());
426
427
        $db->createCommand()->dropIndex($name, $tableName)->execute();
428
429
        $this->assertEmpty($schema->getTableIndexes($tableName, true));
430
431
        $db->createCommand()->createIndex($name, $tableName, ['int1', 'int2'], QueryBuilder::INDEX_UNIQUE)->execute();
432
433
        $this->assertSame(['int1', 'int2'], $schema->getTableIndexes($tableName, true)[0]->getColumnNames());
434
        $this->assertTrue($schema->getTableIndexes($tableName, true)[0]->isUnique());
435
    }
436
437
    public function testCreateTable(): void
438
    {
439
        $db = $this->getConnection();
440
441
        if ($db->getSchema()->getTableSchema('testCreateTable') !== null) {
442
            $db->createCommand()->dropTable('testCreateTable')->execute();
443
        }
444
445
        $db->createCommand()->createTable(
446
            'testCreateTable',
447
            ['id' => Schema::TYPE_PK, 'bar' => Schema::TYPE_INTEGER],
448
        )->execute();
449
        $db->createCommand()->insert('testCreateTable', ['bar' => 1])->execute();
450
        $records = $db->createCommand('SELECT [[id]], [[bar]] FROM {{testCreateTable}};')->queryAll();
451
452
        $this->assertSame([['id' => '1', 'bar' => '1']], $records);
453
    }
454
455
    public function testCreateView(): void
456
    {
457
        $db = $this->getConnection();
458
459
        $subquery = (new Query($db))->select('bar')->from('testCreateViewTable')->where(['>', 'bar', '5']);
460
461
        if ($db->getSchema()->getTableSchema('testCreateView') !== null) {
462
            $db->createCommand()->dropView('testCreateView')->execute();
463
        }
464
465
        if ($db->getSchema()->getTableSchema('testCreateViewTable')) {
466
            $db->createCommand()->dropTable('testCreateViewTable')->execute();
467
        }
468
469
        $db->createCommand()->createTable(
470
            'testCreateViewTable',
471
            ['id' => Schema::TYPE_PK, 'bar' => Schema::TYPE_INTEGER],
472
        )->execute();
473
        $db->createCommand()->insert('testCreateViewTable', ['bar' => 1])->execute();
474
        $db->createCommand()->insert('testCreateViewTable', ['bar' => 6])->execute();
475
        $db->createCommand()->createView('testCreateView', $subquery)->execute();
476
        $records = $db->createCommand('SELECT [[bar]] FROM {{testCreateView}};')->queryAll();
477
478
        $this->assertSame([['bar' => '6']], $records);
479
    }
480
481
    public function testColumnCase(): void
482
    {
483
        $db = $this->getConnection();
484
485
        $this->assertSame(PDO::CASE_NATURAL, $db->getActivePDO()->getAttribute(PDO::ATTR_CASE));
486
487
        $sql = 'SELECT [[customer_id]], [[total]] FROM {{order}}';
488
        $rows = $db->createCommand($sql)->queryAll();
489
490
        $this->assertTrue(isset($rows[0]));
491
        $this->assertTrue(isset($rows[0]['customer_id']));
492
        $this->assertTrue(isset($rows[0]['total']));
493
494
        $db->getActivePDO()->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER);
495
        $rows = $db->createCommand($sql)->queryAll();
496
497
        $this->assertTrue(isset($rows[0]));
498
        $this->assertTrue(isset($rows[0]['customer_id']));
499
        $this->assertTrue(isset($rows[0]['total']));
500
501
        $db->getActivePDO()->setAttribute(PDO::ATTR_CASE, PDO::CASE_UPPER);
502
        $rows = $db->createCommand($sql)->queryAll();
503
504
        $this->assertTrue(isset($rows[0]));
505
        $this->assertTrue(isset($rows[0]['CUSTOMER_ID']));
506
        $this->assertTrue(isset($rows[0]['TOTAL']));
507
    }
508
509
    /**
510
     * @throws Exception
511
     * @throws InvalidConfigException
512
     */
513
    public function testConstruct(): void
514
    {
515
        $db = $this->getConnection();
516
517
        /* null */
518
        $command = $db->createCommand();
519
520
        $this->assertEmpty($command->getSql());
521
522
        /* string */
523
        $sql = 'SELECT * FROM customer';
524
        $command = $db->createCommand($sql);
525
526
        $this->assertSame($sql, $command->getSql());
527
    }
528
529
    public function testDataReaderCreationException(): void
530
    {
531
        $db = $this->getConnection();
532
533
        $this->expectException(InvalidParamException::class);
534
        $this->expectExceptionMessage('The PDOStatement cannot be null.');
535
536
        $sql = 'SELECT * FROM {{customer}}';
537
        new DataReader($db->createCommand($sql));
538
    }
539
540
    public function testDataReaderRewindException(): void
541
    {
542
        $db = $this->getConnection();
543
544
        $this->expectException(InvalidCallException::class);
545
        $this->expectExceptionMessage('DataReader cannot rewind. It is a forward-only reader.');
546
547
        $sql = 'SELECT * FROM {{customer}}';
548
        $reader = $db->createCommand($sql)->query();
549
        $reader->next();
550
        $reader->rewind();
551
    }
552
553
    public function testDropTable(): void
554
    {
555
        $db = $this->getConnection();
556
557
        $tableName = 'type';
558
559
        $this->assertNotNull($db->getSchema()->getTableSchema($tableName));
560
561
        $db->createCommand()->dropTable($tableName)->execute();
562
563
        $this->assertNull($db->getSchema()->getTableSchema($tableName));
564
    }
565
566
    public function testDropView(): void
567
    {
568
        $db = $this->getConnection();
569
570
        /* since it already exists in the fixtures */
571
        $viewName = 'animal_view';
572
573
        $this->assertNotNull($db->getSchema()->getTableSchema($viewName));
574
575
        $db->createCommand()->dropView($viewName)->execute();
576
577
        $this->assertNull($db->getSchema()->getTableSchema($viewName));
578
    }
579
580
    /**
581
     * @throws Exception
582
     * @throws InvalidConfigException
583
     * @throws Throwable
584
     */
585
    public function testExecute(): void
586
    {
587
        $db = $this->getConnection(true);
588
589
        $sql = 'INSERT INTO {{customer}}([[email]], [[name]], [[address]])'
590
            . ' VALUES (\'[email protected]\', \'user4\', \'address4\')';
591
592
        $command = $db->createCommand($sql);
593
594
        $this->assertSame(1, $command->execute());
595
596
        $sql = 'SELECT COUNT(*) FROM {{customer}} WHERE [[name]] = \'user4\'';
597
        $command = $db->createCommand($sql);
598
599
        $this->assertSame('1', $command->queryScalar());
600
601
        $command = $db->createCommand('bad SQL');
602
603
        $this->expectException(Exception::class);
604
        $command->execute();
605
    }
606
607
    /**
608
     * @throws Exception
609
     * @throws InvalidConfigException
610
     */
611
    public function testGetSetSql(): void
612
    {
613
        $db = $this->getConnection();
614
615
        $sql = 'SELECT * FROM customer';
616
        $command = $db->createCommand($sql);
617
618
        $this->assertSame($sql, $command->getSql());
619
620
        $sql2 = 'SELECT * FROM order';
621
        $command->setSql($sql2);
622
623
        $this->assertSame($sql2, $command->getSql());
624
    }
625
626
    /**
627
     * @throws Exception
628
     * @throws InvalidConfigException
629
     * @throws Throwable
630
     */
631
    public function testInsert(): void
632
    {
633
        $db = $this->getConnection();
634
635
        $db->createCommand('DELETE FROM {{customer}}')->execute();
636
        $command = $db->createCommand();
637
        $command->insert(
638
            '{{customer}}',
639
            ['email' => '[email protected]', 'name' => 'test', 'address' => 'test address'],
640
        )->execute();
641
642
        $this->assertSame('1', $db->createCommand('SELECT COUNT(*) FROM {{customer}};')->queryScalar());
643
644
        $record = $db->createCommand('SELECT [[email]], [[name]], [[address]] FROM {{customer}}')->queryOne();
645
646
        $this->assertSame(['email' => '[email protected]', 'name' => 'test', 'address' => 'test address'], $record);
647
    }
648
649
    public function testInsertEx(): void
650
    {
651
        $db = $this->getConnection();
652
653
        $result = $db->createCommand()->insertEx(
654
            'customer',
655
            [
656
                'name' => 'testParams',
657
                'email' => '[email protected]',
658
                'address' => '1',
659
            ]
660
        );
661
662
        $this->assertIsArray($result);
663
        $this->assertNotNull($result['id']);
664
    }
665
666
    public function testInsertExpression(): void
667
    {
668
        $expression = '';
0 ignored issues
show
Unused Code introduced by
The assignment to $expression is dead and can be removed.
Loading history...
669
        $db = $this->getConnection();
670
671
        $db->createCommand('DELETE FROM {{order_with_null_fk}}')->execute();
672
673
        $expression = match ($db->getName()) {
674
            'mysql' => "EXTRACT(YEAR FROM TIMESTAMP 'now')",
675
            'pgsql' => 'YEAR(NOW())',
676
            'sqlite' => "strftime('%Y')",
677
            'sqlsrv' => 'YEAR(GETDATE())',
678
            default => throw new InvalidArgumentException('Unsupported database type.'),
679
        };
680
681
        $command = $db->createCommand();
682
        $command->insert(
683
            '{{order_with_null_fk}}',
684
            ['created_at' => new Expression($expression), 'total' => 1],
685
        )->execute();
686
687
        $this->assertSame('1', $db->createCommand('SELECT COUNT(*) FROM {{order_with_null_fk}}')->queryScalar());
688
689
        $record = $db->createCommand('SELECT [[created_at]] FROM {{order_with_null_fk}}')->queryOne();
690
691
        $this->assertSame(['created_at' => date('Y')], $record);
692
    }
693
694
    public function testsInsertQueryAsColumnValue(): void
695
    {
696
        $time = time();
697
698
        $db = $this->getConnection();
699
700
        $db->createCommand('DELETE FROM {{order_with_null_fk}}')->execute();
701
        $command = $db->createCommand();
702
        $command->insert('{{order}}', ['customer_id' => 1, 'created_at' => $time, 'total' => 42])->execute();
703
704
        if ($db->getName() === 'pgsql') {
705
            $orderId = $db->getLastInsertID('public.order_id_seq');
706
        } else {
707
            $orderId = $db->getLastInsertID();
708
        }
709
710
        $columnValueQuery = new Query($db);
711
        $columnValueQuery->select('created_at')->from('{{order}}')->where(['id' => $orderId]);
712
        $command = $db->createCommand();
713
        $command->insert(
714
            '{{order_with_null_fk}}',
715
            ['customer_id' => $orderId, 'created_at' => $columnValueQuery, 'total' => 42],
716
        )->execute();
717
718
        $this->assertSame(
719
            "$time",
720
            $db->createCommand(
721
                'SELECT [[created_at]] FROM {{order_with_null_fk}} WHERE [[customer_id]] = ' . $orderId,
722
            )->queryScalar()
723
        );
724
725
        $db->createCommand('DELETE FROM {{order_with_null_fk}}')->execute();
726
        $db->createCommand('DELETE FROM {{order}} WHERE [[id]] = ' . $orderId)->execute();
727
    }
728
729
    /**
730
     * Test INSERT INTO ... SELECT SQL statement.
731
     */
732
    public function testInsertSelect(): void
733
    {
734
        $db = $this->getConnection();
735
736
        $db->createCommand('DELETE FROM {{customer}}')->execute();
737
        $command = $db->createCommand();
738
        $command->insert(
739
            '{{customer}}',
740
            ['email' => '[email protected]', 'name' => 'test', 'address' => 'test address'],
741
        )->execute();
742
        $query = (new Query($db))
743
            ->select(['{{customer}}.[[email]] as name', '[[name]] as email', '[[address]]'])
744
            ->from('{{customer}}')
745
            ->where(['and', ['<>', 'name', 'foo'], ['status' => [0, 1, 2, 3]]]);
746
        $command = $db->createCommand();
747
        $command->insert('{{customer}}', $query)->execute();
748
749
        $this->assertSame('2', $db->createCommand('SELECT COUNT(*) FROM {{customer}}')->queryScalar());
750
751
        $record = $db->createCommand('SELECT [[email]], [[name]], [[address]] FROM {{customer}}')->queryAll();
752
753
        $this->assertSame(
754
            [
755
                ['email' => '[email protected]', 'name' => 'test', 'address' => 'test address'],
756
                ['email' => 'test', 'name' => '[email protected]', 'address' => 'test address'],
757
            ],
758
            $record,
759
        );
760
    }
761
762
    /**
763
     * Test INSERT INTO ... SELECT SQL statement with alias syntax.
764
     */
765
    public function testInsertSelectAlias(): void
766
    {
767
        $db = $this->getConnection();
768
769
        $db->createCommand('DELETE FROM {{customer}}')->execute();
770
        $command = $db->createCommand();
771
        $command->insert(
772
            '{{customer}}',
773
            ['email' => '[email protected]', 'name' => 'test', 'address' => 'test address'],
774
        )->execute();
775
        $query = (new Query($db))
776
            ->select(['email' => '{{customer}}.[[email]]', 'address' => 'name', 'name' => 'address'])
777
            ->from('{{customer}}')
778
            ->where(['and', ['<>', 'name', 'foo'], ['status' => [0, 1, 2, 3]]]);
779
        $command = $db->createCommand();
780
        $command->insert('{{customer}}', $query)->execute();
781
782
        $this->assertSame('2', $db->createCommand('SELECT COUNT(*) FROM {{customer}}')->queryScalar());
783
784
        $record = $db->createCommand('SELECT [[email]], [[name]], [[address]] FROM {{customer}}')->queryAll();
785
786
        $this->assertSame(
787
            [
788
                ['email' => '[email protected]', 'name' => 'test', 'address' => 'test address'],
789
                ['email' => '[email protected]', 'name' => 'test address', 'address' => 'test'],
790
            ],
791
            $record,
792
        );
793
    }
794
795
    public function testInsertToBlob(): void
796
    {
797
        $db = $this->getConnection();
798
799
        $db->createCommand()->delete('type')->execute();
800
801
        $columns = [
802
            'int_col' => 1,
803
            'char_col' => 'test',
804
            'float_col' => 3.14,
805
            'bool_col' => true,
806
            'blob_col' => serialize(['test' => 'data', 'num' => 222]),
807
        ];
808
        $db->createCommand()->insert('type', $columns)->execute();
809
        $result = $db->createCommand('SELECT [[blob_col]] FROM {{type}}')->queryOne();
810
811
        $this->assertIsArray($result);
812
813
        $resultBlob = is_resource($result['blob_col']) ? stream_get_contents($result['blob_col']) : $result['blob_col'];
814
815
        $this->assertSame($columns['blob_col'], $resultBlob);
816
    }
817
818
    public function testIntegrityViolation(): void
819
    {
820
        $db = $this->getConnection();
821
822
        $this->expectException(IntegrityException::class);
823
824
        $sql = 'INSERT INTO {{profile}}([[id]], [[description]]) VALUES (123, \'duplicate\')';
825
        $command = $db->createCommand($sql);
826
        $command->execute();
827
        $command->execute();
828
    }
829
830
    public function testLastInsertId(): void
831
    {
832
        $db = $this->getConnection();
833
834
        $sql = 'INSERT INTO {{profile}}([[description]]) VALUES (\'non duplicate\')';
835
        $command = $db->createCommand($sql);
836
        $command->execute();
837
838
        $this->assertSame('3', $db->getLastInsertID());
839
    }
840
841
    public function testLastInsertIdException(): void
842
    {
843
        $db = $this->getConnection();
844
845
        $db->close();
846
847
        $this->expectException(InvalidCallException::class);
848
849
        $db->getLastInsertID();
850
    }
851
852
    /**
853
     * Verify that {{}} are not going to be replaced in parameters.
854
     */
855
    public function testNoTablenameReplacement(): void
856
    {
857
        $db = $this->getConnection();
858
859
        $db->createCommand()->insert(
860
            '{{customer}}',
861
            ['name' => 'Some {{weird}} name', 'email' => '[email protected]', 'address' => 'Some {{%weird}} address'],
862
        )->execute();
863
864
        if ($db->getName() === 'pgsql') {
865
            $customerId = $db->getLastInsertID('public.customer_id_seq');
866
        } else {
867
            $customerId = $db->getLastInsertID();
868
        }
869
870
        $customer = $db->createCommand('SELECT * FROM {{customer}} WHERE id=' . $customerId)->queryOne();
871
872
        $this->assertIsArray($customer);
873
        $this->assertSame('Some {{weird}} name', $customer['name']);
874
        $this->assertSame('Some {{%weird}} address', $customer['address']);
875
876
        $db->createCommand()->update(
877
            '{{customer}}',
878
            ['name' => 'Some {{updated}} name', 'address' => 'Some {{%updated}} address'],
879
            ['id' => $customerId],
880
        )->execute();
881
        $customer = $db->createCommand('SELECT * FROM {{customer}} WHERE id=' . $customerId)->queryOne();
882
883
        $this->assertIsArray($customer);
884
        $this->assertSame('Some {{updated}} name', $customer['name']);
885
        $this->assertSame('Some {{%updated}} address', $customer['address']);
886
    }
887
888
    /**
889
     * @throws Exception
890
     * @throws InvalidConfigException
891
     */
892
    public function testPrepareCancel(): void
893
    {
894
        $db = $this->getConnection();
895
896
        $command = $db->createCommand('SELECT * FROM {{customer}}');
897
898
        $this->assertNull($command->getPdoStatement());
899
900
        $command->prepare();
901
902
        $this->assertNotNull($command->getPdoStatement());
903
904
        $command->cancel();
905
906
        $this->assertNull($command->getPdoStatement());
907
    }
908
909
    /**
910
     * @throws Exception|InvalidConfigException|Throwable
911
     */
912
    public function testQuery(): void
913
    {
914
        $db = $this->getConnection();
915
916
        $sql = 'SELECT * FROM {{customer}}';
917
        $reader = $db->createCommand($sql)->query();
918
919
        $this->assertInstanceOf(DataReaderInterface::class, $reader);
920
921
        /**
922
         * Next line is commented by reason:: For sqlite & pgsql result may be incorrect
923
         * $this->assertSame(3, $reader->count());
924
         */
925
        $this->assertIsInt($reader->count());
926
927
        foreach ($reader as $row) {
928
            $this->assertIsArray($row);
929
            $this->assertTrue((is_countable($row) ? count($row) : 0) >= 6);
930
        }
931
932
        $command = $db->createCommand('bad SQL');
933
934
        $this->expectException(Exception::class);
935
936
        $command->query();
937
    }
938
939
    public function testQueryAll(): void
940
    {
941
        $db = $this->getConnection(true);
942
943
        $rows = $db->createCommand('SELECT [[id]],[[name]] FROM {{customer}}')->queryAll();
944
945
        $this->assertIsArray($rows);
946
        $this->assertCount(3, $rows);
947
948
        $row = $rows[2];
949
950
        $this->assertSame('3', $row['id']);
951
        $this->assertSame('user3', $row['name']);
952
        $this->assertTrue(is_array($rows) && count($rows) > 1 && (is_countable($rows[0]) ? count($rows[0]) : 0) === 2);
953
954
        $rows = $db->createCommand('SELECT * FROM {{customer}} WHERE [[id]] = 10')->queryAll();
955
956
        $this->assertSame([], $rows);
957
    }
958
959
    public function testQueryColumn(): void
960
    {
961
        $db = $this->getConnection(true);
962
963
        $sql = 'SELECT * FROM {{customer}}';
964
        $column = $db->createCommand($sql)->queryColumn();
965
966
        $this->assertSame(['1', '2', '3'], $column);
967
        $this->assertIsArray($column);
968
969
        $command = $db->createCommand('SELECT [[id]] FROM {{customer}} WHERE [[id]] = 10');
970
971
        $this->assertEmpty($command->queryColumn());
972
    }
973
974
    public function testQueryOne(): void
975
    {
976
        $db = $this->getConnection(true);
977
978
        $sql = 'SELECT * FROM {{customer}} ORDER BY [[id]]';
979
        $row = $db->createCommand($sql)->queryOne();
980
981
        $this->assertIsArray($row);
982
        $this->assertSame('1', $row['id']);
983
        $this->assertSame('user1', $row['name']);
984
985
        $sql = 'SELECT * FROM {{customer}} ORDER BY [[id]]';
986
        $command = $db->createCommand($sql);
987
        $command->prepare();
988
        $row = $command->queryOne();
989
990
        $this->assertIsArray($row);
991
        $this->assertSame('1', $row['id']);
992
        $this->assertSame('user1', $row['name']);
993
994
        $sql = 'SELECT * FROM {{customer}} WHERE [[id]] = 10';
995
        $command = $db->createCommand($sql);
996
997
        $this->assertNull($command->queryOne());
998
    }
999
1000
    public function testQueryScalar(): void
1001
    {
1002
        $db = $this->getConnection(true);
1003
1004
        $sql = 'SELECT * FROM {{customer}} ORDER BY [[id]]';
1005
1006
        $this->assertSame('1', $db->createCommand($sql)->queryScalar());
1007
1008
        $sql = 'SELECT [[id]] FROM {{customer}} ORDER BY [[id]]';
1009
        $command = $db->createCommand($sql);
1010
        $command->prepare();
1011
1012
        $this->assertSame('1', $command->queryScalar());
1013
1014
        $command = $db->createCommand('SELECT [[id]] FROM {{customer}} WHERE [[id]] = 10');
1015
1016
        $this->assertFalse($command->queryScalar());
1017
    }
1018
1019
    public function testRenameTable(): void
1020
    {
1021
        $db = $this->getConnection(true);
1022
1023
        $fromTableName = 'type';
1024
        $toTableName = 'new_type';
1025
1026
        if ($db->getSchema()->getTableSchema($toTableName) !== null) {
1027
            $db->createCommand()->dropTable($toTableName)->execute();
1028
        }
1029
1030
        $this->assertNotNull($db->getSchema()->getTableSchema($fromTableName));
1031
        $this->assertNull($db->getSchema()->getTableSchema($toTableName));
1032
1033
        $db->createCommand()->renameTable($fromTableName, $toTableName)->execute();
1034
1035
        $this->assertNull($db->getSchema()->getTableSchema($fromTableName, true));
1036
        $this->assertNotNull($db->getSchema()->getTableSchema($toTableName, true));
1037
    }
1038
1039
    public function testRetryHandler(): void
1040
    {
1041
        $db = $this->getConnection();
1042
1043
        $this->assertNull($db->getTransaction());
1044
1045
        $db->createCommand("INSERT INTO {{profile}}([[description]]) VALUES('command retry')")->execute();
1046
1047
        $this->assertNull($db->getTransaction());
1048
        $this->assertSame(
1049
            '1',
1050
            $db->createCommand(
1051
                "SELECT COUNT(*) FROM {{profile}} WHERE [[description]] = 'command retry'",
1052
            )->queryScalar(),
1053
        );
1054
1055
        $attempts = null;
1056
        $hitHandler = false;
1057
        $hitCatch = false;
1058
1059
        $command = $db->createCommand("INSERT INTO {{profile}}([[id]], [[description]]) VALUES(1, 'command retry')");
1060
1061
        Assert::invokeMethod(
1062
            $command,
1063
            'setRetryHandler',
1064
            [
1065
                static function ($exception, $attempt) use (&$attempts, &$hitHandler) {
1066
                    $attempts = $attempt;
1067
                    $hitHandler = true;
1068
1069
                    return $attempt <= 2;
1070
                },
1071
            ],
1072
        );
1073
1074
        try {
1075
            $command->execute();
1076
        } catch (Exception $e) {
1077
            $hitCatch = true;
1078
1079
            $this->assertInstanceOf(IntegrityException::class, $e);
1080
        }
1081
1082
        $this->assertNull($db->getTransaction());
1083
        $this->assertSame(3, $attempts);
1084
        $this->assertTrue($hitHandler);
1085
        $this->assertTrue($hitCatch);
1086
    }
1087
1088
    public function testTransaction(): void
1089
    {
1090
        $db = $this->getConnection();
1091
1092
        $this->assertNull($db->getTransaction());
1093
1094
        $command = $db->createCommand("INSERT INTO {{profile}}([[description]]) VALUES('command transaction')");
1095
1096
        Assert::invokeMethod($command, 'requireTransaction');
1097
1098
        $command->execute();
1099
1100
        $this->assertNull($db->getTransaction());
1101
        $this->assertSame(
1102
            '1',
1103
            $db->createCommand(
1104
                "SELECT COUNT(*) FROM {{profile}} WHERE [[description]] = 'command transaction'",
1105
            )->queryScalar()
1106
        );
1107
    }
1108
1109
    public function testTruncateTable(): void
1110
    {
1111
        $db = $this->getConnection();
1112
1113
        $rows = $db->createCommand('SELECT * FROM {{animal}}')->queryAll();
1114
1115
        $this->assertCount(2, $rows);
1116
1117
        $db->createCommand()->truncateTable('animal')->execute();
1118
        $rows = $db->createCommand('SELECT * FROM {{animal}}')->queryAll();
1119
1120
        $this->assertCount(0, $rows);
1121
    }
1122
1123
    protected function performAndCompareUpsertResult(ConnectionInterface $db, array $data): void
1124
    {
1125
        $params = $data['params'];
1126
        $expected = $data['expected'] ?? $params[1];
1127
1128
        $command = $db->createCommand();
1129
        call_user_func_array([$command, 'upsert'], $params);
1130
        $command->execute();
1131
1132
        $actual = (new Query($db))
1133
            ->select([
1134
                'email',
1135
                'address' => new Expression($this->upsertTestCharCast),
0 ignored issues
show
Bug Best Practice introduced by
The property upsertTestCharCast does not exist on Yiisoft\Db\Tests\AbstractCommandTest. Did you maybe forget to declare it?
Loading history...
1136
                'status',
1137
            ])
1138
            ->from('T_upsert')
1139
            ->one();
1140
1141
        $this->assertSame($expected, $actual, $this->upsertTestCharCast);
1142
    }
1143
}
1144