packages/ztd-query-sqlite/tests/Unit/Rewrite/Index/IndexHintTokensTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Rewrite\Index;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\UsesClass;
9use PHPUnit\Framework\TestCase;
10use ZtdQuery\Platform\Sqlite\Rewrite\Index\IndexHintTokens;
11use ZtdQuery\Platform\Sqlite\Sql\SqliteLexerProfile;
12use ZtdQuery\Sql\SqlToken;
13use ZtdQuery\Sql\SqlTokenKind;
14use ZtdQuery\Sql\SqlTokenStream;
15
16#[CoversClass(IndexHintTokens::class)]
17#[UsesClass(SqliteLexerProfile::class)]
18final class IndexHintTokensTest extends TestCase
19{
20    public function testHintRangeRecognizesBothSupportedForms(): void
21    {
22        self::assertSame(['start' => 0, 'end' => 15], IndexHintTokens::hintRange(SqlTokenStream::tokenize('INDEXED BY idx1', SqliteLexerProfile::create())->significantTokens(), 0));
23        self::assertSame(['start' => 0, 'end' => 11], IndexHintTokens::hintRange(SqlTokenStream::tokenize('NOT INDEXED', SqliteLexerProfile::create())->significantTokens(), 0));
24        self::assertNull(IndexHintTokens::hintRange(SqlTokenStream::tokenize('INDEXED BY', SqliteLexerProfile::create())->significantTokens(), 0));
25        self::assertNull(IndexHintTokens::hintRange(SqlTokenStream::tokenize('NOT NULL', SqliteLexerProfile::create())->significantTokens(), 0));
26        self::assertNull(IndexHintTokens::hintRange([], 0));
27    }
28
29    public function testTokenIndexAtOrAfterUsesByteOffsets(): void
30    {
31        $tokens = SqlTokenStream::tokenize('users AS u', SqliteLexerProfile::create())->significantTokens();
32        self::assertSame(1, IndexHintTokens::tokenIndexAtOrAfter($tokens, 5));
33        self::assertSame(2, IndexHintTokens::tokenIndexAtOrAfter($tokens, 9));
34        self::assertSame(3, IndexHintTokens::tokenIndexAtOrAfter($tokens, 10));
35    }
36
37    public function testSkipAliasLeavesSourceClausesUntouched(): void
38    {
39        self::assertSame(2, IndexHintTokens::skipAlias(SqlTokenStream::tokenize('AS u INDEXED BY idx', SqliteLexerProfile::create())->significantTokens(), 0));
40        self::assertSame(1, IndexHintTokens::skipAlias(SqlTokenStream::tokenize('u INDEXED BY idx', SqliteLexerProfile::create())->significantTokens(), 0));
41        self::assertSame(0, IndexHintTokens::skipAlias(SqlTokenStream::tokenize('WHERE id = 1', SqliteLexerProfile::create())->significantTokens(), 0));
42        self::assertSame(0, IndexHintTokens::skipAlias([], 0));
43    }
44
45    public function testIsSourceBoundaryRecognizesClauseKeywords(): void
46    {
47        self::assertTrue(IndexHintTokens::isSourceBoundary(new SqlToken(SqlTokenKind::Word, 'JOIN', 0, 0, 0)));
48        self::assertTrue(IndexHintTokens::isSourceBoundary(new SqlToken(SqlTokenKind::Word, 'INDEXED', 0, 0, 0)));
49        self::assertFalse(IndexHintTokens::isSourceBoundary(new SqlToken(SqlTokenKind::QuotedIdentifier, '"JOIN"', 0, 0, 0)));
50        self::assertFalse(IndexHintTokens::isSourceBoundary(new SqlToken(SqlTokenKind::Word, 'alias', 0, 0, 0)));
51    }
52
53    public function testIdentifierEndIndexRequiresACompleteName(): void
54    {
55        self::assertSame(1, IndexHintTokens::identifierEndIndex(SqlTokenStream::tokenize('"index name"', SqliteLexerProfile::create())->significantTokens(), 0));
56        self::assertNull(IndexHintTokens::identifierEndIndex(SqlTokenStream::tokenize('123', SqliteLexerProfile::create())->significantTokens(), 0));
57        self::assertNull(IndexHintTokens::identifierEndIndex([], 0));
58    }
59
60}
61