packages/sql-semantics/tests/Unit/Core/Ast/DialectParserTest.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\TestWith;
10use PHPUnit\Framework\TestCase;
11use RuntimeException;
12use SqlSemantics\Core\Ast\DialectParser;
13use SqlSemantics\Core\Dialect;
14use SqlSemantics\Platform\MySql\Dialect as MySqlDialect;
15use SqlSemantics\Platform\PostgreSql\Dialect as PostgreSqlDialect;
16use SqlSemantics\Platform\Sqlite\Dialect as SqliteDialect;
17
18#[CoversClass(DialectParser::class)]
19#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Core\Policy\SyntaxRules::class)]
20#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\PostgreSql\QueryRules::class)]
21#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\PostgreSql\Platform::class)]
22#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\PostgreSql\TypeRules::class)]
23#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\PostgreSql\NameRules::class)]
24#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\PostgreSql\SchemaRules::class)]
25#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\Sqlite\QueryRules::class)]
26#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\Sqlite\Platform::class)]
27#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\Sqlite\TypeRules::class)]
28#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\Sqlite\NameRules::class)]
29#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\Sqlite\SchemaRules::class)]
30#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\MySql\QueryRules::class)]
31#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\MySql\Platform::class)]
32#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\MySql\TypeRules::class)]
33#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\MySql\NameRules::class)]
34#[\PHPUnit\Framework\Attributes\UsesClass(\SqlSemantics\Platform\MySql\SchemaRules::class)]
35#[Medium]
36final class DialectParserTest extends TestCase
37{
38 #[TestWith([PostgreSqlDialect::PostgreSql, 'parse_toplevel', 'pg-17.2'])]
39 #[TestWith([MySqlDialect::MySql, 'start_entry', 'mysql-8.4.7'])]
40 #[TestWith([SqliteDialect::Sqlite, 'input', 'sqlite-3.47.2'])]
41 public function testParseUsesTheRequestedGrammarAndRetainsSql(Dialect $dialect, string $root, string $version): void
42 {
43 $parser = new DialectParser($dialect, $version);
44 $sql = '/* text */ SELECT 1;';
45 $tree = $parser->parse($sql);
46 self::assertSame($root, $tree->name);
47 self::assertSame($sql, $tree->toString());
48 }
49
50 #[TestWith([PostgreSqlDialect::PostgreSql, 'pg-17.2'])]
51 #[TestWith([MySqlDialect::MySql, 'mysql-8.4.7'])]
52 #[TestWith([SqliteDialect::Sqlite, 'sqlite-3.47.2'])]
53 public function testVersionReturnsTheResolvedRelease(Dialect $dialect, string $version): void
54 {
55 self::assertSame($version, (new DialectParser($dialect))->version());
56 }
57
58 #[TestWith([PostgreSqlDialect::PostgreSql])]
59 #[TestWith([MySqlDialect::MySql])]
60 #[TestWith([SqliteDialect::Sqlite])]
61 public function testRejectsAnUnavailableGrammarRelease(Dialect $dialect): void
62 {
63 $this->expectException(RuntimeException::class);
64 $this->expectExceptionMessage('Unsupported');
65 new DialectParser($dialect, 'unavailable-release');
66 }
67
68 public function testParseScriptPreservesStringAndRoutineSemicolons(): void
69 {
70 $parser = new DialectParser(MySqlDialect::MySql);
71 $trees = $parser->parseScript("CREATE PROCEDURE p() BEGIN SELECT ';'; SELECT 2; END; DROP TABLE IF EXISTS t;");
72 self::assertCount(2, $trees);
73 self::assertStringContainsString("SELECT ';'", $trees[0]->toString());
74 self::assertStringContainsString('DROP TABLE', $trees[1]->toString());
75 }
76
77 public function testParseScriptRejectsAnInvalidTrailingCommand(): void
78 {
79 $this->expectException(\SqlParser\Parser\SyntaxException::class);
80 (new DialectParser(MySqlDialect::MySql))->parseScript('DROP TABLE t; CREATE TABLE');
81 }
82
83}
84