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