Passed
Pull Request — master (#419)
by Def
02:53
created

dropTableForIndexAndConstraintTests()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 1
b 0
f 0
nc 1
nop 2
dl 0
loc 6
rs 10
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
    {
328
        $db = $this->getConnection(true);
329
330
        $schema = $db->getSchema();
331
        $tableSchema = $schema->getTableSchema('type');
332
333
        $this->assertInstanceOf(TableSchemaInterface::class, $tableSchema);
334
335
        $columns = $tableSchema->getColumns();
336
337
        foreach ($columns as $name => $column) {
338
            $type = $column->getType();
339
            $size = $column->getSize();
340
            $dbType = $column->getDbType();
341
342
            if ($name === $columnName) {
343
                $this->assertSame($columnType, $type);
344
                $this->assertSame($columnSize, $size);
345
                $this->assertSame($columnDbType, $dbType);
346
            }
347
        }
348
    }
349
350
    public function testGetTableChecks(): void
351
    {
352
        $db = $this->getConnection(true);
353
354
        $schema = $db->getSchema();
355
        $tableChecks = $schema->getTableChecks('T_constraints_1');
356
357
        $this->assertIsArray($tableChecks);
358
359
        $this->assertContainsOnlyInstancesOf(CheckConstraint::class, $tableChecks);
360
    }
361
362
    /**
363
     * @dataProvider \Yiisoft\Db\Tests\Provider\SchemaProvider::pdoAttributes()
364
     *
365
     * @throws NotSupportedException
366
     */
367
    public function testGetTableNames(array $pdoAttributes): void
368
    {
369
        $db = $this->getConnection(true);
370
371
        foreach ($pdoAttributes as $name => $value) {
372
            if ($name === PDO::ATTR_EMULATE_PREPARES) {
373
                continue;
374
            }
375
376
            $db->getPDO()?->setAttribute($name, $value);
377
        }
378
379
        $schema = $db->getSchema();
380
        $tablesNames = $schema->getTableNames();
381
382
        $this->assertContains('customer', $tablesNames);
383
        $this->assertContains('category', $tablesNames);
384
        $this->assertContains('item', $tablesNames);
385
        $this->assertContains('order', $tablesNames);
386
        $this->assertContains('order_item', $tablesNames);
387
        $this->assertContains('type', $tablesNames);
388
        $this->assertContains('animal', $tablesNames);
389
        $this->assertContains('animal_view', $tablesNames);
390
    }
391
392
    /**
393
     * @dataProvider \Yiisoft\Db\Tests\Provider\SchemaProvider::tableSchema()
394
     */
395
    public function testGetTableSchema(string $name, string $expectedName): void
396
    {
397
        $db = $this->getConnection(true);
398
399
        $tableSchema = $db->getSchema()->getTableSchema($name);
400
401
        $this->assertInstanceOf(TableSchemaInterface::class, $tableSchema);
402
        $this->assertEquals($expectedName, $tableSchema->getName());
403
    }
404
405
    /**
406
     * @dataProvider \Yiisoft\Db\Tests\Provider\SchemaProvider::pdoAttributes()
407
     *
408
     * @throws NotSupportedException
409
     */
410
    public function testGetTableSchemas(array $pdoAttributes): void
411
    {
412
        $db = $this->getConnection(true);
413
414
        foreach ($pdoAttributes as $name => $value) {
415
            if ($name === PDO::ATTR_EMULATE_PREPARES) {
416
                continue;
417
            }
418
419
            $db->getPDO()?->setAttribute($name, $value);
420
        }
421
422
        $schema = $db->getSchema();
423
        $tables = $schema->getTableSchemas();
424
425
        $this->assertCount(count($schema->getTableNames()), $tables);
426
427
        foreach ($tables as $table) {
428
            $this->assertInstanceOf(TableSchemaInterface::class, $table);
429
        }
430
    }
431
432
    /**
433
     * @throws InvalidConfigException
434
     * @throws NotSupportedException
435
     * @throws Exception
436
     */
437
    public function testGetTableSchemaWithAttrCase(): void
438
    {
439
        $db = $this->getConnection(true);
440
441
        $schema = $db->getSchema();
442
        $db->getActivePDO()->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER);
443
444
        $this->assertCount(count($schema->getTableNames()), $schema->getTableSchemas());
445
446
        $db->getActivePDO()->setAttribute(PDO::ATTR_CASE, PDO::CASE_UPPER);
447
448
        $this->assertCount(count($schema->getTableNames()), $schema->getTableSchemas());
449
    }
