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