packages/ztd-query-postgres/tests/Unit/Rewrite/Returning/PgSqlReturningProjectionParserTest.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\Postgres\Rewrite\Returning\PgSqlReturningProjectionParser;
13
14#[CoversClass(PgSqlReturningProjectionParser::class)]
15#[UsesClass(\ZtdQuery\Platform\Postgres\Sql\PgSqlLexerProfile::class)]
16#[CoversClass(\ZtdQuery\Platform\Postgres\Sql\Returning\ProjectionItem::class)]
17final class PgSqlReturningProjectionParserTest extends TestCase
18{
19 public function testParsesPostgresQualifiedQuotedAliasesAndWildcard(): void
20 {
21 $projection = (new PgSqlReturningProjectionParser())->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 testUnquotesEscapedPostgresIdentifiersAndStatementTerminator(): void
37 {
38 $projection = (new PgSqlReturningProjectionParser())->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 testReturnsNullWithoutPostgresReturningClause(): void
47 {
48 self::assertNull((new PgSqlReturningProjectionParser())->parse('INSERT INTO users VALUES (1)'));
49 }
50
51 #[DataProvider('providerInvalidPostgresReturningProjection')]
52 public function testRejectsMalformedPostgresProjection(string $projection): void
53 {
54 $this->expectException(UnsupportedSqlException::class);
55
56 (new PgSqlReturningProjectionParser())->parse('INSERT INTO users VALUES (1) RETURNING ' . $projection);
57 }
58
59 /**
60 * @return array<string, array{string}>
61 */
62 public static function providerInvalidPostgresReturningProjection(): 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 'backtick identifier' => ['`id`'],
80 'number token' => ['12'],
81 'string token' => ["'id'"],
82 'unterminated double quote' => ['"id'],
83 ];
84 }
85}
86