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