Passed
Pull Request — master (#419)
by Def
29:10 queued 26:33
created

CommonSchemaTest::testWorkWithUniqueConstraint()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 28
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 17
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 28
rs 9.7
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Tests\Common;
6
7
use JsonException;
8
use PDO;
9
use Throwable;
10
use Yiisoft\Db\Connection\ConnectionInterface;
11
use Yiisoft\Db\Constraint\CheckConstraint;
12
use Yiisoft\Db\Constraint\Constraint;
13
use Yiisoft\Db\Constraint\DefaultValueConstraint;
14
use Yiisoft\Db\Constraint\ForeignKeyConstraint;
15
use Yiisoft\Db\Constraint\IndexConstraint;
16
use Yiisoft\Db\Exception\Exception;
17
use Yiisoft\Db\Exception\InvalidCallException;
18
use Yiisoft\Db\Exception\InvalidConfigException;
19
use Yiisoft\Db\Exception\NotSupportedException;
20
use Yiisoft\Db\QueryBuilder\QueryBuilder;
21
use Yiisoft\Db\Schema\Schema;
22
use Yiisoft\Db\Schema\TableSchemaInterface;
23
use Yiisoft\Db\Tests\AbstractSchemaTest;
24
use Yiisoft\Db\Tests\Support\AnyCaseValue;
25
use Yiisoft\Db\Tests\Support\AnyValue;
26
use Yiisoft\Db\Tests\Support\DbHelper;
27
28
use function array_keys;
29
use function count;
30
use function gettype;
31
use function is_array;
32
use function is_object;
33
use function json_encode;
34
use function ksort;
35
use function mb_chr;
36
use function sort;
37
use function strtolower;
38
39
abstract class CommonSchemaTest extends AbstractSchemaTest
40
{
41
    /**
42
     * @throws Exception
43
     * @throws InvalidConfigException
44
     * @throws Throwable
45
     */
46
    public function testColumnComment(): void
47
    {
48
        $db = $this->getConnection();
49
50
        $command = $db->createCommand();
51
        $schema = $db->getSchema();
52
53
        if ($schema->getTableSchema('testCommentTable') !== null) {
54
            $command->dropTable('testCommentTable')->execute();
55
        }
56
57
        $command->createTable('testCommentTable', ['bar' => Schema::TYPE_INTEGER,])->execute();
58
        $command->addCommentOnColumn('testCommentTable', 'bar', 'Test comment for column.')->execute();
59
60
        $this->assertSame(
61
            'Test comment for column.',
62
            $schema->getTableSchema('testCommentTable')->getColumn('bar')->getComment(),
63
        );
64
    }
65
66
    /**
67
     * @dataProvider \Yiisoft\Db\Tests\Provider\SchemaProvider::columns()
68
     */
69
    public function testColumnSchema(array $columns): void
70
    {
71
        $this->columnSchema($columns);
72
    }
73
74
    public function testCompositeFk(): void
75
    {
76
        $db = $this->getConnection(true);
77
78
        $schema = $db->getSchema();
79
        $table = $schema->getTableSchema('composite_fk');
80
81
        $this->assertNotNull($table);
82
83
        $fk = $table->getForeignKeys();
84
85
        $expectedKey = match ($db->getName()) {
86
            'mysql', 'sqlsrv' => $fk['FK_composite_fk_order_item'],
87
            default => $fk['fk_composite_fk_order_item'],
88
        };
89
90
        $this->assertCount(1, $fk);
91
        $this->assertTrue(isset($expectedKey));
92
        $this->assertSame('order_item', $expectedKey[0]);
93
        $this->assertSame('order_id', $expectedKey['order_id']);
94
        $this->assertSame('item_id', $expectedKey['item_id']);
95
    }
96
97
    public function testContraintTablesExistance(): void
98
    {
99
        $db = $this->getConnection(true);
100
101
        $tableNames = ['T_constraints_1', 'T_constraints_2', 'T_constraints_3', 'T_constraints_4'];
102
        $schema = $db->getSchema();
103
104
        foreach ($tableNames as $tableName) {
105
            $tableSchema = $schema->getTableSchema($tableName);
106
            $this->assertInstanceOf(TableSchemaInterface::class, $tableSchema, $tableName);
107
        }
108
    }
109
110
    /**
111
     * @throws Exception
112
     * @throws InvalidConfigException
113
     * @throws Throwable
114
     */
115
    public function testFindUniquesIndexes(): void
116
    {
117
        $db = $this->getConnection();
118
119
        $command = $db->createCommand();
120
        $schema = $db->getSchema();
121
122
        try {
123
            $command->dropTable('uniqueIndex')->execute();
124
        } catch (Exception) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
125
        }
126
127
        $command->createTable(
128
            'uniqueIndex',
129
            ['somecol' => 'string', 'someCol2' => 'string', 'someCol3' => 'string'],
130
        )->execute();
131
        $tableSchema = $schema->getTableSchema('uniqueIndex', true);
132
133
        $this->assertNotNull($tableSchema);
134
135
        $uniqueIndexes = $schema->findUniqueIndexes($tableSchema);
0 ignored issues
show
Bug introduced by
It seems like $tableSchema can also be of type null; however, parameter $table of Yiisoft\Db\Schema\Schema...ce::findUniqueIndexes() does only seem to accept Yiisoft\Db\Schema\TableSchemaInterface, maybe add an additional type check? ( Ignorable by Annotation )

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

135
        $uniqueIndexes = $schema->findUniqueIndexes(/** @scrutinizer ignore-type */ $tableSchema);
Loading history...
136
137
        $this->assertSame([], $uniqueIndexes);
138
139
        $command->createIndex('somecolUnique', 'uniqueIndex', 'somecol', QueryBuilder::INDEX_UNIQUE)->execute();
140
        $tableSchema = $schema->getTableSchema('uniqueIndex', true);
141
142
        $this->assertNotNull($tableSchema);
143
144
        $uniqueIndexes = $schema->findUniqueIndexes($tableSchema);
145
146
        $this->assertSame(['somecolUnique' => ['somecol']], $uniqueIndexes);
147
148
        /**
149
         * Create another column with upper case letter that fails postgres.
150
         *
151
         * @link https://github.com/yiisoft/yii2/issues/10613
152
         */
153
        $command->createIndex('someCol2Unique', 'uniqueIndex', 'someCol2', QueryBuilder::INDEX_UNIQUE)->execute();
154
        $tableSchema = $schema->getTableSchema('uniqueIndex', true);
155
156
        $this->assertNotNull($tableSchema);
157
158
        $uniqueIndexes = $schema->findUniqueIndexes($tableSchema);
159
160
        $this->assertSame(['someCol2Unique' => ['someCol2'], 'somecolUnique' => ['somecol']], $uniqueIndexes);
161
162
        /** @link https://github.com/yiisoft/yii2/issues/13814 */
163
        $command->createIndex('another unique index', 'uniqueIndex', 'someCol3', QueryBuilder::INDEX_UNIQUE)->execute();
164
        $tableSchema = $schema->getTableSchema('uniqueIndex', true);
165
166
        $this->assertNotNull($tableSchema);
167
168
        $uniqueIndexes = $schema->findUniqueIndexes($tableSchema);
169
170
        $this->assertSame(
171
            ['another unique index' => ['someCol3'], 'someCol2Unique' => ['someCol2'], 'somecolUnique' => ['somecol']],
172
            $uniqueIndexes,
173
        );
174
    }
175
176
    public function testGetColumnNoExist(): void
177
    {
178
        $db = $this->getConnection(true);
179
180
        $schema = $db->getSchema();
181
        $table = $schema->getTableSchema('negative_default_values');
182
183
        $this->assertNotNull($table);
184
        $this->assertNull($table->getColumn('no_exist'));
185
    }
186
187
    public function testGetDefaultSchema(): void
188
    {
189
        $db = $this->getConnection();
190
191
        $schema = $db->getSchema();
192
193
        $this->assertNull($schema->getDefaultSchema());
194
    }
195
196
    public function testGetNonExistingTableSchema(): void
197
    {
198
        $db = $this->getConnection();
199
200
        $schema = $db->getSchema();
201
202
        $this->assertNull($schema->getTableSchema('nonexisting_table'));
203
    }
204
205
    /**
206
     * @throws Exception
207
     * @throws InvalidCallException
208
     * @throws InvalidConfigException
209
     * @throws Throwable
210
     */
211
    public function testGetPrimaryKey(): void
212
    {
213
        $db = $this->getConnection(true);
214
215
        $command = $db->createCommand();
216
217
        $insertResult = $command->insertEx('animal', ['type' => 'cat']);
218
        $selectResult = $command->setSql(
219
            DbHelper::replaceQuotes(
220
                <<<SQL
221
                SELECT [[id]] FROM [[animal]] WHERE [[type]] = 'cat'
222
                SQL,
223
                $db->getName(),
224
            )
225
        )->queryOne();
226
227
        $this->assertIsArray($insertResult);
228
        $this->assertIsArray($selectResult);
229
        $this->assertEquals($selectResult['id'], $insertResult['id']);
230
    }
231
232
    public function testGetSchemaChecks(): void
233
    {
234
        $db = $this->getConnection();
235
236
        $schema = $db->getSchema();
237
        $tableChecks = $schema->getSchemaChecks();
238
239
        $this->assertIsArray($tableChecks);
240
241
        foreach ($tableChecks as $checks) {
242
            $this->assertIsArray($checks);
243
            $this->assertContainsOnlyInstancesOf(CheckConstraint::class, $checks);
244
        }
245
    }
246
247
    public function testGetSchemaDefaultValues(): void
248
    {
249
        $db = $this->getConnection();
250
251
        $schema = $db->getSchema();
252
        $tableDefaultValues = $schema->getSchemaDefaultValues();
253
254
        $this->assertIsArray($tableDefaultValues);
255
256
        foreach ($tableDefaultValues as $defaultValues) {
257
            $this->assertIsArray($defaultValues);
258
            $this->assertContainsOnlyInstancesOf(DefaultValueConstraint::class, $defaultValues);
259
        }
260
    }
261
262
    public function testGetSchemaForeignKeys(): void
263
    {
264
        $db = $this->getConnection();
265
266
        $schema = $db->getSchema();
267
        $tableForeignKeys = $schema->getSchemaForeignKeys();
268
269
        $this->assertIsArray($tableForeignKeys);
270
271
        foreach ($tableForeignKeys as $foreignKeys) {
272
            $this->assertIsArray($foreignKeys);
273
            $this->assertContainsOnlyInstancesOf(ForeignKeyConstraint::class, $foreignKeys);
274
        }
275
    }
276
277
    public function testGetSchemaIndexes(): void
278
    {
279
        $db = $this->getConnection();
280
281
        $schema = $db->getSchema();
282
        $tableIndexes = $schema->getSchemaIndexes();
283
284
        $this->assertIsArray($tableIndexes);
285
286
        foreach ($tableIndexes as $indexes) {
287
            $this->assertIsArray($indexes);
288
            $this->assertContainsOnlyInstancesOf(IndexConstraint::class, $indexes);
289
        }
290
    }
291
292
    public function testGetSchemaPrimaryKeys(): void
293
    {
294
        $db = $this->getConnection();
295
296
        $schema = $db->getSchema();
297
        $tablePks = $schema->getSchemaPrimaryKeys();
298
299
        $this->assertIsArray($tablePks);
300
        $this->assertContainsOnlyInstancesOf(Constraint::class, $tablePks);
301
    }
302
303
    public function testGetSchemaUniques(): void
304
    {
305
        $db = $this->getConnection();
306
307
        $schema = $db->getSchema();
308
        $tableUniques = $schema->getSchemaUniques();
309
310
        $this->assertIsArray($tableUniques);
311
312
        foreach ($tableUniques as $uniques) {
313
            $this->assertIsArray($uniques);
314
            $this->assertContainsOnlyInstancesOf(Constraint::class, $uniques);
315
        }
316
    }
317
318
    /**
319
     * @dataProvider \Yiisoft\Db\Tests\Provider\SchemaProvider::columnsTypeChar()
320
     */
321
    public function testGetStringFieldsSize(
322
        string $columnName,
323
        string $columnType,
324
        int|null $columnSize,
325
        string $columnDbType
326
    ): void {
327
        $db = $this->getConnection(true);
328
329
        $schema = $db->getSchema();
330
        $tableSchema = $schema->getTableSchema('type');
331
332
        $this->assertInstanceOf(TableSchemaInterface::class, $tableSchema);
333
334
        $columns = $tableSchema->getColumns();
335
336
        foreach ($columns as $name => $column) {
337
            $type = $column->getType();
338
            $size = $column->getSize();
339
            $dbType = $column->getDbType();
340
341
            if ($name === $columnName) {
342
                $this->assertSame($columnType, $type);
343
                $this->assertSame($columnSize, $size);
344
                $this->assertSame($columnDbType, $dbType);
345
            }
346
        }
347
    }
348
349
    public function testGetTableChecks(): void
350
    {
351
        $db = $this->getConnection(true);
352
353
        $schema = $db->getSchema();
354
        $tableChecks = $schema->getTableChecks('T_constraints_1');
355
356
        $this->assertIsArray($tableChecks);
357
358
        $this->assertContainsOnlyInstancesOf(CheckConstraint::class, $tableChecks);
359
    }
360
361
    /**
362
     * @dataProvider \Yiisoft\Db\Tests\Provider\SchemaProvider::pdoAttributes()
363
     *
364
     * @throws NotSupportedException
365
     */
366
    public function testGetTableNames(array $pdoAttributes): void
367
    {
368
        $db = $this->getConnection(true);
369
370
        foreach ($pdoAttributes as $name => $value) {
371
            if ($name === PDO::ATTR_EMULATE_PREPARES) {
372
                continue;
373
            }
374
375
            $db->getPDO()?->setAttribute($name, $value);
376
        }
377
378
        $schema = $db->getSchema();
379
        $tablesNames = $schema->getTableNames();
380
381
        $this->assertContains('customer', $tablesNames);
382
        $this->assertContains('category', $tablesNames);
383
        $this->assertContains('item', $tablesNames);
384
        $this->assertContains('order', $tablesNames);
385
        $this->assertContains('order_item', $tablesNames);
386
        $this->assertContains('type', $tablesNames);
387
        $this->assertContains('animal', $tablesNames);
388
        $this->assertContains('animal_view', $tablesNames);
389
    }
390
391
    /**
392
     * @dataProvider \Yiisoft\Db\Tests\Provider\SchemaProvider::tableSchema()
393
     */
394
    public function testGetTableSchema(string $name, string $expectedName): void
395
    {
396
        $db = $this->getConnection(true);
397
398
        $tableSchema = $db->getSchema()->getTableSchema($name);
399
400
        $this->assertInstanceOf(TableSchemaInterface::class, $tableSchema);
401
        $this->assertEquals($expectedName, $tableSchema->getName());
402
    }
403
404
    /**
405
     * @dataProvider \Yiisoft\Db\Tests\Provider\SchemaProvider::pdoAttributes()
406
     *
407
     * @throws NotSupportedException
408
     */
409
    public function testGetTableSchemas(array $pdoAttributes): void
410
    {
411
        $db = $this->getConnection(true);
412
413
        foreach ($pdoAttributes as $name => $value) {
414
            if ($name === PDO::ATTR_EMULATE_PREPARES) {
415
                continue;
416
            }
417
418
            $db->getPDO()?->setAttribute($name, $value);
419
        }
420
421
        $schema = $db->getSchema();
422
        $tables = $schema->getTableSchemas();
423
424
        $this->assertCount(count($schema->getTableNames()), $tables);
425
426
        foreach ($tables as $table) {
427
            $this->assertInstanceOf(TableSchemaInterface::class, $table);
428
        }
429
    }
430
431
    /**
432
     * @throws InvalidConfigException
433
     * @throws NotSupportedException
434
     * @throws Exception
435
     */
436
    public function testGetTableSchemaWithAttrCase(): void
437
    {
438
        $db = $this->getConnection(true);
439
440
        $schema = $db->getSchema();
441
        $db->getActivePDO()->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER);
442
443
        $this->assertCount(count($schema->getTableNames()), $schema->getTableSchemas());
444
445
        $db->getActivePDO()->setAttribute(PDO::ATTR_CASE, PDO::CASE_UPPER);
446
447
        $this->assertCount(count($schema->getTableNames()), $schema->getTableSchemas());
448
    }
