packages/sql-parser/tests/Unit/Sqlite/Lexer/WordScannerTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Sqlite\Lexer;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\Small;
9use PHPUnit\Framework\Attributes\UsesClass;
10use PHPUnit\Framework\TestCase;
11use SqlParser\Lexer\Cursor;
12use SqlParser\Lexer\Lexeme;
13use SqlParser\Sqlite\Lexer\KeywordTable;
14use SqlParser\Sqlite\Lexer\OperatorScanner;
15use SqlParser\Sqlite\Lexer\Scan;
16use SqlParser\Sqlite\Lexer\TriviaScanner;
17use SqlParser\Sqlite\Lexer\WordScanner;
18
19#[CoversClass(WordScanner::class)]
20#[UsesClass(Cursor::class)]
21#[UsesClass(Lexeme::class)]
22#[UsesClass(KeywordTable::class)]
23#[UsesClass(OperatorScanner::class)]
24#[UsesClass(Scan::class)]
25#[UsesClass(TriviaScanner::class)]
26#[Small]
27final class WordScannerTest extends TestCase
28{
29 public function testScan(): void
30 {
31 $scanner = new WordScanner();
32 $keywords = new KeywordTable(['SELECT' => 'SELECT', 'WINDOW' => 'WINDOW', 'AS' => 'AS']);
33 $trivia = new TriviaScanner();
34
35 self::assertSame('SELECT', $scanner->scan(new Scan(new Cursor('select 1'), $keywords), $trivia)?->name);
36 self::assertSame('a$b', $scanner->scan(new Scan(new Cursor('a$b '), $keywords), $trivia)?->text);
37 self::assertSame('WINDOW', $scanner->scan(new Scan(new Cursor('WINDOW w AS ()'), $keywords), $trivia)?->name);
38 self::assertSame('ID', $scanner->scan(new Scan(new Cursor('WINDOW FROM'), $keywords), $trivia)?->name);
39 self::assertNull($scanner->scan(new Scan(new Cursor('1a'), $keywords), $trivia));
40 }
41
42 public function testContextual(): void
43 {
44 $scanner = new WordScanner();
45 $keywords = new KeywordTable(['WINDOW' => 'WINDOW', 'AS' => 'AS', 'OVER' => 'OVER', 'FILTER' => 'FILTER']);
46 $trivia = new TriviaScanner();
47 $afterParen = new Scan(new Cursor(' (x)'), $keywords);
48 $afterParen->lexemes[] = new Lexeme('RP', ')', 0);
49 $afterName = new Scan(new Cursor(' (x)'), $keywords);
50 $afterName->lexemes[] = new Lexeme('ID', 'x', 0);
51 $beforeName = new Scan(new Cursor(' w'), $keywords);
52 $beforeName->lexemes[] = new Lexeme('RP', ')', 0);
53
54 self::assertSame('OVER', $scanner->contextual($afterParen, 'OVER', $trivia));
55 self::assertSame('FILTER', $scanner->contextual($afterParen, 'FILTER', $trivia));
56 self::assertSame('ID', $scanner->contextual($afterName, 'OVER', $trivia));
57 self::assertSame('OVER', $scanner->contextual($beforeName, 'OVER', $trivia));
58 self::assertSame('ID', $scanner->contextual($beforeName, 'FILTER', $trivia));
59 self::assertSame('WINDOW', $scanner->contextual(new Scan(new Cursor(" 'w' AS"), $keywords), 'WINDOW', $trivia));
60 self::assertSame('ID', $scanner->contextual(new Scan(new Cursor(' w'), $keywords), 'WINDOW', $trivia));
61 }
62
63 public function testAhead(): void
64 {
65 $scanner = new WordScanner();
66 $scan = new Scan(new Cursor("x /* c */ 'y' AS 1"), new KeywordTable(['AS' => 'AS']));
67 $scan->cursor->take(1);
68
69 self::assertSame(['ID', 'AS', 'LITERAL'], $scanner->ahead($scan, new TriviaScanner(), 3));
70 self::assertSame(['ID', 'AS', 'LITERAL', null], $scanner->ahead($scan, new TriviaScanner(), 4));
71 self::assertSame(1, $scan->cursor->offset());
72 }
73
74 public function testProbe(): void
75 {
76 $scanner = new WordScanner();
77 $keywords = new KeywordTable(['ABORT' => 'ABORT', 'SELECT' => 'SELECT', 'LEFT' => 'JOIN_KW']);
78 $probe = static fn (string $sql): Scan => new Scan(new Cursor($sql), $keywords, ['ABORT' => true]);
79
80 self::assertSame('ID', $scanner->probe($probe('abort')));
81 self::assertSame('ID', $scanner->probe($probe('left')));
82 self::assertSame('SELECT', $scanner->probe($probe('select')));
83 self::assertSame('ID', $scanner->probe($probe('"q"')));
84 self::assertSame('ID', $scanner->probe($probe('[q]')));
85 self::assertSame('BLOB', $scanner->probe($probe("x'ff'")));
86 self::assertSame('LITERAL', $scanner->probe($probe('1.5')));
87 self::assertSame('LITERAL', $scanner->probe($probe(':v')));
88 self::assertSame('LP', $scanner->probe($probe('(')));
89 self::assertSame('ILLEGAL', $scanner->probe($probe('!')));
90 }
91
92 public function testSkipQuoted(): void
93 {
94 $scanner = new WordScanner();
95 $closed = new Cursor("'a''b' c");
96 $open = new Cursor("'a");
97 $scanner->skipQuoted($closed, "'");
98 $scanner->skipQuoted($open, "'");
99
100 self::assertSame(' c', $closed->take(2));
101 self::assertTrue($open->eof());
102 }
103}
104