450
451
    public function testGetViewNames(): void
452
    {
453
        $db = $this->getConnection(true);
454
455
        $schema = $db->getSchema();
456
        $views = $schema->getViewNames();
457
458
        $this->assertSame(['animal_view'], $views);
459
    }
460
461
    public function testNegativeDefaultValues(): void
462
    {
463
        $schema = $this->getConnection(true);
464
465
        $schema = $schema->getSchema();
466
        $table = $schema->getTableSchema('negative_default_values');
467
468
        $this->assertNotNull($table);
469
        $this->assertSame(-123, $table->getColumn('tinyint_col')?->getDefaultValue());
470
        $this->assertSame(-123, $table->getColumn('smallint_col')?->getDefaultValue());
471
        $this->assertSame(-123, $table->getColumn('int_col')?->getDefaultValue());
472
        $this->assertSame(-123, $table->getColumn('bigint_col')?->getDefaultValue());
473
        $this->assertSame(-12345.6789, $table->getColumn('float_col')?->getDefaultValue());
474
        $this->assertEquals(-33.22, $table->getColumn('numeric_col')?->getDefaultValue());
475
    }
476
477
    /**
478
     * @throws Exception
479
     * @throws InvalidConfigException
480
     * @throws Throwable
481
     */
482
    public function testQuoterEscapingValue(): void
483
    {
484
        $db = $this->getConnection(true);
485
486
        $quoter = $db->getQuoter();
487
        $db->createCommand(
488
            <<<SQL
489
            DELETE FROM [[quoter]]
490
            SQL
491
        )->execute();
492
        $data = $this->generateQuoterEscapingValues();
493
494
        foreach ($data as $index => $value) {
495
            $quotedName = $quoter->quoteValue('testValue_' . $index);
496
            $quoteValue = $quoter->quoteValue($value);
497
            $db->createCommand(
498
                <<<SQL
499
                INSERT INTO [[quoter]] ([[name]], [[description]]) VALUES ($quotedName, $quoteValue)
500
                SQL,
501
            )->execute();
502
            $result = $db->createCommand(
503
                <<<SQL
504
                SELECT * FROM [[quoter]] WHERE [[name]]=$quotedName
505
                SQL,
506
            )->queryOne();
507
508
            $this->assertSame($value, $result['description']);
509
        }
510
    }
511
512
    /**
513
     * @depends testSchemaCache
514
     */
515
    public function testRefreshTableSchema(): void
516
    {
517
        $db = $this->getConnection(true);
518
519
        $schema = $db->getSchema();
520
        $schema->schemaCacheEnable(true);
521
        $noCacheTable = $schema->getTableSchema('type', true);
522
        $schema->refreshTableSchema('type');
523
        $refreshedTable = $schema->getTableSchema('type');
524
525
        $this->assertNotSame($noCacheTable, $refreshedTable);
526
    }
527
528
    /**
529
     * @throws Exception
530
     * @throws InvalidConfigException
531
     */
532
    public function testSchemaCache(): void
