packages/sql-parser/tests/Unit/MySql/Lexer/OperatorScannerTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\MySql\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\Lexer\LexicalException;
14use SqlParser\MySql\Lexer\KeywordTable;
15use SqlParser\MySql\Lexer\LexerState;
16use SqlParser\MySql\Lexer\OperatorScanner;
17use SqlParser\MySql\Lexer\Scan;
18use SqlParser\MySql\MySqlVersion;
19use SqlParser\MySql\SqlMode;
20
21#[CoversClass(OperatorScanner::class)]
22#[UsesClass(Cursor::class)]
23#[UsesClass(Lexeme::class)]
24#[UsesClass(LexicalException::class)]
25#[UsesClass(KeywordTable::class)]
26#[UsesClass(MySqlVersion::class)]
27#[UsesClass(Scan::class)]
28#[UsesClass(SqlMode::class)]
29#[UsesClass(\SqlParser\Lexer\SourcePosition::class)]
30#[UsesClass(\SqlParser\Resource\SqlVersion::class)]
31#[UsesClass(\SqlParser\Resource\VersionRegistry::class)]
32#[UsesClass(\SqlParser\Lexer\SourceException::class)]
33#[Small]
34final class OperatorScannerTest extends TestCase
35{
36    public function testScan(): void
37    {
38        $scanner = new OperatorScanner();
39        $keywords = new KeywordTable(['<=>' => 'EQUAL_SYM', '<=' => 'LE', '<' => 'LT', '!=' => 'NE', '&&' => 'AND_AND_SYM', '||' => 'OR_OR_SYM', '=' => 'EQ'], []);
40        $scan = static fn (string $sql): Scan => new Scan(new Cursor($sql), $keywords, new SqlMode(), MySqlVersion::resolve());
41
42        self::assertSame('EQUAL_SYM', $scanner->scan($scan('<=> 1'))->name);
43        self::assertSame('LE', $scanner->scan($scan('<= 1'))->name);
44        self::assertSame('LT', $scanner->scan($scan('< 1'))->name);
45        self::assertSame('OR2_SYM', $scanner->scan($scan('|| 1'))->name);
46        self::assertSame('|', $scanner->scan($scan('| 1'))->name);
47        self::assertSame('SET_VAR', $scanner->scan($scan(':= 1'))->name);
48        self::assertSame(':', $scanner->scan($scan(': 1'))->name);
49        self::assertSame('PARAM_MARKER', $scanner->scan($scan('? '))->name);
50        self::assertSame('JSON_UNQUOTED_SEPARATOR_SYM', $scanner->scan($scan("->>'$'"))->name);
51        self::assertSame('(', $scanner->scan($scan('('))->name);
52        self::assertSame('!', $scanner->scan($scan('!x'))->name);
53    }
54
55    public function testScanRejectsAParameterMarkerGluedToAName(): void
56    {
57        $this->expectException(LexicalException::class);
58
59        (new OperatorScanner())->scan(new Scan(new Cursor('?x'), new KeywordTable([], []), new SqlMode(), MySqlVersion::resolve()));
60    }
61
62    public function testScanRejectsAForeignCharacter(): void
63    {
64        $this->expectException(LexicalException::class);
65
66        (new OperatorScanner())->scan(new Scan(new Cursor('\\'), new KeywordTable([], []), new SqlMode(), MySqlVersion::resolve()));
67    }
68
69    public function testMultiCharacter(): void
70    {
71        $scanner = new OperatorScanner();
72        $keywords = new KeywordTable(['<<' => 'SHIFT_LEFT'], []);
73        $modern = new Scan(new Cursor(''), $keywords, new SqlMode(), MySqlVersion::resolve('mysql-8.4.7'));
74        $legacy = new Scan(new Cursor(''), $keywords, new SqlMode(), MySqlVersion::resolve('mysql-5.6.51'));
75
76        self::assertSame('SET_VAR', $scanner->multiCharacter($modern, ':='));
77        self::assertSame('JSON_SEPARATOR_SYM', $scanner->multiCharacter($modern, '->'));
78        self::assertNull($scanner->multiCharacter($legacy, '->'));
79        self::assertSame('SHIFT_LEFT', $scanner->multiCharacter($modern, '<<'));
80        self::assertNull($scanner->multiCharacter($modern, '<-'));
81        self::assertNull($scanner->multiCharacter($modern, 'ab'));
82    }
83
84    public function testSeparator(): void
85    {
86        $scanner = new OperatorScanner();
87        $qualified = new Scan(new Cursor('.x'), new KeywordTable([], []), new SqlMode(), MySqlVersion::resolve());
88        $bare = new Scan(new Cursor('. x'), new KeywordTable([], []), new SqlMode(), MySqlVersion::resolve());
89
90        self::assertSame('.', $scanner->separator($qualified)->name);
91        self::assertSame(LexerState::IdentifierStart, $qualified->next);
92        self::assertSame(LexerState::Start, $bare->next === LexerState::Start ? $scanner->separator($bare)->name === '.' ? LexerState::Start : null : null);
93        self::assertSame(LexerState::Start, $bare->next);
94    }
95}
96