packages/ztd-query-sqlite/tests/Unit/Rewrite/Cte/CteHeaderParserTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Rewrite\Cte;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\UsesClass;
9use PHPUnit\Framework\TestCase;
10use ZtdQuery\Platform\Sqlite\Rewrite\Cte\CteHeaderParser;
11use ZtdQuery\Platform\Sqlite\Sql\SqliteLexerProfile;
12use ZtdQuery\Sql\SqlToken;
13use ZtdQuery\Sql\SqlTokenKind;
14use ZtdQuery\Sql\SqlTokenStream;
15
16#[CoversClass(CteHeaderParser::class)]
17#[UsesClass(SqliteLexerProfile::class)]
18final class CteHeaderParserTest extends TestCase
19{
20 public function testParseHeaderLocatesMainStatementAfterMultipleCtes(): void
21 {
22 $parser = new CteHeaderParser();
23 $sql = 'WITH RECURSIVE "Items"(id) AS NOT MATERIALIZED (SELECT 1), other AS (SELECT 2) SELECT * FROM Items';
24 self::assertSame(['names' => ['items', 'other'], 'statementOffset' => 79], $parser->parseHeader($sql));
25 self::assertSame(['names' => [], 'statementOffset' => null], $parser->parseHeader('SELECT 1'));
26 self::assertSame(['names' => [], 'statementOffset' => null], $parser->parseHeader('WITH t AS (SELECT 1'));
27 }
28
29 public function testFindAsIndexSkipsColumnListPunctuation(): void
30 {
31 $parser = new CteHeaderParser();
32 $tokens = [new SqlToken(SqlTokenKind::Symbol, '(', 0, 0, 0), new SqlToken(SqlTokenKind::Symbol, ')', 1, 0, 0), new SqlToken(SqlTokenKind::Word, 'AS', 3, 0, 0)];
33 self::assertSame(2, $parser->findAsIndex($tokens, 0));
34 self::assertNull($parser->findAsIndex([new SqlToken(SqlTokenKind::Word, 'SELECT', 0, 0, 0)], 0));
35 self::assertNull($parser->findAsIndex([], 0));
36 }
37
38 public function testIsSymbolRejectsNullAndDifferentKinds(): void
39 {
40 $parser = new CteHeaderParser();
41 self::assertFalse($parser->isSymbol(null, '('));
42 self::assertTrue($parser->isSymbol(new SqlToken(SqlTokenKind::Symbol, '(', 0, 0, 0), '('));
43 self::assertFalse($parser->isSymbol(new SqlToken(SqlTokenKind::Word, '(', 0, 0, 0), '('));
44 }
45
46 public function testIdentifierNameDecodesEscapedQuotedNames(): void
47 {
48 $parser = new CteHeaderParser();
49 self::assertSame('t', $parser->identifierName(new SqlToken(SqlTokenKind::Word, 't', 0, 0, 0)));
50 self::assertSame('a"b', $parser->identifierName(new SqlToken(SqlTokenKind::QuotedIdentifier, '"a""b"', 0, 0, 0)));
51 self::assertNull($parser->identifierName(new SqlToken(SqlTokenKind::Number, '1', 0, 0, 0)));
52 }
53
54 public function testBodyEndIndexHandlesMaterializationAndIncompleteGroups(): void
55 {
56 $parser = new CteHeaderParser();
57 $tokens = SqlTokenStream::tokenize('AS NOT MATERIALIZED () SELECT', SqliteLexerProfile::create())->significantTokens();
58 self::assertSame(5, $parser->bodyEndIndex($tokens, 0));
59 self::assertNull($parser->bodyEndIndex([], 0));
60 self::assertNull($parser->bodyEndIndex(SqlTokenStream::tokenize('AS (', SqliteLexerProfile::create())->significantTokens(), 0));
61 }
62
63}
64