533
    {
534
        $db = $this->getConnection(true);
535
536
        $schema = $db->getSchema();
537
        $schema->schemaCacheEnable(true);
538
        $noCacheTable = $schema->getTableSchema('type', true);
539
        $cachedTable = $schema->getTableSchema('type');
540
541
        $this->assertSame($noCacheTable, $cachedTable);
542
543
        $db->createCommand()->renameTable('type', 'type_test');
544
        $noCacheTable = $schema->getTableSchema('type', true);
545
546
        $this->assertNotSame($noCacheTable, $cachedTable);
547
548
        $db->createCommand()->renameTable('type_test', 'type');
549
    }
550
551
    /**
552
     * @dataProvider \Yiisoft\Db\Tests\Provider\SchemaProvider::tableSchemaCachePrefixes()
553
     */
554
    public function testTableSchemaCacheWithTablePrefixes(
555
        string $tablePrefix,
556
        string $tableName,
557
        string $testTablePrefix,
558
        string $testTableName
559
    ): void
560
    {
561
        $db = $this->getConnection(true);
562
563
        $schema = $db->getSchema();
564
        $schema->schemaCacheEnable(true);
565
        $db->setTablePrefix($tablePrefix);
566
        $noCacheTable = $schema->getTableSchema($tableName, true);
567
568
        $this->assertInstanceOf(TableSchemaInterface::class, $noCacheTable);
569
570
        /* Compare */
571
        $db->setTablePrefix($testTablePrefix);
572
        $testNoCacheTable = $schema->getTableSchema($testTableName);
573
574
        $this->assertSame($noCacheTable, $testNoCacheTable);
575
576
        $db->setTablePrefix($tablePrefix);
577
        $schema->refreshTableSchema($tableName);
578
        $refreshedTable = $schema->getTableSchema($tableName);
579
580
        $this->assertInstanceOf(TableSchemaInterface::class, $refreshedTable);
581
        $this->assertNotSame($noCacheTable, $refreshedTable);
582
583
        /* Compare */
584
        $db->setTablePrefix($testTablePrefix);
585
        $schema->refreshTableSchema($testTablePrefix);
586
        $testRefreshedTable = $schema->getTableSchema($testTableName);
587
588
        $this->assertInstanceOf(TableSchemaInterface::class, $testRefreshedTable);
589
        $this->assertEquals($refreshedTable, $testRefreshedTable);
590
        $this->assertNotSame($testNoCacheTable, $testRefreshedTable);
591
    }
592
593
    /**
594
     * @dataProvider \Yiisoft\Db\Tests\Provider\SchemaProvider::constraints()
595
     *
596
     * @throws Exception
597
     * @throws JsonException
598
     */
599
    public function testTableSchemaConstraints(string $tableName, string $type, mixed $expected): void
600
    {
601
        if ($expected === false) {
602
            $this->expectException(NotSupportedException::class);
603
        }
604
605
        $db = $this->getConnection(true);
606
        $schema = $db->getSchema();
607
        $constraints = $schema->{'getTable' . ucfirst($type)}($tableName);
608
609
        $this->assertMetadataEquals($expected, $constraints);
610
611
        $db->close();
612
    }
613
614
    /**
615
     * @dataProvider \Yiisoft\Db\Tests\Provider\SchemaProvider::constraints()
616
     *
617
     * @throws Exception
618
     * @throws JsonException
619
     * @throws InvalidConfigException
620
     */
621
    public function testTableSchemaConstraintsWithPdoLowercase(string $tableName, string $type, mixed $expected): void
622
    {
623
        if ($expected === false) {
624
            $this->expectException(NotSupportedException::class);
625
        }
626
627
        $db = $this->getConnection(true);
628
629
        $schema = $db->getSchema();
630
        $db->getActivePDO()->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER);
631
        $constraints = $schema->{'getTable' . ucfirst($type)}($tableName, true);
632
633
        $this->assertMetadataEquals($expected, $constraints);
634
635
        $db->close();
636
    }
637
638
    /**
639
     * @dataProvider \Yiisoft\Db\Tests\Provider\SchemaProvider::constraints()
640
     *
641
     * @throws Exception
642
     * @throws JsonException
643
     * @throws InvalidConfigException
644
     */
