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