449
450
    public function testGetViewNames(): void
451
    {
452
        $db = $this->getConnection(true);
453
454
        $schema = $db->getSchema();
455
        $views = $schema->getViewNames();
456
457
        $this->assertSame(['animal_view'], $views);
458
    }
459
460
    public function testNegativeDefaultValues(): void
461
    {
462
        $schema = $this->getConnection(true);
463
464
        $schema = $schema->getSchema();
465
        $table = $schema->getTableSchema('negative_default_values');
466
467
        $this->assertNotNull($table);
468
        $this->assertSame(-123, $table->getColumn('tinyint_col')?->getDefaultValue());
469
        $this->assertSame(-123, $table->getColumn('smallint_col')?->getDefaultValue());
470
        $this->assertSame(-123, $table->getColumn('int_col')?->getDefaultValue());
471
        $this->assertSame(-123, $table->getColumn('bigint_col')?->getDefaultValue());
472
        $this->assertSame(-12345.6789, $table->getColumn('float_col')?->getDefaultValue());
473
        $this->assertEquals(-33.22, $table->getColumn('numeric_col')?->getDefaultValue());
474
    }
475
476
    /**
477
     * @throws Exception
478
     * @throws InvalidConfigException
479
     * @throws Throwable
480
     */
481
    public function testQuoterEscapingValue(): void
