packages/ztd-query-sqlite/tests/Unit/Rewrite/Returning/SqliteReturningProjectionParserTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Rewrite\Returning;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\DataProvider;
9use PHPUnit\Framework\Attributes\UsesClass;
10use PHPUnit\Framework\TestCase;
11use ZtdQuery\Exception\UnsupportedSqlException;
12use ZtdQuery\Platform\Sqlite\Rewrite\Returning\SqliteReturningProjectionParser;
13
14#[CoversClass(SqliteReturningProjectionParser::class)]
15#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Returning\ReturningItemParser::class)]
16#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\SqliteLexerProfile::class)]
17final class SqliteReturningProjectionParserTest extends TestCase
18{
19 public function testParsesSqliteQualifiedQuotedAliasesAndWildcard(): void
20 {
21 $projection = (new SqliteReturningProjectionParser())->parse(
22 'UPDATE users SET name = \'x\' RETURNING users.id AS original_id, *, "name" AS display_name',
23 );
24 self::assertNotNull($projection);
25 self::assertSame([
26 ['source' => 'id', 'output' => 'original_id'],
27 ['source' => null, 'output' => null],
28 ['source' => 'name', 'output' => 'display_name'],
29 ], $projection->items());
30
31 self::assertSame([
32 ['original_id' => 1, 'id' => 1, 'name' => 'Alice', 'display_name' => 'Alice'],
33 ], $projection->project([['id' => 1, 'name' => 'Alice']]));
34 }
35
36 public function testUnquotesEscapedSqliteIdentifiersAndStatementTerminator(): void
37 {
38 $projection = (new SqliteReturningProjectionParser())->parse(
39 'INSERT INTO users VALUES (1) RETURNING "source""name" AS `output``name`;',
40 );
41 self::assertNotNull($projection);
42
43 self::assertSame([['output`name' => 'value']], $projection->project([['source"name' => 'value']]));
44 }
45
46 public function testReturnsNullWithoutSqliteReturningClause(): void
47 {
48 self::assertNull((new SqliteReturningProjectionParser())->parse('INSERT INTO users VALUES (1)'));
49 }
50
51 #[DataProvider('providerInvalidSqliteReturningProjection')]
52 public function testRejectsMalformedSqliteProjection(string $projection): void
53 {
54 $this->expectException(UnsupportedSqlException::class);
55
56 (new SqliteReturningProjectionParser())->parse('INSERT INTO users VALUES (1) RETURNING ' . $projection);
57 }
58
59 /**
60 * @return array<string, array{string}>
61 */
62 public static function providerInvalidSqliteReturningProjection(): array
63 {
64 return [
65 'empty' => [''],
66 'expression' => ['id + 1'],
67 'missing alias' => ['id AS'],
68 'invalid alias token' => ['id AS +'],
69 'tokens after alias' => ['id AS alias extra'],
70 'wildcard alias' => ['* AS all_columns'],
71 'leading dot' => ['.id'],
72 'trailing dot' => ['users.'],
73 'double dot' => ['users..id'],
74 'non-dot separator' => ['users + id'],
75 'leading wildcard path' => ['*.id'],
76 'non-trailing wildcard' => ['users.*.id'],
77 'non-wildcard symbol path' => ['users.+'],
78 'empty quoted identifier' => ['""'],
79 'number token' => ['12'],
80 'string token' => ["'id'"],
81 'unterminated double quote' => ['"id'],
82 'unterminated backtick' => ['`id'],
83 ];
84 }
85}
86