645
    public function testTableSchemaConstraintsWithPdoUppercase(string $tableName, string $type, mixed $expected): void
646
    {
647
        if ($expected === false) {
648
            $this->expectException(NotSupportedException::class);
649
        }
650
651
        $db = $this->getConnection(true);
652
653
        $schema = $db->getSchema();
654
        $db->getActivePDO()->setAttribute(PDO::ATTR_CASE, PDO::CASE_UPPER);
655
        $constraints = $schema->{'getTable' . ucfirst($type)}($tableName, true);
656
657
        $this->assertMetadataEquals($expected, $constraints);
658
659
        $db->close();
660
    }
661
662
    private function generateQuoterEscapingValues(): array
663
    {
664
        $result = [];
665
        $stringLength = 16;
666
667
        for ($i = 32; $i < 128 - $stringLength; $i += $stringLength) {
668
            $str = '';
669
670
            for ($symbol = $i; $symbol < $i + $stringLength; $symbol++) {
671
                $str .= mb_chr($symbol, 'UTF-8');
672
            }
673
674
            $result[] = $str;
675
            $str = '';
676
677
            for ($symbol = $i; $symbol < $i + $stringLength; $symbol++) {
678
                $str .= mb_chr($symbol, 'UTF-8') . mb_chr($symbol, 'UTF-8');
679
            }
680
681
            $result[] = $str;
682
        }
683
684
        return $result;
685
    }
686
687
    /**
688
     * @throws JsonException
689
     */
690
    protected function assertMetadataEquals($expected, $actual): void
691
    {
692
        switch (strtolower(gettype($expected))) {
693
            case 'object':
694
                $this->assertIsObject($actual);
695
                break;
696
            case 'array':
697
                $this->assertIsArray($actual);
698
                break;
699
            case 'null':
700
                $this->assertNull($actual);
701
                break;
702
        }
703
704
        if (is_array($expected)) {
705
            $this->normalizeArrayKeys($expected, false);
706
            $this->normalizeArrayKeys($actual, false);
707
        }
708
709
        $this->normalizeConstraints($expected, $actual);
710
711
        if (is_array($expected)) {
712
            $this->normalizeArrayKeys($expected, true);
713
            $this->normalizeArrayKeys($actual, true);
714
        }
715
716
        $this->assertEquals($expected, $actual);
717
    }
718
719
    protected function columnSchema(array $columns, string $table = 'type'): void
720
    {
721
        $db = $this->getConnection(true);
722
723
        $table = $db->getTableSchema($table, true);
724
725
        $this->assertNotNull($table);
726
727
        $expectedColNames = array_keys($columns);
728
        sort($expectedColNames);
729
        $colNames = $table->getColumnNames();
730
        sort($colNames);
731
732
        $this->assertSame($expectedColNames, $colNames);
733
734
        foreach ($table->getColumns() as $name => $column) {
735
            $expected = $columns[$name];
736
737
            $this->assertSame(
738
                $expected['dbType'],
739
                $column->getDbType(),
740
                "dbType of column $name does not match. type is {$column->getType()}, dbType is {$column->getDbType()}."
741
            );
742
            $this->assertSame(
743
                $expected['phpType'],
744
                $column->getPhpType(),
745
                "phpType of column $name does not match. type is {$column->getType()}, dbType is {$column->getDbType()}."
746
            );
747
            $this->assertSame($expected['type'], $column->getType(), "type of column $name does not match.");
748
            $this->assertSame(
749
                $expected['allowNull'],
750
                $column->isAllowNull(),
751
                "allowNull of column $name does not match."
752
            );
753
            $this->assertSame(
754
                $expected['autoIncrement'],
755
                $column->isAutoIncrement(),
756
                "autoIncrement of column $name does not match."
757
            );
758
            $this->assertSame(
759
                $expected['enumValues'],
760
                $column->getEnumValues(),
761
                "enumValues of column $name does not match."
762
            );
763
            $this->assertSame($expected['size'], $column->getSize(), "size of column $name does not match.");
764
            $this->assertSame(
765
                $expected['precision'],
766
                $column->getPrecision(),
767
                "precision of column $name does not match."
768
            );
769
770
            $this->assertSame($expected['scale'], $column->getScale(), "scale of column $name does not match.");
771
772
            if (is_object($expected['defaultValue'])) {
773
                $this->assertIsObject(
774
                    $column->getDefaultValue(),
775
                    "defaultValue of column $name is expected to be an object but it is not."
776
                );
777
                $this->assertSame(
778
                    (string)$expected['defaultValue'],
779
                    (string)$column->getDefaultValue(),
780
                    "defaultValue of column $name does not match."
781
                );
782
            } else {
783
                $this->assertSame(
784
                    $expected['defaultValue'],
785
                    $column->getDefaultValue(),
786
                    "defaultValue of column $name does not match."
787
                );
788
            }
789
790
            /* Pgsql only */
791
            if (isset($expected['dimension'])) {
792
                /** @psalm-suppress UndefinedMethod */
793
                $this->assertSame(
794
                    $expected['dimension'],
795
                    $column->getDimension(),
796
                    "dimension of column $name does not match"
797
                );
798
            }
799
        }
800
801
        $db->close();
802
    }
