packages/ztd-query-core/tests/Unit/Schema/TableDefinitionTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Schema;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\UsesClass;
9use PHPUnit\Framework\TestCase;
10use ZtdQuery\Schema\ColumnDeclaration;
11use ZtdQuery\Schema\ColumnTypeFamily;
12use ZtdQuery\Schema\Key\CandidateKeySet;
13use ZtdQuery\Schema\Key\ForeignKeyDefinition;
14use ZtdQuery\Schema\Key\IdentityGenerationStrategy;
15use ZtdQuery\Schema\Key\PartialUniqueIndex;
16use ZtdQuery\Schema\Partition\TablePartitioning;
17use ZtdQuery\Schema\Partition\TablePartitionKey;
18use ZtdQuery\Schema\Partition\TablePartitionRelation;
19use ZtdQuery\Schema\Partition\TablePartitionStrategy;
20use ZtdQuery\Schema\TableDefinition;
21
22#[UsesClass(ColumnDeclaration::class)]
23#[UsesClass(CandidateKeySet::class)]
24#[UsesClass(ForeignKeyDefinition::class)]
25#[UsesClass(PartialUniqueIndex::class)]
26#[UsesClass(TablePartitioning::class)]
27#[UsesClass(TablePartitionKey::class)]
28#[UsesClass(TablePartitionRelation::class)]
29#[CoversClass(TableDefinition::class)]
30final class TableDefinitionTest extends TestCase
31{
32 public function testKeepsEverythingATableDeclaresAboutItself(): void
33 {
34 $typedColumns = ['id' => new ColumnDeclaration(ColumnTypeFamily::INTEGER, 'INT')];
35
36 $definition = new TableDefinition(
37 ['id', 'name'],
38 ['id' => 'INT', 'name' => 'VARCHAR(255)'],
39 ['id'],
40 ['id'],
41 ['unique_name' => ['name']],
42 $typedColumns,
43 ['name' => "'anonymous'"],
44 ['id' => IdentityGenerationStrategy::MaxValue],
45 ['name' => "CONCAT('user-', id)"],
46 ['fk_parent' => new ForeignKeyDefinition(['id'], 'parents', ['id'])],
47 new TablePartitioning(['p0' => 'id < 10']),
48 );
49
50 self::assertSame(['id', 'name'], $definition->columns);
51 self::assertSame(['id' => 'INT', 'name' => 'VARCHAR(255)'], $definition->columnTypes);
52 self::assertSame(['id'], $definition->primaryKeys);
53 self::assertSame(['id'], $definition->notNullColumns);
54 self::assertSame(['unique_name' => ['name']], $definition->uniqueConstraints);
55 self::assertSame($typedColumns, $definition->typedColumns);
56 self::assertSame(['name' => "'anonymous'"], $definition->columnDefaults);
57 self::assertSame(['id' => IdentityGenerationStrategy::MaxValue], $definition->identityStrategies);
58 self::assertSame(['name' => "CONCAT('user-', id)"], $definition->generatedExpressions);
59 self::assertSame(['fk_parent'], array_keys($definition->foreignKeys));
60 self::assertSame(['id < 10'], $definition->partitioning?->predicatesFor(['p0']));
61 }
62
63 public function testTypedColumnsDefaultsToEmpty(): void
64 {
65 $definition = new TableDefinition(
66 ['id'],
67 ['id' => 'INT'],
68 [],
69 [],
70 [],
71 );
72
73 self::assertSame([], $definition->typedColumns);
74 self::assertSame([], $definition->columnDefaults);
75 self::assertSame([], $definition->identityStrategies);
76 self::assertSame([], $definition->generatedExpressions);
77 self::assertSame([], $definition->foreignKeys);
78 self::assertNull($definition->partitioning);
79 self::assertNull($definition->partitionKey);
80 self::assertNull($definition->partitionRelation);
81 self::assertSame([], $definition->partialUniqueIndexes);
82 }
83
84 public function testWithPartitioningPreservesSchemaAndReturnsNewDefinition(): void
85 {
86 $definition = new TableDefinition(['id'], ['id' => 'INT'], ['id'], ['id'], []);
87 $partitioning = new TablePartitioning(['p0' => 'id < 10']);
88
89 $partitioned = $definition->withPartitioning($partitioning);
90
91 self::assertNotSame($definition, $partitioned);
92 self::assertSame($definition->columns, $partitioned->columns);
93 self::assertSame($definition->primaryKeys, $partitioned->primaryKeys);
94 self::assertSame($partitioning, $partitioned->partitioning);
95 }
96
97 public function testCandidateKeysWithPartitionKeyCandidateKeysPartitionMetadataCopiesPreserveOtherSchemaState(): void
98 {
99 $definition = new TableDefinition(['id'], ['id' => 'INT'], ['id'], ['id'], []);
100 $key = new TablePartitionKey(TablePartitionStrategy::Range, ['id']);
101 $relation = new TablePartitionRelation('events', 'id >= 10');
102
103 $partition = $definition->withPartitionKey($key)->withPartitionRelation($relation);
104
105 self::assertSame($key, $partition->partitionKey);
106 self::assertSame($relation, $partition->partitionRelation);
107 self::assertSame($definition->columns, $partition->columns);
108 self::assertSame($definition->candidateKeys()->keys(), $partition->candidateKeys()->keys());
109 }
110
111 public function testWithPartialUniqueIndexPreservesSchemaAndIndexesByName(): void
112 {
113 $definition = new TableDefinition(['email', 'status'], ['email' => 'TEXT', 'status' => 'TEXT'], [], [], []);
114 $index = new PartialUniqueIndex('users_active_email', ['email'], "status = 'active'");
115
116 $indexed = $definition->withPartialUniqueIndex($index);
117
118 self::assertNotSame($definition, $indexed);
119 self::assertSame([], $definition->partialUniqueIndexes);
120 self::assertSame($index, $indexed->partialUniqueIndexes['users_active_email']);
121 self::assertSame($definition->columns, $indexed->columns);
122 }
123
124 public function testWithPartitionKeyAnswersATableDividedByThatKey(): void
125 {
126 $definition = new TableDefinition(['id'], [], ['id'], [], []);
127 $key = new TablePartitionKey(TablePartitionStrategy::Range, ['id']);
128
129 $divided = $definition->withPartitionKey($key);
130
131 self::assertSame($key, $divided->partitionKey);
132 self::assertNull($definition->partitionKey);
133 }
134
135 public function testWithPartitionRelationAnswersATableThatIsPartOfAnother(): void
136 {
137 $definition = new TableDefinition(['id'], [], ['id'], [], []);
138 $relation = new TablePartitionRelation('parent', 'id < 10');
139
140 $part = $definition->withPartitionRelation($relation);
141
142 self::assertSame($relation, $part->partitionRelation);
143 self::assertNull($definition->partitionRelation);
144 }
145}
146