packages/ztd-query-sqlite/tests/Unit/Shadow/Mutation/Upsert/PrimaryExpressionParserTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Shadow\Mutation\Upsert;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\UsesClass;
9use PHPUnit\Framework\TestCase;
10use ZtdQuery\Platform\Sqlite\Shadow\Mutation\Upsert\ExpressionCursor;
11use ZtdQuery\Platform\Sqlite\Shadow\Mutation\Upsert\PrimaryExpressionParser;
12use ZtdQuery\Platform\Sqlite\Sql\SqliteLexerProfile;
13use ZtdQuery\Shadow\Mutation\UpsertColumnSource;
14use ZtdQuery\Sql\SqlTokenStream;
15
16#[CoversClass(PrimaryExpressionParser::class)]
17#[UsesClass(\ZtdQuery\Platform\Sqlite\Shadow\Mutation\Upsert\ArithmeticExpressionParser::class)]
18#[UsesClass(\ZtdQuery\Platform\Sqlite\Shadow\Mutation\Upsert\ComparisonExpressionParser::class)]
19#[UsesClass(ExpressionCursor::class)]
20#[UsesClass(\ZtdQuery\Platform\Sqlite\Shadow\Mutation\Upsert\ExpressionTokenDecoder::class)]
21#[UsesClass(\ZtdQuery\Platform\Sqlite\Shadow\Mutation\Upsert\LogicalExpressionParser::class)]
22#[UsesClass(SqliteLexerProfile::class)]
23final class PrimaryExpressionParserTest extends TestCase
24{
25    public function testParsePrimaryEvaluatesParenthesizedExpression(): void
26    {
27        $sql = '(count + EXCLUDED.count)';
28        $cursor = new ExpressionCursor($sql, 'users', SqlTokenStream::tokenize($sql, SqliteLexerProfile::create())->significantTokens());
29        $result = (new PrimaryExpressionParser($cursor))->parsePrimary();
30        self::assertSame(7, $result->evaluate(['count' => 4], ['count' => 3], 'users'));
31        self::assertCount($cursor->index, $cursor->tokens);
32    }
33
34    public function testParsePrimaryDecodesStringLiteral(): void
35    {
36        $sql = "'a''b'";
37        $cursor = new ExpressionCursor($sql, 'users', SqlTokenStream::tokenize($sql, SqliteLexerProfile::create())->significantTokens());
38        $result = (new PrimaryExpressionParser($cursor))->parsePrimary();
39        self::assertSame("a'b", $result->evaluate(['count' => 4], ['count' => 3], 'users'));
40        self::assertCount($cursor->index, $cursor->tokens);
41    }
42
43    public function testParsePrimaryPreservesNull(): void
44    {
45        $sql = 'NULL';
46        $cursor = new ExpressionCursor($sql, 'users', SqlTokenStream::tokenize($sql, SqliteLexerProfile::create())->significantTokens());
47        $result = (new PrimaryExpressionParser($cursor))->parsePrimary();
48        self::assertSame(null, $result->evaluate(['count' => 4], ['count' => 3], 'users'));
49        self::assertCount($cursor->index, $cursor->tokens);
50    }
51
52    public function testParseColumnSelectsIncomingRow(): void
53    {
54        $sql = 'EXCLUDED.count';
55        $cursor = new ExpressionCursor($sql, 'users', SqlTokenStream::tokenize($sql, SqliteLexerProfile::create())->significantTokens());
56        $result = (new PrimaryExpressionParser($cursor))->parseColumn();
57        self::assertSame(3, $result->evaluate(['count' => 4], ['count' => 3], 'users'));
58        self::assertCount($cursor->index, $cursor->tokens);
59    }
60
61    public function testColumnSourceDistinguishesTableAndExcluded(): void
62    {
63        $parser = new PrimaryExpressionParser(new ExpressionCursor('', 'users', []));
64        self::assertSame(UpsertColumnSource::Incoming, $parser->columnSource('excluded'));
65        self::assertSame(UpsertColumnSource::Existing, $parser->columnSource('USERS'));
66    }
67
68    public function testColumnSourceRejectsUnrelatedQualifiers(): void
69    {
70        $this->expectException(\ZtdQuery\Exception\UnsupportedSqlException::class);
71        (new PrimaryExpressionParser(new ExpressionCursor('other.count', 'users', [])))->columnSource('other');
72    }
73
74}
75