packages/ztd-query-core/tests/Unit/Shadow/Mutation/Table/CreateTableAsSelectMutationTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Shadow\Mutation\Table;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\UsesClass;
9use PHPUnit\Framework\TestCase;
10use RuntimeException;
11use ZtdQuery\Connection\ResultColumn;
12use ZtdQuery\Connection\ResultSet;
13use ZtdQuery\Schema\ColumnDeclaration;
14use ZtdQuery\Schema\ColumnTypeFamily;
15use ZtdQuery\Schema\TableDefinition;
16use ZtdQuery\Schema\TableDefinitionRegistry;
17use ZtdQuery\Shadow\Mutation\Table\CreateTableAsSelectMutation;
18use ZtdQuery\Shadow\ShadowStore;
19
20#[UsesClass(ColumnDeclaration::class)]
21#[UsesClass(ResultColumn::class)]
22#[UsesClass(ResultSet::class)]
23#[UsesClass(TableDefinition::class)]
24#[UsesClass(TableDefinitionRegistry::class)]
25#[UsesClass(ShadowStore::class)]
26#[CoversClass(CreateTableAsSelectMutation::class)]
27#[UsesClass(\ZtdQuery\Schema\RowSet::class)]
28final class CreateTableAsSelectMutationTest extends TestCase
29{
30 public function testApplyRegistersTableWithColumnsFromSelect(): void
31 {
32 $registry = new TableDefinitionRegistry();
33 $store = new ShadowStore();
34
35 $mutation = new CreateTableAsSelectMutation(
36 'users_copy',
37 ['id', 'name'],
38 $registry,
39 new ColumnDeclaration(ColumnTypeFamily::TEXT, 'fixture_text'),
40 );
41 $mutation->apply($store, [['id' => 1, 'name' => 'Alice']]);
42
43 $definition = $registry->get('users_copy');
44 self::assertNotNull($definition);
45 self::assertContains('id', $definition->columns);
46 self::assertContains('name', $definition->columns);
47 }
48
49 public function testApplyStoresRowsFromSelectResult(): void
50 {
51 $registry = new TableDefinitionRegistry();
52 $store = new ShadowStore();
53
54 $mutation = new CreateTableAsSelectMutation(
55 'users_copy',
56 ['id', 'name'],
57 $registry,
58 new ColumnDeclaration(ColumnTypeFamily::TEXT, 'fixture_text'),
59 );
60 $rows = [
61 ['id' => 1, 'name' => 'Alice'],
62 ['id' => 2, 'name' => 'Bob'],
63 ];
64 $mutation->apply($store, $rows);
65
66 self::assertSame($rows, $store->get('users_copy'));
67 }
68
69 public function testTableNameReturnsNewTableName(): void
70 {
71 $registry = new TableDefinitionRegistry();
72
73 $mutation = new CreateTableAsSelectMutation(
74 'users_copy',
75 ['id'],
76 $registry,
77 new ColumnDeclaration(ColumnTypeFamily::TEXT, 'fixture_text'),
78 );
79
80 self::assertSame('users_copy', $mutation->tableName());
81 }
82
83 public function testApplyThrowsExceptionWhenTableExists(): void
84 {
85 $registry = new TableDefinitionRegistry();
86 $existingDefinition = new TableDefinition(
87 ['id'],
88 ['id' => 'INT'],
89 [],
90 [],
91 [],
92 );
93 $registry->register('users_copy', $existingDefinition);
94 $store = new ShadowStore();
95
96 $mutation = new CreateTableAsSelectMutation(
97 'users_copy',
98 ['id'],
99 $registry,
100 new ColumnDeclaration(ColumnTypeFamily::TEXT, 'fixture_text'),
101 );
102
103 $this->expectException(RuntimeException::class);
104 $this->expectExceptionMessage("Table 'users_copy' already exists.");
105
106 $mutation->apply($store, [['id' => 1]]);
107 }
108
109 public function testApplyWithIfNotExistsSkipsWhenTableExists(): void
110 {
111 $registry = new TableDefinitionRegistry();
112 $originalDefinition = new TableDefinition(
113 ['id'],
114 ['id' => 'INT'],
115 [],
116 [],
117 [],
118 );
119 $registry->register('users_copy', $originalDefinition);
120 $store = new ShadowStore();
121
122 $mutation = new CreateTableAsSelectMutation(
123 'users_copy',
124 ['id', 'name'],
125 $registry,
126 new ColumnDeclaration(ColumnTypeFamily::TEXT, 'fixture_text'),
127 true
128 );
129 $mutation->apply($store, [['id' => 1, 'name' => 'Alice']]);
130
131 $def = $registry->get('users_copy');
132 self::assertNotNull($def);
133 self::assertSame(['id'], $def->columns);
134 }
135
136 public function testApplyInfersColumnsFromResultRowsForSelectStar(): void
137 {
138 $registry = new TableDefinitionRegistry();
139 $store = new ShadowStore();
140
141 $mutation = new CreateTableAsSelectMutation(
142 'users_copy',
143 [],
144 $registry,
145 new ColumnDeclaration(ColumnTypeFamily::TEXT, 'fixture_text'),
146 );
147 $mutation->apply($store, [['id' => 1, 'name' => 'Alice', 'email' => 'alice@example.com']]);
148
149 $definition = $registry->get('users_copy');
150 self::assertNotNull($definition);
151 self::assertContains('id', $definition->columns);
152 self::assertContains('name', $definition->columns);
153 self::assertContains('email', $definition->columns);
154 }
155
156 public function testApplyResultSetCreatesEmptyTableFromMetadata(): void
157 {
158 $registry = new TableDefinitionRegistry();
159 $store = new ShadowStore();
160
161 $mutation = new CreateTableAsSelectMutation(
162 'users_copy',
163 [],
164 $registry,
165 new ColumnDeclaration(ColumnTypeFamily::TEXT, 'fixture_text'),
166 );
167
168 $result = new ResultSet([], [
169 new ResultColumn('id', new ColumnDeclaration(ColumnTypeFamily::INTEGER, 'int4')),
170 new ResultColumn('name', new ColumnDeclaration(ColumnTypeFamily::TEXT, 'text')),
171 ]);
172
173 $mutation->applyResultSet($store, $result);
174
175 $definition = $registry->get('users_copy');
176 self::assertNotNull($definition);
177 self::assertSame(['id', 'name'], $definition->columns);
178 self::assertSame(ColumnTypeFamily::INTEGER, $definition->typedColumns['id']->family);
179 self::assertSame(ColumnTypeFamily::TEXT, $definition->typedColumns['name']->family);
180 self::assertSame([], $store->get('users_copy'));
181 }
182
183 public function testApplyResultSetUsesMetadataTypesInsteadOfTextFallback(): void
184 {
185 $registry = new TableDefinitionRegistry();
186 $store = new ShadowStore();
187 $mutation = new CreateTableAsSelectMutation(
188 'users_copy',
189 ['id'],
190 $registry,
191 new ColumnDeclaration(ColumnTypeFamily::TEXT, 'fixture_text'),
192 );
193 $result = new ResultSet(
194 [['id' => 1]],
195 [new ResultColumn('id', new ColumnDeclaration(ColumnTypeFamily::INTEGER, 'int4'))],
196 );
197
198 $mutation->applyResultSet($store, $result);
199
200 $definition = $registry->get('users_copy');
201 self::assertNotNull($definition);
202 self::assertSame('int4', $definition->columnTypes['id']);
203 self::assertSame(ColumnTypeFamily::INTEGER, $definition->typedColumns['id']->family);
204 }
205
206 public function testApplyResultSetKeepsExplicitColumnNamesWithMetadataTypes(): void
207 {
208 $registry = new TableDefinitionRegistry();
209 $store = new ShadowStore();
210 $mutation = new CreateTableAsSelectMutation(
211 'users_copy',
212 ['display_id'],
213 $registry,
214 new ColumnDeclaration(ColumnTypeFamily::TEXT, 'fixture_text'),
215 );
216 $result = new ResultSet(
217 [['source_id' => 1]],
218 [new ResultColumn('source_id', new ColumnDeclaration(ColumnTypeFamily::INTEGER, 'int4'))],
219 );
220
221 $mutation->applyResultSet($store, $result);
222
223 $definition = $registry->get('users_copy');
224 self::assertNotNull($definition);
225 self::assertSame(['display_id'], $definition->columns);
226 self::assertSame(ColumnTypeFamily::INTEGER, $definition->typedColumns['display_id']->family);
227 }
228
229 public function testApplyKeepsParsedProjectionNamesWhenMetadataIsUnavailable(): void
230 {
231 $registry = new TableDefinitionRegistry();
232 $store = new ShadowStore();
233 $mutation = new CreateTableAsSelectMutation(
234 'users_copy',
235 ['display_name'],
236 $registry,
237 new ColumnDeclaration(ColumnTypeFamily::TEXT, 'fixture_text'),
238 );
239
240 $mutation->apply($store, [['source_name' => 'Alice']]);
241
242 $definition = $registry->get('users_copy');
243 self::assertNotNull($definition);
244 self::assertSame(['display_name'], $definition->columns);
245 self::assertSame('fixture_text', $definition->columnTypes['display_name']);
246 }
247}
248