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