packages/sql-faker/tests/Unit/MySql/MySqlProviderTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\MySql;
6
7use Faker\Factory;
8use Override;
9use PHPUnit\Framework\Attributes\CoversClass;
10use PHPUnit\Framework\Attributes\DataProvider;
11use PHPUnit\Framework\Attributes\Medium;
12use PHPUnit\Framework\Attributes\UsesClass;
13use PHPUnit\Framework\TestCase;
14use SqlFaker\Generation\Derivation\TerminationAnalyzer;
15use SqlFaker\Generation\Plan\GenerationPlan;
16use SqlFaker\Generation\Plan\LexemeConstraint;
17use SqlFaker\Generation\Plan\ProductionPattern;
18use SqlFaker\Generation\Plan\RulePlan;
19use SqlFaker\Generation\SqlGenerator;
20use SqlFaker\Grammar\Model\Grammar;
21use SqlFaker\Grammar\Model\NonTerminal;
22use SqlFaker\Grammar\Model\Production;
23use SqlFaker\Grammar\Model\ProductionRule;
24use SqlFaker\Grammar\Model\Terminal;
25use SqlFaker\Grammar\Model\TerminalInventory;
26use SqlFaker\Grammar\Resource\SqlVersion;
27use SqlFaker\MySql\Generation\GenerationPlans;
28use SqlFaker\MySql\Generation\LexicalGrammar;
29use SqlFaker\MySql\Generation\StatementRule;
30use SqlFaker\MySql\Generation\Value\LiteralGenerator;
31use SqlFaker\MySql\MySqlProvider;
32
33#[CoversClass(MySqlProvider::class)]
34#[CoversClass(LiteralGenerator::class)]
35#[CoversClass(SqlGenerator::class)]
36#[CoversClass(Grammar::class)]
37#[CoversClass(NonTerminal::class)]
38#[CoversClass(Production::class)]
39#[CoversClass(ProductionRule::class)]
40#[CoversClass(Terminal::class)]
41#[CoversClass(TerminationAnalyzer::class)]
42#[CoversClass(StatementRule::class)]
43#[CoversClass(LexicalGrammar::class)]
44#[UsesClass(GenerationPlan::class)]
45#[UsesClass(GenerationPlans::class)]
46#[UsesClass(ProductionPattern::class)]
47#[UsesClass(SqlVersion::class)]
48#[UsesClass(TerminalInventory::class)]
49#[Medium]
50#[UsesClass(\SqlFaker\MySql\Generation\SqlGeneratorFactory::class)]
51#[UsesClass(\SqlFaker\Generation\Derivation\CompletionCosts::class)]
52#[UsesClass(\SqlFaker\Generation\Derivation\Derivation::class)]
53#[UsesClass(\SqlFaker\Generation\Derivation\DerivationTrace::class)]
54#[UsesClass(\SqlFaker\Generation\Derivation\TerminationCost::class)]
55#[UsesClass(\SqlFaker\Generation\Exception\GenerationException::class)]
56#[UsesClass(\SqlFaker\Generation\Candidate\ChoiceLexemeGenerator::class)]
57#[UsesClass(\SqlFaker\Generation\Candidate\FixedLexemeGenerator::class)]
58#[UsesClass(\SqlFaker\Generation\Candidate\IntegerLexemeGenerator::class)]
59#[UsesClass(\SqlFaker\Generation\Lexeme\Lexeme::class)]
60#[UsesClass(\SqlFaker\Generation\Lexeme\LexemeCandidates::class)]
61#[UsesClass(\SqlFaker\Generation\Lexeme\LexemeInput::class)]
62#[UsesClass(\SqlFaker\Generation\Lexeme\LexemeSequence::class)]
63#[UsesClass(\SqlFaker\Generation\Candidate\MatchingLexemeGenerator::class)]
64#[UsesClass(\SqlFaker\Generation\Candidate\ValueLexemeGenerator::class)]
65#[UsesClass(\SqlFaker\Generation\Candidate\RegisteredLexemeGenerator::class)]
66#[UsesClass(\SqlFaker\Generation\Candidate\SequenceLexemeGenerator::class)]
67#[UsesClass(\SqlFaker\Generation\Output\CandidateResolver::class)]
68#[UsesClass(\SqlFaker\Generation\Lexeme\OutputPart::class)]
69#[UsesClass(\SqlFaker\Generation\Lexeme\ResolvedOutput::class)]
70#[UsesClass(\SqlFaker\Generation\Output\ReverseLexemeGenerator::class)]
71#[UsesClass(\SqlFaker\Generation\Output\SqlSerializer::class)]
72#[UsesClass(\SqlFaker\Generation\Output\CombinedSpacingRule::class)]
73#[UsesClass(\SqlFaker\MySql\Generation\Spacing\KeywordPhraseSpacingRule::class)]
74#[UsesClass(\SqlFaker\Generation\Lexeme\LexemeBoundary::class)]
75#[UsesClass(\SqlFaker\Generation\Lexeme\SpacingConstraint::class)]
76#[UsesClass(\SqlFaker\Generation\Token\ProductionOccurrence::class)]
77#[UsesClass(\SqlFaker\Generation\Token\TerminalMappingRule::class)]
78#[UsesClass(\SqlFaker\Generation\Token\TerminalOccurrence::class)]
79#[UsesClass(\SqlFaker\Generation\Token\TerminalSequence::class)]
80#[UsesClass(\SqlFaker\Generation\Derivation\TokenGenerator::class)]
81#[UsesClass(\SqlFaker\Generation\Token\TokenRewriter::class)]
82#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Option\UniqueOptionRule::class)]
83#[UsesClass(\SqlFaker\Generation\Candidate\VersionCase::class)]
84#[UsesClass(\SqlFaker\Generation\Candidate\VersionedLexemeGenerator::class)]
85#[UsesClass(\SqlFaker\Generation\Exception\LexicalException::class)]
86#[UsesClass(\SqlFaker\MySql\Generation\Tokenization\KeywordIndex::class)]
87#[UsesClass(\SqlFaker\Generation\Value\RandomCharacters::class)]
88#[UsesClass(\SqlFaker\Grammar\Resource\SqlVersionRegistry::class)]
89#[UsesClass(\SqlFaker\MySql\Generation\GenerationContext::class)]
90#[UsesClass(\SqlFaker\MySql\Generation\Lexeme\DefinitionFactory::class)]
91#[UsesClass(\SqlFaker\MySql\Generation\Lexeme\KeywordLexemeGenerator::class)]
92#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\AlterDatabaseRule::class)]
93#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\ConstraintEnforcementRule::class)]
94#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\FlushExportRule::class)]
95#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\InstanceActionRule::class)]
96#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\IntegerContextRule::class)]
97#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\LoadSourceCountRule::class)]
98#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\RequiredAliasRule::class)]
99#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\RewriteDefinitions::class)]
100#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\RoleGrantRule::class)]
101#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\SetNamesRule::class)]
102#[UsesClass(\SqlFaker\MySql\Generation\Spacing\CloneAddressSpacingRule::class)]
103#[UsesClass(\SqlFaker\MySql\Generation\Spacing\FunctionSpacingRule::class)]
104#[UsesClass(\SqlFaker\MySql\Generation\Spacing\QualifiedNameSpacingRule::class)]
105#[UsesClass(\SqlFaker\MySql\Generation\Spacing\VariableSpacingRule::class)]
106#[UsesClass(\SqlFaker\MySql\Grammar\MySqlGrammar::class)]
107#[UsesClass(\SqlFaker\MySql\Generation\Tokenization\MySqlTokenizer::class)]
108#[UsesClass(\SqlFaker\MySql\Generation\StartRuleResolver::class)]
109#[UsesClass(\SqlFaker\PostgreSql\Generation\GenerationContext::class)]
110#[UsesClass(\SqlFaker\PostgreSql\Generation\Lexeme\DefinitionFactory::class)]
111#[UsesClass(\SqlFaker\PostgreSql\Generation\Lexeme\HashBoundLexemeGenerator::class)]
112#[UsesClass(\SqlFaker\PostgreSql\Generation\Lexeme\KeywordLexemeGenerator::class)]
113#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\CopySourceRule::class)]
114#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\FetchWithTiesRule::class)]
115#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Name\FunctionNameRule::class)]
116#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\HashPartitionBoundRule::class)]
117#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\LimitOffsetRule::class)]
118#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\LookaheadRule::class)]
119#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\OperatorArgumentsRule::class)]
120#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\OverlapsArgumentsRule::class)]
121#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\PublicationObjectRule::class)]
122#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Name\RelationNameRule::class)]
123#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\RewriteDefinitions::class)]
124#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\TimeZoneIntervalRule::class)]
125#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\WindowFrameRule::class)]
126#[UsesClass(\SqlFaker\PostgreSql\Grammar\PgGrammar::class)]
127#[UsesClass(\SqlFaker\PostgreSql\Generation\LexicalGrammar::class)]
128#[UsesClass(\SqlFaker\PostgreSql\Generation\Lookahead\PgLookahead::class)]
129#[UsesClass(\SqlFaker\PostgreSql\Generation\Tokenization\PgTokenizer::class)]
130#[UsesClass(\SqlFaker\Sqlite\Generation\GenerationContext::class)]
131#[UsesClass(\SqlFaker\Sqlite\Generation\Lexeme\DefinitionFactory::class)]
132#[UsesClass(\SqlFaker\Sqlite\Generation\Lexeme\JoinLexemeGenerator::class)]
133#[UsesClass(\SqlFaker\Sqlite\Generation\Lexeme\JoinModifiers::class)]
134#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\IdentifierListRule::class)]
135#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\JoinRule::class)]
136#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\RewriteDefinitions::class)]
137#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\StrictTableRule::class)]
138#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\TableOptionRule::class)]
139#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\WindowFrameRule::class)]
140#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\WithoutRowidRule::class)]
141#[UsesClass(\SqlFaker\Sqlite\Generation\GrammarAdaptation::class)]
142#[UsesClass(\SqlFaker\Sqlite\Grammar\SqliteGrammar::class)]
143#[UsesClass(\SqlFaker\Sqlite\Generation\LexicalGrammar::class)]
144#[UsesClass(\SqlFaker\Sqlite\Generation\Tokenization\SqliteTokenizer::class)]
145#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Expression\ExpressionGroupingRule::class)]
146#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\AlterEventRule::class)]
147#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\SubqueryContextRule::class)]
148#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\ConstraintAttributesRule::class)]
149#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Name\AnyRelationNameRule::class)]
150#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Name\IndirectionStarRule::class)]
151#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\IntoClauseRule::class)]
152#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\GeneratedColumnRule::class)]
153#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\TransactionCompletionRule::class)]
154#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\GeneratedColumnRule::class)]
155#[UsesClass(\SqlFaker\Sqlite\Generation\Lexeme\WindowNameLexemeGenerator::class)]
156#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Alter\OrderByRule::class)]
157#[UsesClass(\SqlFaker\MySql\Generation\Lexeme\BoundedIntegerLexemeGenerator::class)]
158#[UsesClass(\SqlFaker\MySql\Generation\Lexeme\ReplicationTablePatternLexemeGenerator::class)]
159#[UsesClass(\SqlFaker\MySql\Generation\Lexeme\SizeNumberLexemeGenerator::class)]
160#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\CompoundSelectRule::class)]
161#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\UpsertSourceRule::class)]
162#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\FunctionArgumentRule::class)]
163#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\GeneratedColumnRule::class)]
164#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Replication\StartRule::class)]
165#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Query\SelectOptionsRule::class)]
166#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Routine\TableFunctionRule::class)]
167#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Routine\TypeModifierRule::class)]
168#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Column\IdentityOptionRule::class)]
169#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Query\IntoClauseRule::class)]
170#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Partition\FieldListRule::class)]
171#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Replication\TablePatternRule::class)]
172#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Expression\QuantifiedComparisonRule::class)]
173#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Expression\TableValueConstructorRule::class)]
174#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Query\QueryContextRule::class)]
175#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Query\JoinGroupingRule::class)]
176#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Query\LegacyDerivedTableRule::class)]
177#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Routine\WithinGroupRule::class)]
178#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Column\FieldLengthRule::class)]
179#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Routine\ReturnRule::class)]
180#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Routine\BinlogRule::class)]
181#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Name\ColumnNameRule::class)]
182#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Name\SystemVariableRule::class)]
183#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Column\AutoIncrementRule::class)]
184#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Partition\DefinitionRule::class)]
185#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Partition\ListValueRule::class)]
186#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Routine\LanguageRule::class)]
187#[UsesClass(\SqlFaker\MySql\Generation\Lexeme\FactorLexemeGenerator::class)]
188#[UsesClass(\SqlFaker\MySql\Generation\Lexeme\PrecisionLexemeGenerator::class)]
189#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Name\AliasRule::class)]
190#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Partition\ValueArityRule::class)]
191#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Partition\ValueShape::class)]
192#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Routine\SubstringRule::class)]
193#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Routine\RangeFunctionOrdinalityRule::class)]
194#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Query\WindowFrameRule::class)]
195#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Routine\JsonOptionsRule::class)]
196#[UsesClass(\SqlFaker\Generation\Value\CharacterDomain::class)]
197#[UsesClass(\SqlFaker\Generation\Value\ChoiceDomain::class)]
198#[UsesClass(\SqlFaker\Generation\Value\IntegerDomain::class)]
199#[UsesClass(\SqlFaker\Generation\Value\SequenceDomain::class)]
200#[UsesClass(\SqlFaker\MySql\Generation\Value\DollarQuotedDomain::class)]
201#[UsesClass(\SqlFaker\Generation\Output\BoundaryCompletion::class)]
202#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Column\ConstraintCapabilitiesRule::class)]
203#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Column\ForeignKeyActionRule::class)]
204#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Query\SchemaElementsRule::class)]
205#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Query\ParserOptionsRule::class)]
206#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Routine\JsonTablePathRule::class)]
207#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Routine\AggregateArgumentRule::class)]
208#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Expression\ConcatenationRule::class)]
209#[UsesClass(\SqlFaker\Generation\Derivation\CompletionState::class)]
210#[UsesClass(\SqlFaker\Generation\Derivation\CompletionFrontier::class)]
211#[UsesClass(\SqlFaker\Generation\Derivation\ConstrainedCompletion::class)]
212#[UsesClass(\SqlFaker\Generation\Value\ValueChoices::class)]
213#[UsesClass(\SqlFaker\Generation\Derivation\CompletionMemo::class)]
214#[UsesClass(\SqlFaker\Generation\Derivation\CompletionReduction::class)]
215#[UsesClass(\SqlFaker\Generation\Derivation\ConstraintDependencies::class)]
216#[UsesClass(\SqlFaker\Generation\Choice\BytePlanCompiler::class)]
217#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Name\HostNameRule::class)]
218#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Name\ParserNameRule::class)]
219#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Column\NumericContextRule::class)]
220#[UsesClass(\SqlFaker\MySql\Generation\Lexeme\CharsetLexemeGenerator::class)]
221#[UsesClass(\SqlFaker\MySql\Generation\Lexeme\CharsetValueLexemeGenerator::class)]
222#[UsesClass(\SqlFaker\MySql\Generation\Value\IdentifierDomain::class)]
223#[UsesClass(\SqlFaker\MySql\Generation\Value\QuotedDomain::class)]
224#[UsesClass(\SqlFaker\MySql\Generation\Value\RadixDomain::class)]
225#[UsesClass(\SqlFaker\Generation\Value\Utf8::class)]
226#[UsesClass(\SqlFaker\Generation\Value\WordDomain::class)]
227#[UsesClass(\SqlFaker\Generation\Derivation\Completion\PatternProductions::class)]
228#[UsesClass(\SqlFaker\Generation\Derivation\Completion\CompletionWitness::class)]
229#[UsesClass(\SqlFaker\PostgreSql\Generation\Value\OperatorDomain::class)]
230#[UsesClass(\SqlFaker\Generation\Value\RepeatDomain::class)]
231#[UsesClass(\SqlFaker\MySql\Generation\Lexeme\LexicalDefinition::class)]
232#[UsesClass(RulePlan::class)]
233#[UsesClass(LexemeConstraint::class)]
234#[UsesClass(\SqlFaker\Generation\Plan\Compilation\GrammarCompiler::class)]
235#[UsesClass(\SqlFaker\Generation\Plan\Compilation\Scope::class)]
236#[UsesClass(\SqlFaker\Generation\Plan\Compilation\PreparedGrammar::class)]
237#[UsesClass(\SqlFaker\Generation\Plan\Compilation\ScopedGeneration::class)]
238final class MySqlProviderTest extends TestCase
239{
240 #[Override]
241 protected function setUp(): void
242 {
243 parent::setUp();
244 gc_collect_cycles();
245 }
246
247 public function testRegistersItselfWithTheFakerGenerator(): void
248 {
249 $faker = Factory::create();
250 $provider = new MySqlProvider($faker);
251
252 /**
253 * @var list<object> $providers
254 */
255 $providers = $faker->getProviders();
256 self::assertContains($provider, $providers);
257
258 $identifier = $provider->identifier(3);
259 self::assertNotSame('', $identifier);
260 }
261
262 #[DataProvider('providerTargetedGenerationSeed')]
263 public function testInsertFunctionUpsertStatementDerivesConditionalExpressionFromGrammar(int $seed): void
264 {
265 $faker = Factory::create();
266 $provider = new MySqlProvider($faker);
267 $faker->seed($seed);
268 $sql = $provider->insertFunctionUpsertStatement();
269
270 $tokens = (new LexicalGrammar($faker, 'mysql-8.4.7'))
271 ->tokenize($sql);
272 $valueTokens = array_intersect($tokens, ['VALUE_SYM', 'VALUES']);
273 $values = array_key_first($valueTokens);
274 $update = array_search('UPDATE_SYM', $tokens, true);
275 $conditional = array_search('IF', $tokens, true);
276
277 $faker->seed($seed);
278 self::assertSame($sql, $provider->insertFunctionUpsertStatement(40));
279 self::assertIsInt($values);
280 self::assertIsInt($update);
281 self::assertIsInt($conditional);
282 self::assertLessThan($update, $values);
283 self::assertGreaterThan($update, $conditional);
284 self::assertContains('DUPLICATE_SYM', $tokens);
285 }
286
287 #[DataProvider('providerTargetedGenerationSeed')]
288 public function testFullTextSearchStatementDerivesMatchExpressionFromGrammar(int $seed): void
289 {
290 $faker = Factory::create();
291 $faker->seed($seed);
292 $provider = new MySqlProvider($faker);
293 $sql = $provider->fullTextSearchStatement();
294 $faker->seed($seed);
295 $tokens = (new LexicalGrammar($faker, 'mysql-8.4.7'))
296 ->tokenize($sql);
297 $select = array_search('SELECT_SYM', $tokens, true);
298 $from = array_search('FROM', $tokens, true);
299 $where = array_search('WHERE', $tokens, true);
300 $match = array_search('MATCH', $tokens, true);
301
302 self::assertSame($sql, $provider->fullTextSearchStatement(40));
303 self::assertIsInt($select);
304 self::assertIsInt($from);
305 self::assertNotContains('DUAL_SYM', $tokens);
306 self::assertIsInt($where);
307 self::assertIsInt($match);
308 self::assertGreaterThan($select, $from);
309 self::assertGreaterThan($from, $match);
310 self::assertContains('AGAINST', $tokens);
311 }
312
313 #[DataProvider('providerTargetedGenerationSeed')]
314 public function testTemporaryTableStatement(int $seed): void
315 {
316 $faker = Factory::create();
317 $faker->seed($seed);
318 $provider = new MySqlProvider($faker);
319 $sql = $provider->temporaryTableStatement();
320 $faker->seed($seed);
321
322 $tokens = (new LexicalGrammar($faker, 'mysql-8.4.7'))
323 ->tokenize($sql);
324
325 self::assertSame($sql, $provider->temporaryTableStatement(40));
326 self::assertSame('CREATE', $tokens[0]);
327 self::assertContains('TEMPORARY', $tokens);
328 self::assertContains('TABLE_SYM', $tokens);
329 }
330
331 #[DataProvider('providerTargetedGenerationSeed')]
332 public function testViewStatement(int $seed): void
333 {
334 $faker = Factory::create();
335 $faker->seed($seed);
336 $provider = new MySqlProvider($faker);
337 $sql = $provider->viewStatement();
338 $faker->seed($seed);
339 $tokens = (new LexicalGrammar($faker, 'mysql-8.4.7'))
340 ->tokenize($sql);
341
342 self::assertSame($sql, $provider->viewStatement(40));
343 self::assertSame('CREATE', $tokens[0]);
344 self::assertContains('VIEW_SYM', $tokens);
345 self::assertContains('SELECT_SYM', $tokens);
346 }
347
348 #[DataProvider('providerTargetedGenerationSeed')]
349 public function testGeneratedColumnStatement(int $seed): void
350 {
351 $faker = Factory::create();
352 $faker->seed($seed);
353 $provider = new MySqlProvider($faker);
354 $sql = $provider->generatedColumnStatement();
355 $faker->seed($seed);
356 $tokens = (new LexicalGrammar($faker, 'mysql-8.4.7'))
357 ->tokenize($sql);
358
359 self::assertSame($sql, $provider->generatedColumnStatement(40));
360 self::assertContains('GENERATED', $tokens);
361 self::assertContains('ALWAYS_SYM', $tokens);
362 self::assertContains('STORED_SYM', $tokens);
363 }
364
365 #[DataProvider('providerTargetedGenerationSeed')]
366 public function testForeignKeyCascadeStatement(int $seed): void
367 {
368 $faker = Factory::create();
369 $faker->seed($seed);
370 $provider = new MySqlProvider($faker);
371 $sql = $provider->foreignKeyCascadeStatement();
372 $faker->seed($seed);
373 $tokens = (new LexicalGrammar($faker, 'mysql-8.4.7'))
374 ->tokenize($sql);
375
376 self::assertSame($sql, $provider->foreignKeyCascadeStatement(40));
377 self::assertContains('FOREIGN', $tokens);
378 self::assertContains('REFERENCES', $tokens);
379 self::assertStringContainsString('ON_SYM UPDATE_SYM CASCADE', implode(' ', $tokens));
380 self::assertStringContainsString('ON_SYM DELETE_SYM CASCADE', implode(' ', $tokens));
381 }
382
383 #[DataProvider('providerTargetedGenerationSeed')]
384 public function testPartitionSelectStatement(int $seed): void
385 {
386 $faker = Factory::create();
387 $faker->seed($seed);
388 $provider = new MySqlProvider($faker);
389 $sql = $provider->partitionSelectStatement();
390 $faker->seed($seed);
391 $tokens = (new LexicalGrammar($faker, 'mysql-8.4.7'))
392 ->tokenize($sql);
393 $select = array_search('SELECT_SYM', $tokens, true);
394 $from = array_search('FROM', $tokens, true);
395 $partition = array_search('PARTITION_SYM', $tokens, true);
396
397 self::assertSame($sql, $provider->partitionSelectStatement(40));
398 self::assertIsInt($select);
399 self::assertIsInt($from);
400 self::assertIsInt($partition);
401 self::assertGreaterThan($select, $from);
402 self::assertGreaterThan($from, $partition);
403 }
404
405 public function testSql(): void
406 {
407 $faker = Factory::create();
408 $faker->seed(12345);
409 $provider = new MySqlProvider($faker);
410
411 $result = $provider->sql(maxDepth: 10);
412
413 self::assertNotSame('', $result);
414 }
415
416 public function testSqlWithStatementRule(): void
417 {
418 $faker = Factory::create();
419 $faker->seed(12345);
420 $provider = new MySqlProvider($faker);
421
422 $result = $provider->sql(StatementRule::Select, maxDepth: 3);
423
424 self::assertMatchesRegularExpression('/\bSELECT\b/i', $result);
425 }
426
427 public function testSqlWithNullStatementRuleUsesDefault(): void
428 {
429 $faker = Factory::create();
430 $faker->seed(12345);
431 $provider = new MySqlProvider($faker);
432
433 $result = $provider->sql(null, maxDepth: 3);
434
435 self::assertNotSame('', $result);
436 }
437
438 public function testSqlWithMaxDepth(): void
439 {
440 $faker = Factory::create();
441 $faker->seed(12345);
442 $provider = new MySqlProvider($faker);
443
444 $result = $provider->sql(maxDepth: 5);
445
446 self::assertNotSame('', $result);
447 }
448
449 #[DataProvider('providerSupportedMySqlVersion')]
450 public function testSqlWithoutEmptyRowsUsesTheRestrictedGenerationPlan(string $version): void
451 {
452 $faker = Factory::create();
453 $faker->seed(12345);
454 $provider = new MySqlProvider($faker, $version);
455
456 $result = $provider->sqlWithoutEmptyRows(StatementRule::Insert, maxDepth: 10);
457
458 self::assertMatchesRegularExpression('/\bINSERT\b/i', $result);
459 self::assertDoesNotMatchRegularExpression('/\bVALUES?\s*(?:ROW\s*)?\(\s*\)/i', $result);
460 }
461
462 public function testSqlWithoutEmptyRowsCanGenerateFromTheWholeGrammar(): void
463 {
464 $faker = Factory::create();
465 $faker->seed(12345);
466 $provider = new MySqlProvider($faker);
467
468 $result = $provider->sqlWithoutEmptyRows(maxDepth: 3);
469
470 self::assertNotSame('', $result);
471 }
472
473 public function testLoadDataStatementUsesOfficialGrammarRule(): void
474 {
475 $faker = Factory::create();
476 $faker->seed(164);
477 $provider = new MySqlProvider($faker);
478
479 $result = $provider->loadDataStatement(maxDepth: 8);
480
481 self::assertMatchesRegularExpression('/\bLOAD\b.*\bDATA\b/is', $result);
482 self::assertMatchesRegularExpression('/\bINFILE\b/i', $result);
483 self::assertMatchesRegularExpression('/\bINTO\b/i', $result);
484 }
485
486 public function testSelectStatement(): void
487 {
488 $faker = Factory::create();
489 $faker->seed(12345);
490 $provider = new MySqlProvider($faker);
491
492 $result = $provider->selectStatement(maxDepth: 3);
493
494 self::assertMatchesRegularExpression('/\bSELECT\b/i', $result);
495 }
496
497 public function testInsertStatement(): void
498 {
499 $faker = Factory::create();
500 $faker->seed(12345);
501 $provider = new MySqlProvider($faker);
502
503 $result = $provider->insertStatement(maxDepth: 3);
504
505 self::assertMatchesRegularExpression('/\bINSERT\b/i', $result);
506 }
507
508 public function testUpdateStatement(): void
509 {
510 $faker = Factory::create();
511 $faker->seed(12345);
512 $provider = new MySqlProvider($faker);
513
514 $result = $provider->updateStatement(maxDepth: 3);
515
516 self::assertMatchesRegularExpression('/\bUPDATE\b/i', $result);
517 }
518
519 public function testDeleteStatement(): void
520 {
521 $faker = Factory::create();
522 $faker->seed(12345);
523 $provider = new MySqlProvider($faker);
524
525 $result = $provider->deleteStatement(maxDepth: 3);
526
527 self::assertMatchesRegularExpression('/\bDELETE\b/i', $result);
528 }
529
530 #[DataProvider('providerMultiTableGenerationSeed')]
531 public function testMultiTableUpdateStatement(int $seed): void
532 {
533 $faker = Factory::create();
534 $faker->seed($seed);
535 $provider = new MySqlProvider($faker);
536
537 $tokens = (new LexicalGrammar($faker, 'mysql-8.4.7'))
538 ->tokenize($provider->multiTableUpdateStatement(maxDepth: 20));
539
540 self::assertSame('UPDATE_SYM', $tokens[0]);
541 $set = array_search('SET_SYM', $tokens, true);
542 self::assertIsInt($set);
543 self::assertSame(1, count(array_filter(
544 array_slice($tokens, 0, $set),
545 static fn (string $token): bool => $token === ',',
546 )));
547 self::assertContains('SET_SYM', $tokens);
548 }
549
550 #[DataProvider('providerMultiTableGenerationSeed')]
551 public function testMultiTableDeleteStatement(int $seed): void
552 {
553 $faker = Factory::create();
554 $faker->seed($seed);
555 $provider = new MySqlProvider($faker);
556
557 $tokens = (new LexicalGrammar($faker, 'mysql-8.4.7'))
558 ->tokenize($provider->multiTableDeleteStatement(maxDepth: 20));
559
560 self::assertSame('DELETE_SYM', $tokens[0]);
561 self::assertContains('FROM', $tokens);
562 self::assertGreaterThanOrEqual(2, count(array_filter(
563 $tokens,
564 static fn (string $token): bool => in_array($token, ['IDENT', 'IDENT_QUOTED'], true),
565 )));
566 }
567
568 public function testCreateTableStatement(): void
569 {
570 $faker = Factory::create();
571 $faker->seed(12345);
572 $provider = new MySqlProvider($faker);
573
574 $result = $provider->createTableStatement(maxDepth: 3);
575
576 self::assertMatchesRegularExpression('/\bCREATE\b/i', $result);
577 self::assertMatchesRegularExpression('/\bTABLE\b/i', $result);
578 }
579
580 public function testAlterTableStatement(): void
581 {
582 $faker = Factory::create();
583 $faker->seed(12345);
584 $provider = new MySqlProvider($faker);
585
586 $result = $provider->alterTableStatement(maxDepth: 3);
587
588 self::assertMatchesRegularExpression('/\bALTER\b/i', $result);
589 self::assertMatchesRegularExpression('/\bTABLE\b/i', $result);
590 }
591
592 public function testDropTableStatement(): void
593 {
594 $faker = Factory::create();
595 $faker->seed(12345);
596 $provider = new MySqlProvider($faker);
597
598 $result = $provider->dropTableStatement(maxDepth: 3);
599
600 self::assertMatchesRegularExpression('/\bDROP\b/i', $result);
601 self::assertMatchesRegularExpression('/\bTABLE\b/i', $result);
602 }
603
604 public function testSimpleStatement(): void
605 {
606 $faker = Factory::create();
607 $faker->seed(12345);
608 $provider = new MySqlProvider($faker);
609
610 $result = $provider->simpleStatement(maxDepth: 3);
611
612 self::assertNotSame('', $result);
613 }
614
615 public function testReplaceStatement(): void
616 {
617 $faker = Factory::create();
618 $faker->seed(12345);
619 $provider = new MySqlProvider($faker);
620
621 $result = $provider->replaceStatement(maxDepth: 3);
622
623 self::assertMatchesRegularExpression('/\bREPLACE\b/i', $result);
624 }
625
626 public function testTruncateStatement(): void
627 {
628 $faker = Factory::create();
629 $faker->seed(12345);
630 $provider = new MySqlProvider($faker);
631
632 $result = $provider->truncateStatement(maxDepth: 3);
633
634 self::assertMatchesRegularExpression('/\bTRUNCATE\b/i', $result);
635 }
636
637 public function testCreateIndexStatement(): void
638 {
639 $faker = Factory::create();
640 $faker->seed(12345);
641 $provider = new MySqlProvider($faker);
642
643 $result = $provider->createIndexStatement(maxDepth: 3);
644
645 self::assertMatchesRegularExpression('/\bCREATE\b/i', $result);
646 self::assertMatchesRegularExpression('/\bINDEX\b/i', $result);
647 }
648
649 public function testDropIndexStatement(): void
650 {
651 $faker = Factory::create();
652 $faker->seed(12345);
653 $provider = new MySqlProvider($faker);
654
655 $result = $provider->dropIndexStatement(maxDepth: 3);
656
657 self::assertMatchesRegularExpression('/\bDROP\b/i', $result);
658 self::assertMatchesRegularExpression('/\bINDEX\b/i', $result);
659 }
660
661 public function testBeginStatement(): void
662 {
663 $faker = Factory::create();
664 $faker->seed(12345);
665 $provider = new MySqlProvider($faker);
666
667 $result = $provider->beginStatement(maxDepth: 3);
668
669 self::assertMatchesRegularExpression('/\bBEGIN\b/i', $result);
670 }
671
672 public function testCommitStatement(): void
673 {
674 $faker = Factory::create();
675 $faker->seed(12345);
676 $provider = new MySqlProvider($faker);
677
678 $result = $provider->commitStatement(maxDepth: 3);
679
680 self::assertMatchesRegularExpression('/\bCOMMIT\b/i', $result);
681 }
682
683 public function testRollbackStatement(): void
684 {
685 $faker = Factory::create();
686 $faker->seed(12345);
687 $provider = new MySqlProvider($faker);
688
689 $result = $provider->rollbackStatement(maxDepth: 3);
690
691 self::assertMatchesRegularExpression('/\bROLLBACK\b/i', $result);
692 }
693
694 public function testIdentifier(): void
695 {
696 $faker = Factory::create();
697 $faker->seed(12345);
698 $provider = new MySqlProvider($faker);
699
700 $result = $provider->identifier(3);
701
702 self::assertNotSame('', $result);
703 }
704
705 public function testQuotedIdentifier(): void
706 {
707 $faker = Factory::create();
708 $faker->seed(12345);
709 $provider = new MySqlProvider($faker);
710
711 $result = $provider->quotedIdentifier();
712
713 self::assertMatchesRegularExpression('/^`[a-z_][a-z0-9_]*`$/', $result);
714 }
715
716 public function testStringLiteral(): void
717 {
718 $faker = Factory::create();
719 $faker->seed(12345);
720 $provider = new MySqlProvider($faker);
721
722 $result = $provider->stringLiteral();
723
724 self::assertMatchesRegularExpression("/^'[a-zA-Z0-9_]{1,255}'$/", $result);
725 }
726
727 public function testStringLiteralLengthRange(): void
728 {
729 $faker = Factory::create();
730 $faker->seed(12345);
731 $provider = new MySqlProvider($faker);
732
733 $literal = $provider->stringLiteral();
734 $content = substr($literal, 1, -1);
735
736 self::assertGreaterThanOrEqual(1, strlen($content));
737 self::assertLessThanOrEqual(255, strlen($content));
738 }
739
740 public function testNationalStringLiteral(): void
741 {
742 $faker = Factory::create();
743 $faker->seed(12345);
744 $provider = new MySqlProvider($faker);
745
746 $result = $provider->nationalStringLiteral();
747
748 self::assertMatchesRegularExpression("/^N'[a-zA-Z0-9_]{1,255}'$/", $result);
749 }
750
751 public function testDollarQuotedString(): void
752 {
753 $faker = Factory::create();
754 $faker->seed(12345);
755 $provider = new MySqlProvider($faker);
756
757 $result = $provider->dollarQuotedString();
758
759 self::assertMatchesRegularExpression('/^\$\$[a-zA-Z0-9_]{1,255}\$\$$/', $result);
760 }
761
762 public function testIntegerLiteral(): void
763 {
764 $faker = Factory::create();
765 $faker->seed(12345);
766 $provider = new MySqlProvider($faker);
767
768 $result = $provider->integerLiteral();
769
770 self::assertMatchesRegularExpression('/^[1-9]\d*$/', $result);
771 }
772
773 public function testLongIntegerLiteral(): void
774 {
775 $faker = Factory::create();
776 $faker->seed(12345);
777 $provider = new MySqlProvider($faker);
778
779 $result = $provider->longIntegerLiteral();
780
781 self::assertMatchesRegularExpression('/^\d+$/', $result);
782 self::assertGreaterThanOrEqual(0, (int) $result);
783 self::assertLessThanOrEqual(2147483647, (int) $result);
784 }
785
786 public function testUnsignedBigIntLiteral(): void
787 {
788 $faker = Factory::create();
789 $faker->seed(12345);
790 $provider = new MySqlProvider($faker);
791
792 $result = $provider->unsignedBigIntLiteral();
793
794 self::assertMatchesRegularExpression('/^\d+$/', $result);
795 }
796
797 public function testDecimalLiteral(): void
798 {
799 $faker = Factory::create();
800 $faker->seed(12345);
801 $provider = new MySqlProvider($faker);
802
803 $result = $provider->decimalLiteral();
804
805 self::assertMatchesRegularExpression('/^\d+\.\d{2,}$/', $result);
806 }
807
808 public function testFloatLiteral(): void
809 {
810 $faker = Factory::create();
811 $faker->seed(12345);
812 $provider = new MySqlProvider($faker);
813
814 $result = $provider->floatLiteral();
815
816 self::assertMatchesRegularExpression('/^\d+\.\d+e-?\d+$/', $result);
817 }
818
819 public function testHexLiteral(): void
820 {
821 $faker = Factory::create();
822 $faker->seed(12345);
823 $provider = new MySqlProvider($faker);
824
825 $result = $provider->hexLiteral();
826
827 self::assertMatchesRegularExpression('/^0x[0-9a-f]{1,16}$/', $result);
828 }
829
830 public function testQuotedHexLiteral(): void
831 {
832 $faker = Factory::create();
833 $faker->seed(12345);
834 $provider = new MySqlProvider($faker);
835
836 $result = $provider->quotedHexLiteral(4, 4);
837
838 self::assertMatchesRegularExpression("/^X'[0-9a-f]{8}'$/", $result);
839 }
840
841 public function testQuotedHexLiteralDefaultsToOneThroughEightBytes(): void
842 {
843 $faker = Factory::create();
844 $faker->seed(5);
845 $provider = new MySqlProvider($faker);
846
847 $result = $provider->quotedHexLiteral();
848 $faker->seed(5);
849
850 self::assertSame($provider->quotedHexLiteral(1, 8), $result);
851 self::assertMatchesRegularExpression("/^X'[0-9a-f]{2,16}'$/", $result);
852 self::assertSame(0, (strlen($result) - 3) % 2);
853 }
854
855 #[DataProvider('providerMySqlVersion')]
856 public function testForeignKeyConstraint(string $version): void
857 {
858 $faker = Factory::create();
859 $faker->seed(12345);
860 $provider = new MySqlProvider($faker, $version);
861
862 $result = $provider->foreignKeyConstraint(1);
863
864 self::assertSame(
865 ['CONSTRAINT', 'IDENT', 'FOREIGN', 'KEY_SYM', '(', 'IDENT', ')', 'REFERENCES', 'IDENT', '(', 'IDENT', ')'],
866 (new LexicalGrammar($faker, $version))->tokenize($result),
867 );
868 }
869
870 #[DataProvider('providerTargetedGenerationSeed')]
871 public function testUpdateJoinDerivedStatement(int $seed): void
872 {
873 $faker = Factory::create();
874 $faker->seed($seed);
875 $provider = new MySqlProvider($faker);
876 $sql = $provider->updateJoinDerivedStatement();
877 $faker->seed($seed);
878
879 $tokens = (new LexicalGrammar($faker, 'mysql-8.4.7'))->tokenize($sql);
880 $joinTokens = array_intersect($tokens, ['JOIN_SYM', 'STRAIGHT_JOIN']);
881 $join = array_key_first($joinTokens);
882 $select = array_search('SELECT_SYM', $tokens, true);
883
884 self::assertSame($sql, $provider->updateJoinDerivedStatement(40));
885 self::assertSame('UPDATE_SYM', $tokens[0]);
886 self::assertIsInt($join);
887 self::assertIsInt($select);
888 self::assertGreaterThan($join, $select);
889 self::assertContains('FROM', $tokens);
890 self::assertContains('GROUP_SYM', $tokens);
891 self::assertContains('ON_SYM', $tokens);
892 self::assertContains('SET_SYM', $tokens);
893 }
894
895 #[DataProvider('providerTargetedGenerationSeed')]
896 public function testInsertSelectCompoundStatement(int $seed): void
897 {
898 $faker = Factory::create();
899 $faker->seed($seed);
900 $provider = new MySqlProvider($faker);
901 $sql = $provider->insertSelectCompoundStatement();
902 $faker->seed($seed);
903
904 $tokens = (new LexicalGrammar($faker, 'mysql-8.4.7'))->tokenize($sql);
905
906 self::assertSame($sql, $provider->insertSelectCompoundStatement(40));
907 self::assertSame('INSERT_SYM', $tokens[0]);
908 self::assertContains('UNION_SYM', $tokens);
909 self::assertContains('ALL', $tokens);
910 self::assertGreaterThanOrEqual(
911 2,
912 count(array_filter($tokens, static fn (string $token): bool => $token === 'SELECT_SYM')),
913 );
914 }
915
916 #[DataProvider('providerTargetedGenerationSeed')]
917 public function testInsertRowAliasUpsertStatement(int $seed): void
918 {
919 $faker = Factory::create();
920 $faker->seed($seed);
921 $provider = new MySqlProvider($faker);
922 $sql = $provider->insertRowAliasUpsertStatement();
923 $faker->seed($seed);
924
925 $tokens = (new LexicalGrammar($faker, 'mysql-8.4.7'))->tokenize($sql);
926
927 self::assertSame($sql, $provider->insertRowAliasUpsertStatement(40));
928 self::assertSame('INSERT_SYM', $tokens[0]);
929 self::assertContains('VALUES', $tokens);
930 self::assertContains('AS', $tokens);
931 self::assertContains('DUPLICATE_SYM', $tokens);
932 self::assertContains('UPDATE_SYM', $tokens);
933 }
934
935 public function testBinaryLiteral(): void
936 {
937 $faker = Factory::create();
938 $faker->seed(12345);
939 $provider = new MySqlProvider($faker);
940
941 $result = $provider->binaryLiteral();
942
943 self::assertMatchesRegularExpression('/^0b[01]{1,64}$/', $result);
944 }
945
946 public function testHostname(): void
947 {
948 $faker = Factory::create();
949 $faker->seed(12345);
950 $provider = new MySqlProvider($faker);
951
952 $result = $provider->hostname();
953
954 self::assertMatchesRegularExpression('/^[a-z0-9]+(\.[a-z0-9]+)*$/', $result);
955 }
956
957 public function testExpr(): void
958 {
959 $faker = Factory::create();
960 $faker->seed(12345);
961 $provider = new MySqlProvider($faker);
962
963 $result = $provider->expr(maxDepth: 3);
964
965 self::assertNotSame('', $result);
966 }
967
968 public function testSimpleExpr(): void
969 {
970 $faker = Factory::create();
971 $faker->seed(12345);
972 $provider = new MySqlProvider($faker);
973
974 $result = $provider->simpleExpr(maxDepth: 3);
975
976 self::assertNotSame('', $result);
977 }
978
979 public function testLiteral(): void
980 {
981 $faker = Factory::create();
982 $faker->seed(12345);
983 $provider = new MySqlProvider($faker);
984
985 $result = $provider->literal(maxDepth: 3);
986
987 self::assertNotSame('', $result);
988 }
989
990 public function testPredicate(): void
991 {
992 $faker = Factory::create();
993 $faker->seed(12345);
994 $provider = new MySqlProvider($faker);
995
996 $result = $provider->predicate(maxDepth: 3);
997
998 self::assertNotSame('', $result);
999 }
1000
1001 public function testWhereClause(): void
1002 {
1003 $faker = Factory::create();
1004 $faker->seed(12345);
1005 $provider = new MySqlProvider($faker);
1006
1007 $result = $provider->whereClause(maxDepth: 3);
1008
1009 self::assertMatchesRegularExpression('/\bWHERE\b/i', $result);
1010 }
1011
1012 public function testOrderClause(): void
1013 {
1014 $faker = Factory::create();
1015 $faker->seed(12345);
1016 $provider = new MySqlProvider($faker);
1017
1018 $result = $provider->orderClause(maxDepth: 3);
1019
1020 self::assertSame(
1021 ['ORDER_SYM', 'BY'],
1022 array_slice((new LexicalGrammar($faker, 'mysql-8.4.7'))->tokenize($result), 0, 2),
1023 );
1024 }
1025
1026 public function testLimitClause(): void
1027 {
1028 $faker = Factory::create();
1029 $faker->seed(12345);
1030 $provider = new MySqlProvider($faker);
1031
1032 $result = $provider->limitClause(maxDepth: 3);
1033
1034 self::assertMatchesRegularExpression('/\bLIMIT\b/i', $result);
1035 }
1036
1037 public function testTableReference(): void
1038 {
1039 $faker = Factory::create();
1040 $faker->seed(12345);
1041 $provider = new MySqlProvider($faker);
1042
1043 $result = $provider->tableReference(maxDepth: 3);
1044
1045 self::assertNotSame('', $result);
1046 }
1047
1048 public function testJoinedTable(): void
1049 {
1050 $faker = Factory::create();
1051 $faker->seed(12345);
1052 $provider = new MySqlProvider($faker);
1053
1054 $result = $provider->joinedTable(maxDepth: 3);
1055
1056 self::assertMatchesRegularExpression('/\bJOIN\b/i', $result);
1057 }
1058
1059 public function testTableIdent(): void
1060 {
1061 $faker = Factory::create();
1062 $faker->seed(12345);
1063 $provider = new MySqlProvider($faker);
1064
1065 $result = $provider->tableIdent(maxDepth: 3);
1066
1067 self::assertNotSame('', $result);
1068 }
1069
1070 public function testSubquery(): void
1071 {
1072 $faker = Factory::create();
1073 $faker->seed(12345);
1074 $provider = new MySqlProvider($faker);
1075
1076 $result = $provider->subquery(maxDepth: 3);
1077
1078 self::assertStringContainsString('(', $result);
1079 self::assertStringContainsString(')', $result);
1080 }
1081
1082 public function testWithClause(): void
1083 {
1084 $faker = Factory::create();
1085 $faker->seed(12345);
1086 $provider = new MySqlProvider($faker);
1087
1088 $result = $provider->withClause(maxDepth: 3);
1089
1090 self::assertMatchesRegularExpression('/\bWITH\b/i', $result);
1091 }
1092
1093 public function testSeededGenerationIsReproducible(): void
1094 {
1095 $faker1 = Factory::create();
1096 $provider1 = new MySqlProvider($faker1);
1097 $faker1->seed(99999);
1098 $sql1 = $provider1->sql(maxDepth: 6);
1099
1100 $faker2 = Factory::create();
1101 $provider2 = new MySqlProvider($faker2);
1102 $faker2->seed(99999);
1103 $sql2 = $provider2->sql(maxDepth: 6);
1104
1105 self::assertSame($sql1, $sql2, 'Same seed should produce same output');
1106 }
1107
1108 public function testCanBeUsedViaFakerMagicMethod(): void
1109 {
1110 $faker = Factory::create();
1111 $faker->seed(12345);
1112 new MySqlProvider($faker);
1113
1114 $sql = $faker->format('sql', [null, 10]);
1115 self::assertIsString($sql);
1116 self::assertNotSame('', $sql);
1117 }
1118
1119 #[DataProvider('providerStatementRuleValue')]
1120 public function testSqlWithAllStatementRules(StatementRule $type): void
1121 {
1122 $faker = Factory::create();
1123 $faker->seed(12345);
1124 $provider = new MySqlProvider($faker);
1125
1126 $result = $provider->sql($type, maxDepth: 3);
1127
1128 self::assertNotSame('', $result);
1129 }
1130
1131 public function testDefaultMaxDepthIsPhpIntMax(): void
1132 {
1133 $faker = Factory::create();
1134 $faker->seed(12345);
1135 $provider = new MySqlProvider($faker);
1136
1137 $result = $provider->sql(maxDepth: 10);
1138
1139 self::assertNotSame('', $result);
1140 }
1141
1142 public function testSelectStatementMinimalDepthProducesOutput(): void
1143 {
1144 $faker = Factory::create();
1145 $faker->seed(12345);
1146 $provider = new MySqlProvider($faker);
1147
1148 $result = $provider->selectStatement(maxDepth: 1);
1149
1150 self::assertMatchesRegularExpression('/\bSELECT\b/i', $result);
1151 }
1152
1153 #[DataProvider('providerMultipleGenerationSeeds')]
1154 public function testMultipleGenerationsReturnDifferentResults(int $seed1, int $seed2): void
1155 {
1156 $faker1 = Factory::create();
1157 $faker1->seed($seed1);
1158 $provider1 = new MySqlProvider($faker1);
1159
1160 $faker2 = Factory::create();
1161 $faker2->seed($seed2);
1162 $provider2 = new MySqlProvider($faker2);
1163
1164 self::assertNotSame($provider1->selectStatement(maxDepth: 3), $provider2->selectStatement(maxDepth: 3));
1165 }
1166
1167 public function testQuotedIdentifierDefaultMatchesExplicit(): void
1168 {
1169 $faker = Factory::create();
1170 $p = new MySqlProvider($faker);
1171 $faker->seed(42);
1172 $a = $p->quotedIdentifier();
1173 $faker->seed(42);
1174 self::assertSame($a, $p->quotedIdentifier(1, 64));
1175 }
1176
1177 public function testStringLiteralDefaultMatchesExplicit(): void
1178 {
1179 $faker = Factory::create();
1180 $p = new MySqlProvider($faker);
1181 $faker->seed(42);
1182 $a = $p->stringLiteral();
1183 $faker->seed(42);
1184 self::assertSame($a, $p->stringLiteral(1, 255));
1185 }
1186
1187 public function testNationalStringLiteralDefaultMatchesExplicit(): void
1188 {
1189 $faker = Factory::create();
1190 $p = new MySqlProvider($faker);
1191 $faker->seed(42);
1192 $a = $p->nationalStringLiteral();
1193 $faker->seed(42);
1194 self::assertSame($a, $p->nationalStringLiteral(1, 255));
1195 }
1196
1197 public function testDollarQuotedStringDefaultMatchesExplicit(): void
1198 {
1199 $faker = Factory::create();
1200 $p = new MySqlProvider($faker);
1201 $faker->seed(42);
1202 $a = $p->dollarQuotedString();
1203 $faker->seed(42);
1204 self::assertSame($a, $p->dollarQuotedString(1, 255));
1205 }
1206
1207 public function testIntegerLiteralDefaultMatchesExplicit(): void
1208 {
1209 $faker = Factory::create();
1210 $p = new MySqlProvider($faker);
1211 $faker->seed(42);
1212 $a = $p->integerLiteral();
1213 $faker->seed(42);
1214 self::assertSame($a, $p->integerLiteral(1, 2147483647));
1215 }
1216
1217 public function testLongIntegerLiteralDefaultMatchesExplicit(): void
1218 {
1219 $faker = Factory::create();
1220 $p = new MySqlProvider($faker);
1221 $faker->seed(42);
1222 $a = $p->longIntegerLiteral();
1223 $faker->seed(42);
1224 self::assertSame($a, $p->longIntegerLiteral(0, 2147483647));
1225 }
1226
1227 public function testUnsignedBigIntLiteralDefaultMatchesExplicit(): void
1228 {
1229 $faker = Factory::create();
1230 $p = new MySqlProvider($faker);
1231 $faker->seed(42);
1232 $a = $p->unsignedBigIntLiteral();
1233 $faker->seed(42);
1234 self::assertSame($a, $p->unsignedBigIntLiteral(1, 20));
1235 }
1236
1237 public function testDecimalLiteralDefaultMatchesExplicit(): void
1238 {
1239 $faker = Factory::create();
1240 $p = new MySqlProvider($faker);
1241 $faker->seed(42);
1242 $a = $p->decimalLiteral();
1243 $faker->seed(42);
1244 self::assertSame($a, $p->decimalLiteral(10, 2));
1245 }
1246
1247 public function testFloatLiteralDefaultMatchesExplicit(): void
1248 {
1249 $faker = Factory::create();
1250 $p = new MySqlProvider($faker);
1251 $faker->seed(42);
1252 $a = $p->floatLiteral();
1253 $faker->seed(42);
1254 self::assertSame($a, $p->floatLiteral(10, 2, -38, 38));
1255 }
1256
1257 public function testHexLiteralDefaultMatchesExplicit(): void
1258 {
1259 $faker = Factory::create();
1260 $p = new MySqlProvider($faker);
1261 $faker->seed(42);
1262 $a = $p->hexLiteral();
1263 $faker->seed(42);
1264 self::assertSame($a, $p->hexLiteral(1, 16));
1265 }
1266
1267 public function testBinaryLiteralDefaultMatchesExplicit(): void
1268 {
1269 $faker = Factory::create();
1270 $p = new MySqlProvider($faker);
1271 $faker->seed(42);
1272 $a = $p->binaryLiteral();
1273 $faker->seed(42);
1274 self::assertSame($a, $p->binaryLiteral(1, 64));
1275 }
1276
1277 public function testHostnameDefaultMatchesExplicit(): void
1278 {
1279 $faker = Factory::create();
1280 $p = new MySqlProvider($faker);
1281 $faker->seed(42);
1282 $a = $p->hostname();
1283 $faker->seed(42);
1284 self::assertSame($a, $p->hostname(1, 4, 63));
1285 }
1286
1287 public function testQuotedIdentifierCustomLength(): void
1288 {
1289 $faker = Factory::create();
1290 $faker->seed(12345);
1291 $provider = new MySqlProvider($faker);
1292
1293 $result = $provider->quotedIdentifier(5, 10);
1294
1295 self::assertMatchesRegularExpression('/^`[a-z_][a-z0-9_]{4,9}`$/', $result);
1296 }
1297
1298 public function testStringLiteralCustomLength(): void
1299 {
1300 $faker = Factory::create();
1301 $faker->seed(12345);
1302 $provider = new MySqlProvider($faker);
1303
1304 $result = $provider->stringLiteral(3, 8);
1305 $content = substr($result, 1, -1);
1306
1307 self::assertGreaterThanOrEqual(3, strlen($content));
1308 self::assertLessThanOrEqual(8, strlen($content));
1309 }
1310
1311 public function testNationalStringLiteralCustomLength(): void
1312 {
1313 $faker = Factory::create();
1314 $faker->seed(12345);
1315 $provider = new MySqlProvider($faker);
1316
1317 $result = $provider->nationalStringLiteral(2, 5);
1318 $content = substr($result, 2, -1);
1319
1320 self::assertGreaterThanOrEqual(2, strlen($content));
1321 self::assertLessThanOrEqual(5, strlen($content));
1322 }
1323
1324 public function testDollarQuotedStringCustomLength(): void
1325 {
1326 $faker = Factory::create();
1327 $faker->seed(12345);
1328 $provider = new MySqlProvider($faker);
1329
1330 $result = $provider->dollarQuotedString(2, 6);
1331 $content = substr($result, 2, -2);
1332
1333 self::assertGreaterThanOrEqual(2, strlen($content));
1334 self::assertLessThanOrEqual(6, strlen($content));
1335 }
1336
1337 public function testIntegerLiteralCustomRange(): void
1338 {
1339 $faker = Factory::create();
1340 $faker->seed(12345);
1341 $provider = new MySqlProvider($faker);
1342
1343 $result = $provider->integerLiteral(100, 500);
1344
1345 self::assertGreaterThanOrEqual(100, (int) $result);
1346 self::assertLessThanOrEqual(500, (int) $result);
1347 }
1348
1349 public function testLongIntegerLiteralCustomRange(): void
1350 {
1351 $faker = Factory::create();
1352 $faker->seed(12345);
1353 $provider = new MySqlProvider($faker);
1354
1355 $result = $provider->longIntegerLiteral(10, 100);
1356
1357 self::assertGreaterThanOrEqual(10, (int) $result);
1358 self::assertLessThanOrEqual(100, (int) $result);
1359 }
1360
1361 public function testDecimalLiteralCustomPrecision(): void
1362 {
1363 $faker = Factory::create();
1364 $faker->seed(12345);
1365 $provider = new MySqlProvider($faker);
1366
1367 $result = $provider->decimalLiteral(5, 2);
1368
1369 self::assertMatchesRegularExpression('/^\d+\.\d{2,}$/', $result);
1370 }
1371
1372 public function testFloatLiteralCustomParams(): void
1373 {
1374 $faker = Factory::create();
1375 $faker->seed(12345);
1376 $provider = new MySqlProvider($faker);
1377
1378 $result = $provider->floatLiteral(5, 2, -10, 10);
1379
1380 self::assertMatchesRegularExpression('/^\d+\.\d+e-?\d+$/', $result);
1381 }
1382
1383 public function testHexLiteralCustomLength(): void
1384 {
1385 $faker = Factory::create();
1386 $faker->seed(12345);
1387 $provider = new MySqlProvider($faker);
1388
1389 $result = $provider->hexLiteral(4, 8);
1390
1391 self::assertMatchesRegularExpression('/^0x[0-9a-f]{4,8}$/', $result);
1392 }
1393
1394 public function testBinaryLiteralCustomLength(): void
1395 {
1396 $faker = Factory::create();
1397 $faker->seed(12345);
1398 $provider = new MySqlProvider($faker);
1399
1400 $result = $provider->binaryLiteral(8, 16);
1401
1402 self::assertMatchesRegularExpression('/^0b[01]{8,16}$/', $result);
1403 }
1404
1405 public function testHostnameCustomParams(): void
1406 {
1407 $faker = Factory::create();
1408 $faker->seed(12345);
1409 $provider = new MySqlProvider($faker);
1410
1411 $result = $provider->hostname(2, 3, 5);
1412
1413 self::assertMatchesRegularExpression('/^[a-z0-9]+(\.[a-z0-9]+)+$/', $result);
1414 }
1415
1416 /**
1417 * @return iterable<string, array{StatementRule}>
1418 */
1419 public static function providerStatementRuleValue(): iterable
1420 {
1421 yield 'Select' => [StatementRule::Select];
1422 yield 'Insert' => [StatementRule::Insert];
1423 yield 'Update' => [StatementRule::Update];
1424 yield 'Delete' => [StatementRule::Delete];
1425 yield 'CreateTable' => [StatementRule::CreateTable];
1426 yield 'AlterTable' => [StatementRule::AlterTable];
1427 yield 'DropTable' => [StatementRule::DropTable];
1428 yield 'SimpleStatement' => [StatementRule::SimpleStatement];
1429 }
1430
1431 /**
1432 * @return iterable<string, array{string}>
1433 */
1434 public static function providerMySqlVersion(): iterable
1435 {
1436 foreach (SqlVersion::names('mysql') as $version) {
1437 yield $version => [$version];
1438 }
1439 }
1440
1441 /**
1442 * @return iterable<string, array{int, int}>
1443 */
1444 public static function providerMultipleGenerationSeeds(): iterable
1445 {
1446 yield 'seeds 0 and 1' => [0, 1];
1447 yield 'seeds 5 and 10' => [5, 10];
1448 }
1449
1450 /**
1451 * @return iterable<string, array{int}>
1452 */
1453 public static function providerMultiTableGenerationSeed(): iterable
1454 {
1455 foreach (range(0, 31) as $seed) {
1456 yield "seed {$seed}" => [$seed];
1457 }
1458 }
1459
1460 /**
1461 * @return iterable<string, array{string}>
1462 */
1463 public static function providerSupportedMySqlVersion(): iterable
1464 {
1465 yield 'MySQL 5.6' => ['mysql-5.6.51'];
1466 yield 'MySQL 5.7' => ['mysql-5.7.44'];
1467 yield 'MySQL 8.0' => ['mysql-8.0.44'];
1468 yield 'MySQL 8.1' => ['mysql-8.1.0'];
1469 yield 'MySQL 8.2' => ['mysql-8.2.0'];
1470 yield 'MySQL 8.3' => ['mysql-8.3.0'];
1471 yield 'MySQL 8.4' => ['mysql-8.4.7'];
1472 yield 'MySQL 9.0' => ['mysql-9.0.1'];
1473 yield 'MySQL 9.1' => ['mysql-9.1.0'];
1474 }
1475
1476 /**
1477 * @return iterable<string, array{int}>
1478 */
1479 public static function providerTargetedGenerationSeed(): iterable
1480 {
1481 foreach (range(0, 15) as $seed) {
1482 yield "seed {$seed}" => [$seed];
1483 }
1484 }
1485
1486 public function testPlannerCompilesReusableInstructionsWithoutChangingTheDefaultStart(): void
1487 {
1488 $provider = new MySqlProvider(Factory::create(), 'mysql-8.4.7');
1489 $plan = (new \SqlFaker\Generation\Choice\BytePlanCompiler())->compile('', $provider->planner());
1490 self::assertNull($plan->startRule());
1491 self::assertSame($provider->generate($plan), $provider->generate($plan));
1492 }
1493 public function testPlannerFreezesConstructiveValuesFromFuzzBytes(): void
1494 {
1495 $faker = Factory::create();
1496 $provider = new MySqlProvider($faker, 'mysql-8.4.7');
1497 $planner = $provider->planner();
1498 $constraints = GenerationPlan::constrained('ulong_num', [
1499 'ulong_num' => [ProductionPattern::containing('NUM')],
1500 ])->withExpansionBudget(1);
1501 $outputs = array_map(static fn (int $byte): string => $provider->generate((new \SqlFaker\Generation\Choice\BytePlanCompiler())->compile(
1502 "\0\0\0\0" . str_repeat(chr($byte) . chr($byte), 64),
1503 $planner,
1504 $constraints
1505 )), range(1, 31));
1506 self::assertGreaterThan(8, count(array_unique($outputs)));
1507 $plan = (new \SqlFaker\Generation\Choice\BytePlanCompiler())->compile("\0\0\0\0" . str_repeat("\0\xff", 64), $planner, $constraints);
1508 $first = $provider->generate($plan);
1509 self::assertGreaterThan(2, (int) $first);
1510 self::assertSame($first, $plan->lexemeAt('NUM', 0));
1511 self::assertNotNull($plan->candidateKeyAt('NUM', 0));
1512 $faker->seed(913);
1513 $faker->numberBetween(0, 1000);
1514 self::assertSame($first, $provider->generate($plan));
1515 }
1516
1517 /**
1518 * @param GenerationPlan<true> $constraints
1519 */
1520 #[DataProvider('providerScopedInsertPlan')]
1521 public function testGenerateSchemaBoundInsertAndFreezeChoices(GenerationPlan $constraints, int $seed): void
1522 {
1523 $faker = Factory::create();
1524 $faker->seed($seed);
1525 $provider = new MySqlProvider($faker, 'mysql-8.4.7');
1526 self::assertMatchesRegularExpression("/^INSERT(?: INTO)? users VALUES? \( (?:1[0-9]|20) , '(?:Alice|Bob)' \)$/", $provider->generate($constraints));
1527 $frozen = (new \SqlFaker\Generation\Choice\BytePlanCompiler())->compile(str_repeat(chr($seed), 64), $provider->planner(), $constraints);
1528 $sql = $provider->generate($frozen);
1529 $faker->seed(999);
1530 self::assertSame($sql, $provider->generate($frozen));
1531 self::assertMatchesRegularExpression("/^INSERT(?: INTO)? users VALUES? \( (?:1[0-9]|20) , '(?:Alice|Bob)' \)$/", $sql);
1532 }
1533
1534 /**
1535 * @return iterable<string, array{GenerationPlan<true>, int}>
1536 */
1537 public static function providerScopedInsertPlan(): iterable
1538 {
1539 $number = RulePlan::any()->allowing(ProductionPattern::exactly('NUM_literal'))->withRule('NUM_literal', RulePlan::any()->allowing(ProductionPattern::exactly('int64_literal')))->withRule('int64_literal', RulePlan::any()->allowing(ProductionPattern::exactly('NUM')))->withLexeme('NUM', LexemeConstraint::integers(10, 20));
1540 $string = RulePlan::any()->allowing(ProductionPattern::exactly('text_literal'))->withRule('text_literal', RulePlan::any()->allowing(ProductionPattern::exactly('TEXT_STRING')))->withLexeme('TEXT_STRING', LexemeConstraint::oneOf("'Alice'", "'Bob'"));
1541 $plan = GenerationPlan::fromRule('insert_stmt')->withRule('insert_stmt', RulePlan::any()->allowing(ProductionPattern::containing('insert_from_constructor')))
1542 ->withRule('table_ident', RulePlan::any()->allowing(ProductionPattern::exactly('ident'))->withLexeme('IDENT', LexemeConstraint::oneOf('users')))
1543 ->withRule('values', RulePlan::any()->withItems(RulePlan::any()->withRule('literal', $number), RulePlan::any()->withRule('literal', $string)))
1544 ->requiringNonEmpty()->withExpansionBudget(100);
1545 foreach (['insert_lock_option' => [], 'opt_ignore' => [], 'opt_use_partition' => [], 'opt_values_reference' => [], 'opt_insert_update_list' => [], 'ident' => ['IDENT_sys'],'IDENT_sys' => ['IDENT'],'insert_from_constructor' => ['insert_values'],'values_list' => ['row_value'],'opt_values' => ['values'],'expr_or_default' => ['expr'],'expr' => ['bool_pri'],'bool_pri' => ['predicate'],'predicate' => ['bit_expr'],'bit_expr' => ['simple_expr'],'simple_expr' => ['literal_or_null'],'literal_or_null' => ['literal']] as $name => $symbols) {
1546 $plan = $plan->withRule($name, RulePlan::any()->allowing(ProductionPattern::exactly(...$symbols)));
1547 }
1548
1549 foreach ([0, 1, 7, 31] as $seed) {
1550 yield 'seed ' . $seed => [$plan, $seed];
1551 }
1552 }
1553
1554}
1555