482
    {
483
        $db = $this->getConnection(true);
484
485
        $quoter = $db->getQuoter();
486
        $db->createCommand(
487
            <<<SQL
488
            DELETE FROM [[quoter]]
489
            SQL
490
        )->execute();
491
        $data = $this->generateQuoterEscapingValues();
492
493
        foreach ($data as $index => $value) {
494
            $quotedName = $quoter->quoteValue('testValue_' . $index);
495
            $quoteValue = $quoter->quoteValue($value);
496
            $db->createCommand(
497
                <<<SQL
498
                INSERT INTO [[quoter]] ([[name]], [[description]]) VALUES ($quotedName, $quoteValue)
499
                SQL,
500
            )->execute();
501
            $result = $db->createCommand(
502
                <<<SQL
503
                SELECT * FROM [[quoter]] WHERE [[name]]=$quotedName
504
                SQL,
505
            )->queryOne();
506
507
            $this->assertSame($value, $result['description']);
508
        }
509
    }
510
511
    /**
512
     * @depends testSchemaCache
513
     */
514
    public function testRefreshTableSchema(): void
515
    {
516
        $db = $this->getConnection(true);
517
518
        $schema = $db->getSchema();
519
        $schema->schemaCacheEnable(true);
520
        $noCacheTable = $schema->getTableSchema('type', true);
521
        $schema->refreshTableSchema('type');
522
        $refreshedTable = $schema->getTableSchema('type');
523
524
        $this->assertNotSame($noCacheTable, $refreshedTable);
525
    }
