packages/sql-semantics/tests/Unit/Core/Binding/SyntaxGuardTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Core\Binding;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\DataProvider;
9use PHPUnit\Framework\Attributes\Medium;
10use PHPUnit\Framework\TestCase;
11use SqlSemantics\Core\Binder;
12use SqlSemantics\Core\SchemaBuilder;
13use SqlSemantics\Core\SemanticException;
14use SqlSemantics\Platform\PostgreSql\Dialect as PostgreSqlDialect;
15
16#[CoversClass(\SqlSemantics\Core\Binding\SyntaxGuard::class)]
17#[CoversClass(\SqlSemantics\Core\Binding\ExpressionBinder::class)]
18#[CoversClass(\SqlSemantics\Core\Binding\ExpressionRules::class)]
19#[CoversClass(\SqlSemantics\Core\Binding\FromBinder::class)]
20#[CoversClass(\SqlSemantics\Core\Binding\LiteralBinder::class)]
21#[CoversClass(\SqlSemantics\Core\Binding\NullFacts::class)]
22#[CoversClass(\SqlSemantics\Core\Binding\ProjectionBinder::class)]
23#[CoversClass(\SqlSemantics\Core\Binding\SelectBinder::class)]
24#[CoversClass(\SqlSemantics\Core\Binding\SelectModifiersBinder::class)]
25#[CoversClass(\SqlSemantics\Core\Binding\TypeResolution::class)]
26#[CoversClass(Binder::class)]
27#[CoversClass(SchemaBuilder::class)]
28#[CoversClass(\SqlSemantics\Core\Ast\DialectParser::class)]
29#[CoversClass(\SqlSemantics\Core\Ast\ColumnReader::class)]
30#[CoversClass(\SqlSemantics\Core\Ast\ConstraintReader::class)]
31#[CoversClass(\SqlSemantics\Core\Ast\Identifiers::class)]
32#[CoversClass(\SqlSemantics\Core\Ast\SchemaReader::class)]
33#[CoversClass(\SqlSemantics\Core\Ast\StatementList::class)]
34#[CoversClass(\SqlSemantics\Core\Ast\TokenGroups::class)]
35#[CoversClass(\SqlSemantics\Core\Ast\Tree::class)]
36#[CoversClass(\SqlSemantics\Core\Ast\TypeReader::class)]
37#[CoversClass(\SqlSemantics\Core\Binding\BoundRelation::class)]
38#[CoversClass(\SqlSemantics\Core\Binding\IdentitySequence::class)]
39#[CoversClass(\SqlSemantics\Core\Binding\Scope::class)]
40#[CoversClass(\SqlSemantics\Core\Binding\TableResolver::class)]
41#[CoversClass(\SqlSemantics\Core\Model\ColumnBinding::class)]
42#[CoversClass(\SqlSemantics\Core\Model\Expression::class)]
43#[CoversClass(\SqlSemantics\Core\Model\Join::class)]
44#[CoversClass(\SqlSemantics\Core\Model\Ordering::class)]
45#[CoversClass(\SqlSemantics\Core\Model\OutputColumn::class)]
46#[CoversClass(\SqlSemantics\Core\Model\BoundSelect::class)]
47#[CoversClass(\SqlSemantics\Core\Model\TableUse::class)]
48#[CoversClass(\SqlSemantics\Core\Schema::class)]
49#[CoversClass(\SqlSemantics\Core\Schema\ColumnDefinition::class)]
50#[CoversClass(\SqlSemantics\Core\Schema\TableConstraint::class)]
51#[CoversClass(\SqlSemantics\Core\Schema\TableDefinition::class)]
52#[CoversClass(SemanticException::class)]
53#[CoversClass(\SqlSemantics\Core\Type\TypeDescriptor::class)]
54#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Core\Policy\SyntaxRules::class)]
55#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\PostgreSql\QueryRules::class)]
56#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\PostgreSql\Platform::class)]
57#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\PostgreSql\TypeRules::class)]
58#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\PostgreSql\NameRules::class)]
59#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\PostgreSql\SchemaRules::class)]
60#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\Sqlite\QueryRules::class)]
61#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\Sqlite\Platform::class)]
62#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\Sqlite\TypeRules::class)]
63#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\Sqlite\NameRules::class)]
64#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\Sqlite\SchemaRules::class)]
65#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\MySql\QueryRules::class)]
66#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\MySql\Platform::class)]
67#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\MySql\TypeRules::class)]
68#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\MySql\NameRules::class)]
69#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\MySql\SchemaRules::class)]
70#[Medium]
71final class SyntaxGuardTest extends TestCase
72{
73    #[DataProvider('providerUnsupported')]
74    public function testSelectRejectsUnmodeledConstructs(string $sql): void
75    {
76        $schema = (new SchemaBuilder(PostgreSqlDialect::PostgreSql))->build('CREATE TABLE users (id INTEGER PRIMARY KEY, parent_id INTEGER, score INTEGER NOT NULL)');
77        $this->expectException(SemanticException::class);
78        (new Binder($schema))->bind($sql);
79    }
80
81    /**
82     * @return iterable<string, array{string}>
83     */
84    public static function providerUnsupported(): iterable
85    {
86        yield 'aggregate' => ['SELECT COUNT(*) FROM users'];
87        yield 'grouping' => ['SELECT score FROM users GROUP BY score'];
88        yield 'union' => ['SELECT id FROM users UNION SELECT id FROM users'];
89        yield 'cte' => ['WITH u AS (SELECT id FROM users) SELECT id FROM u'];
90        yield 'scalar subquery' => ['SELECT (SELECT id FROM users)'];
91        yield 'using' => ['SELECT * FROM users a JOIN users b USING (id)'];
92        yield 'natural' => ['SELECT * FROM users a NATURAL JOIN users b'];
93        yield 'unknown function' => ['SELECT mystery(id) FROM users'];
94        yield 'distinct on' => ['SELECT DISTINCT ON (id) id FROM users'];
95        yield 'locking' => ['SELECT id FROM users FOR UPDATE'];
96        yield 'insert select' => ['INSERT INTO users (id) SELECT id FROM users'];
97    }
98}
99