packages/ztd-query-sqlite/tests/Unit/Shadow/Mutation/Table/Alter/AddColumnResolverTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Shadow\Mutation\Table\Alter;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\UsesClass;
9use PHPUnit\Framework\TestCase;
10use ZtdQuery\Platform\Sqlite\Schema\SqliteSchemaParser;
11use ZtdQuery\Platform\Sqlite\Shadow\Mutation\Table\Alter\AddColumnResolver;
12use ZtdQuery\Platform\Sqlite\Shadow\Mutation\Table\AlterTableMutation;
13use ZtdQuery\Schema\TableDefinition;
14use ZtdQuery\Schema\TableDefinitionRegistry;
15use ZtdQuery\Shadow\ShadowStore;
16
17#[CoversClass(AddColumnResolver::class)]
18#[UsesClass(AlterTableMutation::class)]
19#[UsesClass(\ZtdQuery\Platform\Sqlite\Shadow\Mutation\Table\Alter\AlteredTableProjection::class)]
20#[UsesClass(\ZtdQuery\Platform\Sqlite\Shadow\Resolution\MutationTableLookup::class)]
21#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Dml\Expression\AssignmentColumnParser::class)]
22#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Dml\Expression\AssignmentParser::class)]
23#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Dml\Expression\ValueListParser::class)]
24#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Dml\Insert\InsertClauseParser::class)]
25#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\ExpressionSpan::class)]
26#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\IdentifierDecoder::class)]
27#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\LiteralMasker::class)]
28#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\OpaqueSqlSpan::class)]
29#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\QuotedSpan::class)]
30#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\TopLevelKeywordScanner::class)]
31#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Relation\RelationSourceParser::class)]
32#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Statement\StatementClassifier::class)]
33#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Statement\StatementStructure::class)]
34#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Statement\TargetTableParser::class)]
35#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Dml\Update\UpdateClauseParser::class)]
36#[UsesClass(\ZtdQuery\Platform\Sqlite\Schema\Create\ColumnDefinitionParser::class)]
37#[UsesClass(\ZtdQuery\Platform\Sqlite\Schema\Create\TableBodyParser::class)]
38#[UsesClass(\ZtdQuery\Platform\Sqlite\Schema\Create\TableDefinitionBuilder::class)]
39#[UsesClass(\ZtdQuery\Platform\Sqlite\Schema\Create\VirtualTableParser::class)]
40#[UsesClass(\ZtdQuery\Platform\Sqlite\Schema\Key\ForeignKeyEntryParser::class)]
41#[UsesClass(\ZtdQuery\Platform\Sqlite\Schema\Key\ForeignKeyTokens::class)]
42#[UsesClass(\ZtdQuery\Platform\Sqlite\Schema\SqliteColumnTypeMapper::class)]
43#[UsesClass(\ZtdQuery\Platform\Sqlite\Schema\Key\SqliteForeignKeyDefinitionParser::class)]
44#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\SqliteIdentifierQuoter::class)]
45#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\SqliteLexerProfile::class)]
46#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\SqliteLexicalMasker::class)]
47#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\SqliteParser::class)]
48#[UsesClass(SqliteSchemaParser::class)]
49#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Relation\SqliteSelectRelationParser::class)]
50final class AddColumnResolverTest extends TestCase
51{
52 public function testResolveAlterAddColumnProjectsDefaultAndRegistersNewSchema(): void
53 {
54 $registry = new TableDefinitionRegistry();
55 $definition = new TableDefinition(['id', 'name'], ['id' => 'INTEGER', 'name' => 'TEXT'], ['id'], ['id'], []);
56 $registry->register('users', $definition);
57 $store = new ShadowStore();
58 $store->set('users', [['id' => 1, 'name' => 'Alice']]);
59 $mutation = (new AddColumnResolver($registry, new SqliteSchemaParser()))->resolveAlterAddColumn('ALTER TABLE users ADD score INTEGER DEFAULT 0', 'users', 'score INTEGER DEFAULT 0');
60 self::assertInstanceOf(AlterTableMutation::class, $mutation);
61 self::assertSame('SELECT "id", "name", 0 AS "score" FROM "users"', $mutation->resultSelect());
62 self::assertSame($definition, $registry->get('users'));
63 $mutation->apply($store, [['id' => 1, 'name' => 'Alice', 'score' => 0]]);
64 self::assertSame(['id', 'name', 'score'], $registry->get('users')->columns);
65 self::assertSame([['id' => 1, 'name' => 'Alice', 'score' => 0]], $store->get('users'));
66 }
67
68 public function testResolveAlterAddColumnRejectsDuplicateColumnIgnoringCase(): void
69 {
70 $registry = new TableDefinitionRegistry();
71 $definition = new TableDefinition(['id', 'name'], ['id' => 'INTEGER', 'name' => 'TEXT'], ['id'], ['id'], []);
72 $registry->register('users', $definition);
73 $store = new ShadowStore();
74 $store->set('users', [['id' => 1, 'name' => 'Alice']]);
75 $this->expectException(\ZtdQuery\Exception\ColumnAlreadyExistsException::class);
76 (new AddColumnResolver($registry, new SqliteSchemaParser()))->resolveAlterAddColumn('ALTER TABLE users ADD NAME TEXT', 'users', 'NAME TEXT');
77 }
78
79 public function testResolveAlterAddColumnRejectsMultipleColumns(): void
80 {
81 $registry = new TableDefinitionRegistry();
82 $definition = new TableDefinition(['id', 'name'], ['id' => 'INTEGER', 'name' => 'TEXT'], ['id'], ['id'], []);
83 $registry->register('users', $definition);
84 $store = new ShadowStore();
85 $store->set('users', [['id' => 1, 'name' => 'Alice']]);
86 $this->expectException(\ZtdQuery\Exception\UnsupportedSqlException::class);
87 (new AddColumnResolver($registry, new SqliteSchemaParser()))->resolveAlterAddColumn('ALTER TABLE users ADD a TEXT, b TEXT', 'users', 'a TEXT, b TEXT');
88 }
89
90}
91