packages/ztd-query-core/tests/Unit/Sql/SqlTokenScannerTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Sql;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\UsesClass;
9use PHPUnit\Framework\TestCase;
10use Tests\Fake\FakeSqlLexerProfiles;
11use ZtdQuery\Sql\Profile\SqlCommentProfile;
12use ZtdQuery\Sql\Profile\SqlParameterProfile;
13use ZtdQuery\Sql\Profile\SqlQuoteProfile;
14use ZtdQuery\Sql\Profile\SqlSymbolProfile;
15use ZtdQuery\Sql\SqlLexerProfile;
16use ZtdQuery\Sql\SqlToken;
17use ZtdQuery\Sql\SqlTokenKind;
18use ZtdQuery\Sql\SqlTokenScanner;
19
20#[CoversClass(SqlTokenScanner::class)]
21#[UsesClass(SqlToken::class)]
22#[UsesClass(\ZtdQuery\Sql\Reader\SqlLexeme::class)]
23#[UsesClass(\ZtdQuery\Sql\Reader\SqlTriviaReader::class)]
24#[UsesClass(\ZtdQuery\Sql\Reader\SqlBlockCommentReader::class)]
25#[UsesClass(\ZtdQuery\Sql\Reader\SqlDelimitedReader::class)]
26#[UsesClass(\ZtdQuery\Sql\Reader\SqlParameterReader::class)]
27#[UsesClass(\ZtdQuery\Sql\Reader\SqlWordReader::class)]
28#[UsesClass(SqlLexerProfile::class)]
29#[UsesClass(\ZtdQuery\Sql\LexicalDelimiters::class)]
30#[UsesClass(\ZtdQuery\Sql\LexicalPattern::class)]
31#[UsesClass(SqlCommentProfile::class)]
32#[UsesClass(SqlParameterProfile::class)]
33#[UsesClass(SqlQuoteProfile::class)]
34#[UsesClass(SqlSymbolProfile::class)]
35final class SqlTokenScannerTest extends TestCase
36{
37 public function testScanLosesNothingOfTheStatementItRead(): void
38 {
39 $sql = "SELECT /* c */ \"a\", 'b' -- tail\nFROM t";
40
41 $tokens = (new SqlTokenScanner())->scan($sql, FakeSqlLexerProfiles::standard());
42
43 self::assertSame($sql, implode('', array_map(static fn (SqlToken $t): string => $t->text, $tokens)));
44 }
45
46 public function testScanReadsEachLexemeAsWhatItIs(): void
47 {
48 $tokens = (new SqlTokenScanner())->scan("SELECT 1, 'a'", FakeSqlLexerProfiles::standard());
49
50 self::assertSame(
51 [
52 SqlTokenKind::Word,
53 SqlTokenKind::Whitespace,
54 SqlTokenKind::Number,
55 SqlTokenKind::Symbol,
56 SqlTokenKind::Whitespace,
57 SqlTokenKind::String,
58 ],
59 array_map(static fn (SqlToken $t): SqlTokenKind => $t->kind, $tokens),
60 );
61 }
62
63 public function testScanCountsHowDeeplyEachLexemeIsNested(): void
64 {
65 $tokens = (new SqlTokenScanner())->scan('((1))', FakeSqlLexerProfiles::standard());
66
67 self::assertSame([0, 1, 2, 1, 0], array_map(static fn (SqlToken $t): int => $t->depth, $tokens));
68 }
69
70 public function testScanReadsAnUnterminatedStringToTheEndOfTheStatement(): void
71 {
72 $tokens = (new SqlTokenScanner())->scan("SELECT 'a", FakeSqlLexerProfiles::standard());
73
74 self::assertSame("'a", $tokens[count($tokens) - 1]->text);
75 }
76
77 public function testScanReadsAWholeCommentAsOneLexeme(): void
78 {
79 $tokens = (new SqlTokenScanner())->scan('/* a b */1', FakeSqlLexerProfiles::standard());
80
81 self::assertSame('/* a b */', $tokens[0]->text);
82 }
83
84}
85