526
527
    /**
528
     * @throws Exception
529
     * @throws InvalidConfigException
530
     */
531
    public function testSchemaCache(): void
532
    {
533
        $db = $this->getConnection(true);
534
535
        $schema = $db->getSchema();
536
        $schema->schemaCacheEnable(true);
537
        $noCacheTable = $schema->getTableSchema('type', true);
538
        $cachedTable = $schema->getTableSchema('type');
539
540
        $this->assertSame($noCacheTable, $cachedTable);
541
542
        $db->createCommand()->renameTable('type', 'type_test');
543
        $noCacheTable = $schema->getTableSchema('type', true);
544
545
        $this->assertNotSame($noCacheTable, $cachedTable);
546
547
        $db->createCommand()->renameTable('type_test', 'type');
548
    }
549
550
    /**
551
     * @dataProvider \Yiisoft\Db\Tests\Provider\SchemaProvider::tableSchemaCachePrefixes()
552
     */
553
    public function testTableSchemaCacheWithTablePrefixes(
554
        string $tablePrefix,
555
        string $tableName,
556
        string $testTablePrefix,
557
        string $testTableName
558
    ): void {
559
        $db = $this->getConnection(true);
560
561
        $schema = $db->getSchema();
562
        $schema->schemaCacheEnable(true);
563
        $db->setTablePrefix($tablePrefix);
564
        $noCacheTable = $schema->getTableSchema($tableName, true);
565
566
        $this->assertInstanceOf(TableSchemaInterface::class, $noCacheTable);
567
568
        /* Compare */
569
        $db->setTablePrefix($testTablePrefix);
570
        $testNoCacheTable = $schema->getTableSchema($testTableName);
571
572
        $this->assertSame($noCacheTable, $testNoCacheTable);
573
574
        $db->setTablePrefix($tablePrefix);
575
        $schema->refreshTableSchema($tableName);
576
        $refreshedTable = $schema->getTableSchema($tableName);
577
578
        $this->assertInstanceOf(TableSchemaInterface::class, $refreshedTable);
579
        $this->assertNotSame($noCacheTable, $refreshedTable);
580
581
        /* Compare */
582
        $db->setTablePrefix($testTablePrefix);
583
        $schema->refreshTableSchema($testTablePrefix);
584
        $testRefreshedTable = $schema->getTableSchema($testTableName);
585
586
        $this->assertInstanceOf(TableSchemaInterface::class, $testRefreshedTable);
587
        $this->assertEquals($refreshedTable, $testRefreshedTable);
588
        $this->assertNotSame($testNoCacheTable, $testRefreshedTable);
589
    }
