packages/sql-semantics-mysql/tests/Unit/QueryRulesTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit;
6
7use PHPUnit\Framework\TestCase;
8use SqlSemantics\Core\Binder;
9use SqlSemantics\Core\Model\Expression;
10use SqlSemantics\Core\Model\ExpressionKind;
11use SqlSemantics\Core\SchemaBuilder;
12use SqlSemantics\Core\Type\Nullability;
13use SqlSemantics\Core\Type\TypeDescriptor;
14use SqlSemantics\Platform\MySql\Dialect;
15
16#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\SemanticException::class)]
17#[\PHPUnit\Framework\Attributes\CoversClass(SchemaBuilder::class)]
18#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Schema::class)]
19#[\PHPUnit\Framework\Attributes\CoversClass(Binder::class)]
20#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Binding\NullFacts::class)]
21#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Binding\TypeResolution::class)]
22#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Binding\IdentitySequence::class)]
23#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Binding\SyntaxGuard::class)]
24#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Binding\Scope::class)]
25#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Binding\BoundRelation::class)]
26#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Binding\FromBinder::class)]
27#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Binding\SelectModifiersBinder::class)]
28#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Binding\TableResolver::class)]
29#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Binding\SelectBinder::class)]
30#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Binding\ProjectionBinder::class)]
31#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Binding\ExpressionRules::class)]
32#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Binding\ExpressionBinder::class)]
33#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Binding\LiteralBinder::class)]
34#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Schema\ColumnDefinition::class)]
35#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Schema\TableDefinition::class)]
36#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Schema\ConstraintKind::class)]
37#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Schema\TableConstraint::class)]
38#[\PHPUnit\Framework\Attributes\CoversClass(Nullability::class)]
39#[\PHPUnit\Framework\Attributes\CoversClass(TypeDescriptor::class)]
40#[\PHPUnit\Framework\Attributes\CoversClass(Expression::class)]
41#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Model\Join::class)]
42#[\PHPUnit\Framework\Attributes\CoversClass(ExpressionKind::class)]
43#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Model\BoundSelect::class)]
44#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Model\TableUse::class)]
45#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Model\ColumnBinding::class)]
46#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Model\Ordering::class)]
47#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Model\OutputColumn::class)]
48#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Model\JoinKind::class)]
49#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Ast\TypeReader::class)]
50#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Ast\DialectParser::class)]
51#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Ast\TokenGroups::class)]
52#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Ast\Tree::class)]
53#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Ast\ColumnReader::class)]
54#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Ast\SchemaReader::class)]
55#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Ast\ConstraintReader::class)]
56#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Ast\Identifiers::class)]
57#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Ast\StatementList::class)]
58#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Core\Policy\SyntaxRules::class)]
59#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Platform\MySql\QueryRules::class)]
60#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Platform\MySql\Platform::class)]
61#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Platform\MySql\TypeRules::class)]
62#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Platform\MySql\NameRules::class)]
63#[\PHPUnit\Framework\Attributes\CoversClass(\SqlSemantics\Platform\MySql\SchemaRules::class)]
64#[\PHPUnit\Framework\Attributes\CoversClass(Dialect::class)]
65#[\PHPUnit\Framework\Attributes\Medium]
66final class QueryRulesTest extends TestCase
67{
68    public function testProjectionItemsPreserveOutputOrder(): void
69    {
70        $schema = (new SchemaBuilder(Dialect::MySql))->build('CREATE TABLE items (id INTEGER PRIMARY KEY, label TEXT)');
71        $bound = (new Binder($schema))->bind('SELECT label, id FROM items');
72        self::assertSame(['label', 'id'], array_column($bound->outputs, 'name'));
73    }
74
75    public function testProjectionTokensExpandQualifiedStar(): void
76    {
77        $schema = (new SchemaBuilder(Dialect::MySql))->build('CREATE TABLE items (id INTEGER PRIMARY KEY, label TEXT)');
78        $bound = (new Binder($schema))->bind('SELECT i.* FROM items i');
79        self::assertSame(['id', 'label'], array_column($bound->outputs, 'name'));
80    }
81
82    public function testOrderingNodesPreserveDirection(): void
83    {
84        $schema = (new SchemaBuilder(Dialect::MySql))->build('CREATE TABLE items (id INTEGER PRIMARY KEY, label TEXT)');
85        $bound = (new Binder($schema))->bind('SELECT id, label FROM items ORDER BY label DESC, id ASC');
86        self::assertCount(2, $bound->orderBy);
87        self::assertTrue($bound->orderBy[0]->descending);
88        self::assertFalse($bound->orderBy[1]->descending);
89    }
90
91    public function testRelationResolvesAnAlias(): void
92    {
93        $schema = (new SchemaBuilder(Dialect::MySql))->build('CREATE TABLE items (id INTEGER PRIMARY KEY, label TEXT)');
94        $bound = (new Binder($schema))->bind('SELECT i.id FROM items i');
95        self::assertSame('i', $bound->relations[0]->alias);
96    }
97
98    public function testJoinedTracksNullableInput(): void
99    {
100        $schema = (new SchemaBuilder(Dialect::MySql))->build('CREATE TABLE items (id INTEGER PRIMARY KEY, label TEXT)');
101        $bound = (new Binder($schema))->bind('SELECT b.id FROM items a LEFT JOIN items b ON a.id = b.id');
102        self::assertSame(['j0'], $bound->outputs[0]->expression->nullExtendedBy);
103    }
104}
105