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