590
591
    /**
592
     * @dataProvider \Yiisoft\Db\Tests\Provider\SchemaProvider::constraints()
593
     *
594
     * @throws Exception
595
     * @throws JsonException
596
     */
597
    public function testTableSchemaConstraints(string $tableName, string $type, mixed $expected): void
598
    {
599
        if ($expected === false) {
600
            $this->expectException(NotSupportedException::class);
601
        }
602
603
        $db = $this->getConnection(true);
604
        $schema = $db->getSchema();
605
        $constraints = $schema->{'getTable' . ucfirst($type)}($tableName);
606
607
        $this->assertMetadataEquals($expected, $constraints);
608
609
        $db->close();
610
    }
611
612
    /**
613
     * @dataProvider \Yiisoft\Db\Tests\Provider\SchemaProvider::constraints()
614
     *
615
     * @throws Exception
616
     * @throws JsonException
617
     * @throws InvalidConfigException
618
     */
619
    public function testTableSchemaConstraintsWithPdoLowercase(string $tableName, string $type, mixed $expected): void
620
    {
621
        if ($expected === false) {
622
            $this->expectException(NotSupportedException::class);
623
        }
624
625
        $db = $this->getConnection(true);
626
627
        $schema = $db->getSchema();
628
        $db->getActivePDO()->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER);
629
        $constraints = $schema->{'getTable' . ucfirst($type)}($tableName, true);
630
631
        $this->assertMetadataEquals($expected, $constraints);
632
633
        $db->close();
634
    }
635
636
    /**
637
     * @dataProvider \Yiisoft\Db\Tests\Provider\SchemaProvider::constraints()
638
     *
639
     * @throws Exception
640
     * @throws JsonException
641
     * @throws InvalidConfigException
642
     */
643
    public function testTableSchemaConstraintsWithPdoUppercase(string $tableName, string $type, mixed $expected): void
644
    {
645
        if ($expected === false) {
646
            $this->expectException(NotSupportedException::class);
647
        }
648
649
        $db = $this->getConnection(true);
650
651
        $schema = $db->getSchema();
652
        $db->getActivePDO()->setAttribute(PDO::ATTR_CASE, PDO::CASE_UPPER);
653
        $constraints = $schema->{'getTable' . ucfirst($type)}($tableName, true);
654
655
        $this->assertMetadataEquals($expected, $constraints);
656
657
        $db->close();
658
    }
659
660
    private function generateQuoterEscapingValues(): array
661
    {
662
        $result = [];
663
        $stringLength = 16;
664
665
        for ($i = 32; $i < 128 - $stringLength; $i += $stringLength) {
666
            $str = '';
667
668
            for ($symbol = $i; $symbol < $i + $stringLength; $symbol++) {
669
                $str .= mb_chr($symbol, 'UTF-8');
670
            }
671
672
            $result[] = $str;
673
            $str = '';
674
675
            for ($symbol = $i; $symbol < $i + $stringLength; $symbol++) {
676
                $str .= mb_chr($symbol, 'UTF-8') . mb_chr($symbol, 'UTF-8');
677
            }
678
679
            $result[] = $str;
680
        }
681
682
        return $result;
683
    }
