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