packages/sql-faker/tests/Unit/PostgreSql/Generation/Rewrite/Routine/JsonOptionsRuleTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\SqlFaker\PostgreSql\Generation\Rewrite\Routine;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\DataProvider;
9use PHPUnit\Framework\Attributes\UsesClass;
10use PHPUnit\Framework\TestCase;
11use SqlFaker\Generation\Token\ProductionOccurrence;
12use SqlFaker\Generation\Token\TerminalOccurrence;
13use SqlFaker\Generation\Token\TerminalSequence;
14use SqlFaker\PostgreSql\Generation\Rewrite\Routine\JsonOptionsRule;
15
16#[CoversClass(JsonOptionsRule::class)]
17#[UsesClass(ProductionOccurrence::class)]
18#[UsesClass(TerminalOccurrence::class)]
19#[UsesClass(TerminalSequence::class)]
20final class JsonOptionsRuleTest extends TestCase
21{
22    public function testBehaviorsPreservesEmptyAndUnrelatedChildProductions(): void
23    {
24        $terminal = new TerminalOccurrence('TRUE_P', 10, [0, 1, 3], ['func_expr_common_subexpr', 'json_behavior_clause_opt', 'other']);
25        $input = new TerminalSequence([$terminal], [$terminal], [], [
26            new ProductionOccurrence(0, null, 'func_expr_common_subexpr', 0),
27            new ProductionOccurrence(1, 0, 'json_behavior_clause_opt', 0),
28            new ProductionOccurrence(2, 1, 'json_behavior', 0),
29            new ProductionOccurrence(3, 1, 'other', 0),
30        ]);
31        self::assertSame($input, (new JsonOptionsRule())->behaviors($input, 0, ['ERROR_P']));
32    }
33
34    #[DataProvider('providerBehaviors')]
35    public function testRewritePreservesAllowedBehaviorsAndRepairsForbiddenAlternatives(string $kind, string $behavior, string $expected): void
36    {
37        $owner = $kind === 'column' ? 'json_table_column_definition' : 'func_expr_common_subexpr';
38        $clause = in_array($kind, ['JSON_EXISTS', 'column'], true) ? 'json_on_error_clause_opt' : 'json_behavior_clause_opt';
39        $terminals = [
40            new TerminalOccurrence($kind, 10, [0], [$owner]),
41            new TerminalOccurrence($behavior, 11, [0, 1, 2], [$owner, $clause, 'json_behavior']),
42            new TerminalOccurrence('ON', 12, [0, 1], [$owner, $clause]),
43            new TerminalOccurrence('ERROR_P', 13, [0, 1], [$owner, $clause]),
44        ];
45        $input = new TerminalSequence($terminals, $terminals, [], [
46            new ProductionOccurrence(0, null, $owner, 0),
47            new ProductionOccurrence(1, 0, $clause, 0),
48            new ProductionOccurrence(2, 1, 'json_behavior', 0),
49        ]);
50        $rule = new JsonOptionsRule();
51        $result = $rule->rewrite($input);
52        self::assertSame([$kind, $expected, 'ON', 'ERROR_P'], $result->names());
53        self::assertSame($input->original, $result->original);
54        self::assertSame($input->productions, $result->productions);
55        self::assertSame($input->terminals[2], $result->terminals[2]);
56        self::assertSame($result, $rule->rewrite($result));
57    }
58
59    /**
60     * @return iterable<string, array{string, string, string}>
61     */
62    public static function providerBehaviors(): iterable
63    {
64        yield 'exists default' => ['JSON_EXISTS', 'DEFAULT', 'ERROR_P'];
65        yield 'column default' => ['column', 'DEFAULT', 'ERROR_P'];
66        yield 'exists null' => ['JSON_EXISTS', 'NULL_P', 'ERROR_P'];
67        yield 'exists empty' => ['JSON_EXISTS', 'EMPTY_P', 'ERROR_P'];
68        yield 'exists true' => ['JSON_EXISTS', 'TRUE_P', 'TRUE_P'];
69        yield 'exists false' => ['JSON_EXISTS', 'FALSE_P', 'FALSE_P'];
70        yield 'exists unknown' => ['JSON_EXISTS', 'UNKNOWN', 'UNKNOWN'];
71        yield 'value empty' => ['JSON_VALUE', 'EMPTY_P', 'ERROR_P'];
72        yield 'value default' => ['JSON_VALUE', 'DEFAULT', 'DEFAULT'];
73        yield 'value null' => ['JSON_VALUE', 'NULL_P', 'NULL_P'];
74        yield 'query true' => ['JSON_QUERY', 'TRUE_P', 'ERROR_P'];
75        yield 'query empty' => ['JSON_QUERY', 'EMPTY_P', 'EMPTY_P'];
76        yield 'query default' => ['JSON_QUERY', 'DEFAULT', 'DEFAULT'];
77        yield 'unrelated function' => ['JSON_OBJECT', 'TRUE_P', 'TRUE_P'];
78    }
79
80    #[DataProvider('providerQuotes')]
81    public function testRewriteScopesQuotesToTheDirectWrapper(string $wrapper, string $quotes, bool $removed): void
82    {
83        $terminals = [
84            new TerminalOccurrence('JSON_QUERY', 10, [0], ['func_expr_common_subexpr']),
85            new TerminalOccurrence($wrapper, 11, [0, 1], ['func_expr_common_subexpr', 'json_wrapper_behavior']),
86            new TerminalOccurrence($quotes, 12, [0, 2], ['func_expr_common_subexpr', 'json_quotes_clause_opt']),
87            new TerminalOccurrence('OMIT', 13, [0, 3, 4], ['func_expr_common_subexpr', 'nested', 'json_quotes_clause_opt']),
88        ];
89        $input = new TerminalSequence($terminals, $terminals, [], [
90            new ProductionOccurrence(0, null, 'func_expr_common_subexpr', 0),
91            new ProductionOccurrence(1, 0, 'json_wrapper_behavior', 0),
92            new ProductionOccurrence(2, 0, 'json_quotes_clause_opt', 0),
93            new ProductionOccurrence(3, 0, 'nested', 0),
94            new ProductionOccurrence(4, 3, 'json_quotes_clause_opt', 0),
95        ]);
96        $rule = new JsonOptionsRule();
97        $result = $rule->rewrite($input);
98        self::assertSame($removed ? [$terminals[0], $terminals[1], $terminals[3]] : $terminals, $result->terminals);
99        self::assertSame($input->original, $result->original);
100        self::assertSame($result, $rule->rewrite($result));
101    }
102
103    /**
104     * @return iterable<string, array{string, string, bool}>
105     */
106    public static function providerQuotes(): iterable
107    {
108        yield 'omit wrapped strings' => ['WITH', 'OMIT', true];
109        yield 'keep wrapped strings' => ['WITH', 'KEEP', false];
110        yield 'omit unwrapped strings' => ['WITHOUT', 'OMIT', false];
111    }
112
113    public function testReturningFormatRemovesOnlyJsonValueOutputFormatAndRetainsNestedJsonInputFormat(): void
114    {
115        $terminals = [
116            new TerminalOccurrence('JSON_VALUE', 10, [0], ['func_expr_common_subexpr']),
117            new TerminalOccurrence('FORMAT', 11, [0, 1, 2], ['func_expr_common_subexpr', 'json_returning_clause_opt', 'json_format_clause_opt']),
118            new TerminalOccurrence('FORMAT', 12, [0, 3, 4], ['func_expr_common_subexpr', 'json_value_expr', 'json_format_clause_opt']),
119        ];
120        $input = new TerminalSequence($terminals, $terminals, [], [
121            new ProductionOccurrence(0, null, 'func_expr_common_subexpr', 0),
122            new ProductionOccurrence(1, 0, 'json_returning_clause_opt', 0),
123            new ProductionOccurrence(2, 1, 'json_format_clause_opt', 0),
124            new ProductionOccurrence(3, 0, 'json_value_expr', 0),
125            new ProductionOccurrence(4, 3, 'json_format_clause_opt', 0),
126            new ProductionOccurrence(5, null, 'func_expr_common_subexpr', 0),
127        ]);
128        $rule = new JsonOptionsRule();
129        $result = $rule->returningFormat($input, 0);
130        self::assertEquals($result, $rule->rewrite($input));
131        self::assertSame([$terminals[0], $terminals[2]], $result->terminals);
132        self::assertSame($input->original, $result->original);
133        self::assertSame($input->productions, $result->productions);
134        self::assertSame($result, $rule->rewrite($result));
135    }
136}
137