Passed
Push — master ( d2a90c...b469e1 )
by Wilmer
10:43 queued 08:51
created

SchemaTest::checkConstraint()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 5
nc 1
nop 3
dl 0
loc 9
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Tests;
6
7
use PDO;
8
use Yiisoft\Db\Constraint\CheckConstraint;
9
use Yiisoft\Db\Constraint\Constraint;
10
use Yiisoft\Db\Constraint\ForeignKeyConstraint;
11
use Yiisoft\Db\Constraint\IndexConstraint;
12
use Yiisoft\Db\Exception\NotSupportedException;
13
use Yiisoft\Db\Expression\Expression;
14
use Yiisoft\Db\Schema\ColumnSchema;
15
use Yiisoft\Db\Schema\Schema;
16
use Yiisoft\Db\Schema\TableSchema;
17
18
abstract class SchemaTest extends DatabaseTestCase
19
{
20
    /**
21
     * @var string[]
22
     */
23
    protected array $expectedSchemas;
24
25
    public function pdoAttributesProvider(): array
26
    {
27
        return [
28
            [[PDO::ATTR_EMULATE_PREPARES => true]],
29
            [[PDO::ATTR_EMULATE_PREPARES => false]],
30
        ];
31
    }
32
33
    public function testGetSchemaNames()
34
    {
35
        /* @var $schema Schema */
36
        $schema = $this->getConnection()->getSchema();
37
38
        $schemas = $schema->getSchemaNames();
39
40
        $this->assertNotEmpty($schemas);
41
42
        foreach ($this->expectedSchemas as $schema) {
43
            $this->assertContains($schema, $schemas);
44
        }
45
    }
46
47
    /**
48
     * @dataProvider pdoAttributesProvider
49
     *
50
     * @param array $pdoAttributes
51
     */
52
    public function testGetTableNames($pdoAttributes): void
53
    {
54
        $connection = $this->getConnection(true, true, true);
55
56
        foreach ($pdoAttributes as $name => $value) {
57
            $connection->getPDO()->setAttribute($name, $value);
58
        }
59
60
        /* @var $schema Schema */
61
        $schema = $connection->getSchema();
62
63
        $tables = $schema->getTableNames();
64
65
        $this->assertTrue(\in_array('customer', $tables));
66
        $this->assertTrue(\in_array('category', $tables));
67
        $this->assertTrue(\in_array('item', $tables));
68
        $this->assertTrue(\in_array('order', $tables));
69
        $this->assertTrue(\in_array('order_item', $tables));
70
        $this->assertTrue(\in_array('type', $tables));
71
        $this->assertTrue(\in_array('animal', $tables));
72
        $this->assertTrue(\in_array('animal_view', $tables));
73
    }
74
75
    /**
76
     * @dataProvider pdoAttributesProvider
77
     *
78
     * @param array $pdoAttributes
79
     */
80
    public function testGetTableSchemas($pdoAttributes)
81
    {
82
        $connection = $this->getConnection();
83
84
        foreach ($pdoAttributes as $name => $value) {
85
            $connection->getPDO()->setAttribute($name, $value);
86
        }
87
88
        /* @var $schema Schema */
89
        $schema = $connection->getSchema();
90
91
        $tables = $schema->getTableSchemas();
92
93
        $this->assertEquals(\count($schema->getTableNames()), \count($tables));
94
95
        foreach ($tables as $table) {
96
            $this->assertInstanceOf(TableSchema::class, $table);
97
        }
98
    }
99
100
    public function testGetTableSchemasWithAttrCase()
101
    {
102
        $db = $this->getConnection(false);
103
104
        $db->getSlavePdo()->setAttribute(\PDO::ATTR_CASE, \PDO::CASE_LOWER);
105
        $this->assertEquals(\count($db->getSchema()->getTableNames()), \count($db->getSchema()->getTableSchemas()));
106
107
        $db->getSlavePdo()->setAttribute(\PDO::ATTR_CASE, \PDO::CASE_UPPER);
108
        $this->assertEquals(\count($db->getSchema()->getTableNames()), \count($db->getSchema()->getTableSchemas()));
109
    }
110
111
    public function testGetNonExistingTableSchema()
112
    {
113
        $this->assertNull($this->getConnection()->getSchema()->getTableSchema('nonexisting_table'));
114
    }
115
116
    public function testSchemaCache()
117
    {
118
        /* @var $db Connection */
119
        $db = $this->getConnection();
120
121
        /* @var $schema Schema */
122
        $schema = $db->getSchema();
123
124
        $schema->getDb()->setEnableSchemaCache(true);
125
        $schema->getDb()->setSchemaCache($this->cache);
126
127
        $noCacheTable = $schema->getTableSchema('type', true);
128
        $cachedTable = $schema->getTableSchema('type', false);
129
130
        $this->assertEquals($noCacheTable, $cachedTable);
131
132
        $db->createCommand()->renameTable('type', 'type_test');
133
134
        $noCacheTable = $schema->getTableSchema('type', true);
135
136
        $this->assertNotSame($noCacheTable, $cachedTable);
137
138
        $db->createCommand()->renameTable('type_test', 'type');
139
    }
140
141
    /**
142
     * @depends testSchemaCache
143
     */
144
    public function testRefreshTableSchema()
145
    {
146
        /* @var $schema Schema */
147
        $schema = $this->getConnection()->getSchema();
148
149
        $schema->getDb()->setEnableSchemaCache(true);
150
        $schema->getDb()->setSchemaCache($this->cache);
151
152
        $noCacheTable = $schema->getTableSchema('type', true);
153
        $schema->refreshTableSchema('type');
154
        $refreshedTable = $schema->getTableSchema('type', false);
155
156
        $this->assertNotSame($noCacheTable, $refreshedTable);
157
    }
158
159
    public function tableSchemaCachePrefixesProvider()
160
    {
161
        $configs = [
162
            [
163
                'prefix' => '',
164
                'name'   => 'type',
165
            ],
166
            [
167
                'prefix' => '',
168
                'name'   => '{{%type}}',
169
            ],
170
            [
171
                'prefix' => 'ty',
172
                'name'   => '{{%pe}}',
173
            ],
174
        ];
175
        $data = [];
176
        foreach ($configs as $config) {
177
            foreach ($configs as $testConfig) {
178
                if ($config === $testConfig) {
179
                    continue;
180
                }
181
182
                $description = sprintf(
183
                    "%s (with '%s' prefix) against %s (with '%s' prefix)",
184
                    $config['name'],
185
                    $config['prefix'],
186
                    $testConfig['name'],
187
                    $testConfig['prefix']
188
                );
189
                $data[$description] = [
190
                    $config['prefix'],
191
                    $config['name'],
192
                    $testConfig['prefix'],
193
                    $testConfig['name'],
194
                ];
195
            }
196
        }
197
198
        return $data;
199
    }
200
201
    /**
202
     * @dataProvider tableSchemaCachePrefixesProvider
203
     * @depends testSchemaCache
204
     */
205
    public function testTableSchemaCacheWithTablePrefixes($tablePrefix, $tableName, $testTablePrefix, $testTableName)
206
    {
207
        /* @var $schema Schema */
208
        $schema = $this->getConnection()->getSchema();
209
210
        $schema->getDb()->setEnableSchemaCache(true);
211
        $schema->getDb()->setSchemaCache($this->cache);
212
        $schema->getDb()->setTablePrefix($tablePrefix);
213
        $noCacheTable = $schema->getTableSchema($tableName, true);
214
215
        $this->assertInstanceOf(TableSchema::class, $noCacheTable);
216
217
        // Compare
218
        $schema->getDb()->setTablePrefix($testTablePrefix);
219
        $testNoCacheTable = $schema->getTableSchema($testTableName);
220
221
        $this->assertSame($noCacheTable, $testNoCacheTable);
222
223
        $schema->getDb()->setTablePrefix($tablePrefix);
224
        $schema->refreshTableSchema($tableName);
225
        $refreshedTable = $schema->getTableSchema($tableName, false);
226
227
        $this->assertInstanceOf(TableSchema::class, $refreshedTable);
228
        $this->assertNotSame($noCacheTable, $refreshedTable);
229
230
        // Compare
231
        $schema->getDb()->setTablePrefix($testTablePrefix);
232
        $schema->refreshTableSchema($testTablePrefix);
233
        $testRefreshedTable = $schema->getTableSchema($testTableName, false);
234
235
        $this->assertInstanceOf(TableSchema::class, $testRefreshedTable);
236
        $this->assertEquals($refreshedTable, $testRefreshedTable);
237
        $this->assertNotSame($testNoCacheTable, $testRefreshedTable);
238
    }
239
240
    public function testCompositeFk()
241
    {
242
        /* @var $schema Schema */
243
        $schema = $this->getConnection()->getSchema();
244
245
        $table = $schema->getTableSchema('composite_fk');
246
247
        $fk = $table->getForeignKeys();
248
        $this->assertCount(1, $fk);
249
        $this->assertTrue(isset($fk['FK_composite_fk_order_item']));
250
        $this->assertEquals('order_item', $fk['FK_composite_fk_order_item'][0]);
251
        $this->assertEquals('order_id', $fk['FK_composite_fk_order_item']['order_id']);
252
        $this->assertEquals('item_id', $fk['FK_composite_fk_order_item']['item_id']);
253
    }
254
255
    public function testGetPDOType()
256
    {
257
        $values = [
258
            [null, \PDO::PARAM_NULL],
259
            ['', \PDO::PARAM_STR],
260
            ['hello', \PDO::PARAM_STR],
261
            [0, \PDO::PARAM_INT],
262
            [1, \PDO::PARAM_INT],
263
            [1337, \PDO::PARAM_INT],
264
            [true, \PDO::PARAM_BOOL],
265
            [false, \PDO::PARAM_BOOL],
266
            [$fp = fopen(__FILE__, 'rb'), \PDO::PARAM_LOB],
267
        ];
268
269
        /* @var $schema Schema */
270
        $schema = $this->getConnection()->getSchema();
271
272
        foreach ($values as $value) {
273
            $this->assertEquals($value[1], $schema->getPdoType($value[0]), 'type for value ' . print_r($value[0], true) . ' does not match.');
274
        }
275
        fclose($fp);
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, 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

275
        fclose(/** @scrutinizer ignore-type */ $fp);
Loading history...
276
    }
277
278
    public function getExpectedColumns()
279
    {
280
        return [
281
            'int_col' => [
282
                'type' => 'integer',
283
                'dbType' => 'int(11)',
284
                'phpType' => 'integer',
285
                'allowNull' => false,
286
                'autoIncrement' => false,
287
                'enumValues' => null,
288
                'size' => 11,
289
                'precision' => 11,
290
                'scale' => null,
291
                'defaultValue' => null,
292
            ],
293
            'int_col2' => [
294
                'type' => 'integer',
295
                'dbType' => 'int(11)',
296
                'phpType' => 'integer',
297
                'allowNull' => true,
298
                'autoIncrement' => false,
299
                'enumValues' => null,
300
                'size' => 11,
301
                'precision' => 11,
302
                'scale' => null,
303
                'defaultValue' => 1,
304
            ],
305
            'tinyint_col' => [
306
                'type' => 'tinyint',
307
                'dbType' => 'tinyint(3)',
308
                'phpType' => 'integer',
309
                'allowNull' => true,
310
                'autoIncrement' => false,
311
                'enumValues' => null,
312
                'size' => 3,
313
                'precision' => 3,
314
                'scale' => null,
315
                'defaultValue' => 1,
316
            ],
317
            'smallint_col' => [
318
                'type' => 'smallint',
319
                'dbType' => 'smallint(1)',
320
                'phpType' => 'integer',
321
                'allowNull' => true,
322
                'autoIncrement' => false,
323
                'enumValues' => null,
324
                'size' => 1,
325
                'precision' => 1,
326
                'scale' => null,
327
                'defaultValue' => 1,
328
            ],
329
            'char_col' => [
330
                'type' => 'char',
331
                'dbType' => 'char(100)',
332
                'phpType' => 'string',
333
                'allowNull' => false,
334
                'autoIncrement' => false,
335
                'enumValues' => null,
336
                'size' => 100,
337
                'precision' => 100,
338
                'scale' => null,
339
                'defaultValue' => null,
340
            ],
341
            'char_col2' => [
342
                'type' => 'string',
343
                'dbType' => 'varchar(100)',
344
                'phpType' => 'string',
345
                'allowNull' => true,
346
                'autoIncrement' => false,
347
                'enumValues' => null,
348
                'size' => 100,
349
                'precision' => 100,
350
                'scale' => null,
351
                'defaultValue' => 'something',
352
            ],
353
            'char_col3' => [
354
                'type' => 'text',
355
                'dbType' => 'text',
356
                'phpType' => 'string',
357
                'allowNull' => true,
358
                'autoIncrement' => false,
359
                'enumValues' => null,
360
                'size' => null,
361
                'precision' => null,
362
                'scale' => null,
363
                'defaultValue' => null,
364
            ],
365
            'enum_col' => [
366
                'type' => 'string',
367
                'dbType' => "enum('a','B','c,D')",
368
                'phpType' => 'string',
369
                'allowNull' => true,
370
                'autoIncrement' => false,
371
                'enumValues' => ['a', 'B', 'c,D'],
372
                'size' => null,
373
                'precision' => null,
374
                'scale' => null,
375
                'defaultValue' => null,
376
            ],
377
            'float_col' => [
378
                'type' => 'double',
379
                'dbType' => 'double(4,3)',
380
                'phpType' => 'double',
381
                'allowNull' => false,
382
                'autoIncrement' => false,
383
                'enumValues' => null,
384
                'size' => 4,
385
                'precision' => 4,
386
                'scale' => 3,
387
                'defaultValue' => null,
388
            ],
389
            'float_col2' => [
390
                'type' => 'double',
391
                'dbType' => 'double',
392
                'phpType' => 'double',
393
                'allowNull' => true,
394
                'autoIncrement' => false,
395
                'enumValues' => null,
396
                'size' => null,
397
                'precision' => null,
398
                'scale' => null,
399
                'defaultValue' => 1.23,
400
            ],
401
            'blob_col' => [
402
                'type' => 'binary',
403
                'dbType' => 'blob',
404
                'phpType' => 'resource',
405
                'allowNull' => true,
406
                'autoIncrement' => false,
407
                'enumValues' => null,
408
                'size' => null,
409
                'precision' => null,
410
                'scale' => null,
411
                'defaultValue' => null,
412
            ],
413
            'numeric_col' => [
414
                'type' => 'decimal',
415
                'dbType' => 'decimal(5,2)',
416
                'phpType' => 'string',
417
                'allowNull' => true,
418
                'autoIncrement' => false,
419
                'enumValues' => null,
420
                'size' => 5,
421
                'precision' => 5,
422
                'scale' => 2,
423
                'defaultValue' => '33.22',
424
            ],
425
            'time' => [
426
                'type' => 'timestamp',
427
                'dbType' => 'timestamp',
428
                'phpType' => 'string',
429
                'allowNull' => false,
430
                'autoIncrement' => false,
431
                'enumValues' => null,
432
                'size' => null,
433
                'precision' => null,
434
                'scale' => null,
435
                'defaultValue' => '2002-01-01 00:00:00',
436
            ],
437
            'bool_col' => [
438
                'type' => 'tinyint',
439
                'dbType' => 'tinyint(1)',
440
                'phpType' => 'integer',
441
                'allowNull' => false,
442
                'autoIncrement' => false,
443
                'enumValues' => null,
444
                'size' => 1,
445
                'precision' => 1,
446
                'scale' => null,
447
                'defaultValue' => null,
448
            ],
449
            'bool_col2' => [
450
                'type' => 'tinyint',
451
                'dbType' => 'tinyint(1)',
452
                'phpType' => 'integer',
453
                'allowNull' => true,
454
                'autoIncrement' => false,
455
                'enumValues' => null,
456
                'size' => 1,
457
                'precision' => 1,
458
                'scale' => null,
459
                'defaultValue' => 1,
460
            ],
461
            'ts_default' => [
462
                'type' => 'timestamp',
463
                'dbType' => 'timestamp',
464
                'phpType' => 'string',
465
                'allowNull' => false,
466
                'autoIncrement' => false,
467
                'enumValues' => null,
468
                'size' => null,
469
                'precision' => null,
470
                'scale' => null,
471
                'defaultValue' => new Expression('CURRENT_TIMESTAMP'),
472
            ],
473
            'bit_col' => [
474
                'type' => 'integer',
475
                'dbType' => 'bit(8)',
476
                'phpType' => 'integer',
477
                'allowNull' => false,
478
                'autoIncrement' => false,
479
                'enumValues' => null,
480
                'size' => 8,
481
                'precision' => 8,
482
                'scale' => null,
483
                'defaultValue' => 130, // b'10000010'
484
            ],
485
            'json_col' => [
486
                'type' => 'json',
487
                'dbType' => 'json',
488
                'phpType' => 'array',
489
                'allowNull' => true,
490
                'autoIncrement' => false,
491
                'enumValues' => null,
492
                'size' => null,
493
                'precision' => null,
494
                'scale' => null,
495
                'defaultValue' => null,
496
            ],
497
        ];
498
    }
499
500
    public function testNegativeDefaultValues()
501
    {
502
        /* @var $schema Schema */
503
        $schema = $this->getConnection()->getSchema();
504
505
        $table = $schema->getTableSchema('negative_default_values');
506
507
        $this->assertEquals(-123, $table->getColumn('tinyint_col')->getDefaultValue());
508
        $this->assertEquals(-123, $table->getColumn('smallint_col')->getDefaultValue());
509
        $this->assertEquals(-123, $table->getColumn('int_col')->getDefaultValue());
510
        $this->assertEquals(-123, $table->getColumn('bigint_col')->getDefaultValue());
511
        $this->assertEquals(-12345.6789, $table->getColumn('float_col')->getDefaultValue());
512
        $this->assertEquals(-33.22, $table->getColumn('numeric_col')->getDefaultValue());
513
    }
514
515
    public function testColumnSchema()
516
    {
517
        $columns = $this->getExpectedColumns();
518
519
        $table = $this->getConnection(false)->getSchema()->getTableSchema('type', true);
520
521
        $expectedColNames = \array_keys($columns);
522
523
        sort($expectedColNames);
524
525
        $colNames = $table->getColumnNames();
526
527
        sort($colNames);
528
529
        $this->assertEquals($expectedColNames, $colNames);
530
531
        foreach ($table->getColumns() as $name => $column) {
532
            $expected = $columns[$name];
533
            $this->assertSame(
534
                $expected['dbType'],
535
                $column->getDbType(),
536
                "dbType of column $name does not match. type is {$column->getType()}, dbType is {$column->getDbType()}."
537
            );
538
            $this->assertSame(
539
                $expected['phpType'],
540
                $column->getPhpType(),
541
                "phpType of column $name does not match. type is {$column->getType()}, dbType is {$column->getDbType()}."
542
            );
543
            $this->assertSame($expected['type'], $column->getType(), "type of column $name does not match.");
544
            $this->assertSame(
545
                $expected['allowNull'],
546
                $column->isAllowNull(),
547
                "allowNull of column $name does not match."
548
            );
549
            $this->assertSame(
550
                $expected['autoIncrement'],
551
                $column->isAutoIncrement(),
552
                "autoIncrement of column $name does not match."
553
            );
554
            $this->assertSame(
555
                $expected['enumValues'],
556
                $column->getEnumValues(),
557
                "enumValues of column $name does not match."
558
            );
559
            $this->assertSame($expected['size'], $column->getSize(), "size of column $name does not match.");
560
            $this->assertSame(
561
                $expected['precision'],
562
                $column->getPrecision(),
563
                "precision of column $name does not match."
564
            );
565
            $this->assertSame($expected['scale'], $column->getScale(), "scale of column $name does not match.");
566
            if (\is_object($expected['defaultValue'])) {
567
                $this->assertIsObject(
568
                    $column->getDefaultValue(),
569
                    "defaultValue of column $name is expected to be an object but it is not."
570
                );
571
                $this->assertEquals(
572
                    (string) $expected['defaultValue'],
573
                    (string) $column->getDefaultValue(),
574
                    "defaultValue of column $name does not match."
575
                );
576
            } else {
577
                $this->assertEquals(
578
                    $expected['defaultValue'],
579
                    $column->getDefaultValue(),
580
                    "defaultValue of column $name does not match."
581
                );
582
            }
583
            if (isset($expected['dimension'])) { // PgSQL only
584
                $this->assertSame(
585
                    $expected['dimension'],
586
                    $column->getDimension(),
587
                    "dimension of column $name does not match"
588
                );
589
            }
590
        }
591
    }
592
593
    public function testColumnSchemaDbTypecastWithEmptyCharType()
594
    {
595
        $columnSchema = new ColumnSchema();
596
597
        $columnSchema->setType(Schema::TYPE_CHAR);
598
599
        $this->assertSame('', $columnSchema->dbTypecast(''));
600
    }
601
602
    public function testFindUniqueIndexes()
603
    {
604
        $db = $this->getConnection();
605
606
        try {
607
            $db->createCommand()->dropTable('uniqueIndex')->execute();
608
        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
609
        }
610
611
        $db->createCommand()->createTable('uniqueIndex', [
612
            'somecol'  => 'string',
613
            'someCol2' => 'string',
614
        ])->execute();
615
616
        /* @var $schema Schema */
617
        $schema = $db->getSchema();
618
619
        $uniqueIndexes = $schema->findUniqueIndexes($schema->getTableSchema('uniqueIndex', true));
620
        $this->assertEquals([], $uniqueIndexes);
621
622
        $db->createCommand()->createIndex('somecolUnique', 'uniqueIndex', 'somecol', true)->execute();
623
624
        $uniqueIndexes = $schema->findUniqueIndexes($schema->getTableSchema('uniqueIndex', true));
625
        $this->assertEquals([
626
            'somecolUnique' => ['somecol'],
627
        ], $uniqueIndexes);
628
629
        // create another column with upper case letter that fails postgres
630
        // see https://github.com/yiisoft/yii2/issues/10613
631
        $db->createCommand()->createIndex('someCol2Unique', 'uniqueIndex', 'someCol2', true)->execute();
632
633
        $uniqueIndexes = $schema->findUniqueIndexes($schema->getTableSchema('uniqueIndex', true));
634
        $this->assertEquals([
635
            'somecolUnique'  => ['somecol'],
636
            'someCol2Unique' => ['someCol2'],
637
        ], $uniqueIndexes);
638
639
        // see https://github.com/yiisoft/yii2/issues/13814
640
        $db->createCommand()->createIndex('another unique index', 'uniqueIndex', 'someCol2', true)->execute();
641
642
        $uniqueIndexes = $schema->findUniqueIndexes($schema->getTableSchema('uniqueIndex', true));
643
        $this->assertEquals([
644
            'somecolUnique'        => ['somecol'],
645
            'someCol2Unique'       => ['someCol2'],
646
            'another unique index' => ['someCol2'],
647
        ], $uniqueIndexes);
648
    }
649
650
    public function testContraintTablesExistance()
651
    {
652
        $tableNames = [
653
            'T_constraints_1',
654
            'T_constraints_2',
655
            'T_constraints_3',
656
            'T_constraints_4',
657
        ];
658
659
        $schema = $this->getConnection()->getSchema();
660
661
        foreach ($tableNames as $tableName) {
662
            $tableSchema = $schema->getTableSchema($tableName);
663
            $this->assertInstanceOf(TableSchema::class, $tableSchema, $tableName);
664
        }
665
    }
666
667
    public function constraintsProvider()
668
    {
669
        return [
670
            '1: index' => [
671
                'T_constraints_1',
672
                'indexes',
673
                [
674
                    $this->indexConstraint(AnyValue::getInstance(), ['C_id'], true, true),
675
                    $this->indexConstraint('CN_unique', ['C_unique'], false, true)
676
                ]
677
            ],
678
            '1: check' => [
679
                'T_constraints_1',
680
                'checks',
681
                [
682
                    $this->checkConstraint(AnyValue::getInstance(), "C_check <> ''", ['C_check'])
683
                ]
684
            ],
685
            '1: primary key' => [
686
                'T_constraints_1',
687
                'primaryKey',
688
                $this->constraint(AnyValue::getInstance(), ['C_id'])
689
            ],
690
            '1: unique' => [
691
                'T_constraints_1',
692
                'uniques',
693
                [
694
                    $this->constraint('CN_unique', ['C_unique'])
695
                ],
696
            ],
697
            '1: default' => ['T_constraints_1', 'defaultValues', false],
698
            '2: primary key' => [
699
                'T_constraints_2',
700
                'primaryKey',
701
                $this->constraint('CN_pk', ['C_id_1', 'C_id_2'])
702
            ],
703
            '2: unique' => [
704
                'T_constraints_2',
705
                'uniques',
706
                [
707
                    $this->constraint('CN_constraints_2_multi', ['C_index_2_1', 'C_index_2_2'])
708
                ]
709
            ],
710
            '2: index' => [
711
                'T_constraints_2',
712
                'indexes',
713
                [
714
                    $this->indexConstraint(AnyValue::getInstance(), ['C_id_1', 'C_id_2'], true, true),
715
                    $this->indexConstraint('CN_constraints_2_single', ['C_index_1'], false, false),
716
                    $this->indexConstraint('CN_constraints_2_multi', ['C_index_2_1', 'C_index_2_2'], false, true),
717
                ]
718
            ],
719
            '2: check' => ['T_constraints_2', 'checks', []],
720
            '2: default' => ['T_constraints_2', 'defaultValues', false],
721
            '3: index' => [
722
                'T_constraints_3',
723
                'indexes',
724
                [
725
                    $this->indexConstraint('CN_constraints_3', ['C_fk_id_1', 'C_fk_id_2'], false, false)
726
                ]
727
            ],
728
            '3: primary key' => ['T_constraints_3', 'primaryKey', null],
729
            '3: unique' => ['T_constraints_3', 'uniques', []],
730
            '3: check' => ['T_constraints_3', 'checks', []],
731
            '3: default' => ['T_constraints_3', 'defaultValues', false],
732
            '3: foreign key' => [
733
                'T_constraints_3',
734
                'foreignKeys',
735
                [
736
                    $this->foreignKeyConstraint(
737
                        'CN_constraints_3',
738
                        'T_constraints_2',
739
                        'CASCADE',
740
                        'CASCADE',
741
                        ['C_fk_id_1', 'C_fk_id_2'],
742
                        ['C_id_1', 'C_id_2']
743
                    )
744
                ]
745
            ],
746
            '4: primary key' => [
747
                'T_constraints_4',
748
                'primaryKey',
749
                $this->constraint(AnyValue::getInstance(), ['C_id'])
750
            ],
751
            '4: unique' => [
752
                'T_constraints_4',
753
                'uniques',
754
                [
755
                    $this->constraint('CN_constraints_4', ['C_col_1', 'C_col_2'])
756
                ]
757
            ],
758
            '4: check' => ['T_constraints_4', 'checks', []],
759
            '4: default' => ['T_constraints_4', 'defaultValues', false],
760
        ];
761
    }
762
763
    public function lowercaseConstraintsProvider()
764
    {
765
        return $this->constraintsProvider();
766
    }
767
768
    public function uppercaseConstraintsProvider()
769
    {
770
        return $this->constraintsProvider();
771
    }
772
773
    /**
774
     * @dataProvider constraintsProvider
775
     *
776
     * @param string $tableName
777
     * @param string $type
778
     * @param mixed  $expected
779
     */
780
    public function testTableSchemaConstraints($tableName, $type, $expected)
781
    {
782
        if ($expected === false) {
783
            $this->expectException(NotSupportedException::class);
784
        }
785
786
        $constraints = $this->getConnection(false)->getSchema()->{'getTable' . ucfirst($type)}($tableName);
787
788
        $this->assertMetadataEquals($expected, $constraints);
789
    }
790
791
    /**
792
     * @dataProvider uppercaseConstraintsProvider
793
     *
794
     * @param string $tableName
795
     * @param string $type
796
     * @param mixed  $expected
797
     */
798
    public function testTableSchemaConstraintsWithPdoUppercase($tableName, $type, $expected)
799
    {
800
        if ($expected === false) {
801
            $this->expectException(NotSupportedException::class);
802
        }
803
804
        $connection = $this->getConnection(false);
805
        $connection->getSlavePdo()->setAttribute(PDO::ATTR_CASE, PDO::CASE_UPPER);
806
        $constraints = $connection->getSchema()->{'getTable' . ucfirst($type)}($tableName, true);
807
        $this->assertMetadataEquals($expected, $constraints);
808
    }
809
810
    /**
811
     * @dataProvider lowercaseConstraintsProvider
812
     *
813
     * @param string $tableName
814
     * @param string $type
815
     * @param mixed  $expected
816
     */
817
    public function testTableSchemaConstraintsWithPdoLowercase($tableName, $type, $expected)
818
    {
819
        if ($expected === false) {
820
            $this->expectException(NotSupportedException::class);
821
        }
822
823
        $connection = $this->getConnection(false);
824
825
        $connection->getSlavePdo()->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER);
826
827
        $constraints = $connection->getSchema()->{'getTable' . ucfirst($type)}($tableName, true);
828
829
        $this->assertMetadataEquals($expected, $constraints);
830
    }
