packages/ztd-query-sqlite/tests/Unit/Shadow/Resolution/TableMutationResolverTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Shadow\Resolution;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\UsesClass;
9use PHPUnit\Framework\TestCase;
10use ZtdQuery\Exception\UnknownSchemaException;
11use ZtdQuery\Exception\UnsupportedSqlException;
12use ZtdQuery\Platform\Sqlite\Schema\SqliteSchemaParser;
13use ZtdQuery\Platform\Sqlite\Shadow\Mutation\Table\AlterTableMutation;
14use ZtdQuery\Platform\Sqlite\Shadow\Resolution\TableMutationResolver;
15use ZtdQuery\Platform\Sqlite\Sql\SqliteIdentifierQuoter;
16use ZtdQuery\Platform\Sqlite\Sql\SqliteLexicalMasker;
17use ZtdQuery\Platform\Sqlite\Sql\SqliteParser;
18use ZtdQuery\Schema\ColumnDeclaration;
19use ZtdQuery\Schema\ColumnTypeFamily;
20use ZtdQuery\Schema\TableDefinition;
21use ZtdQuery\Schema\TableDefinitionRegistry;
22use ZtdQuery\Shadow\Mutation\Table\CreateTableMutation;
23use ZtdQuery\Shadow\Mutation\Table\DropTableMutation;
24use ZtdQuery\Shadow\ShadowStore;
25
26#[CoversClass(TableMutationResolver::class)]
27#[UsesClass(AlterTableMutation::class)]
28#[UsesClass(\ZtdQuery\Platform\Sqlite\Shadow\Mutation\Table\Alter\AddColumnResolver::class)]
29#[UsesClass(\ZtdQuery\Platform\Sqlite\Shadow\Mutation\Table\Alter\AlteredTableProjection::class)]
30#[UsesClass(\ZtdQuery\Platform\Sqlite\Shadow\Mutation\Table\Alter\ColumnDefinitionEditor::class)]
31#[UsesClass(\ZtdQuery\Platform\Sqlite\Shadow\Mutation\Table\Alter\DropColumnResolver::class)]
32#[UsesClass(\ZtdQuery\Platform\Sqlite\Shadow\Mutation\Table\Alter\RenameResolver::class)]
33#[UsesClass(\ZtdQuery\Platform\Sqlite\Shadow\Resolution\MutationTableLookup::class)]
34#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Alter\AlterOperationParser::class)]
35#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Dml\Expression\AssignmentColumnParser::class)]
36#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Dml\Expression\AssignmentParser::class)]
37#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Dml\Expression\ValueListParser::class)]
38#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Dml\Insert\InsertClauseParser::class)]
39#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\ExpressionSpan::class)]
40#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\IdentifierDecoder::class)]
41#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\LiteralMasker::class)]
42#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\OpaqueSqlSpan::class)]
43#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\QuotedSpan::class)]
44#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\TopLevelKeywordScanner::class)]
45#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Relation\RelationSourceParser::class)]
46#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Statement\StatementClassifier::class)]
47#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Statement\StatementStructure::class)]
48#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Statement\TargetTableParser::class)]
49#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Dml\Update\UpdateClauseParser::class)]
50#[UsesClass(\ZtdQuery\Platform\Sqlite\Schema\Create\ColumnDefinitionParser::class)]
51#[UsesClass(\ZtdQuery\Platform\Sqlite\Schema\Create\TableBodyParser::class)]
52#[UsesClass(\ZtdQuery\Platform\Sqlite\Schema\Create\TableDefinitionBuilder::class)]
53#[UsesClass(\ZtdQuery\Platform\Sqlite\Schema\Create\VirtualTableParser::class)]
54#[UsesClass(\ZtdQuery\Platform\Sqlite\Schema\Key\ForeignKeyEntryParser::class)]
55#[UsesClass(\ZtdQuery\Platform\Sqlite\Schema\Key\ForeignKeyTokens::class)]
56#[UsesClass(\ZtdQuery\Platform\Sqlite\Schema\SqliteColumnTypeMapper::class)]
57#[UsesClass(\ZtdQuery\Platform\Sqlite\Schema\Key\SqliteForeignKeyDefinitionParser::class)]
58#[UsesClass(SqliteIdentifierQuoter::class)]
59#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\SqliteLexerProfile::class)]
60#[UsesClass(SqliteLexicalMasker::class)]
61#[UsesClass(SqliteParser::class)]
62#[UsesClass(SqliteSchemaParser::class)]
63#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Relation\SqliteSelectRelationParser::class)]
64final class TableMutationResolverTest extends TestCase
65{
66    public function testResolveCreateTable(): void
67    {
68        $resolver = new TableMutationResolver(new SqliteParser(), new TableDefinitionRegistry(), new SqliteSchemaParser());
69        $mutation = $resolver->resolveCreateTable('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)');
70        self::assertInstanceOf(CreateTableMutation::class, $mutation);
71    }
72
73    public function testResolveCreateTableAlreadyExistsThrows(): void
74    {
75        $registry = new TableDefinitionRegistry();
76        $registry->register('users', new TableDefinition(['id', 'name', 'email'], ['id' => 'INTEGER', 'name' => 'TEXT', 'email' => 'TEXT'], ['id'], ['id', 'name'], []));
77        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
78        $this->expectException(UnsupportedSqlException::class);
79        $resolver->resolveCreateTable('CREATE TABLE users (id INTEGER PRIMARY KEY)');
80    }
81
82    public function testResolveCreateTableIfNotExists(): void
83    {
84        $registry = new TableDefinitionRegistry();
85        $registry->register('users', new TableDefinition(['id', 'name', 'email'], ['id' => 'INTEGER', 'name' => 'TEXT', 'email' => 'TEXT'], ['id'], ['id', 'name'], []));
86        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
87        $mutation = $resolver->resolveCreateTable('CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY)');
88        self::assertInstanceOf(CreateTableMutation::class, $mutation);
89    }
90
91    public function testResolveDropTable(): void
92    {
93        $registry = new TableDefinitionRegistry();
94        $registry->register('users', new TableDefinition(['id', 'name', 'email'], ['id' => 'INTEGER', 'name' => 'TEXT', 'email' => 'TEXT'], ['id'], ['id', 'name'], []));
95        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
96        $mutation = $resolver->resolveDropTable('DROP TABLE users');
97        self::assertInstanceOf(DropTableMutation::class, $mutation);
98    }
99
100    public function testResolveDropTableUnknownThrows(): void
101    {
102        $resolver = new TableMutationResolver(new SqliteParser(), new TableDefinitionRegistry(), new SqliteSchemaParser());
103        $this->expectException(UnknownSchemaException::class);
104        $resolver->resolveDropTable('DROP TABLE unknown_table');
105    }
106
107    public function testResolveDropTableIfExists(): void
108    {
109        $resolver = new TableMutationResolver(new SqliteParser(), new TableDefinitionRegistry(), new SqliteSchemaParser());
110        $mutation = $resolver->resolveDropTable('DROP TABLE IF EXISTS unknown_table');
111        self::assertInstanceOf(DropTableMutation::class, $mutation);
112    }
113
114    public function testResolveAlterTableAddColumn(): void
115    {
116        $registry = new TableDefinitionRegistry();
117        $registry->register('users', new TableDefinition(['id', 'name', 'email'], ['id' => 'INTEGER', 'name' => 'TEXT', 'email' => 'TEXT'], ['id'], ['id', 'name'], []));
118        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
119        $mutation = $resolver->resolveAlterTable('ALTER TABLE users ADD COLUMN phone TEXT');
120        self::assertInstanceOf(AlterTableMutation::class, $mutation);
121    }
122
123    public function testResolveAlterTableDropColumn(): void
124    {
125        $registry = new TableDefinitionRegistry();
126        $registry->register('users', new TableDefinition(['id', 'name', 'email'], ['id' => 'INTEGER', 'name' => 'TEXT', 'email' => 'TEXT'], ['id'], ['id', 'name'], []));
127        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
128        $mutation = $resolver->resolveAlterTable('ALTER TABLE users DROP COLUMN email');
129        self::assertInstanceOf(AlterTableMutation::class, $mutation);
130    }
131
132    public function testResolveAlterTableRenameColumn(): void
133    {
134        $registry = new TableDefinitionRegistry();
135        $registry->register('users', new TableDefinition(['id', 'name', 'email'], ['id' => 'INTEGER', 'name' => 'TEXT', 'email' => 'TEXT'], ['id'], ['id', 'name'], []));
136        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
137        $mutation = $resolver->resolveAlterTable('ALTER TABLE users RENAME COLUMN name TO full_name');
138        self::assertInstanceOf(AlterTableMutation::class, $mutation);
139    }
140
141    public function testResolveAlterTableUnknownThrows(): void
142    {
143        $resolver = new TableMutationResolver(new SqliteParser(), new TableDefinitionRegistry(), new SqliteSchemaParser());
144        $this->expectException(UnknownSchemaException::class);
145        $resolver->resolveAlterTable('ALTER TABLE unknown_table ADD COLUMN x TEXT');
146    }
147
148    public function testResolveCreateTableWithoutTargetThrows(): void
149    {
150        $resolver = new TableMutationResolver(new SqliteParser(), new TableDefinitionRegistry(), new SqliteSchemaParser());
151        $this->expectException(UnsupportedSqlException::class);
152        $resolver->resolveCreateTable('CREATE TABLE');
153    }
154
155    public function testResolveDropTableWithoutTargetThrows(): void
156    {
157        $resolver = new TableMutationResolver(new SqliteParser(), new TableDefinitionRegistry(), new SqliteSchemaParser());
158        $this->expectException(UnsupportedSqlException::class);
159        $resolver->resolveDropTable('DROP TABLE');
160    }
161
162    public function testResolveAlterTableRenameTable(): void
163    {
164        $registry = new TableDefinitionRegistry();
165        $registry->register('users', new TableDefinition(['id', 'name'], ['id' => 'INTEGER', 'name' => 'TEXT'], ['id'], ['id'], []));
166        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
167        $mutation = $resolver->resolveAlterTable('ALTER TABLE users RENAME TO people');
168        self::assertInstanceOf(AlterTableMutation::class, $mutation);
169    }
170
171    public function testResolveAlterTableUnsupportedOperationThrows(): void
172    {
173        $registry = new TableDefinitionRegistry();
174        $registry->register('users', new TableDefinition(['id', 'name'], ['id' => 'INTEGER', 'name' => 'TEXT'], ['id'], ['id'], []));
175        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
176        $this->expectException(UnsupportedSqlException::class);
177        $resolver->resolveAlterTable('ALTER TABLE users SOMETHING WEIRD');
178    }
179
180    public function testResolveAlterTableAddColumnWithType(): void
181    {
182        $registry = new TableDefinitionRegistry();
183        $registry->register('users', new TableDefinition(['id', 'name'], ['id' => 'INTEGER', 'name' => 'TEXT'], ['id'], ['id'], []));
184        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
185        $mutation = $resolver->resolveAlterTable('ALTER TABLE users ADD COLUMN age INTEGER');
186        self::assertInstanceOf(AlterTableMutation::class, $mutation);
187    }
188
189    public function testResolveAlterTableAddColumnWithoutType(): void
190    {
191        $registry = new TableDefinitionRegistry();
192        $registry->register('users', new TableDefinition(['id', 'name'], ['id' => 'INTEGER', 'name' => 'TEXT'], ['id'], ['id'], []));
193        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
194        $mutation = $resolver->resolveAlterTable('ALTER TABLE users ADD COLUMN notes');
195        self::assertInstanceOf(AlterTableMutation::class, $mutation);
196    }
197
198    public function testResolveAlterTableDropColumnUpdatesSchema(): void
199    {
200        $registry = new TableDefinitionRegistry();
201        $registry->register('users', new TableDefinition(['id', 'name', 'email'], ['id' => 'INTEGER', 'name' => 'TEXT', 'email' => 'TEXT'], ['id'], ['id', 'name'], ['email_UNIQUE' => ['email']]));
202        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
203        $mutation = $resolver->resolveAlterTable('ALTER TABLE users DROP COLUMN email');
204        self::assertInstanceOf(AlterTableMutation::class, $mutation);
205    }
206
207    public function testResolveAlterTableRenameColumnUpdatesSchema(): void
208    {
209        $registry = new TableDefinitionRegistry();
210        $registry->register('users', new TableDefinition(['id', 'name'], ['id' => 'INTEGER', 'name' => 'TEXT'], ['id'], ['id', 'name'], ['name_UNIQUE' => ['name']]));
211        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
212        $mutation = $resolver->resolveAlterTable('ALTER TABLE users RENAME COLUMN name TO full_name');
213        self::assertInstanceOf(AlterTableMutation::class, $mutation);
214    }
215
216    public function testResolveAlterTableWithoutTargetThrows(): void
217    {
218        $resolver = new TableMutationResolver(new SqliteParser(), new TableDefinitionRegistry(), new SqliteSchemaParser());
219        $this->expectException(UnsupportedSqlException::class);
220        $resolver->resolveAlterTable('ALTER TABLE');
221    }
222
223    public function testResolveAlterTableAddWithoutColumnKeyword(): void
224    {
225        $registry = new TableDefinitionRegistry();
226        $registry->register('users', new TableDefinition(['id', 'name'], ['id' => 'INTEGER', 'name' => 'TEXT'], ['id'], ['id'], []));
227        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
228        $mutation = $resolver->resolveAlterTable('ALTER TABLE users ADD phone TEXT');
229        self::assertInstanceOf(AlterTableMutation::class, $mutation);
230    }
231
232    public function testResolveAlterTableRenameWithoutColumnKeyword(): void
233    {
234        $registry = new TableDefinitionRegistry();
235        $registry->register('users', new TableDefinition(['id', 'name'], ['id' => 'INTEGER', 'name' => 'TEXT'], ['id'], ['id'], []));
236        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
237        $mutation = $resolver->resolveAlterTable('ALTER TABLE users RENAME name TO full_name');
238        self::assertInstanceOf(AlterTableMutation::class, $mutation);
239    }
240
241    public function testResolveAlterTableAddColumnUnknownTableThrows(): void
242    {
243        $registry = new TableDefinitionRegistry();
244        $registry->register('users', new TableDefinition(['id'], [], [], [], []));
245        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
246        $mutation = $resolver->resolveAlterTable('ALTER TABLE users ADD COLUMN x');
247        self::assertInstanceOf(AlterTableMutation::class, $mutation);
248    }
249
250    public function testResolveAlterTableDropColumnUnknownThrows(): void
251    {
252        $registry = new TableDefinitionRegistry();
253        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
254        $this->expectException(UnknownSchemaException::class);
255        $resolver->resolveAlterTable('ALTER TABLE missing DROP COLUMN x');
256    }
257
258    public function testResolveAlterTableRenameColumnUnknownThrows(): void
259    {
260        $registry = new TableDefinitionRegistry();
261        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
262        $this->expectException(UnknownSchemaException::class);
263        $resolver->resolveAlterTable('ALTER TABLE missing RENAME COLUMN x TO y');
264    }
265
266    public function testResolveCreateTableIfNotExistsLowercase(): void
267    {
268        $resolver = new TableMutationResolver(new SqliteParser(), new TableDefinitionRegistry(), new SqliteSchemaParser());
269        $mutation = $resolver->resolveCreateTable('create table if not exists t (id integer primary key)');
270        self::assertInstanceOf(CreateTableMutation::class, $mutation);
271    }
272
273    public function testResolveDropTableIfExistsLowercase(): void
274    {
275        $resolver = new TableMutationResolver(new SqliteParser(), new TableDefinitionRegistry(), new SqliteSchemaParser());
276        $mutation = $resolver->resolveDropTable('drop table if exists nonexistent');
277        self::assertInstanceOf(DropTableMutation::class, $mutation);
278    }
279
280    public function testResolveAlterTableAddColumnLowercase(): void
281    {
282        $registry = new TableDefinitionRegistry();
283        $registry->register('users', new TableDefinition(['id'], ['id' => 'INTEGER'], ['id'], [], []));
284        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
285        $mutation = $resolver->resolveAlterTable('alter table users add column email text');
286        self::assertInstanceOf(AlterTableMutation::class, $mutation);
287    }
288
289    public function testResolveAlterTableDropColumnLowercase(): void
290    {
291        $registry = new TableDefinitionRegistry();
292        $registry->register('users', new TableDefinition(['id', 'email'], ['id' => 'INTEGER', 'email' => 'TEXT'], ['id'], [], []));
293        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
294        $mutation = $resolver->resolveAlterTable('alter table users drop column email');
295        self::assertInstanceOf(AlterTableMutation::class, $mutation);
296    }
297
298    public function testResolveAlterTableRenameToLowercase(): void
299    {
300        $registry = new TableDefinitionRegistry();
301        $registry->register('users', new TableDefinition(['id'], ['id' => 'INTEGER'], ['id'], [], []));
302        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
303        $mutation = $resolver->resolveAlterTable('alter table users rename to people');
304        self::assertInstanceOf(AlterTableMutation::class, $mutation);
305    }
306
307    public function testResolveAlterTableRenameColumnLowercase(): void
308    {
309        $registry = new TableDefinitionRegistry();
310        $registry->register('users', new TableDefinition(['id', 'name'], ['id' => 'INTEGER', 'name' => 'TEXT'], ['id'], [], []));
311        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
312        $mutation = $resolver->resolveAlterTable('alter table users rename column name to full_name');
313        self::assertInstanceOf(AlterTableMutation::class, $mutation);
314    }
315
316    public function testResolveAlterTableAddColumnNoKeyword(): void
317    {
318        $registry = new TableDefinitionRegistry();
319        $registry->register('t', new TableDefinition(['id'], ['id' => 'INTEGER'], ['id'], [], []));
320        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
321        $mutation = $resolver->resolveAlterTable('ALTER TABLE t ADD email TEXT');
322        self::assertInstanceOf(AlterTableMutation::class, $mutation);
323    }
324
325    public function testResolveAlterTableAddColumnWithParenthesizedType(): void
326    {
327        $registry = new TableDefinitionRegistry();
328        $registry->register('t', new TableDefinition(['id'], ['id' => 'INTEGER'], ['id'], [], []));
329        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
330        $mutation = $resolver->resolveAlterTable('ALTER TABLE t ADD COLUMN price DECIMAL(10,2)');
331        self::assertInstanceOf(AlterTableMutation::class, $mutation);
332    }
333
334    public function testResolveAlterTableAddColumnWithPrimaryKeyword(): void
335    {
336        $registry = new TableDefinitionRegistry();
337        $registry->register('t', new TableDefinition(['id'], ['id' => 'INTEGER'], ['id'], [], []));
338        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
339        $mutation = $resolver->resolveAlterTable('ALTER TABLE t ADD COLUMN note PRIMARY KEY');
340        self::assertInstanceOf(AlterTableMutation::class, $mutation);
341    }
342
343    public function testResolveAlterTableAddColumnVerifyNewColumns(): void
344    {
345        $registry = new TableDefinitionRegistry();
346        $registry->register('users', new TableDefinition(['id', 'name'], ['id' => 'INTEGER', 'name' => 'TEXT'], ['id'], ['id'], []));
347        $store = new ShadowStore();
348        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
349        $mutation = $resolver->resolveAlterTable('ALTER TABLE users ADD COLUMN phone TEXT');
350        self::assertInstanceOf(AlterTableMutation::class, $mutation);
351        self::assertSame('users', $mutation->tableName());
352        $registry->unregister('users');
353        $mutation->apply($store, []);
354        $def = $registry->get('users');
355        self::assertNotNull($def);
356        self::assertContains('phone', $def->columns);
357        self::assertContains('id', $def->columns);
358        self::assertContains('name', $def->columns);
359        self::assertSame('TEXT', $def->columnTypes['phone']);
360    }
361
362    public function testResolveAlterTableDropColumnVerifyRemovedFromAllLists(): void
363    {
364        $registry = new TableDefinitionRegistry();
365        $registry->register('users', new TableDefinition(['id', 'name', 'email'], ['id' => 'INTEGER', 'name' => 'TEXT', 'email' => 'TEXT'], ['id'], ['id', 'name'], ['email_unique' => ['email']]));
366        $store = new ShadowStore();
367        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
368        $mutation = $resolver->resolveAlterTable('ALTER TABLE users DROP COLUMN email');
369        self::assertInstanceOf(AlterTableMutation::class, $mutation);
370        self::assertSame('users', $mutation->tableName());
371        $registry->unregister('users');
372        $mutation->apply($store, []);
373        $def = $registry->get('users');
374        self::assertNotNull($def);
375        self::assertNotContains('email', $def->columns);
376        self::assertContains('id', $def->columns);
377        self::assertContains('name', $def->columns);
378        self::assertArrayNotHasKey('email', $def->columnTypes);
379        self::assertNotContains('email', $def->notNullColumns);
380    }
381
382    public function testResolveAlterTableRenameColumnVerifyAllUpdated(): void
383    {
384        $registry = new TableDefinitionRegistry();
385        $registry->register('users', new TableDefinition(['id', 'name'], ['id' => 'INTEGER', 'name' => 'TEXT'], ['id'], ['id', 'name'], ['name_unique' => ['name']]));
386        $store = new ShadowStore();
387        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
388        $mutation = $resolver->resolveAlterTable('ALTER TABLE users RENAME COLUMN name TO full_name');
389        self::assertInstanceOf(AlterTableMutation::class, $mutation);
390        self::assertSame('users', $mutation->tableName());
391        $registry->unregister('users');
392        $mutation->apply($store, []);
393        $def = $registry->get('users');
394        self::assertNotNull($def);
395        self::assertContains('full_name', $def->columns);
396        self::assertNotContains('name', $def->columns);
397        self::assertArrayHasKey('full_name', $def->columnTypes);
398        self::assertArrayNotHasKey('name', $def->columnTypes);
399        self::assertSame('TEXT', $def->columnTypes['full_name']);
400        self::assertContains('full_name', $def->notNullColumns);
401        self::assertNotContains('name', $def->notNullColumns);
402    }
403
404    public function testResolveAlterTableRenameColumnPrimaryKeysUpdated(): void
405    {
406        $registry = new TableDefinitionRegistry();
407        $registry->register('t', new TableDefinition(['id', 'code'], ['id' => 'INTEGER', 'code' => 'TEXT'], ['id', 'code'], [], []));
408        $store = new ShadowStore();
409        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
410        $mutation = $resolver->resolveAlterTable('ALTER TABLE t RENAME COLUMN code TO code_new');
411        self::assertInstanceOf(AlterTableMutation::class, $mutation);
412        $registry->unregister('t');
413        $mutation->apply($store, []);
414        $def = $registry->get('t');
415        self::assertNotNull($def);
416        self::assertContains('code_new', $def->primaryKeys);
417        self::assertNotContains('code', $def->primaryKeys);
418    }
419
420    public function testResolveAlterTableRenameColumnUniqueConstraintsUpdated(): void
421    {
422        $registry = new TableDefinitionRegistry();
423        $registry->register('t', new TableDefinition(['id', 'email'], ['id' => 'INTEGER', 'email' => 'TEXT'], ['id'], [], ['email_uq' => ['email']]));
424        $store = new ShadowStore();
425        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
426        $mutation = $resolver->resolveAlterTable('ALTER TABLE t RENAME COLUMN email TO mail');
427        $registry->unregister('t');
428        $mutation->apply($store, []);
429        $def = $registry->get('t');
430        self::assertNotNull($def);
431        self::assertSame(['mail'], $def->uniqueConstraints['email_uq']);
432    }
433
434    public function testResolveAlterTableRenameColumnTypedColumnsUpdated(): void
435    {
436        $registry = new TableDefinitionRegistry();
437        $registry->register('t', new TableDefinition(['id', 'val'], ['id' => 'INTEGER', 'val' => 'REAL'], ['id'], [], [], ['id' => new ColumnDeclaration(ColumnTypeFamily::INTEGER, 'INTEGER'), 'val' => new ColumnDeclaration(ColumnTypeFamily::FLOAT, 'REAL')]));
438        $store = new ShadowStore();
439        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
440        $mutation = $resolver->resolveAlterTable('ALTER TABLE t RENAME COLUMN val TO value');
441        $registry->unregister('t');
442        $mutation->apply($store, []);
443        $def = $registry->get('t');
444        self::assertNotNull($def);
445        self::assertArrayHasKey('value', $def->typedColumns);
446        self::assertArrayNotHasKey('val', $def->typedColumns);
447    }
448
449    public function testResolveAlterTableDropColumnPreservesPrimaryKeys(): void
450    {
451        $registry = new TableDefinitionRegistry();
452        $registry->register('t', new TableDefinition(['id', 'name', 'email'], ['id' => 'INTEGER', 'name' => 'TEXT', 'email' => 'TEXT'], ['id'], ['id'], []));
453        $store = new ShadowStore();
454        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
455        $mutation = $resolver->resolveAlterTable('ALTER TABLE t DROP COLUMN email');
456        $registry->unregister('t');
457        $mutation->apply($store, []);
458        $def = $registry->get('t');
459        self::assertNotNull($def);
460        self::assertSame(['id'], $def->primaryKeys);
461    }
462
463    public function testResolveAlterTableDropColumnRemovesPrimaryKeyIfDropped(): void
464    {
465        $registry = new TableDefinitionRegistry();
466        $registry->register('t', new TableDefinition(['id', 'code'], ['id' => 'INTEGER', 'code' => 'TEXT'], ['id', 'code'], [], []));
467        $store = new ShadowStore();
468        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
469        $mutation = $resolver->resolveAlterTable('ALTER TABLE t DROP COLUMN code');
470        $registry->unregister('t');
471        $mutation->apply($store, []);
472        $def = $registry->get('t');
473        self::assertNotNull($def);
474        self::assertSame(['id'], $def->primaryKeys);
475        self::assertNotContains('code', $def->primaryKeys);
476    }
477
478    public function testResolveAlterTableDropColumnRemovesUniqueConstraintIfEmpty(): void
479    {
480        $registry = new TableDefinitionRegistry();
481        $registry->register('t', new TableDefinition(['id', 'email'], ['id' => 'INTEGER', 'email' => 'TEXT'], ['id'], [], ['email_uq' => ['email']]));
482        $store = new ShadowStore();
483        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
484        $mutation = $resolver->resolveAlterTable('ALTER TABLE t DROP COLUMN email');
485        $registry->unregister('t');
486        $mutation->apply($store, []);
487        $def = $registry->get('t');
488        self::assertNotNull($def);
489        self::assertArrayNotHasKey('email_uq', $def->uniqueConstraints);
490    }
491
492    public function testResolveAlterTableDropColumnPreservesPartialUniqueConstraint(): void
493    {
494        $registry = new TableDefinitionRegistry();
495        $registry->register('t', new TableDefinition(['id', 'name', 'email'], ['id' => 'INTEGER', 'name' => 'TEXT', 'email' => 'TEXT'], ['id'], [], ['name_email_uq' => ['name', 'email']]));
496        $store = new ShadowStore();
497        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
498        $mutation = $resolver->resolveAlterTable('ALTER TABLE t DROP COLUMN email');
499        $registry->unregister('t');
500        $mutation->apply($store, []);
501        $def = $registry->get('t');
502        self::assertNotNull($def);
503        self::assertArrayHasKey('name_email_uq', $def->uniqueConstraints);
504        self::assertSame(['name'], $def->uniqueConstraints['name_email_uq']);
505    }
506
507    public function testResolveAlterTableAddColumnVerifyParenthesizedType(): void
508    {
509        $registry = new TableDefinitionRegistry();
510        $registry->register('t', new TableDefinition(['id'], ['id' => 'INTEGER'], ['id'], [], []));
511        $store = new ShadowStore();
512        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
513        $mutation = $resolver->resolveAlterTable('ALTER TABLE t ADD COLUMN price DECIMAL(10,2)');
514        $registry->unregister('t');
515        $mutation->apply($store, []);
516        $def = $registry->get('t');
517        self::assertNotNull($def);
518        self::assertContains('price', $def->columns);
519        self::assertSame('DECIMAL(10,2)', $def->columnTypes['price']);
520    }
521
522    public function testResolveCreateTableTableName(): void
523    {
524        $resolver = new TableMutationResolver(new SqliteParser(), new TableDefinitionRegistry(), new SqliteSchemaParser());
525        $mutation = $resolver->resolveCreateTable('CREATE TABLE orders (id INTEGER PRIMARY KEY)');
526        self::assertInstanceOf(CreateTableMutation::class, $mutation);
527        self::assertSame('orders', $mutation->tableName());
528    }
529
530    public function testResolveDropTableTableName(): void
531    {
532        $registry = new TableDefinitionRegistry();
533        $registry->register('users', new TableDefinition(['id'], ['id' => 'INTEGER'], ['id'], [], []));
534        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
535        $mutation = $resolver->resolveDropTable('DROP TABLE users');
536        self::assertInstanceOf(DropTableMutation::class, $mutation);
537        self::assertSame('users', $mutation->tableName());
538    }
539
540    public function testResolveAlterTableRenameToDropsOldTable(): void
541    {
542        $registry = new TableDefinitionRegistry();
543        $registry->register('old_t', new TableDefinition(['id'], ['id' => 'INTEGER'], ['id'], [], []));
544        $store = new ShadowStore();
545        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
546        $mutation = $resolver->resolveAlterTable('ALTER TABLE old_t RENAME TO new_t');
547        self::assertInstanceOf(AlterTableMutation::class, $mutation);
548        self::assertSame('old_t', $mutation->tableName());
549    }
550
551    public function testResolveAlterTableDropColumnTypedColumnsUpdated(): void
552    {
553        $registry = new TableDefinitionRegistry();
554        $registry->register('t', new TableDefinition(['id', 'val'], ['id' => 'INTEGER', 'val' => 'TEXT'], ['id'], [], [], ['id' => new ColumnDeclaration(ColumnTypeFamily::INTEGER, 'INTEGER'), 'val' => new ColumnDeclaration(ColumnTypeFamily::TEXT, 'TEXT')]));
555        $store = new ShadowStore();
556        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
557        $mutation = $resolver->resolveAlterTable('ALTER TABLE t DROP COLUMN val');
558        $registry->unregister('t');
559        $mutation->apply($store, []);
560        $def = $registry->get('t');
561        self::assertNotNull($def);
562        self::assertArrayNotHasKey('val', $def->typedColumns);
563        self::assertArrayHasKey('id', $def->typedColumns);
564    }
565
566    public function testResolveAlterTableAddColumnWithTypedColumns(): void
567    {
568        $registry = new TableDefinitionRegistry();
569        $registry->register('t', new TableDefinition(['id'], ['id' => 'INTEGER'], ['id'], [], [], ['id' => new ColumnDeclaration(ColumnTypeFamily::INTEGER, 'INTEGER')]));
570        $store = new ShadowStore();
571        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
572        $mutation = $resolver->resolveAlterTable('ALTER TABLE t ADD COLUMN val TEXT');
573        $registry->unregister('t');
574        $mutation->apply($store, []);
575        $def = $registry->get('t');
576        self::assertNotNull($def);
577        self::assertArrayHasKey('val', $def->typedColumns);
578    }
579
580    public function testResolveAlterTableDropColumnNotNullUpdated(): void
581    {
582        $registry = new TableDefinitionRegistry();
583        $registry->register('t', new TableDefinition(['id', 'name', 'email'], ['id' => 'INTEGER', 'name' => 'TEXT', 'email' => 'TEXT'], ['id'], ['id', 'name', 'email'], []));
584        $store = new ShadowStore();
585        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
586        $mutation = $resolver->resolveAlterTable('ALTER TABLE t DROP COLUMN name');
587        $registry->unregister('t');
588        $mutation->apply($store, []);
589        $def = $registry->get('t');
590        self::assertNotNull($def);
591        self::assertNotContains('name', $def->notNullColumns);
592        self::assertContains('id', $def->notNullColumns);
593        self::assertContains('email', $def->notNullColumns);
594    }
595
596    public function testResolveAlterTableRenameColumnNotNullUpdated(): void
597    {
598        $registry = new TableDefinitionRegistry();
599        $registry->register('t', new TableDefinition(['id', 'name'], ['id' => 'INTEGER', 'name' => 'TEXT'], ['id'], ['id', 'name'], []));
600        $store = new ShadowStore();
601        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
602        $mutation = $resolver->resolveAlterTable('ALTER TABLE t RENAME COLUMN name TO full_name');
603        $registry->unregister('t');
604        $mutation->apply($store, []);
605        $def = $registry->get('t');
606        self::assertNotNull($def);
607        self::assertContains('full_name', $def->notNullColumns);
608        self::assertNotContains('name', $def->notNullColumns);
609    }
610
611    public function testResolveCreateTableIfNotExistsLowercaseDoesNotThrow(): void
612    {
613        $store = new ShadowStore();
614        $registry = new TableDefinitionRegistry();
615        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
616        $mutation = $resolver->resolveCreateTable('CREATE TABLE if not exists t (id INTEGER)');
617        self::assertInstanceOf(CreateTableMutation::class, $mutation);
618    }
619
620    public function testResolveCreateTableAlreadyExistsNoIfNotExistsThrows(): void
621    {
622        $store = new ShadowStore();
623        $registry = new TableDefinitionRegistry();
624        $registry->register('t', new TableDefinition(['id'], ['id' => 'INTEGER'], ['id'], [], []));
625        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
626        $this->expectException(UnsupportedSqlException::class);
627        $resolver->resolveCreateTable('CREATE TABLE t (id INTEGER)');
628    }
629
630    public function testResolveAlterTableOnNonExistentTableThrows(): void
631    {
632        $store = new ShadowStore();
633        $registry = new TableDefinitionRegistry();
634        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
635        $this->expectException(UnknownSchemaException::class);
636        $resolver->resolveAlterTable('ALTER TABLE nonexistent ADD COLUMN a INTEGER');
637    }
638
639    public function testResolveAlterTableUnsupportedOperationThrowsForUnknownAction(): void
640    {
641        $store = new ShadowStore();
642        $registry = new TableDefinitionRegistry();
643        $registry->register('t', new TableDefinition(['id'], ['id' => 'INTEGER'], ['id'], [], []));
644        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
645        $this->expectException(UnsupportedSqlException::class);
646        $resolver->resolveAlterTable('ALTER TABLE t WHATEVER');
647    }
648
649    public function testResolveAlterTableAddColumnWithoutColumnKeyword(): void
650    {
651        $store = new ShadowStore();
652        $registry = new TableDefinitionRegistry();
653        $registry->register('t', new TableDefinition(['id'], ['id' => 'INTEGER'], ['id'], [], []));
654        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
655        $mutation = $resolver->resolveAlterTable('ALTER TABLE t ADD name TEXT');
656        $registry->unregister('t');
657        $mutation->apply($store, []);
658        $def = $registry->get('t');
659        self::assertNotNull($def);
660        self::assertContains('name', $def->columns);
661    }
662
663    public function testResolveAlterTableRenameColumnWithoutColumnKeyword(): void
664    {
665        $store = new ShadowStore();
666        $registry = new TableDefinitionRegistry();
667        $registry->register('t', new TableDefinition(['id', 'old_name'], ['id' => 'INTEGER', 'old_name' => 'TEXT'], ['id'], [], []));
668        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
669        $mutation = $resolver->resolveAlterTable('ALTER TABLE t RENAME old_name TO new_name');
670        $registry->unregister('t');
671        $mutation->apply($store, []);
672        $def = $registry->get('t');
673        self::assertNotNull($def);
674        self::assertContains('new_name', $def->columns);
675        self::assertNotContains('old_name', $def->columns);
676    }
677
678    public function testResolveAlterTableAddColumnAppliesNewColumnToDefinition(): void
679    {
680        $store = new ShadowStore();
681        $registry = new TableDefinitionRegistry();
682        $registry->register('t', new TableDefinition(['id'], ['id' => 'INTEGER'], ['id'], [], []));
683        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
684        $mutation = $resolver->resolveAlterTable('ALTER TABLE t ADD COLUMN status TEXT');
685        $registry->unregister('t');
686        $mutation->apply($store, []);
687        $def = $registry->get('t');
688        self::assertNotNull($def);
689        self::assertContains('status', $def->columns);
690    }
691
692    public function testResolveAlterTableAddColumnAppliesParenthesizedType(): void
693    {
694        $store = new ShadowStore();
695        $registry = new TableDefinitionRegistry();
696        $registry->register('t', new TableDefinition(['id'], ['id' => 'INTEGER'], ['id'], [], []));
697        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
698        $mutation = $resolver->resolveAlterTable('ALTER TABLE t ADD COLUMN price DECIMAL(10,2)');
699        $registry->unregister('t');
700        $mutation->apply($store, []);
701        $def = $registry->get('t');
702        self::assertNotNull($def);
703        self::assertContains('price', $def->columns);
704        self::assertSame('DECIMAL(10,2)', $def->columnTypes['price']);
705    }
706
707    public function testResolveDropTableIfExistsOnNonExistentTableDoesNotThrow(): void
708    {
709        $store = new ShadowStore();
710        $registry = new TableDefinitionRegistry();
711        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
712        $mutation = $resolver->resolveDropTable('DROP TABLE IF EXISTS nonexistent');
713        self::assertInstanceOf(DropTableMutation::class, $mutation);
714    }
715
716    public function testResolveDropTableNonExistentWithoutIfExistsThrows(): void
717    {
718        $store = new ShadowStore();
719        $registry = new TableDefinitionRegistry();
720        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
721        $this->expectException(UnknownSchemaException::class);
722        $resolver->resolveDropTable('DROP TABLE nonexistent');
723    }
724
725    public function testResolveCreateTableIfNotExistsExistingTableNoError(): void
726    {
727        $store = new ShadowStore();
728        $registry = new TableDefinitionRegistry();
729        $registry->register('t', new TableDefinition(['id'], ['id' => 'INTEGER'], ['id'], [], []));
730        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
731        $mutation = $resolver->resolveCreateTable('CREATE TABLE IF NOT EXISTS t (id INTEGER)');
732        self::assertInstanceOf(CreateTableMutation::class, $mutation);
733    }
734
735    public function testResolveDropTableExistingReturnsDropMutation(): void
736    {
737        $store = new ShadowStore();
738        $registry = new TableDefinitionRegistry();
739        $registry->register('t', new TableDefinition(['id'], ['id' => 'INTEGER'], ['id'], [], []));
740        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
741        $mutation = $resolver->resolveDropTable('DROP TABLE t');
742        self::assertInstanceOf(DropTableMutation::class, $mutation);
743        self::assertSame('t', $mutation->tableName());
744    }
745
746    public function testResolveAlterTableWithAddWithoutColumnKeywordDetected(): void
747    {
748        $store = new ShadowStore();
749        $registry = new TableDefinitionRegistry();
750        $registry->register('t', new TableDefinition(['id'], ['id' => 'INTEGER'], ['id'], [], []));
751        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
752        $mutation = $resolver->resolveAlterTable('ALTER TABLE t ADD email TEXT');
753        $registry->unregister('t');
754        $mutation->apply($store, []);
755        $def = $registry->get('t');
756        self::assertNotNull($def);
757        self::assertContains('email', $def->columns);
758        self::assertSame('TEXT', $def->columnTypes['email']);
759    }
760
761    public function testResolveCreateTableCaseInsensitiveIfNotExists(): void
762    {
763        $store = new ShadowStore();
764        $registry = new TableDefinitionRegistry();
765        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
766        $mutation = $resolver->resolveCreateTable('create table if not exists t (id integer primary key)');
767        self::assertInstanceOf(CreateTableMutation::class, $mutation);
768    }
769
770    public function testResolveAlterTableCaseInsensitiveAddColumn(): void
771    {
772        $store = new ShadowStore();
773        $registry = new TableDefinitionRegistry();
774        $registry->register('t', new TableDefinition(['id'], ['id' => 'INTEGER'], ['id'], [], []));
775        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
776        $mutation = $resolver->resolveAlterTable('alter table t add column name text');
777        $registry->unregister('t');
778        $mutation->apply($store, []);
779        $def = $registry->get('t');
780        self::assertNotNull($def);
781        self::assertContains('name', $def->columns);
782    }
783
784    public function testResolveAlterTableCaseInsensitiveDropColumn(): void
785    {
786        $store = new ShadowStore();
787        $registry = new TableDefinitionRegistry();
788        $registry->register('t', new TableDefinition(['id', 'name'], ['id' => 'INTEGER', 'name' => 'TEXT'], ['id'], ['id'], ['idx_name' => ['name']]));
789        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
790        $mutation = $resolver->resolveAlterTable('alter table t drop column name');
791        $registry->unregister('t');
792        $mutation->apply($store, []);
793        $def = $registry->get('t');
794        self::assertNotNull($def);
795        self::assertNotContains('name', $def->columns);
796    }
797
798    public function testResolveAlterTableCaseInsensitiveRenameColumn(): void
799    {
800        $store = new ShadowStore();
801        $registry = new TableDefinitionRegistry();
802        $registry->register('t', new TableDefinition(['id', 'name'], ['id' => 'INTEGER', 'name' => 'TEXT'], ['id'], ['name'], ['idx_name' => ['name']]));
803        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
804        $mutation = $resolver->resolveAlterTable('alter table t rename column name to full_name');
805        $registry->unregister('t');
806        $mutation->apply($store, []);
807        $def = $registry->get('t');
808        self::assertNotNull($def);
809        self::assertContains('full_name', $def->columns);
810        self::assertNotContains('name', $def->columns);
811    }
812
813    public function testResolveAlterTableCaseInsensitiveRenameTo(): void
814    {
815        $store = new ShadowStore();
816        $registry = new TableDefinitionRegistry();
817        $registry->register('t', new TableDefinition(['id'], ['id' => 'INTEGER'], ['id'], [], []));
818        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
819        $mutation = $resolver->resolveAlterTable('alter table t rename to t2');
820        self::assertInstanceOf(AlterTableMutation::class, $mutation);
821    }
822
823    public function testResolveDropTableCaseInsensitiveIfExists(): void
824    {
825        $store = new ShadowStore();
826        $registry = new TableDefinitionRegistry();
827        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
828        $mutation = $resolver->resolveDropTable('drop table if exists nonexistent');
829        self::assertInstanceOf(DropTableMutation::class, $mutation);
830    }
831
832    public function testResolveCreateTableIfNotExistsExistingTableLowercase(): void
833    {
834        $registry = new TableDefinitionRegistry();
835        $registry->register('t', new TableDefinition(['id'], ['id' => 'INTEGER'], ['id'], [], []));
836        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
837        $mutation = $resolver->resolveCreateTable('create table if not exists t (id integer primary key)');
838        self::assertInstanceOf(CreateTableMutation::class, $mutation);
839    }
840
841    public function testResolveDropTableIfExistsLowercaseRegex(): void
842    {
843        $registry = new TableDefinitionRegistry();
844        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
845        $mutation = $resolver->resolveDropTable('drop table if exists nonexistent');
846        self::assertInstanceOf(DropTableMutation::class, $mutation);
847    }
848
849    public function testResolveCreateTableIfNotExistsWithoutExistingTable(): void
850    {
851        $resolver = new TableMutationResolver(new SqliteParser(), new TableDefinitionRegistry(), new SqliteSchemaParser());
852        $mutation = $resolver->resolveCreateTable('CREATE TABLE IF NOT EXISTS t (id INTEGER PRIMARY KEY)');
853        self::assertInstanceOf(CreateTableMutation::class, $mutation);
854        self::assertSame('t', $mutation->tableName());
855    }
856
857    public function testResolveCreateTableWithoutIfNotExistsAlreadyExistsThrows(): void
858    {
859        $registry = new TableDefinitionRegistry();
860        $registry->register('t', new TableDefinition(['id'], ['id' => 'INTEGER'], ['id'], [], []));
861        $resolver = new TableMutationResolver(new SqliteParser(), $registry, new SqliteSchemaParser());
862        $this->expectException(UnsupportedSqlException::class);
863        $resolver->resolveCreateTable('CREATE TABLE t (id INTEGER PRIMARY KEY)');
864    }
865
866}
867