packages/sql-faker/tests/Unit/MySql/Generation/Rewrite/Partition/ValueArityRuleTest.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\ValueArityRule;
15use SqlFaker\MySql\Generation\Rewrite\Partition\ValueShape;
16
17#[CoversClass(ValueArityRule::class)]
18#[UsesClass(ValueShape::class)]
19#[UsesClass(ProductionOccurrence::class)]
20#[UsesClass(TerminalOccurrence::class)]
21#[UsesClass(TerminalSequence::class)]
22final class ValueArityRuleTest extends TestCase
23{
24    #[DataProvider('providerDeclarations')]
25    public function testRewriteCoordinatesAllValuesInTheSamePartitionList(TerminalSequence $input, string $expected): void
26    {
27        $rule = new ValueArityRule();
28        $result = $rule->rewrite($input);
29        self::assertSame(explode(' ', $expected), $result->names());
30        self::assertSame($input->original, $result->original);
31        self::assertSame($input->productions, $result->productions);
32        self::assertCount(count($result->terminals), array_unique(array_column($result->terminals, 'id')));
33        self::assertSame($result, $rule->rewrite($result));
34        $retained = array_values(array_filter($result->terminals, static fn (TerminalOccurrence $token): bool => $token->id >= 0));
35        self::assertSame(array_map(static fn (TerminalOccurrence $token): TerminalOccurrence => $input->original[$token->id], $retained), $retained);
36    }
37
38    /**
39     * @return iterable<array{TerminalSequence, string}>
40     */
41    public static function providerDeclarations(): iterable
42    {
43        $cases = [
44            ['RANGE_SYM COLUMNS ( IDENT , IDENT , IDENT )', 'VALUES LESS_SYM THAN_SYM MAX_VALUE_SYM', 'VALUES LESS_SYM THAN_SYM ( NUM )', 'VALUES LESS_SYM THAN_SYM ( MAX_VALUE_SYM , MAX_VALUE_SYM , MAX_VALUE_SYM ) VALUES LESS_SYM THAN_SYM ( NUM , NUM , NUM )'],
45            ['LIST_SYM ( IDENT )', 'VALUES IN_SYM ( ( NUM , NUM ) )', 'VALUES IN_SYM ( NUM )', 'VALUES IN_SYM ( NUM , NUM ) VALUES IN_SYM ( NUM )'],
46            ['LIST_SYM COLUMNS ( IDENT , IDENT )', 'VALUES IN_SYM ( NUM )', 'VALUES IN_SYM ( ( NUM , NUM , NUM ) )', 'VALUES IN_SYM ( ( NUM , NUM ) ) VALUES IN_SYM ( ( NUM , NUM ) )'],
47            [null, 'VALUES IN_SYM ( ( NUM ) , ( NUM , NUM , NUM ) )', 'VALUES IN_SYM ( NUM )', 'VALUES IN_SYM ( ( NUM , NUM ) , ( NUM , NUM ) ) VALUES IN_SYM ( ( NUM , NUM ) )'],
48            [null, 'VALUES IN_SYM ( NUM , NUM )', 'VALUES IN_SYM ( ( NUM , NUM ) )', 'VALUES IN_SYM ( NUM , NUM ) VALUES IN_SYM ( NUM , NUM )'],
49            [null, 'VALUES LESS_SYM THAN_SYM ( NUM , NUM )', 'VALUES LESS_SYM THAN_SYM MAX_VALUE_SYM', 'VALUES LESS_SYM THAN_SYM ( NUM , NUM ) VALUES LESS_SYM THAN_SYM ( MAX_VALUE_SYM , MAX_VALUE_SYM )'],
50            [null, 'VALUES LESS_SYM THAN_SYM MAX_VALUE_SYM', 'VALUES LESS_SYM THAN_SYM ( NUM , NUM )', 'VALUES LESS_SYM THAN_SYM MAX_VALUE_SYM VALUES LESS_SYM THAN_SYM ( NUM )'],
51        ];
52        foreach ($cases as [$declaration, $first, $second, $expected]) {
53            $productions = [new ProductionOccurrence(0, null, 'partition_clause', 0), new ProductionOccurrence(1, 0, 'part_type_def', 0), new ProductionOccurrence(2, 0, 'part_def_list', 0), new ProductionOccurrence(3, 2, 'opt_part_values', 0), new ProductionOccurrence(4, 2, 'opt_part_values', 0)];
54            $tokens = [];
55            foreach ([1 => $declaration, 3 => $first, 4 => $second] as $id => $text) {
56                if ($text === null) {
57                    continue;
58                }
59                $ancestors = $id === 1 ? [0, 1] : [0, 2, $id];
60                $rules = $id === 1 ? ['partition_clause', 'part_type_def'] : ['partition_clause', 'part_def_list', 'opt_part_values'];
61                foreach (explode(' ', $text) as $name) {
62                    $tokens[] = new TerminalOccurrence($name, count($tokens), $ancestors, $rules);
63                }
64            }
65            yield [new TerminalSequence($tokens, $tokens, productions: $productions), ($declaration === null ? '' : $declaration . ' ') . $expected];
66        }
67    }
68
69    public function testRewriteLeavesEmptyAndUnscopedFragmentsAlone(): void
70    {
71        $rule = new ValueArityRule();
72        $empty = new TerminalSequence([], productions: [new ProductionOccurrence(0, null, 'opt_part_values', 0)]);
73        self::assertSame($empty, $rule->rewrite($empty));
74        $fragment = new TerminalSequence([new TerminalOccurrence('VALUES', 1, [0], ['opt_part_values'])], productions: $empty->productions);
75        self::assertSame($fragment, $rule->rewrite($fragment));
76        self::assertNull($rule->declaredWidth($fragment, 0));
77        self::assertNull($rule->declaredWidth($fragment, null));
78    }
79
80    public function testDeclaredWidthReadsTheColumnsOfTheOwningClause(): void
81    {
82        $names = ['LIST_SYM', 'COLUMNS', '(', 'IDENT', ',', 'IDENT', ')'];
83        $tokens = array_map(static fn (string $name, int $id): TerminalOccurrence => new TerminalOccurrence($name, $id, [0, 1], ['partition_clause', 'part_type_def']), $names, array_keys($names));
84        $input = new TerminalSequence($tokens, productions: [new ProductionOccurrence(0, null, 'partition_clause', 0), new ProductionOccurrence(1, 0, 'part_type_def', 0)]);
85        self::assertSame(2, (new ValueArityRule())->declaredWidth($input, 0));
86        self::assertNull((new ValueArityRule())->declaredWidth($input, 1));
87    }
88}
89