packages/sql-semantics/tests/Unit/Core/Ast/ColumnPropertiesTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Core\Ast;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\Medium;
9use PHPUnit\Framework\TestCase;
10use SqlSemantics\Core\Binder;
11use SqlSemantics\Core\SchemaBuilder;
12use SqlSemantics\Core\SemanticException;
13use SqlSemantics\Facade\Schema;
14use SqlSemantics\Platform\MySql\Dialect as MySqlDialect;
15use SqlSemantics\Platform\PostgreSql\Dialect as PostgreSqlDialect;
16use SqlSemantics\Platform\Sqlite\Dialect as SqliteDialect;
17use SqlSemantics\Statement\Writer;
18
19#[CoversClass(Binder::class)]
20#[CoversClass(SchemaBuilder::class)]
21#[CoversClass(\SqlSemantics\Core\Ast\DialectParser::class)]
22#[CoversClass(\SqlSemantics\Core\Binding\ExpressionBinder::class)]
23#[CoversClass(\SqlSemantics\Core\Binding\ExpressionRules::class)]
24#[CoversClass(\SqlSemantics\Core\Binding\FromBinder::class)]
25#[CoversClass(\SqlSemantics\Core\Binding\LiteralBinder::class)]
26#[CoversClass(\SqlSemantics\Core\Binding\NullFacts::class)]
27#[CoversClass(\SqlSemantics\Core\Binding\ProjectionBinder::class)]
28#[CoversClass(\SqlSemantics\Core\Binding\SelectBinder::class)]
29#[CoversClass(\SqlSemantics\Core\Binding\SyntaxGuard::class)]
30#[CoversClass(\SqlSemantics\Core\Binding\SelectModifiersBinder::class)]
31#[CoversClass(\SqlSemantics\Core\Binding\TypeResolution::class)]
32#[CoversClass(\SqlSemantics\Core\Ast\ColumnReader::class)]
33#[CoversClass(\SqlSemantics\Core\Ast\ConstraintReader::class)]
34#[CoversClass(\SqlSemantics\Core\Ast\Identifiers::class)]
35#[CoversClass(\SqlSemantics\Core\Ast\SchemaReader::class)]
36#[CoversClass(\SqlSemantics\Core\Ast\StatementList::class)]
37#[CoversClass(\SqlSemantics\Core\Ast\TokenGroups::class)]
38#[CoversClass(\SqlSemantics\Core\Ast\Tree::class)]
39#[CoversClass(\SqlSemantics\Core\Ast\TypeReader::class)]
40#[CoversClass(\SqlSemantics\Core\Binding\BoundRelation::class)]
41#[CoversClass(\SqlSemantics\Core\Binding\IdentitySequence::class)]
42#[CoversClass(\SqlSemantics\Core\Binding\Scope::class)]
43#[CoversClass(\SqlSemantics\Core\Binding\TableResolver::class)]
44#[CoversClass(\SqlSemantics\Core\Model\ColumnBinding::class)]
45#[CoversClass(\SqlSemantics\Core\Model\Expression::class)]
46#[CoversClass(\SqlSemantics\Core\Model\Join::class)]
47#[CoversClass(\SqlSemantics\Core\Model\Ordering::class)]
48#[CoversClass(\SqlSemantics\Core\Model\OutputColumn::class)]
49#[CoversClass(\SqlSemantics\Core\Model\BoundSelect::class)]
50#[CoversClass(\SqlSemantics\Core\Model\TableUse::class)]
51#[CoversClass(\SqlSemantics\Core\Schema::class)]
52#[CoversClass(\SqlSemantics\Core\Schema\ColumnDefinition::class)]
53#[CoversClass(\SqlSemantics\Core\Schema\TableConstraint::class)]
54#[CoversClass(\SqlSemantics\Core\Schema\TableDefinition::class)]
55#[CoversClass(SemanticException::class)]
56#[CoversClass(\SqlSemantics\Core\Type\TypeDescriptor::class)]
57#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Core\Policy\SyntaxRules::class)]
58#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\PostgreSql\QueryRules::class)]
59#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\PostgreSql\Platform::class)]
60#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\PostgreSql\TypeRules::class)]
61#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\PostgreSql\NameRules::class)]
62#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\PostgreSql\SchemaRules::class)]
63#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\Sqlite\QueryRules::class)]
64#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\Sqlite\Platform::class)]
65#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\Sqlite\TypeRules::class)]
66#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\Sqlite\NameRules::class)]
67#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\Sqlite\SchemaRules::class)]
68#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\MySql\QueryRules::class)]
69#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\MySql\Platform::class)]
70#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\MySql\TypeRules::class)]
71#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\MySql\NameRules::class)]
72#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\MySql\SchemaRules::class)]
73#[Medium]
74#[CoversClass(\SqlSemantics\Core\Analysis\SchemaAnalyzer::class)]
75#[CoversClass(\SqlSemantics\Core\Analysis\ValueReader::class)]
76#[CoversClass(\SqlSemantics\Core\Ast\ColumnProperties::class)]
77#[CoversClass(\SqlSemantics\Core\Ast\SchemaChanges::class)]
78#[CoversClass(\SqlSemantics\Core\Schema\ColumnGeneration::class)]
79#[CoversClass(\SqlSemantics\Core\Schema\Invariant::class)]
80#[CoversClass(Schema::class)]
81#[CoversClass(\SqlSemantics\Statement\ImmutableGraph::class)]
82#[CoversClass(Writer::class)]
83#[CoversClass(\SqlSemantics\Statement\Assertion::class)]
84final class ColumnPropertiesTest extends TestCase
85{
86    public function testGenerationDoesNotConfuseDefaultCastsWithGeneratedColumns(): void
87    {
88        $state = (new Schema(PostgreSqlDialect::PostgreSql))->analyze('CREATE TABLE t (id INT DEFAULT CAST(1 AS INT))');
89        self::assertNull($state->tables[0]->columns[0]->generation);
90        self::assertNotNull($state->tables[0]->columns[0]->defaultExpression);
91    }
92
93    public function testGenerationPreservesVirtualAndIdentityKinds(): void
94    {
95        $state = (new Schema(SqliteDialect::Sqlite))->analyze('CREATE TABLE t (stored INT, x INT AS (stored + 1) VIRTUAL)');
96        self::assertSame('virtual', $state->tables[0]->columns[1]->generation?->kind->value);
97        $identity = (new Schema(PostgreSqlDialect::PostgreSql))->analyze('CREATE TABLE t (id INT GENERATED BY DEFAULT AS IDENTITY (START WITH 10))')->tables[0]->columns[0];
98        self::assertSame('identity', $identity->generation?->kind->value);
99        self::assertNull($identity->generation->expression);
100    }
101
102    public function testCollationKeepsQuotedNames(): void
103    {
104        $column = (new Schema(PostgreSqlDialect::PostgreSql))->analyze('CREATE TABLE t (label TEXT COLLATE "C")')->tables[0]->columns[0];
105        self::assertNotNull($column->collation);
106        self::assertSame('COLLATE "C"', Writer::render($column->collation));
107    }
108
109    public function testAutoIncrementIsAnExplicitStateProperty(): void
110    {
111        $state = (new Schema(MySqlDialect::MySql))->analyze('CREATE TABLE t (id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, note TEXT)');
112        self::assertTrue($state->tables[0]->columns[0]->autoIncrement);
113        self::assertFalse($state->tables[0]->columns[1]->autoIncrement);
114        self::assertSame('BIGINT UNSIGNED', $state->tables[0]->columns[0]->type->name);
115    }
116
117
118    public function testGenerationKeepsNestedQueryStructureInsideTheColumn(): void
119    {
120        $state = (new Schema(MySqlDialect::MySql))->analyze('CREATE TABLE t (id INT AS ((SELECT 1)))');
121        self::assertSame(['id'], array_column($state->tables[0]->columns, 'name'));
122        self::assertNotNull($state->tables[0]->columns[0]->generation);
123        self::assertNotNull($state->tables[0]->columns[0]->generation->expression);
124        self::assertStringContainsString('SELECT 1', Writer::render($state->tables[0]->columns[0]->generation->expression));
125    }
126
127    public function testGenerationDoesNotTreatNamedDefaultsAsComputedColumns(): void
128    {
129        $column = (new Schema(PostgreSqlDialect::PostgreSql))->analyze('CREATE TABLE t (id INT CONSTRAINT d DEFAULT CAST(1 AS INT))')->tables[0]->columns[0];
130        self::assertNull($column->generation);
131        self::assertNotNull($column->defaultExpression);
132    }
133
134    public function testAutoIncrementDoesNotInspectDefaultExpressionIdentifiers(): void
135    {
136        $column = (new Schema(PostgreSqlDialect::PostgreSql))->analyze('CREATE TABLE t (id INT DEFAULT auto_increment())')->tables[0]->columns[0];
137        self::assertFalse($column->autoIncrement);
138        self::assertSame(\SqlSemantics\Core\Type\Nullability::MaybeNull, $column->nullability);
139    }
140
141}
142