packages/ztd-query-sqlite/tests/Unit/Rewrite/FullText/MatchExpressionRewriterTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Rewrite\FullText;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\UsesClass;
9use PHPUnit\Framework\TestCase;
10use ZtdQuery\Platform\Sqlite\Rewrite\FullText\MatchExpressionRewriter;
11use ZtdQuery\Platform\Sqlite\Sql\SqliteLexerProfile;
12use ZtdQuery\Sql\SqlToken;
13use ZtdQuery\Sql\SqlTokenKind;
14use ZtdQuery\Sql\SqlTokenStream;
15
16#[CoversClass(MatchExpressionRewriter::class)]
17#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Dml\Expression\AssignmentColumnParser::class)]
18#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Dml\Expression\AssignmentParser::class)]
19#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Dml\Expression\ValueListParser::class)]
20#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Dml\Insert\InsertClauseParser::class)]
21#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\ExpressionSpan::class)]
22#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\IdentifierDecoder::class)]
23#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\LiteralMasker::class)]
24#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\OpaqueSqlSpan::class)]
25#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\QuotedSpan::class)]
26#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\TopLevelKeywordScanner::class)]
27#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Relation\RelationSourceParser::class)]
28#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Statement\StatementClassifier::class)]
29#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Statement\StatementStructure::class)]
30#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Statement\TargetTableParser::class)]
31#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Dml\Update\UpdateClauseParser::class)]
32#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\FullText\FullTextColumns::class)]
33#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\SqliteIdentifierQuoter::class)]
34#[UsesClass(SqliteLexerProfile::class)]
35#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\SqliteLexicalMasker::class)]
36#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\SqliteParser::class)]
37#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Relation\SqliteSelectRelationParser::class)]
38final class MatchExpressionRewriterTest extends TestCase
39{
40    public function testExpressionEditProjectsAllDocumentColumns(): void
41    {
42        $stream = SqlTokenStream::tokenize("docs MATCH 'hello'", SqliteLexerProfile::create());
43        $rewriter = new MatchExpressionRewriter(new \ZtdQuery\Platform\Sqlite\Sql\SqliteParser(), new \ZtdQuery\Platform\Sqlite\Sql\SqliteIdentifierQuoter());
44        $edit = $rewriter->expressionEdit($stream, $stream->significantTokens()[1], ['docs' => ['columns' => ['title', 'body']]]);
45        self::assertNotNull($edit);
46        self::assertSame(0, $edit['start']);
47        self::assertSame(18, $edit['end']);
48        self::assertStringContainsString('COALESCE(CAST("title" AS TEXT)', $edit['replacement']);
49        self::assertStringContainsString('COALESCE(CAST("body" AS TEXT)', $edit['replacement']);
50        self::assertStringContainsString("CAST(('hello') AS TEXT)", $edit['replacement']);
51    }
52
53    public function testExpressionEditRejectsAnUnknownDocument(): void
54    {
55        $stream = SqlTokenStream::tokenize("unknown MATCH 'hello'", SqliteLexerProfile::create());
56        $rewriter = new MatchExpressionRewriter(new \ZtdQuery\Platform\Sqlite\Sql\SqliteParser(), new \ZtdQuery\Platform\Sqlite\Sql\SqliteIdentifierQuoter());
57        self::assertNull($rewriter->expressionEdit($stream, $stream->significantTokens()[1], ['docs' => ['columns' => ['body']]]));
58    }
59
60    public function testIsIdentifierRejectsLiteralTokens(): void
61    {
62        self::assertTrue(MatchExpressionRewriter::isIdentifier(new SqlToken(SqlTokenKind::Word, 'body', 0, 0, 0)));
63        self::assertTrue(MatchExpressionRewriter::isIdentifier(new SqlToken(SqlTokenKind::QuotedIdentifier, '"body"', 0, 0, 0)));
64        self::assertFalse(MatchExpressionRewriter::isIdentifier(new SqlToken(SqlTokenKind::String, "'body'", 0, 0, 0)));
65    }
66
67    public function testIsQueryExpressionAcceptsOnlyStringOrParameter(): void
68    {
69        self::assertTrue(MatchExpressionRewriter::isQueryExpression(new SqlToken(SqlTokenKind::String, "'query'", 0, 0, 0)));
70        self::assertTrue(MatchExpressionRewriter::isQueryExpression(new SqlToken(SqlTokenKind::Parameter, '?', 0, 0, 0)));
71        self::assertFalse(MatchExpressionRewriter::isQueryExpression(new SqlToken(SqlTokenKind::Word, 'query', 0, 0, 0)));
72    }
73
74}
75