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