684
685
    /**
686
     * @throws JsonException
687
     */
688
    protected function assertMetadataEquals($expected, $actual): void
689
    {
690
        switch (strtolower(gettype($expected))) {
691
            case 'object':
692
                $this->assertIsObject($actual);
693
                break;
694
            case 'array':
695
                $this->assertIsArray($actual);
696
                break;
697
            case 'null':
698
                $this->assertNull($actual);
699
                break;
700
        }
701
702
        if (is_array($expected)) {
703
            $this->normalizeArrayKeys($expected, false);
704
            $this->normalizeArrayKeys($actual, false);
705
        }
706
707
        $this->normalizeConstraints($expected, $actual);
708
709
        if (is_array($expected)) {
710
            $this->normalizeArrayKeys($expected, true);
711
            $this->normalizeArrayKeys($actual, true);
712
        }
713
714
        $this->assertEquals($expected, $actual);
715
    }
716
717
    protected function columnSchema(array $columns, string $table = 'type'): void
718
    {
719
        $db = $this->getConnection(true);
720
721
        $table = $db->getTableSchema($table, true);
722
723
        $this->assertNotNull($table);
724
725
        $expectedColNames = array_keys($columns);
726
        sort($expectedColNames);
727
        $colNames = $table->getColumnNames();
728
        sort($colNames);
729
730
        $this->assertSame($expectedColNames, $colNames);
731
732
        foreach ($table->getColumns() as $name => $column) {
733
            $expected = $columns[$name];
734
735
            $this->assertSame(
736
                $expected['dbType'],
737
                $column->getDbType(),
738
                "dbType of column $name does not match. type is {$column->getType()}, dbType is {$column->getDbType()}."
739
            );
740
            $this->assertSame(
741
                $expected['phpType'],
742
                $column->getPhpType(),
743
                "phpType of column $name does not match. type is {$column->getType()}, dbType is {$column->getDbType()}."
744
            );
745
            $this->assertSame($expected['type'], $column->getType(), "type of column $name does not match.");
746
            $this->assertSame(
747
                $expected['allowNull'],
748
                $column->isAllowNull(),
749
                "allowNull of column $name does not match."
750
            );
751
            $this->assertSame(
752
                $expected['autoIncrement'],
753
                $column->isAutoIncrement(),
754
                "autoIncrement of column $name does not match."
755
            );
756
            $this->assertSame(
757
                $expected['enumValues'],
758
                $column->getEnumValues(),
759
                "enumValues of column $name does not match."
760
            );
761
            $this->assertSame($expected['size'], $column->getSize(), "size of column $name does not match.");
762
            $this->assertSame(
763
                $expected['precision'],
764
                $column->getPrecision(),
765
                "precision of column $name does not match."
766
            );
767
768
            $this->assertSame($expected['scale'], $column->getScale(), "scale of column $name does not match.");
769
770
            if (is_object($expected['defaultValue'])) {
771
                $this->assertIsObject(
772
                    $column->getDefaultValue(),
773
                    "defaultValue of column $name is expected to be an object but it is not."
774
                );
775
                $this->assertSame(
776
                    (string) $expected['defaultValue'],
777
                    (string) $column->getDefaultValue(),
778
                    "defaultValue of column $name does not match."
779
                );
780
            } else {
781
                $this->assertSame(
782
                    $expected['defaultValue'],
783
                    $column->getDefaultValue(),
784
                    "defaultValue of column $name does not match."
785
                );
786
            }
787
788
            /* Pgsql only */
789
            if (isset($expected['dimension'])) {
790
                /** @psalm-suppress UndefinedMethod */
791
                $this->assertSame(
792
                    $expected['dimension'],
793
                    $column->getDimension(),
794
                    "dimension of column $name does not match"
795
                );
796
            }
797
        }
798
799
        $db->close();
800
    }
801
802
    /**
803
     * @throws JsonException
804
     */
805
    private function normalizeArrayKeys(array &$array, bool $caseSensitive): void
806
    {
807
        $newArray = [];
808
809
        foreach ($array as $value) {
810
            if ($value instanceof Constraint) {
811
                $key = (array) $value;
812
                unset(
813
                    $key["\000Yiisoft\Db\Constraint\Constraint\000name"],
814
                    $key["\u0000Yiisoft\\Db\\Constraint\\ForeignKeyConstraint\u0000foreignSchemaName"]
815
                );
816
817
                foreach ($key as $keyName => $keyValue) {
818
                    if ($keyValue instanceof AnyCaseValue) {
819
                        $key[$keyName] = $keyValue->value;
820
                    } elseif ($keyValue instanceof AnyValue) {
821
                        $key[$keyName] = '[AnyValue]';
822
                    }
823
                }
824
825
                ksort($key, SORT_STRING);
826
                $newArray[$caseSensitive
827
                    ? json_encode($key, JSON_THROW_ON_ERROR)
828
                    : strtolower(json_encode($key, JSON_THROW_ON_ERROR))] = $value;
829
            } else {
830
                $newArray[] = $value;
831
            }
832
        }
833
834
        ksort($newArray, SORT_STRING);
835
        $array = $newArray;
836
    }
