packages/sql-faker/tests/Unit/MySql/Generation/GenerationPlansTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\SqlFaker\MySql\Generation;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\UsesClass;
9use PHPUnit\Framework\TestCase;
10use SqlFaker\Generation\Plan\GenerationPlan;
11use SqlFaker\Generation\Plan\ProductionPattern;
12use SqlFaker\Grammar\Model\Grammar;
13use SqlFaker\Grammar\Model\ProductionRule;
14use SqlFaker\MySql\Generation\GenerationPlans;
15
16#[CoversClass(GenerationPlans::class)]
17#[UsesClass(GenerationPlan::class)]
18#[UsesClass(ProductionPattern::class)]
19#[UsesClass(Grammar::class)]
20#[UsesClass(ProductionRule::class)]
21#[UsesClass(\SqlFaker\Generation\Choice\BytePlanCompiler::class)]
22final class GenerationPlansTest extends TestCase
23{
24 public function testWithoutEmptyRowsConstrainsEveryOptionalValuesOccurrence(): void
25 {
26 $all = GenerationPlans::withoutEmptyRows();
27 $insert = GenerationPlans::withoutEmptyRows('insert_stmt');
28 $firstPattern = $all->patternAt('opt_values', 0);
29 $laterPattern = $all->patternAt('opt_values', 100);
30 $insertPattern = $insert->patternAt('opt_values', 100);
31
32 self::assertNull($all->startRule());
33 self::assertSame('insert_stmt', $insert->startRule());
34 self::assertNotNull($firstPattern);
35 self::assertNotNull($laterPattern);
36 self::assertNotNull($insertPattern);
37 self::assertFalse($firstPattern->matches([]));
38 self::assertTrue($firstPattern->matches(['values']));
39 self::assertFalse($laterPattern->matches([]));
40 self::assertTrue($insertPattern->matches(['values']));
41 }
42
43 public function testMultiTableUpdateStatementRestrictsTheUpdateGrammar(): void
44 {
45 $plan = GenerationPlans::multiTableUpdateStatement();
46
47 self::assertSame('update_stmt', $plan->startRule());
48 self::assertTrue($plan->patternAt('opt_with_clause', 0)?->matches([]) ?? false);
49 self::assertTrue(
50 $plan->patternAt('table_reference_list', 0)?->matches([
51 'table_reference_list',
52 ',',
53 'table_reference',
54 ]) ?? false,
55 );
56 self::assertTrue($plan->patternAt('table_reference_list', 1)?->matches(['table_reference']) ?? false);
57 self::assertTrue($plan->patternAt('table_reference', 0)?->matches(['table_factor']) ?? false);
58 self::assertTrue($plan->patternAt('table_reference', 1)?->matches(['table_factor']) ?? false);
59 self::assertTrue($plan->patternAt('table_factor', 0)?->matches(['single_table']) ?? false);
60 self::assertTrue($plan->patternAt('table_factor', 1)?->matches(['single_table']) ?? false);
61 self::assertTrue($plan->patternAt('opt_use_partition', 0)?->matches([]) ?? false);
62 self::assertTrue($plan->patternAt('opt_use_partition', 1)?->matches([]) ?? false);
63 self::assertTrue($plan->patternAt('update_list', 0)?->matches(['update_list', ',', 'update_elem']) ?? false);
64 self::assertTrue($plan->patternAt('update_list', 1)?->matches(['update_elem']) ?? false);
65 }
66
67 public function testMultiTableDeleteStatementRestrictsTheDeleteGrammar(): void
68 {
69 $plan = GenerationPlans::multiTableDeleteStatement();
70
71 self::assertSame('delete_stmt', $plan->startRule());
72 self::assertTrue(
73 $plan->patternAt('delete_stmt', 0)?->matches(['table_alias_ref_list', 'table_reference_list']) ?? false,
74 );
75 self::assertTrue($plan->patternAt('opt_with_clause', 0)?->matches([]) ?? false);
76 self::assertTrue(
77 $plan->patternAt('table_alias_ref_list', 0)?->matches([
78 'table_alias_ref_list',
79 ',',
80 'table_ident_opt_wild',
81 ]) ?? false,
82 );
83 self::assertTrue(
84 $plan->patternAt('table_alias_ref_list', 1)?->matches(['table_ident_opt_wild']) ?? false,
85 );
86 self::assertTrue(
87 $plan->patternAt('table_reference_list', 0)?->matches([
88 'table_reference_list',
89 ',',
90 'table_reference',
91 ]) ?? false,
92 );
93 self::assertTrue($plan->patternAt('table_reference_list', 1)?->matches(['table_reference']) ?? false);
94 self::assertTrue($plan->patternAt('table_reference', 0)?->matches(['table_factor']) ?? false);
95 self::assertTrue($plan->patternAt('table_reference', 1)?->matches(['table_factor']) ?? false);
96 self::assertTrue($plan->patternAt('table_factor', 0)?->matches(['single_table']) ?? false);
97 self::assertTrue($plan->patternAt('table_factor', 1)?->matches(['single_table']) ?? false);
98 self::assertTrue($plan->patternAt('opt_use_partition', 0)?->matches([]) ?? false);
99 self::assertTrue($plan->patternAt('opt_use_partition', 1)?->matches([]) ?? false);
100 }
101
102 public function testForeignKeyConstraintAdaptsToTheGrammarVersion(): void
103 {
104 $modernGrammar = new Grammar('table_constraint_def', [
105 'table_constraint_def' => new ProductionRule('table_constraint_def', []),
106 'opt_constraint_name' => new ProductionRule('opt_constraint_name', []),
107 ]);
108 $legacyGrammar = new Grammar('key_def', []);
109
110 $modern = GenerationPlans::foreignKeyConstraint($modernGrammar);
111 $legacy = GenerationPlans::foreignKeyConstraint($legacyGrammar);
112
113 self::assertSame('table_constraint_def', $modern->startRule());
114 self::assertTrue(
115 $modern->patternAt('table_constraint_def', 0)?->matches(['FOREIGN', 'KEY_SYM']) ?? false,
116 );
117 self::assertTrue($modern->patternAt('opt_constraint_name', 0)?->matches(['CONSTRAINT']) ?? false);
118 self::assertTrue($modern->patternAt('opt_ident', 0)?->matches(['ident']) ?? false);
119 self::assertTrue($modern->patternAt('opt_ref_list', 0)?->matches(['reference']) ?? false);
120 self::assertSame('key_def', $legacy->startRule());
121 self::assertTrue($legacy->patternAt('key_def', 0)?->matches(['FOREIGN', 'KEY_SYM']) ?? false);
122 self::assertTrue($legacy->patternAt('opt_constraint', 0)?->matches(['constraint']) ?? false);
123 self::assertTrue($legacy->patternAt('opt_ident', 0)?->matches(['ident']) ?? false);
124 self::assertTrue($legacy->patternAt('opt_ref_list', 0)?->matches(['reference']) ?? false);
125 }
126
127 public function testUpdateJoinDerivedStatementRestrictsTheDerivedTableGrammar(): void
128 {
129 $plan = GenerationPlans::updateJoinDerivedStatement();
130
131 self::assertSame('update_stmt', $plan->startRule());
132 self::assertTrue($plan->patternAt('opt_with_clause', 0)?->matches([]) ?? false);
133 self::assertTrue($plan->patternAt('table_reference', 0)?->matches(['joined_table']) ?? false);
134 self::assertTrue($plan->patternAt('table_reference', 1)?->matches(['table_factor']) ?? false);
135 self::assertTrue($plan->patternAt('table_reference', 2)?->matches(['table_factor']) ?? false);
136 self::assertTrue($plan->patternAt('table_reference', 3)?->matches(['table_factor']) ?? false);
137 self::assertTrue($plan->patternAt('joined_table', 0)?->matches(['ON_SYM']) ?? false);
138 self::assertTrue($plan->patternAt('table_factor', 0)?->matches(['single_table']) ?? false);
139 self::assertTrue($plan->patternAt('table_factor', 1)?->matches(['derived_table']) ?? false);
140 self::assertTrue($plan->patternAt('table_factor', 2)?->matches(['single_table']) ?? false);
141 self::assertTrue($plan->patternAt('opt_from_clause', 0)?->matches(['table_reference_list']) ?? false);
142 self::assertTrue($plan->patternAt('opt_group_clause', 0)?->matches(['GROUP_SYM']) ?? false);
143 }
144
145 public function testInsertSelectCompoundStatementRestrictsTheUnionGrammar(): void
146 {
147 $plan = GenerationPlans::insertSelectCompoundStatement();
148
149 self::assertSame('insert_stmt', $plan->startRule());
150 self::assertTrue($plan->patternAt('insert_stmt', 0)?->matches(['insert_query_expression']) ?? false);
151 self::assertTrue($plan->patternAt('query_expression_body', 0)?->matches(['UNION_SYM']) ?? false);
152 self::assertTrue($plan->patternAt('query_expression_body', 1)?->matches(['query_primary']) ?? false);
153 self::assertTrue($plan->patternAt('query_expression_body', 2)?->matches(['query_primary']) ?? false);
154 self::assertTrue($plan->patternAt('query_primary', 0)?->matches(['query_specification']) ?? false);
155 self::assertTrue($plan->patternAt('union_option', 0)?->matches(['ALL']) ?? false);
156 }
157
158 public function testInsertRowAliasUpsertStatementRestrictsTheValuesGrammar(): void
159 {
160 $plan = GenerationPlans::insertRowAliasUpsertStatement();
161
162 self::assertSame('insert_stmt', $plan->startRule());
163 self::assertTrue($plan->patternAt('insert_stmt', 0)?->matches(['insert_from_constructor']) ?? false);
164 self::assertTrue(
165 $plan->patternAt('insert_from_constructor', 0)?->matches(['insert_values']) ?? false,
166 );
167 self::assertTrue($plan->patternAt('value_or_values', 0)?->matches(['VALUES']) ?? false);
168 self::assertTrue($plan->patternAt('opt_values_reference', 0)?->matches(['alias']) ?? false);
169 self::assertTrue($plan->patternAt('opt_insert_update_list', 0)?->matches(['update']) ?? false);
170 }
171
172 public function testInsertFunctionUpsertStatementRestrictsTheConflictFunctionGrammar(): void
173 {
174 $plan = GenerationPlans::insertFunctionUpsertStatement();
175
176 self::assertSame('insert_stmt', $plan->startRule());
177 self::assertTrue($plan->patternAt('insert_stmt', 0)?->matches(['insert_from_constructor']) ?? false);
178 self::assertTrue(
179 $plan->patternAt('insert_from_constructor', 0)?->matches(['insert_values']) ?? false,
180 );
181 self::assertTrue($plan->patternAt('values_list', 0)?->matches(['row_value']) ?? false);
182 self::assertTrue($plan->patternAt('opt_values', 0)?->matches(['values']) ?? false);
183 self::assertTrue($plan->patternAt('values', 0)?->matches(['expr_or_default']) ?? false);
184 self::assertTrue($plan->patternAt('expr_or_default', 0)?->matches(['DEFAULT_SYM']) ?? false);
185 self::assertTrue($plan->patternAt('expr_or_default', 1)?->matches(['expr']) ?? false);
186 self::assertTrue($plan->patternAt('opt_insert_update_list', 0)?->matches(['update']) ?? false);
187 self::assertTrue($plan->patternAt('simple_expr', 0)?->matches(['function_call_conflict']) ?? false);
188 self::assertTrue($plan->patternAt('function_call_conflict', 0)?->matches(['IF']) ?? false);
189 }
190
191 public function testTemporaryTableStatementRequiresTheTemporaryProduction(): void
192 {
193 $plan = GenerationPlans::temporaryTableStatement();
194
195 self::assertSame('create_table_stmt', $plan->startRule());
196 self::assertTrue($plan->patternAt('opt_temporary', 0)?->matches(['TEMPORARY']) ?? false);
197 }
198
199 public function testViewStatementRestrictsTheCreateGrammar(): void
200 {
201 $plan = GenerationPlans::viewStatement();
202
203 self::assertSame('create', $plan->startRule());
204 self::assertTrue(
205 $plan->patternAt('create', 0)?->matches(['CREATE', 'view_or_trigger_or_sp_or_event']) ?? false,
206 );
207 self::assertTrue(
208 $plan->patternAt('view_or_trigger_or_sp_or_event', 0)?->matches([
209 'no_definer',
210 'init_lex_create_info',
211 'no_definer_tail',
212 ]) ?? false,
213 );
214 self::assertTrue($plan->patternAt('no_definer_tail', 0)?->matches(['view_tail']) ?? false);
215 self::assertTrue($plan->patternAt('query_primary', 0)?->matches(['query_specification']) ?? false);
216 }
217
218 public function testGeneratedColumnStatementRequiresTheGeneratedAttribute(): void
219 {
220 $plan = GenerationPlans::generatedColumnStatement();
221
222 self::assertSame('create_table_stmt', $plan->startRule());
223 self::assertTrue($plan->patternAt('create_table_stmt', 0)?->matches(['table_element_list']) ?? false);
224 self::assertTrue($plan->patternAt('table_element', 0)?->matches(['column_def']) ?? false);
225 self::assertTrue($plan->patternAt('field_def', 0)?->matches(['opt_generated_always', 'expr']) ?? false);
226 self::assertTrue($plan->patternAt('opt_generated_always', 0)?->matches(['GENERATED']) ?? false);
227 self::assertTrue($plan->patternAt('opt_stored_attribute', 0)?->matches(['STORED_SYM']) ?? false);
228 }
229
230 public function testForeignKeyCascadeStatementRequiresBothCascadeActions(): void
231 {
232 $plan = GenerationPlans::foreignKeyCascadeStatement();
233
234 self::assertSame('create_table_stmt', $plan->startRule());
235 self::assertTrue($plan->patternAt('create_table_stmt', 0)?->matches(['table_element_list']) ?? false);
236 self::assertTrue($plan->patternAt('table_element', 0)?->matches(['table_constraint_def']) ?? false);
237 self::assertTrue(
238 $plan->patternAt('table_constraint_def', 0)?->matches(['FOREIGN', 'KEY_SYM', 'references']) ?? false,
239 );
240 self::assertTrue($plan->patternAt('opt_ref_list', 0)?->matches(['reference']) ?? false);
241 self::assertTrue(
242 $plan->patternAt('opt_on_update_delete', 0)?->matches(['UPDATE_SYM', 'DELETE_SYM']) ?? false,
243 );
244 self::assertTrue($plan->patternAt('delete_option', 0)?->matches(['CASCADE']) ?? false);
245 self::assertTrue($plan->patternAt('delete_option', 1)?->matches(['CASCADE']) ?? false);
246 }
247
248 public function testPartitionSelectStatementRequiresThePartitionClause(): void
249 {
250 $plan = GenerationPlans::partitionSelectStatement();
251
252 self::assertSame('select_stmt', $plan->startRule());
253 self::assertTrue(
254 $plan->patternAt('query_expression', 0)?->matches([
255 'query_expression_body',
256 'opt_order_clause',
257 'opt_limit_clause',
258 ]) ?? false,
259 );
260 self::assertTrue($plan->patternAt('query_expression_body', 0)?->matches(['query_primary']) ?? false);
261 self::assertTrue($plan->patternAt('query_primary', 0)?->matches(['query_specification']) ?? false);
262 self::assertTrue($plan->patternAt('opt_from_clause', 0)?->matches(['from_tables']) ?? false);
263 self::assertTrue($plan->patternAt('from_tables', 0)?->matches(['table_reference_list']) ?? false);
264 self::assertTrue($plan->patternAt('table_factor', 0)?->matches(['single_table']) ?? false);
265 self::assertTrue($plan->patternAt('opt_use_partition', 0)?->matches(['PARTITION_SYM']) ?? false);
266 }
267
268 public function testLoadDataStatementStartsFromTheLoadGrammar(): void
269 {
270 $plan = GenerationPlans::loadDataStatement();
271
272 self::assertSame('load_stmt', $plan->startRule());
273 }
274
275 public function testFullTextSearchStatementRequiresMatchAgainstGrammar(): void
276 {
277 $plan = GenerationPlans::fullTextSearchStatement();
278
279 self::assertSame('select_stmt', $plan->startRule());
280 self::assertTrue($plan->patternAt('query_primary', 0)?->matches(['query_specification']) ?? false);
281 self::assertTrue($plan->patternAt('select_item_list', 0)?->matches(['*']) ?? false);
282 self::assertTrue($plan->patternAt('opt_from_clause', 0)?->matches(['from_tables']) ?? false);
283 self::assertTrue($plan->patternAt('from_tables', 0)?->matches(['table_reference_list']) ?? false);
284 self::assertTrue($plan->patternAt('opt_where_clause', 0)?->matches(['expr']) ?? false);
285 self::assertTrue($plan->patternAt('simple_expr', 0)?->matches(['MATCH', 'AGAINST']) ?? false);
286 }
287
288 public function testQuotedIdentifierPlansThatLexeme(): void
289 {
290 $plan = GenerationPlans::quotedIdentifier(1, 2);
291
292 self::assertSame('quoted_identifier', $plan->lexicalTarget());
293 self::assertSame(['minLength' => 1, 'maxLength' => 2], $plan->parameters());
294 }
295
296 public function testStringLiteralPlansThatLexeme(): void
297 {
298 $plan = GenerationPlans::stringLiteral(1, 2);
299
300 self::assertSame('string_literal', $plan->lexicalTarget());
301 self::assertSame(['minLength' => 1, 'maxLength' => 2], $plan->parameters());
302 }
303
304 public function testNationalStringLiteralPlansThatLexeme(): void
305 {
306 $plan = GenerationPlans::nationalStringLiteral(1, 2);
307
308 self::assertSame('national_string_literal', $plan->lexicalTarget());
309 self::assertSame(['minLength' => 1, 'maxLength' => 2], $plan->parameters());
310 }
311
312 public function testDollarQuotedStringPlansThatLexeme(): void
313 {
314 $plan = GenerationPlans::dollarQuotedString(1, 2);
315
316 self::assertSame('dollar_quoted_string', $plan->lexicalTarget());
317 self::assertSame(['minLength' => 1, 'maxLength' => 2], $plan->parameters());
318 }
319
320 public function testIntegerLiteralPlansThatLexeme(): void
321 {
322 $plan = GenerationPlans::integerLiteral(1, 2);
323
324 self::assertSame('integer_literal', $plan->lexicalTarget());
325 self::assertSame(['min' => 1, 'max' => 2], $plan->parameters());
326 }
327
328 public function testLongIntegerLiteralPlansThatLexeme(): void
329 {
330 $plan = GenerationPlans::longIntegerLiteral(1, 2);
331
332 self::assertSame('long_integer_literal', $plan->lexicalTarget());
333 self::assertSame(['min' => 1, 'max' => 2], $plan->parameters());
334 }
335
336 public function testUnsignedBigIntLiteralPlansThatLexeme(): void
337 {
338 $plan = GenerationPlans::unsignedBigIntLiteral(1, 2);
339
340 self::assertSame('unsigned_big_int_literal', $plan->lexicalTarget());
341 self::assertSame(['minLength' => 1, 'maxLength' => 2], $plan->parameters());
342 }
343
344 public function testDecimalLiteralPlansThatLexeme(): void
345 {
346 $plan = GenerationPlans::decimalLiteral(1, 2);
347
348 self::assertSame('decimal_literal', $plan->lexicalTarget());
349 self::assertSame(['precision' => 1, 'scale' => 2], $plan->parameters());
350 }
351
352 public function testFloatLiteralPlansThatLexeme(): void
353 {
354 $plan = GenerationPlans::floatLiteral(1, 2, 3, 4);
355
356 self::assertSame('float_literal', $plan->lexicalTarget());
357 self::assertSame(['precision' => 1, 'scale' => 2, 'minExponent' => 3, 'maxExponent' => 4], $plan->parameters());
358 }
359
360 public function testHexLiteralPlansThatLexeme(): void
361 {
362 $plan = GenerationPlans::hexLiteral(1, 2);
363
364 self::assertSame('hex_literal', $plan->lexicalTarget());
365 self::assertSame(['minLength' => 1, 'maxLength' => 2], $plan->parameters());
366 }
367
368 public function testQuotedHexLiteralPlansThatLexeme(): void
369 {
370 $plan = GenerationPlans::quotedHexLiteral(1, 2);
371
372 self::assertSame('quoted_hex_literal', $plan->lexicalTarget());
373 self::assertSame(['minBytes' => 1, 'maxBytes' => 2], $plan->parameters());
374 }
375
376 public function testBinaryLiteralPlansThatLexeme(): void
377 {
378 $plan = GenerationPlans::binaryLiteral(1, 2);
379
380 self::assertSame('binary_literal', $plan->lexicalTarget());
381 self::assertSame(['minLength' => 1, 'maxLength' => 2], $plan->parameters());
382 }
383
384 public function testHostnamePlansThatLexeme(): void
385 {
386 $plan = GenerationPlans::hostname(1, 2, 3);
387
388 self::assertSame('hostname', $plan->lexicalTarget());
389 self::assertSame(['minParts' => 1, 'maxParts' => 2, 'maxPartLength' => 3], $plan->parameters());
390 }
391}
392