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