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