packages/sql-semantics/tests/Unit/Core/PlatformTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Core;
6
7use PHPUnit\Framework\TestCase;
8use SqlParser\Lexer\Token;
9use SqlSemantics\Core\Binder;
10use SqlSemantics\Core\Dialect;
11use SqlSemantics\Core\Model\Expression;
12use SqlSemantics\Core\Model\ExpressionKind;
13use SqlSemantics\Core\SchemaBuilder;
14use SqlSemantics\Core\Type\Nullability;
15use SqlSemantics\Core\Type\TypeDescriptor;
16use SqlSemantics\Platform\PostgreSql\Dialect as PostgreSqlDialect;
17
18#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\SemanticException::class)]
19#[\PHPUnit\Framework\Attributes\CoversClass(SchemaBuilder::class)]
20#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Schema::class)]
21#[\PHPUnit\Framework\Attributes\CoversClass(Binder::class)]
22#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Binding\NullFacts::class)]
23#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Binding\TypeResolution::class)]
24#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Binding\IdentitySequence::class)]
25#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Binding\SyntaxGuard::class)]
26#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Binding\Scope::class)]
27#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Binding\BoundRelation::class)]
28#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Binding\FromBinder::class)]
29#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Binding\SelectModifiersBinder::class)]
30#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Binding\TableResolver::class)]
31#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Binding\SelectBinder::class)]
32#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Binding\ProjectionBinder::class)]
33#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Binding\ExpressionRules::class)]
34#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Binding\ExpressionBinder::class)]
35#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Binding\LiteralBinder::class)]
36#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Schema\ColumnDefinition::class)]
37#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Schema\TableDefinition::class)]
38#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Schema\ConstraintKind::class)]
39#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Schema\TableConstraint::class)]
40#[\PHPUnit\Framework\Attributes\CoversClass(Nullability::class)]
41#[\PHPUnit\Framework\Attributes\CoversClass(TypeDescriptor::class)]
42#[\PHPUnit\Framework\Attributes\CoversClass(Expression::class)]
43#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Model\Join::class)]
44#[\PHPUnit\Framework\Attributes\CoversClass(ExpressionKind::class)]
45#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Model\BoundSelect::class)]
46#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Model\TableUse::class)]
47#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Model\ColumnBinding::class)]
48#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Model\Ordering::class)]
49#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Model\OutputColumn::class)]
50#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Model\JoinKind::class)]
51#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Ast\TypeReader::class)]
52#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Ast\DialectParser::class)]
53#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Ast\TokenGroups::class)]
54#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Ast\Tree::class)]
55#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Ast\ColumnReader::class)]
56#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Ast\SchemaReader::class)]
57#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Ast\ConstraintReader::class)]
58#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Ast\Identifiers::class)]
59#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Ast\StatementList::class)]
60#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Policy\SyntaxRules::class)]
61#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Platform\PostgreSql\QueryRules::class)]
62#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Platform\PostgreSql\Platform::class)]
63#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Platform\PostgreSql\TypeRules::class)]
64#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Platform\PostgreSql\NameRules::class)]
65#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Platform\PostgreSql\SchemaRules::class)]
66#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Platform\Sqlite\QueryRules::class)]
67#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Platform\Sqlite\Platform::class)]
68#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Platform\Sqlite\TypeRules::class)]
69#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Platform\Sqlite\NameRules::class)]
70#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Platform\Sqlite\SchemaRules::class)]
71#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Platform\MySql\QueryRules::class)]
72#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Platform\MySql\Platform::class)]
73#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Platform\MySql\TypeRules::class)]
74#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Platform\MySql\NameRules::class)]
75#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Platform\MySql\SchemaRules::class)]
76#[\PHPUnit\Framework\Attributes\Medium]
77#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Core\Analysis\ValueReader::class)]
78#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Statement\Statement::class)]
79#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Statement\Writer::class)]
80#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Statement\Element::class)]
81final class PlatformTest extends TestCase
82{
83    public function testParserAcceptsAnApplicationImplementation(): void
84    {
85        $parser = self::createStub(\SqlParser\Parser\SqlParser::class);
86        $parser->method('version')->willReturn('application-1');
87        $platform = self::createStub(\SqlSemantics\Core\Platform::class);
88        $platform->method('parser')->willReturn($parser);
89        $dialect = self::createStub(Dialect::class);
90        $dialect->method('platform')->willReturn($platform);
91        self::assertSame('application-1', (new \SqlSemantics\Core\Ast\DialectParser($dialect))->version());
92    }
93    public function testDefaultSchemaUsesTheLanguageNamespace(): void
94    {
95        $accept = static fn (\SqlSemantics\Core\Platform $rules): \SqlSemantics\Core\Platform => $rules;
96        self::assertSame(PostgreSqlDialect::PostgreSql->platform()::class, $accept(PostgreSqlDialect::PostgreSql->platform())::class);
97        self::assertSame('public', PostgreSqlDialect::PostgreSql->platform()->defaultSchema());
98    }
99    public function testStatementNamesIdentifyTheParserRoot(): void
100    {
101        $accept = static fn (\SqlSemantics\Core\Platform $rules): \SqlSemantics\Core\Platform => $rules;
102        self::assertSame(PostgreSqlDialect::PostgreSql->platform()::class, $accept(PostgreSqlDialect::PostgreSql->platform())::class);
103        self::assertSame(PostgreSqlDialect::PostgreSql->platform()->statementNames()[0], PostgreSqlDialect::PostgreSql->platform()->parser()->parse('SELECT 1')->name);
104    }
105    public function testSyntaxRecognizesSelectBody(): void
106    {
107        $accept = static fn (\SqlSemantics\Core\Platform $rules): \SqlSemantics\Core\Platform => $rules;
108        self::assertSame(PostgreSqlDialect::PostgreSql->platform()::class, $accept(PostgreSqlDialect::PostgreSql->platform())::class);
109        $tree = PostgreSqlDialect::PostgreSql->platform()->parser()->parse('SELECT 1');
110        self::assertNotEmpty(\SqlSemantics\Core\Ast\Tree::outer($tree, PostgreSqlDialect::PostgreSql->platform()->syntax()->nodes('selectBody')));
111    }
112    public function testNamesDecodeQuotedIdentifiers(): void
113    {
114        $accept = static fn (\SqlSemantics\Core\Platform $rules): \SqlSemantics\Core\Platform => $rules;
115        self::assertSame(PostgreSqlDialect::PostgreSql->platform()::class, $accept(PostgreSqlDialect::PostgreSql->platform())::class);
116        self::assertSame('Mixed', PostgreSqlDialect::PostgreSql->platform()->names()->name(new Token(1, 'ID', '"Mixed"', 0)));
117    }
118    public function testTypesRetainDialectIdentity(): void
119    {
120        $accept = static fn (\SqlSemantics\Core\Platform $rules): \SqlSemantics\Core\Platform => $rules;
121        self::assertSame(PostgreSqlDialect::PostgreSql->platform()::class, $accept(PostgreSqlDialect::PostgreSql->platform())::class);
122        self::assertSame(PostgreSqlDialect::PostgreSql, PostgreSqlDialect::PostgreSql->platform()->types()->boolean()->dialect);
123    }
124    public function testSchemaKeepsDeclarations(): void
125    {
126        $accept = static fn (\SqlSemantics\Core\Platform $rules): \SqlSemantics\Core\Platform => $rules;
127        self::assertSame(PostgreSqlDialect::PostgreSql->platform()::class, $accept(PostgreSqlDialect::PostgreSql->platform())::class);
128        $schema = (new SchemaBuilder(PostgreSqlDialect::PostgreSql))->build('CREATE TABLE items (id INTEGER PRIMARY KEY, label TEXT)');
129        self::assertCount(2, $schema->tables[0]->columns);
130    }
131    public function testQueryKeepsProjectionOrder(): void
132    {
133        $accept = static fn (\SqlSemantics\Core\Platform $rules): \SqlSemantics\Core\Platform => $rules;
134        self::assertSame(PostgreSqlDialect::PostgreSql->platform()::class, $accept(PostgreSqlDialect::PostgreSql->platform())::class);
135        $schema = (new SchemaBuilder(PostgreSqlDialect::PostgreSql))->build('CREATE TABLE items (id INTEGER PRIMARY KEY, label TEXT)');
136        $bound = (new Binder($schema))->bind('SELECT label, id FROM items');
137        self::assertSame(['label', 'id'], array_column($bound->outputs, 'name'));
138    }
139    public function testValuesReconstructsUsingTheParserRelease(): void
140    {
141        $platform = PostgreSqlDialect::PostgreSql->platform();
142        $parser = $platform->parser();
143        $value = $platform->values($parser->version())->read($parser->parse('SELECT 42'));
144        self::assertInstanceOf(\SqlSemantics\Statement\Command::class, $value);
145        self::assertSame('SELECT 42', (new \SqlSemantics\Statement\Statement($value))->toString());
146    }
147}
148