packages/sql-faker/tests/Unit/MySql/Generation/Rewrite/Partition/DefinitionRuleTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\SqlFaker\MySql\Generation\Rewrite\Partition;
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\Partition\DefinitionRule;
15
16#[CoversClass(DefinitionRule::class)]
17#[UsesClass(ProductionOccurrence::class)]
18#[UsesClass(TerminalOccurrence::class)]
19#[UsesClass(TerminalSequence::class)]
20final class DefinitionRuleTest extends TestCase
21{
22 #[DataProvider('providerDeclaredKinds')]
23 public function testDeclaredKindUsesTheEnclosingType(string $name, ?int $clause, ?string $expected): void
24 {
25 $input = new TerminalSequence([new TerminalOccurrence($name, 10, [0, 1], ['partition_clause', 'part_type_def'])], productions: [new ProductionOccurrence(0, null, 'partition_clause', 0), new ProductionOccurrence(1, 0, 'part_type_def', 0)]);
26 self::assertSame($expected, (new DefinitionRule())->declaredKind($input, $clause));
27 }
28
29 /**
30 * @return list<array{string, int|null, string|null}>
31 */
32 public static function providerDeclaredKinds(): array
33 {
34 return [['RANGE_SYM', 0, 'RANGE'], ['LIST_SYM', 0, 'LIST'], ['KEY_SYM', 0, 'HASH'], ['HASH_SYM', 0, 'HASH'], ['RANGE_SYM', null, null], ['LIST_SYM', 1, null]];
35 }
36
37 public function testInferCountRemovesOnlyTheExplicitCount(): void
38 {
39 $input = new TerminalSequence([new TerminalOccurrence('HASH_SYM', 10, [0], ['partition_clause']), new TerminalOccurrence('PARTITIONS_SYM', 11, [0, 1], ['partition_clause', 'opt_num_parts']), new TerminalOccurrence('NUM', 12, [0, 1], ['partition_clause', 'opt_num_parts'])], productions: [new ProductionOccurrence(0, null, 'partition_clause', 0), new ProductionOccurrence(1, 0, 'opt_num_parts', 0)]);
40 $rule = new DefinitionRule();
41 self::assertSame($input, $rule->inferCount($input, null));
42 $result = $rule->inferCount($input, 0);
43 self::assertSame(['HASH_SYM'], $result->names());
44 self::assertSame($input->original, $result->original);
45 self::assertSame($result, $rule->inferCount($result, 0));
46 }
47
48 public function testReplaceValuesPreservesFragmentsWithoutAnInsertionPoint(): void
49 {
50 $input = new TerminalSequence([], productions: [new ProductionOccurrence(0, null, 'part_definition', 0), new ProductionOccurrence(1, 0, 'opt_part_values', 0)]);
51 self::assertSame($input, (new DefinitionRule())->replaceValues($input, 0, 1, 'LIST'));
52 }
53
54 /**
55 * @param list<string> $expected
56 */
57 #[DataProvider('providerKinds')]
58 public function testRewriteKeepsOneKindAcrossTheDefinitionList(TerminalSequence $input, int $values, array $expected): void
59 {
60 $result = (new DefinitionRule())->rewrite($input);
61 $range = $result->range($values);
62 self::assertSame($expected, $range === null ? [] : array_slice($result->names(), $range[0], $range[1] - $range[0]));
63 self::assertNotContains('PARTITIONS_SYM', $result->names());
64 self::assertSame($input->original, $result->original);
65 self::assertSame($input->productions, $result->productions);
66 self::assertCount(count($result->terminals), array_unique(array_column($result->terminals, 'id')));
67 self::assertSame($result, (new DefinitionRule())->rewrite($result));
68 }
69
70 /**
71 * @return iterable<array{TerminalSequence, int, list<string>}>
72 */
73 public static function providerKinds(): iterable
74 {
75 $cases = [
76 ['KEY_SYM', 'RANGE', 'LIST', []],
77 ['HASH_SYM', 'HASH', 'HASH', []],
78 ['RANGE_SYM', 'HASH', 'LIST', ['VALUES', 'LESS_SYM', 'THAN_SYM', 'MAX_VALUE_SYM']],
79 ['LIST_SYM', 'RANGE', 'HASH', ['VALUES', 'IN_SYM', '(', 'NUM', ')']],
80 [null, 'RANGE', 'HASH', ['VALUES', 'LESS_SYM', 'THAN_SYM', 'MAX_VALUE_SYM']],
81 [null, 'LIST', 'HASH', ['VALUES', 'IN_SYM', '(', 'NUM', ')']],
82 [null, 'HASH', 'RANGE', []],
83 ];
84 foreach ($cases as [$type, $first, $second, $expected]) {
85 if ($type === null) {
86 $root = 'alter_table';
87 $tokens = [];
88 } else {
89 $root = 'partition_clause';
90 $tokens = [new TerminalOccurrence($type, 100, [0, 1], [$root, 'part_type_def']), new TerminalOccurrence('PARTITIONS_SYM', 101, [0, 2], [$root, 'opt_num_parts']), new TerminalOccurrence('NUM', 102, [0, 2], [$root, 'opt_num_parts'])];
91 }
92 $productions = [new ProductionOccurrence(0, null, $root, 0), new ProductionOccurrence(1, 0, 'part_type_def', 0), new ProductionOccurrence(2, 0, 'opt_num_parts', 0), new ProductionOccurrence(3, 0, 'part_def_list', 0)];
93 foreach ([$first, $second] as $index => $kind) {
94 $id = 4 + $index * 3;
95 $productions[] = new ProductionOccurrence($id, 3, 'part_definition', 0);
96 $productions[] = new ProductionOccurrence($id + 1, $id, 'ident', 0);
97 $productions[] = new ProductionOccurrence($id + 2, $id, 'opt_part_values', 0);
98 $tokens[] = new TerminalOccurrence('PARTITION_SYM', 110 + $index * 10, [0, 3, $id], [$root, 'part_def_list', 'part_definition']);
99 $tokens[] = new TerminalOccurrence('IDENT', 111 + $index * 10, [0, 3, $id, $id + 1], [$root, 'part_def_list', 'part_definition', 'ident']);
100 $names = match ($kind) {
101 'LIST' => ['VALUES', 'IN_SYM', '(', 'NUM', ')'], 'RANGE' => ['VALUES', 'LESS_SYM', 'THAN_SYM', 'MAX_VALUE_SYM'], 'HASH' => []
102 };
103 foreach ($names as $offset => $name) {
104 $tokens[] = new TerminalOccurrence($name, 112 + $index * 10 + $offset, [0, 3, $id, $id + 2], [$root, 'part_def_list', 'part_definition', 'opt_part_values']);
105 }
106 }
107 $input = new TerminalSequence($tokens, productions: $productions);
108 yield [$input, 6, $expected];
109 yield [$input, 9, $expected];
110 }
111 }
112
113 public function testRewritePreservesAStandaloneDefinitionWithoutAList(): void
114 {
115 $input = new TerminalSequence([new TerminalOccurrence('PARTITION_SYM', 10, [0], ['part_definition'])], productions: [new ProductionOccurrence(0, null, 'part_definition', 0), new ProductionOccurrence(1, 0, 'opt_part_values', 0)]);
116 self::assertSame($input, (new DefinitionRule())->rewrite($input));
117 $empty = new TerminalSequence([], productions: [new ProductionOccurrence(0, null, 'part_definition', 0)]);
118 self::assertSame($empty, (new DefinitionRule())->rewrite($empty));
119 }
120}
121