803
804
    /**
805
     * @throws JsonException
806
     */
807
    private function normalizeArrayKeys(array &$array, bool $caseSensitive): void
808
    {
809
        $newArray = [];
810
811
        foreach ($array as $value) {
812
            if ($value instanceof Constraint) {
813
                $key = (array)$value;
814
                unset(
815
                    $key["\000Yiisoft\Db\Constraint\Constraint\000name"],
816
                    $key["\u0000Yiisoft\\Db\\Constraint\\ForeignKeyConstraint\u0000foreignSchemaName"]
817
                );
818
819
                foreach ($key as $keyName => $keyValue) {
820
                    if ($keyValue instanceof AnyCaseValue) {
821
                        $key[$keyName] = $keyValue->value;
822
                    } elseif ($keyValue instanceof AnyValue) {
823
                        $key[$keyName] = '[AnyValue]';
824
                    }
825
                }
826
827
                ksort($key, SORT_STRING);
828
                $newArray[$caseSensitive
829
                    ? json_encode($key, JSON_THROW_ON_ERROR)
830
                    : strtolower(json_encode($key, JSON_THROW_ON_ERROR))] = $value;
831
            } else {
832
                $newArray[] = $value;
833
            }
834
        }
835
836
        ksort($newArray, SORT_STRING);
837
        $array = $newArray;
838
    }
839
840
    private function normalizeConstraints($expected, $actual): void
841
    {
842
        if (is_array($expected)) {
843
            foreach ($expected as $key => $value) {
844
                if (!$value instanceof Constraint || !isset($actual[$key]) || !$actual[$key] instanceof Constraint) {
845
                    continue;
846
                }
847
848
                $this->normalizeConstraintPair($value, $actual[$key]);
849
            }
850
        } elseif ($expected instanceof Constraint && $actual instanceof Constraint) {
851
            $this->normalizeConstraintPair($expected, $actual);
852
        }
853
    }
854
855
    private function normalizeConstraintPair(Constraint $expectedConstraint, Constraint $actualConstraint): void
856
    {
857
        if ($expectedConstraint::class !== $actualConstraint::class) {
858
            return;
859
        }
860
861
        foreach (array_keys((array)$expectedConstraint) as $name) {
862
            if ($expectedConstraint->getName() instanceof AnyValue) {
863
                $actualConstraint->name($expectedConstraint->getName());
864
            } elseif ($expectedConstraint->getName() instanceof AnyCaseValue) {
865
                $actualConstraintName = $actualConstraint->getName();
866
867
                $this->assertIsString($actualConstraintName);
868
869
                $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

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