packages/sql-faker/tests/Unit/MySql/Generation/Rewrite/Expression/ExpressionGroupingRuleTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\SqlFaker\MySql\Generation\Rewrite\Expression;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\UsesClass;
9use PHPUnit\Framework\TestCase;
10use SqlFaker\Generation\Derivation\DerivationTrace;
11use SqlFaker\Generation\Token\ProductionOccurrence;
12use SqlFaker\Generation\Token\TerminalOccurrence;
13use SqlFaker\Generation\Token\TerminalSequence;
14use SqlFaker\Grammar\Model\NonTerminal;
15use SqlFaker\Grammar\Model\Production;
16use SqlFaker\Grammar\Model\Terminal;
17use SqlFaker\MySql\Generation\Rewrite\Expression\ExpressionGroupingRule;
18
19#[CoversClass(ExpressionGroupingRule::class)]
20#[UsesClass(DerivationTrace::class)]
21#[UsesClass(ProductionOccurrence::class)]
22#[UsesClass(TerminalOccurrence::class)]
23#[UsesClass(TerminalSequence::class)]
24#[UsesClass(NonTerminal::class)]
25#[UsesClass(Production::class)]
26#[UsesClass(Terminal::class)]
27final class ExpressionGroupingRuleTest extends TestCase
28{
29 public function testRewritePreservesBothNestedOperandsOfANonAssociativeComparison(): void
30 {
31 $trace = new DerivationTrace('a_expr');
32 $trace->expand(0, new Production([new NonTerminal('a_expr'), new Terminal('<'), new NonTerminal('a_expr')]), 0);
33 $trace->expand(0, new Production([new Terminal('1'), new Terminal('='), new Terminal('2')]), 1);
34 $trace->expand(4, new Production([new Terminal('3'), new Terminal('>'), new Terminal('4')]), 2);
35 $input = $trace->terminals();
36 $rule = new ExpressionGroupingRule(['a_expr', 'b_expr'], 'source');
37 $result = $rule->rewrite($input);
38 self::assertSame(['(', '1', '=', '2', ')', '<', '(', '3', '>', '4', ')'], $result->names());
39 self::assertSame($input->original, $result->original);
40 self::assertSame($input->productions, $result->productions);
41 self::assertSame($result->terminals, $rule->rewrite($result)->terminals);
42 self::assertSame([0, 1], $result->terminals[0]->ancestors);
43 }
44
45 public function testRewriteGroupsARestrictedOperandInsideTheGeneralExpressionFamily(): void
46 {
47 $trace = new DerivationTrace('a_expr');
48 $trace->expand(0, new Production([new Terminal('1'), new Terminal('BETWEEN'), new NonTerminal('b_expr'), new Terminal('AND'), new NonTerminal('a_expr')]), 0);
49 $trace->expand(2, new Production([new Terminal('2'), new Terminal('+'), new Terminal('3')]), 1);
50 $trace->expand(6, new Production([new Terminal('4')]), 0);
51 self::assertSame(['1', 'BETWEEN', '(', '2', '+', '3', ')', 'AND', '4'], (new ExpressionGroupingRule(['a_expr', 'b_expr'], 'source'))->rewrite($trace->terminals())->names());
52 }
53
54 public function testRewritePreservesTopLevelExpressionsAndUnrelatedNestedRules(): void
55 {
56 $trace = new DerivationTrace('stmt');
57 $trace->expand(0, new Production([new Terminal('SELECT'), new NonTerminal('a_expr')]), 0);
58 $trace->expand(1, new Production([new NonTerminal('function'), new Terminal('+'), new NonTerminal('a_expr')]), 0);
59 $trace->expand(1, new Production([new Terminal('F'), new Terminal('('), new Terminal(')')]), 0);
60 $trace->expand(5, new Production([]), 0);
61 $input = $trace->terminals();
62 self::assertSame($input, (new ExpressionGroupingRule(['a_expr'], 'source'))->rewrite($input));
63 }
64 public function testRangesIncludesNestedAncestorsAndIgnoresEmptyOccurrences(): void
65 {
66 $a = new TerminalOccurrence('1', 2, [0, 1], ['a_expr', 'a_expr']);
67 $b = new TerminalOccurrence('+', 3, [0], ['a_expr']);
68 $rule = new ExpressionGroupingRule(['a_expr'], 'source');
69 self::assertSame([0 => [0, 2], 1 => [0, 1]], $rule->ranges(new TerminalSequence([$a, $b])));
70 self::assertSame([], $rule->ranges(new TerminalSequence([])));
71 $empty = new TerminalSequence([]);
72 self::assertSame($empty, $rule->rewrite($empty));
73 }
74 public function testRewriteUsesTheConfiguredDialectTerminalNames(): void
75 {
76 $trace = new DerivationTrace('expr');
77 $trace->expand(0, new Production([new NonTerminal('expr'), new Terminal('EQ'), new Terminal('INTEGER')]), 0);
78 $trace->expand(0, new Production([new Terminal('INTEGER'), new Terminal('PLUS'), new Terminal('INTEGER')]), 1);
79 $rule = new ExpressionGroupingRule(['expr'], 'parse.y', 'LP', 'RP');
80 self::assertSame(['LP', 'INTEGER', 'PLUS', 'INTEGER', 'RP', 'EQ', 'INTEGER'], $rule->rewrite($trace->terminals())->names());
81 }
82
83 public function testRewriteRetainsNestedBoundaryProvenanceAndPreviouslyInsertedIdentities(): void
84 {
85 $a = new TerminalOccurrence('NOT', -1, [0, 1, 2], ['a_expr', 'b_expr', 'a_expr'], 'earlier');
86 $b = new TerminalOccurrence('TRUE', 10, [0, 1, 2], ['a_expr', 'b_expr', 'a_expr']);
87 $tail = new TerminalOccurrence('TAIL', 11, [0], ['a_expr']);
88 $input = new TerminalSequence([$a, $b, $tail], [$a, $b, $tail], ['earlier'], [
89 new ProductionOccurrence(0, null, 'a_expr', 0),
90 new ProductionOccurrence(1, 0, 'b_expr', 0),
91 new ProductionOccurrence(2, 1, 'a_expr', 0),
92 ]);
93 $result = (new ExpressionGroupingRule(['a_expr', 'b_expr'], 'group'))->rewrite($input);
94 self::assertSame(['(', '(', 'NOT', 'TRUE', ')', ')', 'TAIL'], $result->names());
95 $ids = array_map(static fn (TerminalOccurrence $terminal): int => $terminal->id, $result->terminals);
96 self::assertCount(count($ids), array_unique($ids));
97 self::assertLessThan(-1, max($ids[0], $ids[1], $ids[4], $ids[5]));
98 self::assertSame([0, 1], $result->terminals[0]->ancestors);
99 self::assertSame(['a_expr', 'b_expr'], $result->terminals[0]->rules);
100 self::assertSame([0, 1, 2], $result->terminals[4]->ancestors);
101 self::assertSame(['a_expr', 'b_expr', 'a_expr'], $result->terminals[4]->rules);
102 self::assertSame([0, 1], $result->terminals[5]->ancestors);
103 self::assertSame($a, $result->terminals[2]);
104 self::assertSame($b, $result->terminals[3]);
105 self::assertSame(['earlier', 'group'], $result->rewrites);
106 }
107
108 public function testRewriteContinuesAfterAnAlreadyGroupedOperand(): void
109 {
110 $left = new TerminalOccurrence('(', -1, [0, 1], ['expr', 'expr'], 'group');
111 $leftEnd = new TerminalOccurrence(')', -2, [0, 1], ['expr', 'expr'], 'group');
112 $right = new TerminalOccurrence('NOT', 10, [0, 2], ['expr', 'expr']);
113 $rightEnd = new TerminalOccurrence('TRUE', 11, [0, 2], ['expr', 'expr']);
114 $input = new TerminalSequence([$left, $leftEnd, $right, $rightEnd], [], [], [
115 new ProductionOccurrence(0, null, 'expr', 0),
116 new ProductionOccurrence(1, 0, 'expr', 0),
117 new ProductionOccurrence(2, 0, 'expr', 0),
118 ]);
119 $result = (new ExpressionGroupingRule(['expr'], 'group'))->rewrite($input);
120 self::assertSame(['(', ')', '(', 'NOT', 'TRUE', ')'], $result->names());
121 self::assertSame($left, $result->terminals[0]);
122 self::assertSame($leftEnd, $result->terminals[1]);
123 }
124}
125