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