packages/sql-faker/tests/Unit/MySql/Generation/Rewrite/IntegerContextRuleTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\SqlFaker\MySql\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\MySql\Generation\Rewrite\IntegerContextRule;
16
17#[CoversClass(IntegerContextRule::class)]
18#[UsesClass(DerivationTrace::class)]
19#[UsesClass(NonTerminal::class)]
20#[UsesClass(Production::class)]
21#[UsesClass(Terminal::class)]
22#[UsesClass(\SqlFaker\Generation\Token\ProductionOccurrence::class)]
23#[UsesClass(\SqlFaker\Generation\Token\TerminalOccurrence::class)]
24#[UsesClass(\SqlFaker\Generation\Token\TerminalSequence::class)]
25final class IntegerContextRuleTest extends TestCase
26{
27    public function testWeightStringLengthRestrictsOnlyTheDeclaredLength(): void
28    {
29        $trace = new DerivationTrace('ws_num_codepoints');
30        $trace->expand(0, new Production([new Terminal('('), new NonTerminal('real_ulong_num'), new Terminal(')')]), 0);
31        $trace->expand(1, new Production([new Terminal('NUM')]), 0);
32        $input = $trace->terminals();
33        $result = (new IntegerContextRule())->weightStringLength($input);
34        self::assertSame(['(', 'WEIGHT_STRING_LENGTH', ')'], $result->names());
35        self::assertSame($input->original, $result->original);
36        self::assertSame($input->productions, $result->productions);
37    }
38
39    #[DataProvider('providerBoundedOptions')]
40    public function testOptionsLimitsOnlyTheDeclaredNumericOption(string $context, string $option, string $expected): void
41    {
42        $trace = new DerivationTrace($context);
43        $trace->expand(0, new Production([new Terminal($option), new Terminal('EQ'), new NonTerminal('ulong_num'), new Terminal('LONG_NUM')]), 0);
44        $trace->expand(2, new Production([new Terminal('LONG_NUM')]), 0);
45        self::assertSame([$option, 'EQ', $expected, 'LONG_NUM'], (new IntegerContextRule())->rewrite($trace->terminals())->names());
46        self::assertSame([$option, 'EQ', $expected, 'LONG_NUM'], (new IntegerContextRule())->options($trace->terminals())->names());
47    }
48
49    /**
50     * @return list<array{string, string, string}>
51     */
52    public static function providerBoundedOptions(): array
53    {
54        return [['source_def', 'SOURCE_DELAY_SYM', 'SOURCE_DELAY_NUMBER'], ['master_def', 'MASTER_DELAY_SYM', 'SOURCE_DELAY_NUMBER'], ['create_table_option', 'STATS_SAMPLE_PAGES_SYM', 'STATS_SAMPLE_PAGES_NUMBER'], ['create_table_option', 'AUTO_INC', 'LONG_NUM'], ['source_def', 'STATS_SAMPLE_PAGES_SYM', 'LONG_NUM'], ['ordinary', 'SOURCE_DELAY_SYM', 'LONG_NUM']];
55    }
56
57    #[DataProvider('providerNewNumericContexts')]
58    public function testOptionsKeepsNumericLimitsWithinTheirGrammarScope(string $context, string $option, string $number, string $expected): void
59    {
60        $trace = new DerivationTrace($context);
61        $trace->expand(0, new Production([new Terminal($option), new Terminal('EQ'), new NonTerminal($number)]), 0);
62        $trace->expand(2, new Production([new Terminal('ULONGLONG_NUM')]), 0);
63        $input = $trace->terminals();
64        $result = (new IntegerContextRule())->rewrite($input);
65        self::assertSame([$option, 'EQ', $expected], $result->names());
66        self::assertSame($input->original, $result->original);
67        self::assertSame($input->productions, $result->productions);
68    }
69
70    /**
71     * @return list<array{string, string, string, string}>
72     */
73    public static function providerNewNumericContexts(): array
74    {
75        return [
76            ['create_table_option', 'AVG_ROW_LENGTH', 'ulonglong_num', 'AVG_ROW_LENGTH_NUMBER'],
77            ['create_table_option', 'AVG_ROW_LENGTH', 'ulong_num', 'AVG_ROW_LENGTH_NUMBER'],
78            ['opt_key_algo', 'ALGORITHM_SYM', 'real_ulong_num', 'KEY_ALGORITHM_NUMBER'],
79            ['opt_num_parts', 'PARTITIONS_SYM', 'real_ulong_num', 'PARTITION_COUNT_NUMBER'],
80            ['opt_num_subparts', 'SUBPARTITIONS_SYM', 'real_ulong_num', 'PARTITION_COUNT_NUMBER'],
81            ['ordinary', 'ALGORITHM_SYM', 'real_ulong_num', 'ULONGLONG_NUM'],
82            ['create_table_option', 'AUTO_INC', 'ulonglong_num', 'ULONGLONG_NUM'],
83        ];
84    }
85
86    #[DataProvider('providerYearWidths')]
87    public function testYearWidthConstrainsOnlyExplicitYearWidths(string $type, bool $strict): void
88    {
89        $trace = new DerivationTrace('type');
90        $trace->expand(0, new Production([new Terminal($type), new NonTerminal('opt_field_length')]), 0);
91        $trace->expand(1, new Production([new NonTerminal('field_length')]), 0);
92        $trace->expand(1, new Production([new Terminal('('), new Terminal('LONG_NUM'), new Terminal(')')]), 0);
93        $rule = new IntegerContextRule($strict);
94        $result = $rule->yearWidth($trace->terminals());
95        self::assertSame([$type, '(', $type === 'YEAR_SYM' && $strict ? 'YEAR_WIDTH_NUMBER' : 'LONG_NUM', ')'], $result->names());
96        self::assertSame($result->names(), $rule->rewrite($trace->terminals())->names());
97    }
98
99    /**
100     * @return list<array{string, bool}>
101     */
102    public static function providerYearWidths(): array
103    {
104        return [['YEAR_SYM', true], ['CHAR_SYM', true], ['YEAR_SYM', false]];
105    }
106
107    public function testRewriteKeepsTheDefaultStatisticsOption(): void
108    {
109        $trace = new DerivationTrace('create_table_option');
110        $trace->expand(0, new Production([new Terminal('STATS_SAMPLE_PAGES_SYM'), new Terminal('DEFAULT_SYM')]), 0);
111        $input = $trace->terminals();
112        self::assertSame($input, (new IntegerContextRule())->rewrite($input));
113    }
114
115    #[DataProvider('providerKeyBlockSizes')]
116    public function testOptionsBoundsKeyBlockSizeInOldAndModernGrammars(string $context, string $number, string $expected): void
117    {
118        $trace = new DerivationTrace($context);
119        $trace->expand(0, new Production([new Terminal('KEY_BLOCK_SIZE'), new Terminal('EQ'), new NonTerminal($number), new Terminal('LONG_NUM')]), 0);
120        $trace->expand(2, new Production([new Terminal('LONG_NUM')]), 0);
121        self::assertSame(['KEY_BLOCK_SIZE', 'EQ', $expected, 'LONG_NUM'], (new IntegerContextRule())->options($trace->terminals())->names());
122        self::assertSame(['KEY_BLOCK_SIZE', 'EQ', $expected, 'LONG_NUM'], (new IntegerContextRule())->rewrite($trace->terminals())->names());
123    }
124
125    /**
126     * @return list<array{string, string, string}>
127     */
128    public static function providerKeyBlockSizes(): array
129    {
130        return [['create_table_option', 'ulong_num', 'KEY_BLOCK_SIZE_NUMBER'], ['create_table_option', 'ulonglong_num', 'KEY_BLOCK_SIZE_NUMBER'], ['ordinary', 'ulonglong_num', 'LONG_NUM'], ['source_def', 'ulonglong_num', 'LONG_NUM'], ['create_table_option', 'expr', 'LONG_NUM']];
131    }
132
133    public function testRewriteReplacesOnlyDiagnosticDecimalAlternatives(): void
134    {
135        $trace = new DerivationTrace('root');
136        $trace->expand(0, new Production([new NonTerminal('dec_num_error'), new Terminal('DECIMAL_NUM')]), 0);
137        $trace->expand(0, new Production([new Terminal('DECIMAL_NUM')]), 0);
138        $input = $trace->terminals();
139        $result = (new IntegerContextRule())->rewrite($input);
140        self::assertSame(['NUM', 'DECIMAL_NUM'], $result->names());
141        self::assertSame($input->terminals[0]->id, $result->terminals[0]->id);
142        self::assertSame($input->original, $result->original);
143        self::assertCount(count($result->terminals), array_unique(array_map(static fn ($terminal): int => $terminal->id, $result->terminals)));
144    }
145
146    public function testRewriteConstrainsTheCheckedReplicationFlag(): void
147    {
148        $trace = new DerivationTrace('source_def');
149        $trace->expand(0, new Production([new Terminal('SOURCE_CONNECTION_AUTO_FAILOVER_SYM'), new Terminal('EQ'), new NonTerminal('real_ulong_num')]), 0);
150        $trace->expand(2, new Production([new Terminal('NUM')]), 0);
151        self::assertSame(['SOURCE_CONNECTION_AUTO_FAILOVER_SYM', 'EQ', 'REPLICATION_FLAG_NUMBER'], (new IntegerContextRule())->rewrite($trace->terminals())->names());
152    }
153
154    #[DataProvider('providerReplicationFlags')]
155    public function testRewriteConstrainsEachBooleanReplicationOption(string $option, string $number, bool $restricted): void
156    {
157        $trace = new DerivationTrace('source_def');
158        $trace->expand(0, new Production([new Terminal($option), new Terminal('EQ'), new NonTerminal($number)]), 0);
159        $trace->expand(2, new Production([new Terminal('NUM')]), 0);
160        $input = $trace->terminals();
161        $result = (new IntegerContextRule())->rewrite($input);
162        self::assertSame([$option, 'EQ', $restricted ? 'REPLICATION_FLAG_NUMBER' : 'NUM'], $result->names());
163        self::assertSame($input->original, $result->original);
164        self::assertSame($input->productions, $result->productions);
165        self::assertSame($input->terminals[2]->id, $result->terminals[2]->id);
166    }
167
168    /**
169     * @return list<array{string, string, bool}>
170     */
171    public static function providerReplicationFlags(): array
172    {
173        return [
174            ['GTID_ONLY_SYM', 'real_ulong_num', true],
175            ['REQUIRE_ROW_FORMAT_SYM', 'ulong_num', true],
176            ['SOURCE_CONNECTION_AUTO_FAILOVER_SYM', 'real_ulong_num', true],
177            ['SOURCE_AUTO_POSITION_SYM', 'ulong_num', false],
178            ['GTID_ONLY_SYM', 'expr', false],
179        ];
180    }
181
182    public function testMappedKeepsCompatiblePlanOccurrenceIdentity(): void
183    {
184        $trace = new DerivationTrace('number');
185        $trace->expand(0, new Production([new Terminal('NUM')]), 0);
186        $input = $trace->terminals();
187        $result = (new IntegerContextRule())->mapped($input, 0, 'FLAG', 'source');
188        self::assertSame($input->terminals[0]->id, $result->terminals[0]->id);
189        self::assertSame($input, (new IntegerContextRule())->mapped($input, 999, 'FLAG', 'source'));
190    }
191    public function testRewritePreservesDefaultAndRestrictsOnlyTheNumericTernaryOption(): void
192    {
193        $trace = new DerivationTrace('root');
194        $trace->expand(0, new Production([new NonTerminal('ternary_option'), new NonTerminal('ternary_option')]), 0);
195        $trace->expand(0, new Production([new NonTerminal('ulong_num')]), 0);
196        $trace->expand(0, new Production([new Terminal('LONG_NUM')]), 2);
197        $trace->expand(1, new Production([new Terminal('DEFAULT_SYM')]), 1);
198        self::assertSame(['TERNARY_OPTION_NUMBER', 'DEFAULT_SYM'], (new IntegerContextRule())->rewrite($trace->terminals())->names());
199    }
200
201    public function testRewriteConstrainsOnlyIdentifierFormSizesAndResetIndices(): void
202    {
203        $trace = new DerivationTrace('root');
204        $trace->expand(0, new Production([new NonTerminal('size_number'), new NonTerminal('size_number'), new NonTerminal('source_reset_options'), new Terminal('IDENT'), new Terminal('NUM')]), 0);
205        $trace->expand(0, new Production([new NonTerminal('IDENT_sys')]), 1);
206        $trace->expand(0, new Production([new Terminal('IDENT_QUOTED')]), 1);
207        $trace->expand(1, new Production([new NonTerminal('real_ulonglong_num')]), 0);
208        $trace->expand(1, new Production([new Terminal('LONG_NUM')]), 3);
209        $trace->expand(2, new Production([new Terminal('TO_SYM'), new NonTerminal('real_ulonglong_num')]), 1);
210        $trace->expand(3, new Production([new Terminal('HEX_NUM')]), 1);
211        $input = $trace->terminals();
212        $rule = new IntegerContextRule();
213        $result = $rule->rewrite($input);
214        self::assertSame(['SIZE_NUMBER', 'LONG_NUM', 'TO_SYM', 'BINLOG_RESET_INDEX', 'IDENT', 'NUM'], $result->names());
215        self::assertSame($input->original, $result->original);
216        self::assertCount(count($result->terminals), array_unique(array_map(static fn ($terminal): int => $terminal->id, $result->terminals)));
217        self::assertSame($input->productions, $result->productions);
218        self::assertSame(array_map(static fn ($terminal): int => $terminal->id, $input->terminals), array_map(static fn ($terminal): int => $terminal->id, $result->terminals));
219    }
220
221    public function testRewritePreservesEmptyResetOptions(): void
222    {
223        $trace = new DerivationTrace('source_reset_options');
224        $trace->expand(0, new Production([]), 0);
225        $input = $trace->terminals();
226        self::assertSame($input, (new IntegerContextRule())->rewrite($input));
227    }
228}
229