831
832
    private function assertMetadataEquals($expected, $actual)
833
    {
834
        switch (strtolower(\gettype($expected))) {
835
            case 'object':
836
                $this->assertIsObject($actual);
837
                break;
838
            case 'array':
839
                $this->assertIsArray($actual);
840
                break;
841
            case 'null':
842
                $this->assertNull($actual);
843
                break;
844
        }
845
846
        if (\is_array($expected)) {
847
            $this->normalizeArrayKeys($expected, false);
848
            $this->normalizeArrayKeys($actual, false);
849
        }
850
851
        $this->normalizeConstraints($expected, $actual);
852
853
        if (\is_array($expected)) {
854
            $this->normalizeArrayKeys($expected, true);
855
            $this->normalizeArrayKeys($actual, true);
856
        }
857
858
        $this->assertEquals($expected, $actual);
859
    }
860
861
    private function constraint($name, array $columnNames = []): Constraint
862
    {
863
        $ct = new Constraint();
864
865
        $ct->name($name);
866
        $ct->columnNames($columnNames);
867
868
        return $ct;
869
    }
870
871
    private function checkConstraint($name, string $expression, array $columnNames = []): CheckConstraint
872
    {
873
        $cht = new CheckConstraint();
874
875
        $cht->name($name);
876
        $cht->columnNames($columnNames);
877
        $cht->expression($expression);
878
879
        return $cht;
880
    }
