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