packages/sql-faker/tests/Unit/Sqlite/Generation/Rewrite/CompoundSelectRuleTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\SqlFaker\Sqlite\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\Derivation\DerivationTrace;
12use SqlFaker\Grammar\Model\NonTerminal;
13use SqlFaker\Grammar\Model\Production;
14use SqlFaker\Grammar\Model\Terminal;
15use SqlFaker\Sqlite\Generation\Rewrite\CompoundSelectRule;
16
17#[CoversClass(CompoundSelectRule::class)]
18#[UsesClass(DerivationTrace::class)]
19#[UsesClass(\SqlFaker\Generation\Token\ProductionOccurrence::class)]
20#[UsesClass(\SqlFaker\Generation\Token\TerminalOccurrence::class)]
21#[UsesClass(\SqlFaker\Generation\Token\TerminalSequence::class)]
22#[UsesClass(NonTerminal::class)]
23#[UsesClass(Production::class)]
24#[UsesClass(Terminal::class)]
25final class CompoundSelectRuleTest extends TestCase
26{
27 /**
28 * @param non-empty-list<string> $operator
29 */
30 #[DataProvider('providerOperators')]
31 public function testRewriteKeepsNestedAndFinalClausesForEveryCompoundOperator(array $operator): void
32 {
33 $trace = new DerivationTrace('selectnowith');
34 $trace->expand(0, new Production([new NonTerminal('selectnowith'), new NonTerminal('multiselect_op'), new NonTerminal('oneselect')]), 1);
35 $trace->expand(0, new Production([new NonTerminal('oneselect')]), 0);
36 $trace->expand(0, new Production([new Terminal('SELECT'), new NonTerminal('expr'), new NonTerminal('orderby_opt'), new NonTerminal('limit_opt')]), 0);
37 $trace->expand(1, new Production([new Terminal('LP'), new NonTerminal('selectnowith'), new Terminal('RP')]), 0);
38 $trace->expand(2, new Production([new NonTerminal('oneselect')]), 0);
39 $trace->expand(2, new Production([new Terminal('SELECT'), new Terminal('INTEGER'), new NonTerminal('orderby_opt'), new NonTerminal('limit_opt')]), 0);
40 $trace->expand(4, new Production([new Terminal('ORDER'), new Terminal('BY'), new Terminal('INTEGER')]), 0);
41 $trace->expand(7, new Production([new Terminal('LIMIT'), new Terminal('INTEGER')]), 0);
42 $trace->expand(10, new Production([new Terminal('ORDER'), new Terminal('BY'), new Terminal('INTEGER')]), 0);
43 $trace->expand(13, new Production([new Terminal('LIMIT'), new Terminal('INTEGER')]), 0);
44 $trace->expand(15, new Production(array_map(static fn (string $name): Terminal => new Terminal($name), $operator)), 0);
45 $trace->expand(15 + count($operator), new Production([new Terminal('SELECT'), new Terminal('INTEGER'), new NonTerminal('orderby_opt'), new NonTerminal('limit_opt')]), 0);
46 $trace->expand(17 + count($operator), new Production([new Terminal('ORDER'), new Terminal('BY'), new Terminal('INTEGER')]), 0);
47 $trace->expand(20 + count($operator), new Production([new Terminal('LIMIT'), new Terminal('INTEGER')]), 0);
48 $input = $trace->terminals();
49 $rule = new CompoundSelectRule();
50 $result = $rule->rewrite($input);
51 self::assertSame(['SELECT', 'LP', 'SELECT', 'INTEGER', 'ORDER', 'BY', 'INTEGER', 'LIMIT', 'INTEGER', 'RP', ...$operator, 'SELECT', 'INTEGER', 'ORDER', 'BY', 'INTEGER', 'LIMIT', 'INTEGER'], $result->names());
52 self::assertSame($input->original, $result->original);
53 self::assertCount(count($result->terminals), array_unique(array_map(static fn ($terminal): int => $terminal->id, $result->terminals)));
54 self::assertSame($input->productions, $result->productions);
55 self::assertSame($result, $rule->rewrite($result));
56 }
57
58 /**
59 * @return list<array{non-empty-list<string>}>
60 */
61 public static function providerOperators(): array
62 {
63 return [[['UNION']], [['UNION', 'ALL']], [['INTERSECT']], [['EXCEPT']]];
64 }
65
66 public function testRewriteLeavesASingleSelectAndEmptyPriorClausesUnchanged(): void
67 {
68 $trace = new DerivationTrace('selectnowith');
69 $trace->expand(0, new Production([new NonTerminal('selectnowith'), new Terminal('UNION'), new NonTerminal('oneselect')]), 1);
70 $trace->expand(0, new Production([new NonTerminal('oneselect')]), 0);
71 $trace->expand(0, new Production([new Terminal('SELECT'), new Terminal('INTEGER'), new NonTerminal('orderby_opt'), new NonTerminal('limit_opt')]), 0);
72 $trace->expand(2, new Production([]), 0);
73 $trace->expand(2, new Production([]), 0);
74 $trace->expand(3, new Production([new Terminal('SELECT'), new Terminal('INTEGER'), new NonTerminal('limit_opt')]), 0);
75 $trace->expand(5, new Production([new Terminal('LIMIT'), new Terminal('INTEGER')]), 0);
76 $input = $trace->terminals();
77 self::assertSame($input, (new CompoundSelectRule())->rewrite($input));
78 $single = new DerivationTrace('selectnowith');
79 $single->expand(0, new Production([new NonTerminal('oneselect')]), 0);
80 $single->expand(0, new Production([new Terminal('SELECT'), new Terminal('INTEGER'), new NonTerminal('limit_opt')]), 0);
81 $single->expand(2, new Production([new Terminal('LIMIT'), new Terminal('INTEGER')]), 0);
82 $input = $single->terminals();
83 self::assertSame($input, (new CompoundSelectRule())->rewrite($input));
84 }
85}
86