packages/sql-parser/tests/Unit/Sqlite/SqliteParserTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Sqlite;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\Small;
9use PHPUnit\Framework\Attributes\UsesClass;
10use PHPUnit\Framework\TestCase;
11use RuntimeException;
12use SqlParser\Lexer\LexicalException;
13use SqlParser\Lexer\Token;
14use SqlParser\Parser\SyntaxException;
15use SqlParser\Sqlite\SqliteParser;
16use SqlParser\Sqlite\SqliteVersion;
17
18#[CoversClass(SqliteParser::class)]
19#[UsesClass(SqliteVersion::class)]
20#[UsesClass(SyntaxException::class)]
21#[UsesClass(LexicalException::class)]
22#[UsesClass(Token::class)]
23#[UsesClass(\SqlParser\Grammar\SymbolTable::class)]
24#[UsesClass(\SqlParser\Lexer\Cursor::class)]
25#[UsesClass(\SqlParser\Lexer\Lexeme::class)]
26#[UsesClass(\SqlParser\Lexer\SourcePosition::class)]
27#[UsesClass(\SqlParser\Lexer\TerminalIndex::class)]
28#[UsesClass(\SqlParser\Parser\LrParser::class)]
29#[UsesClass(\SqlParser\Parser\Node::class)]
30#[UsesClass(\SqlParser\Resource\SqlVersion::class)]
31#[UsesClass(\SqlParser\Resource\VersionRegistry::class)]
32#[UsesClass(\SqlParser\Sqlite\Lexer\KeywordTable::class)]
33#[UsesClass(\SqlParser\Sqlite\Lexer\NumberScanner::class)]
34#[UsesClass(\SqlParser\Sqlite\Lexer\OperatorScanner::class)]
35#[UsesClass(\SqlParser\Sqlite\Lexer\QuotedScanner::class)]
36#[UsesClass(\SqlParser\Sqlite\Lexer\Scan::class)]
37#[UsesClass(\SqlParser\Sqlite\Lexer\SqliteLexer::class)]
38#[UsesClass(\SqlParser\Sqlite\Lexer\TriviaScanner::class)]
39#[UsesClass(\SqlParser\Sqlite\Lexer\VariableScanner::class)]
40#[UsesClass(\SqlParser\Sqlite\Lexer\WordScanner::class)]
41#[UsesClass(\SqlParser\Table\ActionCode::class)]
42#[UsesClass(\SqlParser\Table\PackedRows::class)]
43#[UsesClass(\SqlParser\Table\ParseTable::class)]
44#[UsesClass(\SqlParser\Table\TableCodec::class)]
45#[UsesClass(\SqlParser\Table\TableFile::class)]
46#[UsesClass(\SqlParser\Table\TableRule::class)]
47#[UsesClass(\SqlParser\Lexer\SourceException::class)]
48#[Small]
49final class SqliteParserTest extends TestCase
50{
51    public function testVersion(): void
52    {
53        self::assertSame('sqlite-3.47.2', (new SqliteParser())->version());
54    }
55
56    public function testVersionRejectsAnUnknownRelease(): void
57    {
58        $this->expectException(RuntimeException::class);
59
60        new SqliteParser('sqlite-2.8.17');
61    }
62
63    public function testTokenize(): void
64    {
65        $tokens = (new SqliteParser())->tokenize('SELECT abort, x FROM t WHERE y = :v');
66
67        self::assertSame(['SELECT', 'ABORT', 'COMMA', 'ID', 'FROM', 'ID', 'WHERE', 'ID', 'EQ', 'VARIABLE', 'SEMI', '$end'], array_map(static fn (Token $token): string => $token->name, $tokens));
68        self::assertSame('', $tokens[10]->text);
69    }
70
71    public function testTokenizeRejectsAnUnterminatedString(): void
72    {
73        $this->expectException(LexicalException::class);
74
75        (new SqliteParser())->tokenize("SELECT 'abc");
76    }
77
78    public function testParse(): void
79    {
80        $sql = 'SELECT id FROM users WHERE id = ?; DELETE FROM t';
81        $tree = (new SqliteParser())->parse($sql);
82
83        self::assertSame('input', $tree->name);
84        self::assertCount(2, $tree->find('cmd'));
85        self::assertSame('SELECT id FROM users WHERE id = ?', $tree->find('select')[0]->text($sql));
86    }
87
88    public function testParseLetsKeywordsFallBackToIdentifiers(): void
89    {
90        $tree = (new SqliteParser())->parse('SELECT abort, window, over FROM temp');
91
92        self::assertCount(1, $tree->find('select'));
93    }
94
95    public function testParseWritesTheTextBackWithItsCommentsAndWhitespace(): void
96    {
97        $sql = "  SELECT /* one */ a , b\nFROM  t ;  -- done\n";
98
99        self::assertSame($sql, (new SqliteParser())->parse($sql)->toString());
100        self::assertSame('SELECT a FROM t  ', (new SqliteParser())->parse('SELECT a FROM t  ')->toString());
101        self::assertSame('-- nothing at all', (new SqliteParser())->parse('-- nothing at all')->toString());
102    }
103
104    public function testParseAcceptsAnEmptyInput(): void
105    {
106        self::assertSame('input', (new SqliteParser())->parse('')->name);
107    }
108
109    public function testParseRejectsAStatementThatEndsEarly(): void
110    {
111        $this->expectException(SyntaxException::class);
112
113        (new SqliteParser())->parse('SELECT 1 FROM');
114    }
115
116    public function testVersions(): void
117    {
118        self::assertSame(['sqlite-3.47.2'], SqliteParser::versions());
119    }
120}
121