837
838
    private function normalizeConstraints($expected, $actual): void
839
    {
840
        if (is_array($expected)) {
841
            foreach ($expected as $key => $value) {
842
                if (!$value instanceof Constraint || !isset($actual[$key]) || !$actual[$key] instanceof Constraint) {
843
                    continue;
844
                }
845
846
                $this->normalizeConstraintPair($value, $actual[$key]);
847
            }
848
        } elseif ($expected instanceof Constraint && $actual instanceof Constraint) {
849
            $this->normalizeConstraintPair($expected, $actual);
850
        }
851
    }
852
853
    private function normalizeConstraintPair(Constraint $expectedConstraint, Constraint $actualConstraint): void
854
    {
855
        if ($expectedConstraint::class !== $actualConstraint::class) {
856
            return;
857
        }
858
859
        foreach (array_keys((array) $expectedConstraint) as $name) {
860
            if ($expectedConstraint->getName() instanceof AnyValue) {
861
                $actualConstraint->name($expectedConstraint->getName());
862
            } elseif ($expectedConstraint->getName() instanceof AnyCaseValue) {
863
                $actualConstraintName = $actualConstraint->getName();
864
865
                $this->assertIsString($actualConstraintName);
866
867
                $actualConstraint->name(new AnyCaseValue($actualConstraintName));
0 ignored issues
show
Bug introduced by
It seems like $actualConstraintName can also be of type null and object; however, parameter $value of Yiisoft\Db\Tests\Support...aseValue::__construct() does only seem to accept array|string, maybe add an additional type check? ( Ignorable by Annotation )

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

867
                $actualConstraint->name(new AnyCaseValue(/** @scrutinizer ignore-type */ $actualConstraintName));
Loading history...
868
            }
869
        }
870
    }
871
872
    public function testWorkWithUniqueConstraint(): void
873
    {
874
        $tableName = 'test_table_with';
875
        $constraintName = 't_constraint';
876
        $columnName = 't_field';
877
878
        $db = $this->getConnection();
879
880
        $this->createTableForIndexAndConstraintTests($db, $tableName, $columnName);
881
        $db->createCommand()->addUnique($constraintName, $tableName, $columnName)->execute();
882
883
        /** @var Constraint[] $constraints */
884
        $constraints = $db->getSchema()->getTableUniques($tableName, true);
885
886
        $this->assertIsArray($constraints);
887
        $this->assertCount(1, $constraints);
888
        $this->assertInstanceOf(Constraint::class, $constraints[0]);
889
        $this->assertEquals($constraintName, $constraints[0]->getName());
890
        $this->assertEquals([$columnName], $constraints[0]->getColumnNames());
891
892
        $db->createCommand()->dropUnique($constraintName, $tableName)->execute();
893
894
        $constraints = $db->getSchema()->getTableUniques($tableName, true);
895
896
        $this->assertIsArray($constraints);
897
        $this->assertCount(0, $constraints);
898
899
        $this->dropTableForIndexAndConstraintTests($db, $tableName);
900
    }
901
902
    public function testWorkWithCheckConstraint(): void
903
    {
904
        $tableName = 'test_table_with';
905
        $constraintName = 't_constraint';
906
        $columnName = 't_field';
907
908
        $db = $this->getConnection();
909
910
        $this->createTableForIndexAndConstraintTests($db, $tableName, $columnName, 'int');
911
        $db->createCommand()->addCheck($constraintName, $tableName, $db->getQuoter()->quoteColumnName($columnName) . ' > 0')->execute();
912
913
        /** @var CheckConstraint[] $constraints */
914
        $constraints = $db->getSchema()->getTableChecks($tableName, true);
915
916
        $this->assertIsArray($constraints);
917
        $this->assertCount(1, $constraints);
918
        $this->assertInstanceOf(CheckConstraint::class, $constraints[0]);
919
        $this->assertEquals($constraintName, $constraints[0]->getName());
920
        $this->assertEquals([$columnName], $constraints[0]->getColumnNames());
921
        $this->assertStringContainsString($columnName, $constraints[0]->getExpression());
922
923
        $db->createCommand()->dropCheck($constraintName, $tableName)->execute();
924
925
        $constraints = $db->getSchema()->getTableChecks($tableName, true);
926
927
        $this->assertIsArray($constraints);
928
        $this->assertCount(0, $constraints);
929
930
        $this->dropTableForIndexAndConstraintTests($db, $tableName);
931
    }
932
933
    public function testWorkWithDefaultValueConstraint(): void
