packages/sql-semantics/tests/Unit/Core/Ast/TreeTest.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\Attributes\UsesClass;
10use PHPUnit\Framework\TestCase;
11use SqlSemantics\Core\SemanticException;
12
13#[CoversClass(\SqlSemantics\Core\Ast\Tree::class)]
14#[UsesClass(\SqlSemantics\Core\Binding\ExpressionBinder::class)]
15#[UsesClass(\SqlSemantics\Core\Binding\ExpressionRules::class)]
16#[UsesClass(\SqlSemantics\Core\Binding\FromBinder::class)]
17#[UsesClass(\SqlSemantics\Core\Binding\LiteralBinder::class)]
18#[UsesClass(\SqlSemantics\Core\Binding\NullFacts::class)]
19#[UsesClass(\SqlSemantics\Core\Binding\ProjectionBinder::class)]
20#[UsesClass(\SqlSemantics\Core\Binding\SelectBinder::class)]
21#[UsesClass(\SqlSemantics\Core\Binding\SyntaxGuard::class)]
22#[UsesClass(\SqlSemantics\Core\Binding\SelectModifiersBinder::class)]
23#[UsesClass(\SqlSemantics\Core\Binding\TypeResolution::class)]
24#[UsesClass(\SqlSemantics\Core\Binder::class)]
25#[UsesClass(\SqlSemantics\Core\SchemaBuilder::class)]
26#[UsesClass(\SqlSemantics\Core\Ast\DialectParser::class)]
27#[UsesClass(\SqlSemantics\Core\Ast\ColumnReader::class)]
28#[UsesClass(\SqlSemantics\Core\Ast\ConstraintReader::class)]
29#[UsesClass(\SqlSemantics\Core\Ast\Identifiers::class)]
30#[UsesClass(\SqlSemantics\Core\Ast\SchemaReader::class)]
31#[UsesClass(\SqlSemantics\Core\Ast\StatementList::class)]
32#[UsesClass(\SqlSemantics\Core\Ast\TokenGroups::class)]
33#[UsesClass(\SqlSemantics\Core\Ast\TypeReader::class)]
34#[UsesClass(\SqlSemantics\Core\Binding\BoundRelation::class)]
35#[UsesClass(\SqlSemantics\Core\Binding\IdentitySequence::class)]
36#[UsesClass(\SqlSemantics\Core\Binding\Scope::class)]
37#[UsesClass(\SqlSemantics\Core\Binding\TableResolver::class)]
38#[UsesClass(\SqlSemantics\Core\Model\ColumnBinding::class)]
39#[UsesClass(\SqlSemantics\Core\Model\Expression::class)]
40#[UsesClass(\SqlSemantics\Core\Model\Join::class)]
41#[UsesClass(\SqlSemantics\Core\Model\Ordering::class)]
42#[UsesClass(\SqlSemantics\Core\Model\OutputColumn::class)]
43#[UsesClass(\SqlSemantics\Core\Model\BoundSelect::class)]
44#[UsesClass(\SqlSemantics\Core\Model\TableUse::class)]
45#[UsesClass(\SqlSemantics\Core\Schema::class)]
46#[UsesClass(\SqlSemantics\Core\Schema\ColumnDefinition::class)]
47#[UsesClass(\SqlSemantics\Core\Schema\TableConstraint::class)]
48#[UsesClass(\SqlSemantics\Core\Schema\TableDefinition::class)]
49#[UsesClass(SemanticException::class)]
50#[UsesClass(\SqlSemantics\Core\Type\TypeDescriptor::class)]
51#[UsesClass(\SqlSemantics\Core\Policy\SyntaxRules::class)]
52#[UsesClass(\SqlSemantics\Platform\PostgreSql\QueryRules::class)]
53#[UsesClass(\SqlSemantics\Platform\PostgreSql\Platform::class)]
54#[UsesClass(\SqlSemantics\Platform\PostgreSql\TypeRules::class)]
55#[UsesClass(\SqlSemantics\Platform\PostgreSql\NameRules::class)]
56#[UsesClass(\SqlSemantics\Platform\PostgreSql\SchemaRules::class)]
57#[UsesClass(\SqlSemantics\Platform\Sqlite\QueryRules::class)]
58#[UsesClass(\SqlSemantics\Platform\Sqlite\Platform::class)]
59#[UsesClass(\SqlSemantics\Platform\Sqlite\TypeRules::class)]
60#[UsesClass(\SqlSemantics\Platform\Sqlite\NameRules::class)]
61#[UsesClass(\SqlSemantics\Platform\Sqlite\SchemaRules::class)]
62#[UsesClass(\SqlSemantics\Platform\MySql\QueryRules::class)]
63#[UsesClass(\SqlSemantics\Platform\MySql\Platform::class)]
64#[UsesClass(\SqlSemantics\Platform\MySql\TypeRules::class)]
65#[UsesClass(\SqlSemantics\Platform\MySql\NameRules::class)]
66#[UsesClass(\SqlSemantics\Platform\MySql\SchemaRules::class)]
67#[Medium]
68final class TreeTest extends TestCase
69{
70 public function testOuterStopsAtTheRequestedGrammarBoundary(): void
71 {
72 $tree = (new \SqlParser\PostgreSql\PostgreSqlParser())->parse('SELECT 1+2');
73 $nodes = \SqlSemantics\Core\Ast\Tree::outer($tree, ['a_expr']);
74 self::assertCount(1, $nodes);
75 self::assertSame('1 + 2', \SqlSemantics\Core\Ast\Tree::text($nodes[0]));
76 self::assertCount(3, $tree->find('a_expr'));
77 }
78
79 public function testChildDoesNotSearchNestedScopes(): void
80 {
81 $tree = (new \SqlParser\PostgreSql\PostgreSqlParser())->parse('SELECT id FROM users');
82 self::assertNull(\SqlSemantics\Core\Ast\Tree::child($tree, ['columnref']));
83 self::assertNotNull(\SqlSemantics\Core\Ast\Tree::child($tree->find('c_expr')[0], ['columnref']));
84 }
85
86 public function testSignificantRemovesEmptyProductions(): void
87 {
88 $token = new \SqlParser\Lexer\Token(1, 'ICONST', '1', 7);
89 $node = new \SqlParser\Parser\Node('expr', 0, [new \SqlParser\Parser\Node('empty', 0, []), $token]);
90 self::assertSame([$token], \SqlSemantics\Core\Ast\Tree::significant($node));
91 }
92
93 public function testTextKeepsTerminalSpellings(): void
94 {
95 $token = new \SqlParser\Lexer\Token(1, 'SCONST', "'a b'", 0);
96 self::assertSame("'a b'", \SqlSemantics\Core\Ast\Tree::text($token));
97 }
98
99 public function testUnsupportedCarriesOriginalSyntax(): void
100 {
101 $node = new \SqlParser\Parser\Node('expr', 0, []);
102 $this->expectException(SemanticException::class);
103 $this->expectExceptionMessage('Unsupported custom operation');
104 \SqlSemantics\Core\Ast\Tree::unsupported($node, 'custom operation');
105 }
106
107 public function testAssertChildrenRejectsUnknownClauses(): void
108 {
109 $tree = (new \SqlParser\PostgreSql\PostgreSqlParser())->parse('SELECT id FROM users');
110 $this->expectException(SemanticException::class);
111 \SqlSemantics\Core\Ast\Tree::assertChildren($tree->find('simple_select')[0], [], ['SELECT']);
112 }
113}
114