881
882
    private function foreignKeyConstraint(
883
        $name,
884
        string $foreignTableName,
885
        string $onDelete,
886
        string $onUpdate,
887
        array $columnNames = [],
888
        array $foreignColumnNames = []
889
    ): ForeignKeyConstraint {
890
        $fk = new ForeignKeyConstraint();
891
892
        $fk->name($name);
893
        $fk->columnNames($columnNames);
894
        $fk->foreignTableName($foreignTableName);
895
        $fk->foreignColumnNames($foreignColumnNames);
896
        $fk->onUpdate($onUpdate);
897
        $fk->onDelete($onDelete);
898
899
        return $fk;
900
    }
901
902
    private function indexConstraint($name, array $columnNames = [], bool $isPrimary = false, bool $isUnique = false): IndexConstraint
903
    {
904
        $ic = new IndexConstraint();
905
906
        $ic->name($name);
907
        $ic->columnNames($columnNames);
908
        $ic->unique($isUnique);
909
        $ic->primary($isPrimary);
910
911
        return $ic;
912
    }
913
914
    private function normalizeArrayKeys(array &$array, $caseSensitive)
915
    {
916
        $newArray = [];
917
918
        foreach ($array as $value) {
919
            if ($value instanceof Constraint) {
920
                $key = (array) $value;
921
                unset(
922
                    $key["\000Yiisoft\Db\Constraint\Constraint\000name"],
923
                    $key["\u0000Yiisoft\\Db\\Constraint\\ForeignKeyConstraint\u0000foreignSchemaName"]
924
                );
925
926
                foreach ($key as $keyName => $keyValue) {
927
                    if ($keyValue instanceof AnyCaseValue) {
928
                        $key[$keyName] = $keyValue->value;
929
                    } elseif ($keyValue instanceof AnyValue) {
930
                        $key[$keyName] = '[AnyValue]';
931
                    }
932
                }
933
934
                ksort($key, SORT_STRING);
935
                $newArray[$caseSensitive ? json_encode($key) : strtolower(json_encode($key))] = $value;
936
            } else {
937
                $newArray[] = $value;
938
            }
939
        }
940
941
        ksort($newArray, SORT_STRING);
942
943
        $array = $newArray;
944
    }
945
946
    private function normalizeConstraints(&$expected, &$actual)
947
    {
948
        if (\is_array($expected)) {
949
            foreach ($expected as $key => $value) {
950
                if (!$value instanceof Constraint || !isset($actual[$key]) || !$actual[$key] instanceof Constraint) {
951
                    continue;
952
                }
953
954
                $this->normalizeConstraintPair($value, $actual[$key]);
955
            }
956
        } elseif ($expected instanceof Constraint && $actual instanceof Constraint) {
957
            $this->normalizeConstraintPair($expected, $actual);
958
        }
959
    }
960
961
    private function normalizeConstraintPair(Constraint $expectedConstraint, Constraint $actualConstraint)
962
    {
963
        if (get_class($expectedConstraint) !== get_class($actualConstraint)) {
964
            return;
965
        }
966
967
        foreach (array_keys((array) $expectedConstraint) as $name) {
968
            if ($expectedConstraint->getName() instanceof AnyValue) {
969
                $actualConstraint->name($expectedConstraint->getName());
970
            } elseif ($expectedConstraint->getName() instanceof AnyCaseValue) {
971
                $actualConstraint->name(new AnyCaseValue($actualConstraint->getName()));
972
            }
973
        }
974
    }
975
}
976