934
    {
935
        $tableName = 'test_table_with';
936
        $constraintName = 't_constraint';
937
        $columnName = 't_field';
938
939
        $db = $this->getConnection();
940
941
        $this->createTableForIndexAndConstraintTests($db, $tableName, $columnName);
942
        $db->createCommand()->addDefaultValue($constraintName, $tableName, $columnName, 919)->execute();
943
944
        /** @var DefaultValueConstraint[] $constraints */
945
        $constraints = $db->getSchema()->getTableDefaultValues($tableName, true);
946
947
        $this->assertIsArray($constraints);
948
        $this->assertCount(1, $constraints);
949
        $this->assertInstanceOf(DefaultValueConstraint::class, $constraints[0]);
950
        $this->assertEquals($constraintName, $constraints[0]->getName());
951
        $this->assertEquals([$columnName], $constraints[0]->getColumnNames());
952
        $this->assertStringContainsString('919', $constraints[0]->getValue());
953
954
        $db->createCommand()->dropDefaultValue($constraintName, $tableName)->execute();
955
956
        $constraints = $db->getSchema()->getTableDefaultValues($tableName, true);
957
958
        $this->assertIsArray($constraints);
959
        $this->assertCount(0, $constraints);
960
961
        $this->dropTableForIndexAndConstraintTests($db, $tableName);
962
    }
963
964
    public function testWorkWithPrimaryKeyConstraint(): void
965
    {
966
        $tableName = 'test_table_with';
967
        $constraintName = 't_constraint';
968
        $columnName = 't_field';
969
970
        $db = $this->getConnection();
971
972
        $this->createTableForIndexAndConstraintTests($db, $tableName, $columnName);
973
        $db->createCommand()->addPrimaryKey($constraintName, $tableName, $columnName)->execute();
974
975
        $constraints = $db->getSchema()->getTablePrimaryKey($tableName, true);
976
977
        $this->assertInstanceOf(Constraint::class, $constraints);
978
        $this->assertEquals($constraintName, $constraints->getName());
979
        $this->assertEquals([$columnName], $constraints->getColumnNames());
980
981
        $db->createCommand()->dropPrimaryKey($constraintName, $tableName)->execute();
982
983
        $constraints = $db->getSchema()->getTablePrimaryKey($tableName, true);
984
985
        $this->assertNull($constraints);
986
987
        $this->dropTableForIndexAndConstraintTests($db, $tableName);
988
    }
989
990
    /**
991
     * @dataProvider withIndexDataProvider
992
     */
993
    public function testWorkWithIndex(
994
        ?string $indexType = null,
995
        ?string $indexMethod = null,
996
        ?string $columnType = null,
997
        bool $isPrimary = false,
998
        bool $isUnique = false
999
    ): void {
1000
        $tableName = 'test_table_with';
1001
        $indexName = 't_index';
1002
        $columnName = 't_field';
1003
1004
        $db = $this->getConnection();
1005
        $qb = $db->getQueryBuilder();
1006
1007
        $this->createTableForIndexAndConstraintTests($db, $tableName, $columnName, $columnType);
1008
1009
        $indexSql = $qb->createIndex($indexName, $tableName, $columnName, $indexType, $indexMethod);
1010
        $db->createCommand($indexSql)->execute();
1011
1012
        /** @var IndexConstraint[] $indexes */
1013
        $indexes = $db->getSchema()->getTableIndexes($tableName, true);
1014
        $this->assertIsArray($indexes);
1015
        $this->assertCount(1, $indexes);
1016
        $this->assertInstanceOf(IndexConstraint::class, $indexes[0]);
1017
        $this->assertEquals($indexName, $indexes[0]->getName());
1018
        $this->assertEquals([$columnName], $indexes[0]->getColumnNames());
1019
        $this->assertSame($isUnique, $indexes[0]->isUnique());
1020
        $this->assertSame($isPrimary, $indexes[0]->isPrimary());
1021
1022
        $this->dropTableForIndexAndConstraintTests($db, $tableName);
1023
    }
1024
1025
    public function withIndexDataProvider(): array
1026
    {
1027
        return [
1028
            [
1029
                'indexType' => QueryBuilder::INDEX_UNIQUE,
1030
                'indexMethod' => null,
1031
                'columnType' => null,
1032
                'isPrimary' => false,
1033
                'isUnique' => true,
1034
            ],
1035
        ];
1036
    }
1037
1038
    protected function createTableForIndexAndConstraintTests(ConnectionInterface $db, string $tableName, string $columnName, ?string $columnType = null): void
1039
    {
1040
        $qb = $db->getQueryBuilder();
1041
1042
        if ($db->getTableSchema($tableName) !== null) {
1043
            $db->createCommand($qb->dropTable($tableName))->execute();
1044
        }
1045
1046
        $createTableSql = $qb->createTable(
1047
            $tableName,
1048
            [
1049
                $columnName => $columnType ?? 'int NOT NULL',
1050
            ],
1051
        );
1052
1053
        $db->createCommand($createTableSql)->execute();
1054
        $tableSchema = $db->getTableSchema($tableName, true);
1055
        $this->assertInstanceOf(TableSchemaInterface::class, $tableSchema);
1056
    }
1057
1058
    protected function dropTableForIndexAndConstraintTests(ConnectionInterface $db, $tableName): void
1059
    {
1060
        $qb = $db->getQueryBuilder();
1061
1062
        $db->createCommand($qb->dropTable($tableName))->execute();
1063
        $this->assertNull($db->getTableSchema($tableName, true));
1064
    }
1065
}
1066