packages/ztd-query-sqlite/tests/Unit/Schema/Create/TableDefinitionBuilderTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Schema\Create;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\UsesClass;
9use PHPUnit\Framework\TestCase;
10use ZtdQuery\Platform\Sqlite\Schema\Create\TableDefinitionBuilder;
11
12#[CoversClass(TableDefinitionBuilder::class)]
13#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Dml\Expression\AssignmentColumnParser::class)]
14#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Dml\Expression\AssignmentParser::class)]
15#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Dml\Expression\ValueListParser::class)]
16#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Dml\Insert\InsertClauseParser::class)]
17#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\ExpressionSpan::class)]
18#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\IdentifierDecoder::class)]
19#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\LiteralMasker::class)]
20#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\OpaqueSqlSpan::class)]
21#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\QuotedSpan::class)]
22#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\TopLevelKeywordScanner::class)]
23#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Relation\RelationSourceParser::class)]
24#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Statement\StatementClassifier::class)]
25#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Statement\StatementStructure::class)]
26#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Statement\TargetTableParser::class)]
27#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Dml\Update\UpdateClauseParser::class)]
28#[UsesClass(\ZtdQuery\Platform\Sqlite\Schema\Create\ColumnDefinitionParser::class)]
29#[UsesClass(\ZtdQuery\Platform\Sqlite\Schema\Create\TableBodyParser::class)]
30#[UsesClass(\ZtdQuery\Platform\Sqlite\Schema\Key\ForeignKeyEntryParser::class)]
31#[UsesClass(\ZtdQuery\Platform\Sqlite\Schema\Key\ForeignKeyTokens::class)]
32#[UsesClass(\ZtdQuery\Platform\Sqlite\Schema\SqliteColumnTypeMapper::class)]
33#[UsesClass(\ZtdQuery\Platform\Sqlite\Schema\Key\SqliteForeignKeyDefinitionParser::class)]
34#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\SqliteLexerProfile::class)]
35#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\SqliteLexicalMasker::class)]
36#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\SqliteParser::class)]
37#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Relation\SqliteSelectRelationParser::class)]
38final class TableDefinitionBuilderTest extends TestCase
39{
40    public function testAddDefinitionCollectsTypedColumnsAndPrimaryKeys(): void
41    {
42        $builder = new TableDefinitionBuilder();
43        $builder->addDefinition('id INTEGER PRIMARY KEY');
44        $builder->addDefinition('name TEXT NOT NULL');
45        $builder->addDefinition('');
46        $definition = $builder->build('CREATE TABLE users(id INTEGER PRIMARY KEY, name TEXT NOT NULL)');
47        self::assertNotNull($definition);
48        self::assertSame(['id', 'name'], $definition->columns);
49        self::assertSame(['id'], $definition->primaryKeys);
50        self::assertSame(['id', 'name'], $definition->notNullColumns);
51        self::assertSame(\ZtdQuery\Schema\Key\IdentityGenerationStrategy::MaxValue, $definition->identityStrategies['id']);
52    }
53
54    public function testAddConstraintPreservesCompositeAndUniqueKeys(): void
55    {
56        $builder = new TableDefinitionBuilder();
57        $builder->addDefinition('a INTEGER');
58        $builder->addDefinition('b INTEGER');
59        $builder->addConstraint('CONSTRAINT pk PRIMARY KEY(a, b)');
60        $builder->addConstraint('UNIQUE(b)');
61        $definition = $builder->build('CREATE TABLE t(a INTEGER, b INTEGER, PRIMARY KEY(a, b))');
62        self::assertNotNull($definition);
63        self::assertSame(['a', 'b'], $definition->primaryKeys);
64        self::assertSame(['unique_0' => ['b']], $definition->uniqueConstraints);
65        self::assertSame([], $definition->identityStrategies);
66    }
67
68    public function testAddColumnPreservesDefaultsAndGeneratedExpressions(): void
69    {
70        $builder = new TableDefinitionBuilder();
71        $builder->addColumn([
72            'name' => 'total', 'type' => 'INTEGER', 'notNull' => false, 'primaryKey' => false,
73            'unique' => true, 'default' => '0', 'generatedExpression' => '(2 + 3)',
74        ]);
75        $definition = $builder->build('CREATE TABLE t(total INTEGER)');
76        self::assertNotNull($definition);
77        self::assertSame(['total_UNIQUE' => ['total']], $definition->uniqueConstraints);
78        self::assertSame(['total' => '0'], $definition->columnDefaults);
79        self::assertSame(['total' => '(2 + 3)'], $definition->generatedExpressions);
80    }
81
82    public function testBuildRejectsMissingColumnsAndUnknownUniqueColumns(): void
83    {
84        $builder = new TableDefinitionBuilder();
85        self::assertNull($builder->build('CREATE TABLE t()'));
86        $builder->addDefinition('id INTEGER');
87        $builder->addConstraint('UNIQUE(missing)');
88        self::assertNull($builder->build('CREATE TABLE t(id INTEGER, UNIQUE(missing))'));
89    }
90
91    public function testBuildDisablesIdentityForWithoutRowid(): void
92    {
93        $builder = new TableDefinitionBuilder();
94        $builder->addDefinition('id INTEGER PRIMARY KEY');
95        self::assertSame([], $builder->build('CREATE TABLE t(id INTEGER PRIMARY KEY) WITHOUT ROWID')?->identityStrategies);
96    }
97
98}
99