packages/sql-faker/tests/Unit/MySql/Generation/Rewrite/IntoClauseRuleTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\SqlFaker\MySql\Generation\Rewrite;
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\MySql\Generation\Rewrite\IntoClauseRule;
15
16#[CoversClass(IntoClauseRule::class)]
17#[UsesClass(ProductionOccurrence::class)]
18#[UsesClass(TerminalOccurrence::class)]
19#[UsesClass(TerminalSequence::class)]
20final class IntoClauseRuleTest extends TestCase
21{
22 public function testRewriteRemovesViewDestinationsAndKeepsIndependentSelectOutput(): void
23 {
24 $view = new TerminalOccurrence('INTO', 10, [0, 1, 2], ['view_query_block', 'query_specification', 'into_clause']);
25 $outfile = new TerminalOccurrence('OUTFILE', 11, [0, 1, 2], ['view_query_block', 'query_specification', 'into_clause']);
26 $path = new TerminalOccurrence('TEXT_STRING', 12, [0, 1, 2], ['view_query_block', 'query_specification', 'into_clause']);
27 $outer = new TerminalOccurrence('INTO', 13, [3, 4], ['select_stmt', 'into_clause']);
28 $terminals = [$view, $outfile, $path, $outer];
29 $input = new TerminalSequence($terminals, $terminals, [], [
30 new ProductionOccurrence(0, null, 'view_query_block', 0),
31 new ProductionOccurrence(1, 0, 'query_specification', 0),
32 new ProductionOccurrence(2, 1, 'into_clause', 0),
33 new ProductionOccurrence(3, null, 'select_stmt', 0),
34 new ProductionOccurrence(4, 3, 'into_clause', 0),
35 ]);
36 $rule = new IntoClauseRule();
37 $result = $rule->rewrite($input);
38 self::assertSame([$outer], $result->terminals);
39 self::assertSame($input->original, $result->original);
40 self::assertSame($input->productions, $result->productions);
41 self::assertSame(['sql/sql_yacc.yy:view_query_block:into'], $result->rewrites);
42 self::assertSame($result, $rule->rewrite($result));
43 }
44
45 public function testRewriteRemovesOnlySubqueryDestinations(): void
46 {
47 $inner = new TerminalOccurrence('INTO', 4, [0, 1, 2], ['stmt', 'subquery', 'into_clause']);
48 $outer = new TerminalOccurrence('INTO', 5, [0, 3], ['stmt', 'into_clause']);
49 $input = new TerminalSequence([$inner, $outer], [$inner, $outer], [], [new ProductionOccurrence(2, 1, 'into_clause', 0), new ProductionOccurrence(3, 0, 'into_clause', 0), new ProductionOccurrence(6, 0, 'into_clause', 0)]);
50 $rule = new IntoClauseRule();
51 $result = $rule->rewrite($input);
52 self::assertSame([$outer], $result->terminals);
53 self::assertSame($input->original, $result->original);
54 self::assertCount(count($result->terminals), array_unique(array_map(static fn ($terminal): int => $terminal->id, $result->terminals)));
55 self::assertSame($input->productions, $result->productions);
56 self::assertSame(['sql/parse_tree_nodes.cc:PT_subquery:into'], $result->rewrites);
57 self::assertSame($result, $rule->rewrite($result));
58 }
59
60 #[DataProvider('providerSetOperations')]
61 public function testRewritePreservesTheFinalDestinationAcrossSetOperations(string $operator): void
62 {
63 $first = new TerminalOccurrence('INTO', 10, [0, 1], ['query_expression_body', 'into_clause']);
64 $union = new TerminalOccurrence($operator, 11, [0], ['query_expression_body']);
65 $last = new TerminalOccurrence('INTO', 12, [0, 2], ['query_expression_body', 'into_clause']);
66 $nestedUnion = new TerminalOccurrence('UNION_SYM', 13, [0, 3], ['query_expression_body', 'query_expression_body']);
67 $input = new TerminalSequence([$first, $union, $last, $nestedUnion], [$first, $union, $last, $nestedUnion], [], [new ProductionOccurrence(1, 0, 'into_clause', 0), new ProductionOccurrence(2, 0, 'into_clause', 0)]);
68 $rule = new IntoClauseRule();
69 $result = $rule->rewrite($input);
70 self::assertSame([$union, $last, $nestedUnion], $result->terminals);
71 self::assertSame($input->original, $result->original);
72 self::assertCount(count($result->terminals), array_unique(array_map(static fn ($terminal): int => $terminal->id, $result->terminals)));
73 self::assertSame($input->productions, $result->productions);
74 self::assertSame(['sql/sql_lex.cc:new_set_operation_query:into'], $result->rewrites);
75 self::assertSame($result, $rule->rewrite($result));
76 }
77
78 public function testRewriteKeepsOneDestinationPerSelectStatement(): void
79 {
80 $first = new TerminalOccurrence('INTO', 10, [0, 1, 2], ['select_stmt', 'query_specification', 'into_clause']);
81 $last = new TerminalOccurrence('INTO', 11, [0, 3], ['select_stmt', 'into_clause']);
82 $other = new TerminalOccurrence('INTO', 12, [4, 5], ['select_stmt', 'into_clause']);
83 $nested = new TerminalOccurrence('INTO', 13, [0, 6, 7], ['select_stmt', 'subquery', 'into_clause']);
84 $input = new TerminalSequence([$first, $nested, $last, $other], [$first, $nested, $last, $other], [], [new ProductionOccurrence(2, 1, 'into_clause', 0), new ProductionOccurrence(7, 6, 'into_clause', 0), new ProductionOccurrence(3, 0, 'into_clause', 0), new ProductionOccurrence(5, 4, 'into_clause', 0)]);
85 $rule = new IntoClauseRule();
86 $result = $rule->rewrite($input);
87 self::assertSame([$last, $other], $result->terminals);
88 self::assertSame($input->original, $result->original);
89 self::assertSame($input->productions, $result->productions);
90 self::assertContains('sql/parse_tree_nodes.cc:PT_select_stmt:multiple-into', $result->rewrites);
91 self::assertSame($result, $rule->rewrite($result));
92 }
93
94 public function testRewritePreservesDestinationsBeforeUnrelatedOperatorUses(): void
95 {
96 $into = new TerminalOccurrence('INTO', 10, [0, 1], ['query_expression_body', 'into_clause']);
97 $ordinary = new TerminalOccurrence('SELECT_SYM', 11, [0], ['query_expression_body']);
98 $unrelated = new TerminalOccurrence('UNION_SYM', 12, [0], ['other']);
99 $unowned = new TerminalOccurrence('UNION_SYM', 13);
100 $input = new TerminalSequence([$into, $ordinary, $unrelated, $unowned], [], [], [new ProductionOccurrence(1, 0, 'into_clause', 0)]);
101 self::assertSame($input, (new IntoClauseRule())->rewrite($input));
102 }
103
104 /**
105 * @return iterable<string, array{string}>
106 */
107 public static function providerSetOperations(): iterable
108 {
109 yield 'union' => ['UNION_SYM'];
110 yield 'except' => ['EXCEPT_SYM'];
111 yield 'intersect' => ['INTERSECT_SYM'];
112 }
113}
114