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