packages/sql-faker/tests/Unit/SqliteProviderTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\SqlFaker;
6
7use Faker\Factory;
8use Faker\Generator;
9use Override;
10use PHPUnit\Framework\Attributes\CoversClass;
11use PHPUnit\Framework\Attributes\DataProvider;
12use PHPUnit\Framework\Attributes\Medium;
13use PHPUnit\Framework\Attributes\UsesClass;
14use PHPUnit\Framework\TestCase;
15use SqlFaker\Generation\Derivation\TerminationAnalyzer;
16use SqlFaker\Generation\Plan\GenerationPlan;
17use SqlFaker\Generation\Plan\ProductionPattern;
18use SqlFaker\Generation\SqlGenerator;
19use SqlFaker\Grammar\Model\Grammar;
20use SqlFaker\Grammar\Model\NonTerminal;
21use SqlFaker\Grammar\Model\Production;
22use SqlFaker\Grammar\Model\ProductionRule;
23use SqlFaker\Grammar\Model\Terminal;
24use SqlFaker\Grammar\Model\TerminalInventory;
25use SqlFaker\Grammar\Resource\SqlVersion;
26use SqlFaker\Sqlite\Generation\GenerationPlans;
27use SqlFaker\Sqlite\Generation\LexicalGrammar;
28use SqlFaker\Sqlite\Generation\Value\LiteralGenerator;
29use SqlFaker\Sqlite\Grammar\SqliteGrammar;
30use SqlFaker\Sqlite\StatementType;
31use SqlFaker\SqliteProvider;
32use UnexpectedValueException;
33
34#[CoversClass(SqliteProvider::class)]
35#[CoversClass(LiteralGenerator::class)]
36#[CoversClass(SqlGenerator::class)]
37#[CoversClass(SqliteGrammar::class)]
38#[CoversClass(Grammar::class)]
39#[CoversClass(NonTerminal::class)]
40#[CoversClass(Production::class)]
41#[CoversClass(ProductionRule::class)]
42#[CoversClass(Terminal::class)]
43#[CoversClass(TerminationAnalyzer::class)]
44#[CoversClass(StatementType::class)]
45#[CoversClass(LexicalGrammar::class)]
46#[UsesClass(GenerationPlan::class)]
47#[UsesClass(ProductionPattern::class)]
48#[UsesClass(SqlVersion::class)]
49#[UsesClass(TerminalInventory::class)]
50#[UsesClass(GenerationPlans::class)]
51#[Medium]
52#[UsesClass(\SqlFaker\Provider\SqlGeneratorFactory::class)]
53#[UsesClass(\SqlFaker\Generation\Derivation\CompletionCosts::class)]
54#[UsesClass(\SqlFaker\Generation\Derivation\Derivation::class)]
55#[UsesClass(\SqlFaker\Generation\Derivation\DerivationTrace::class)]
56#[UsesClass(\SqlFaker\Generation\Derivation\TerminationCost::class)]
57#[UsesClass(\SqlFaker\Generation\Exception\GenerationException::class)]
58#[UsesClass(\SqlFaker\Generation\Candidate\ChoiceLexemeGenerator::class)]
59#[UsesClass(\SqlFaker\Generation\Candidate\FixedLexemeGenerator::class)]
60#[UsesClass(\SqlFaker\Generation\Candidate\IntegerLexemeGenerator::class)]
61#[UsesClass(\SqlFaker\Generation\Lexeme\Lexeme::class)]
62#[UsesClass(\SqlFaker\Generation\Lexeme\LexemeCandidates::class)]
63#[UsesClass(\SqlFaker\Generation\Lexeme\LexemeInput::class)]
64#[UsesClass(\SqlFaker\Generation\Lexeme\LexemeSequence::class)]
65#[UsesClass(\SqlFaker\Generation\Candidate\MatchingLexemeGenerator::class)]
66#[UsesClass(\SqlFaker\Generation\Candidate\ValueLexemeGenerator::class)]
67#[UsesClass(\SqlFaker\Generation\Candidate\RegisteredLexemeGenerator::class)]
68#[UsesClass(\SqlFaker\Generation\Candidate\SequenceLexemeGenerator::class)]
69#[UsesClass(\SqlFaker\Generation\Output\CandidateResolver::class)]
70#[UsesClass(\SqlFaker\Generation\Lexeme\OutputPart::class)]
71#[UsesClass(\SqlFaker\Generation\Lexeme\ResolvedOutput::class)]
72#[UsesClass(\SqlFaker\Generation\Output\ReverseLexemeGenerator::class)]
73#[UsesClass(\SqlFaker\Generation\Output\SqlSerializer::class)]
74#[UsesClass(\SqlFaker\Generation\Output\CombinedSpacingRule::class)]
75#[UsesClass(\SqlFaker\MySql\Generation\Spacing\KeywordPhraseSpacingRule::class)]
76#[UsesClass(\SqlFaker\Generation\Lexeme\LexemeBoundary::class)]
77#[UsesClass(\SqlFaker\Generation\Lexeme\SpacingConstraint::class)]
78#[UsesClass(\SqlFaker\Generation\Token\ProductionOccurrence::class)]
79#[UsesClass(\SqlFaker\Generation\Token\TerminalMappingRule::class)]
80#[UsesClass(\SqlFaker\Generation\Token\TerminalOccurrence::class)]
81#[UsesClass(\SqlFaker\Generation\Token\TerminalSequence::class)]
82#[UsesClass(\SqlFaker\Generation\Derivation\TokenGenerator::class)]
83#[UsesClass(\SqlFaker\Generation\Token\TokenRewriter::class)]
84#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Option\UniqueOptionRule::class)]
85#[UsesClass(\SqlFaker\Generation\Candidate\VersionCase::class)]
86#[UsesClass(\SqlFaker\Generation\Candidate\VersionedLexemeGenerator::class)]
87#[UsesClass(\SqlFaker\Generation\Exception\LexicalException::class)]
88#[UsesClass(\SqlFaker\Sqlite\Generation\Tokenization\KeywordIndex::class)]
89#[UsesClass(\SqlFaker\Generation\Value\RandomCharacters::class)]
90#[UsesClass(\SqlFaker\Grammar\Resource\SqlVersionRegistry::class)]
91#[UsesClass(\SqlFaker\MySql\Generation\GenerationContext::class)]
92#[UsesClass(\SqlFaker\MySql\Generation\Lexeme\DefinitionFactory::class)]
93#[UsesClass(\SqlFaker\MySql\Generation\Lexeme\KeywordLexemeGenerator::class)]
94#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\AlterDatabaseRule::class)]
95#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\ConstraintEnforcementRule::class)]
96#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\FlushExportRule::class)]
97#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\InstanceActionRule::class)]
98#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\IntegerContextRule::class)]
99#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\LoadSourceCountRule::class)]
100#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\RequiredAliasRule::class)]
101#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\RewriteDefinitions::class)]
102#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\RoleGrantRule::class)]
103#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\SetNamesRule::class)]
104#[UsesClass(\SqlFaker\MySql\Generation\Spacing\CloneAddressSpacingRule::class)]
105#[UsesClass(\SqlFaker\MySql\Generation\Spacing\FunctionSpacingRule::class)]
106#[UsesClass(\SqlFaker\MySql\Generation\Spacing\QualifiedNameSpacingRule::class)]
107#[UsesClass(\SqlFaker\MySql\Generation\Spacing\VariableSpacingRule::class)]
108#[UsesClass(\SqlFaker\MySql\Grammar\MySqlGrammar::class)]
109#[UsesClass(\SqlFaker\MySql\Generation\LexicalGrammar::class)]
110#[UsesClass(\SqlFaker\MySql\Generation\Tokenization\MySqlTokenizer::class)]
111#[UsesClass(\SqlFaker\MySql\Generation\StartRuleResolver::class)]
112#[UsesClass(\SqlFaker\PostgreSql\Generation\GenerationContext::class)]
113#[UsesClass(\SqlFaker\PostgreSql\Generation\Lexeme\DefinitionFactory::class)]
114#[UsesClass(\SqlFaker\PostgreSql\Generation\Lexeme\HashBoundLexemeGenerator::class)]
115#[UsesClass(\SqlFaker\PostgreSql\Generation\Lexeme\KeywordLexemeGenerator::class)]
116#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\CopySourceRule::class)]
117#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\FetchWithTiesRule::class)]
118#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Name\FunctionNameRule::class)]
119#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\HashPartitionBoundRule::class)]
120#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\LimitOffsetRule::class)]
121#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\LookaheadRule::class)]
122#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\OperatorArgumentsRule::class)]
123#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\OverlapsArgumentsRule::class)]
124#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\PublicationObjectRule::class)]
125#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Name\RelationNameRule::class)]
126#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\RewriteDefinitions::class)]
127#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\TimeZoneIntervalRule::class)]
128#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\WindowFrameRule::class)]
129#[UsesClass(\SqlFaker\PostgreSql\Grammar\PgGrammar::class)]
130#[UsesClass(\SqlFaker\PostgreSql\Generation\LexicalGrammar::class)]
131#[UsesClass(\SqlFaker\PostgreSql\Generation\Lookahead\PgLookahead::class)]
132#[UsesClass(\SqlFaker\PostgreSql\Generation\Tokenization\PgTokenizer::class)]
133#[UsesClass(\SqlFaker\Sqlite\Generation\GenerationContext::class)]
134#[UsesClass(\SqlFaker\Sqlite\Generation\Lexeme\DefinitionFactory::class)]
135#[UsesClass(\SqlFaker\Sqlite\Generation\Lexeme\JoinLexemeGenerator::class)]
136#[UsesClass(\SqlFaker\Sqlite\Generation\Lexeme\JoinModifiers::class)]
137#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\IdentifierListRule::class)]
138#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\JoinRule::class)]
139#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\RewriteDefinitions::class)]
140#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\StrictTableRule::class)]
141#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\TableOptionRule::class)]
142#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\WindowFrameRule::class)]
143#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\WithoutRowidRule::class)]
144#[UsesClass(\SqlFaker\Sqlite\Generation\GrammarAdaptation::class)]
145#[UsesClass(\SqlFaker\Sqlite\Generation\Tokenization\SqliteTokenizer::class)]
146#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\Expression\ExpressionGroupingRule::class)]
147#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\AlterEventRule::class)]
148#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\SubqueryContextRule::class)]
149#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\ConstraintAttributesRule::class)]
150#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Name\AnyRelationNameRule::class)]
151#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Name\IndirectionStarRule::class)]
152#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\IntoClauseRule::class)]
153#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\GeneratedColumnRule::class)]
154#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\TransactionCompletionRule::class)]
155#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\GeneratedColumnRule::class)]
156#[UsesClass(\SqlFaker\Sqlite\Generation\Lexeme\WindowNameLexemeGenerator::class)]
157#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Alter\OrderByRule::class)]
158#[UsesClass(\SqlFaker\MySql\Generation\Lexeme\BoundedIntegerLexemeGenerator::class)]
159#[UsesClass(\SqlFaker\MySql\Generation\Lexeme\ReplicationTablePatternLexemeGenerator::class)]
160#[UsesClass(\SqlFaker\MySql\Generation\Lexeme\SizeNumberLexemeGenerator::class)]
161#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\CompoundSelectRule::class)]
162#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\UpsertSourceRule::class)]
163#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\FunctionArgumentRule::class)]
164#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\GeneratedColumnRule::class)]
165#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Replication\StartRule::class)]
166#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Query\SelectOptionsRule::class)]
167#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Routine\TableFunctionRule::class)]
168#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Routine\TypeModifierRule::class)]
169#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Column\IdentityOptionRule::class)]
170#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Query\IntoClauseRule::class)]
171#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Partition\FieldListRule::class)]
172#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Replication\TablePatternRule::class)]
173#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Expression\QuantifiedComparisonRule::class)]
174#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Expression\TableValueConstructorRule::class)]
175#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Query\QueryContextRule::class)]
176#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Query\JoinGroupingRule::class)]
177#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Routine\WithinGroupRule::class)]
178#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Column\FieldLengthRule::class)]
179#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Routine\ReturnRule::class)]
180#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Name\ColumnNameRule::class)]
181#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Name\SystemVariableRule::class)]
182#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Column\AutoIncrementRule::class)]
183#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Partition\DefinitionRule::class)]
184#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Partition\ListValueRule::class)]
185#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Routine\LanguageRule::class)]
186#[UsesClass(\SqlFaker\MySql\Generation\Lexeme\FactorLexemeGenerator::class)]
187#[UsesClass(\SqlFaker\MySql\Generation\Lexeme\PrecisionLexemeGenerator::class)]
188#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Name\AliasRule::class)]
189#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Partition\ValueArityRule::class)]
190#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Partition\ValueShape::class)]
191#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Routine\SubstringRule::class)]
192#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Routine\RangeFunctionOrdinalityRule::class)]
193#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Query\WindowFrameRule::class)]
194#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Routine\JsonOptionsRule::class)]
195#[UsesClass(\SqlFaker\Generation\Value\CharacterDomain::class)]
196#[UsesClass(\SqlFaker\Generation\Value\ChoiceDomain::class)]
197#[UsesClass(\SqlFaker\Generation\Value\IntegerDomain::class)]
198#[UsesClass(\SqlFaker\Generation\Value\SequenceDomain::class)]
199#[UsesClass(\SqlFaker\MySql\Generation\Value\DollarQuotedDomain::class)]
200#[UsesClass(\SqlFaker\Generation\Output\BoundaryCompletion::class)]
201#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Column\ConstraintCapabilitiesRule::class)]
202#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Column\ForeignKeyActionRule::class)]
203#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Query\SchemaElementsRule::class)]
204#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Query\ParserOptionsRule::class)]
205#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Routine\JsonTablePathRule::class)]
206#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Routine\AggregateArgumentRule::class)]
207#[UsesClass(\SqlFaker\Generation\Derivation\CompletionState::class)]
208#[UsesClass(\SqlFaker\Generation\Derivation\CompletionFrontier::class)]
209#[UsesClass(\SqlFaker\Generation\Derivation\ConstrainedCompletion::class)]
210#[UsesClass(\SqlFaker\Generation\Value\ValueChoices::class)]
211#[UsesClass(\SqlFaker\Generation\Derivation\CompletionMemo::class)]
212#[UsesClass(\SqlFaker\Generation\Derivation\CompletionReduction::class)]
213#[UsesClass(\SqlFaker\Generation\Derivation\ConstraintDependencies::class)]
214#[UsesClass(\SqlFaker\Generation\Choice\BytePlanCompiler::class)]
215#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Name\HostNameRule::class)]
216#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Name\ParserNameRule::class)]
217#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Column\NumericContextRule::class)]
218#[UsesClass(\SqlFaker\MySql\Generation\Lexeme\CharsetLexemeGenerator::class)]
219#[UsesClass(\SqlFaker\MySql\Generation\Lexeme\CharsetValueLexemeGenerator::class)]
220#[UsesClass(\SqlFaker\Sqlite\Generation\Value\IdentifierDomain::class)]
221#[UsesClass(\SqlFaker\Sqlite\Generation\Value\QuotedDomain::class)]
222#[UsesClass(\SqlFaker\Generation\Value\RepeatDomain::class)]
223#[UsesClass(\SqlFaker\Generation\Value\WordDomain::class)]
224#[UsesClass(\SqlFaker\Generation\Derivation\Completion\PatternProductions::class)]
225#[UsesClass(\SqlFaker\Generation\Derivation\Completion\CompletionWitness::class)]
226#[UsesClass(\SqlFaker\PostgreSql\Generation\Value\OperatorDomain::class)]
227#[UsesClass(\SqlFaker\MySql\Generation\Value\RadixDomain::class)]
228#[UsesClass(\SqlFaker\Generation\Value\Utf8::class)]
229#[UsesClass(\SqlFaker\Sqlite\Generation\Lexeme\LexicalDefinition::class)]
230final class SqliteProviderTest extends TestCase
231{
232    #[DataProvider('providerTargetedGenerationSeed')]
233    public function testInsertFunctionUpsertStatementDerivesFunctionExpressionFromGrammar(int $seed): void
234    {
235        $faker = Factory::create();
236        $faker->seed($seed);
237        $provider = new SqliteProvider($faker);
238        $sql = $provider->insertFunctionUpsertStatement();
239        $faker->seed($seed);
240
241        $tokens = (new LexicalGrammar($faker, 'sqlite-3.47.2'))
242            ->tokenize($sql);
243        $values = array_search('VALUES', $tokens, true);
244        $conflict = array_search('CONFLICT', $tokens, true);
245        $set = array_search('SET', $tokens, true);
246        $functionOpen = array_search('LP', array_slice($tokens, (int) $set, null, true), true);
247
248        self::assertSame($sql, $provider->insertFunctionUpsertStatement(40));
249        self::assertSame('INSERT', $tokens[0]);
250        self::assertIsInt($values);
251        self::assertIsInt($conflict);
252        self::assertIsInt($set);
253        self::assertIsInt($functionOpen);
254        self::assertLessThan($conflict, $values);
255        self::assertGreaterThan($set, $functionOpen);
256        self::assertContains('UPDATE', $tokens);
257    }
258
259    #[DataProvider('providerTargetedGenerationSeed')]
260    public function testMultiDmlStatementDerivesTwoStatementBatchFromGrammar(int $seed): void
261    {
262        $faker = Factory::create();
263        $faker->seed($seed);
264        $provider = new SqliteProvider($faker);
265        $sql = $provider->multiDmlStatement();
266        $faker->seed($seed);
267        $tokens = (new LexicalGrammar($faker, 'sqlite-3.47.2'))->tokenize($sql);
268        $separator = array_search('SEMI', $tokens, true);
269
270        self::assertSame($sql, $provider->multiDmlStatement(40));
271        self::assertIsInt($separator);
272        self::assertContains($tokens[0], ['INSERT', 'UPDATE', 'DELETE']);
273        self::assertContains($tokens[$separator + 1], ['INSERT', 'UPDATE', 'DELETE']);
274        self::assertSame(2, count(array_filter($tokens, static fn (string $token): bool => $token === 'SEMI')));
275    }
276
277    #[DataProvider('providerMultiDmlSelection')]
278    public function testMultiDmlStatementCanSelectEveryDmlFamily(
279        int $firstChoice,
280        int $secondChoice,
281        string $first,
282        string $second,
283    ): void {
284        $generator = new class ([$firstChoice, $secondChoice]) extends Generator {
285            /** @var list<int> */
286            private readonly array $choices;
287            private int $call = 0;
288
289            /** @param list<int> $choices */
290            public function __construct(array $choices)
291            {
292                parent::__construct();
293                $this->choices = $choices;
294            }
295
296            /**
297             * @param mixed $min
298             * @param mixed $max
299             *
300             * @throws UnexpectedValueException When the bound is not an integer
301             */
302            #[Override]
303            public function numberBetween($min = 0, $max = 2147483647): int
304            {
305                if ($this->call < count($this->choices)) {
306                    $choice = $this->choices[$this->call];
307                    ++$this->call;
308                    if ($min !== 0 || $max !== 2 || $choice < $min || $choice > $max) {
309                        throw new UnexpectedValueException();
310                    }
311
312                    return $choice;
313                }
314                if (!is_int($min)) {
315                    throw new UnexpectedValueException();
316                }
317
318                return $min;
319            }
320        };
321        $sql = (new SqliteProvider($generator))->multiDmlStatement();
322        $tokens = (new LexicalGrammar(Factory::create(), 'sqlite-3.47.2'))->tokenize($sql);
323        $separator = array_search('SEMI', $tokens, true);
324
325        self::assertIsInt($separator);
326        self::assertSame($first, $tokens[0]);
327        self::assertSame($second, $tokens[$separator + 1]);
328    }
329
330    #[DataProvider('providerTargetedGenerationSeed')]
331    public function testFullTextSearchStatementDerivesMatchExpressionFromGrammar(int $seed): void
332    {
333        $faker = Factory::create();
334        $faker->seed($seed);
335        $provider = new SqliteProvider($faker);
336        $sql = $provider->fullTextSearchStatement();
337        $faker->seed($seed);
338        $tokens = (new LexicalGrammar($faker, 'sqlite-3.47.2'))
339            ->tokenize($sql);
340        $where = array_search('WHERE', $tokens, true);
341        $match = array_search('MATCH', $tokens, true);
342
343        self::assertSame($sql, $provider->fullTextSearchStatement(40));
344        self::assertSame('SELECT', $tokens[0]);
345        self::assertContains('FROM', $tokens);
346        self::assertIsInt($where);
347        self::assertIsInt($match);
348        self::assertGreaterThan($where, $match);
349    }
350
351    #[DataProvider('providerTargetedGenerationSeed')]
352    public function testTemporaryTableStatement(int $seed): void
353    {
354        $faker = Factory::create();
355        $faker->seed($seed);
356        $provider = new SqliteProvider($faker);
357        $sql = $provider->temporaryTableStatement();
358        $faker->seed($seed);
359
360        $tokens = (new LexicalGrammar($faker, 'sqlite-3.47.2'))
361            ->tokenize($sql);
362
363        self::assertSame($sql, $provider->temporaryTableStatement(40));
364        self::assertSame('CREATE', $tokens[0]);
365        self::assertContains('TEMP', $tokens);
366        self::assertContains('TABLE', $tokens);
367    }
368
369    #[DataProvider('providerTargetedGenerationSeed')]
370    public function testViewStatement(int $seed): void
371    {
372        $faker = Factory::create();
373        $faker->seed($seed);
374        $provider = new SqliteProvider($faker);
375        $sql = $provider->viewStatement();
376        $faker->seed($seed);
377        $tokens = (new LexicalGrammar($faker, 'sqlite-3.47.2'))
378            ->tokenize($sql);
379
380        self::assertSame($sql, $provider->viewStatement(40));
381        self::assertSame('CREATE', $tokens[0]);
382        self::assertContains('VIEW', $tokens);
383        self::assertContains('SELECT', $tokens);
384    }
385
386    #[DataProvider('providerTargetedGenerationSeed')]
387    public function testGeneratedColumnStatement(int $seed): void
388    {
389        $faker = Factory::create();
390        $faker->seed($seed);
391        $provider = new SqliteProvider($faker);
392        $sql = $provider->generatedColumnStatement();
393        $faker->seed($seed);
394        $tokens = (new LexicalGrammar($faker, 'sqlite-3.47.2'))
395            ->tokenize($sql);
396
397        self::assertSame($sql, $provider->generatedColumnStatement(40));
398        self::assertContains('GENERATED', $tokens);
399        self::assertContains('ALWAYS', $tokens);
400        self::assertContains('AS', $tokens);
401    }
402
403    #[DataProvider('providerTargetedGenerationSeed')]
404    public function testForeignKeyCascadeStatement(int $seed): void
405    {
406        $faker = Factory::create();
407        $faker->seed($seed);
408        $provider = new SqliteProvider($faker);
409        $sql = $provider->foreignKeyCascadeStatement();
410        $faker->seed($seed);
411        $tokens = (new LexicalGrammar($faker, 'sqlite-3.47.2'))
412            ->tokenize($sql);
413
414        self::assertSame($sql, $provider->foreignKeyCascadeStatement(40));
415        self::assertContains('FOREIGN', $tokens);
416        self::assertContains('REFERENCES', $tokens);
417        self::assertStringContainsString('ON UPDATE CASCADE', implode(' ', $tokens));
418        self::assertStringContainsString('ON DELETE CASCADE', implode(' ', $tokens));
419    }
420
421    #[Override]
422    protected function setUp(): void
423    {
424        parent::setUp();
425        gc_collect_cycles();
426    }
427
428    public function testRegistersItselfWithTheFakerGenerator(): void
429    {
430        $faker = Factory::create();
431        $provider = new SqliteProvider($faker);
432
433        /** @var list<object> $providers */
434        $providers = $faker->getProviders();
435        self::assertContains($provider, $providers);
436
437        $identifier = $provider->identifier(3);
438        self::assertNotSame('', $identifier);
439    }
440
441    public function testSql(): void
442    {
443        $faker = Factory::create();
444        $faker->seed(12345);
445        $provider = new SqliteProvider($faker);
446
447        $result = $provider->sql(maxDepth: 6);
448
449        self::assertNotSame('', $result);
450    }
451
452    public function testSqlWithStatementType(): void
453    {
454        $faker = Factory::create();
455        $faker->seed(12345);
456        $provider = new SqliteProvider($faker);
457
458        $result = $provider->sql(StatementType::Select, maxDepth: 6);
459
460        self::assertTrue(
461            str_contains($result, 'SELECT') || str_contains($result, 'VALUES'),
462            "SelectStmt should produce SELECT or VALUES: {$result}"
463        );
464    }
465
466    public function testSqlWithNullStatementTypeUsesRandom(): void
467    {
468        $faker = Factory::create();
469        $faker->seed(12345);
470        $provider = new SqliteProvider($faker);
471
472        $result = $provider->sql(null, maxDepth: 6);
473
474        self::assertNotSame('', $result);
475    }
476
477    public function testSqlWithMaxDepth(): void
478    {
479        $faker = Factory::create();
480        $faker->seed(12345);
481        $provider = new SqliteProvider($faker);
482
483        $result = $provider->sql(maxDepth: 8);
484
485        self::assertNotSame('', $result);
486    }
487
488    public function testSelectStatement(): void
489    {
490        $faker = Factory::create();
491        $faker->seed(12345);
492        $provider = new SqliteProvider($faker);
493
494        $result = $provider->selectStatement(maxDepth: 6);
495
496        self::assertNotEmpty($result);
497        self::assertTrue(
498            str_contains($result, 'SELECT') || str_contains($result, 'VALUES'),
499            "select should contain SELECT or VALUES: {$result}"
500        );
501    }
502
503    public function testInsertStatement(): void
504    {
505        $faker = Factory::create();
506        $faker->seed(12345);
507        $provider = new SqliteProvider($faker);
508
509        $result = $provider->insertStatement(maxDepth: 6);
510
511        self::assertNotEmpty($result);
512        self::assertTrue(
513            str_contains($result, 'INSERT') || str_contains($result, 'REPLACE'),
514            "insert should contain INSERT or REPLACE: {$result}"
515        );
516    }
517
518    public function testUpdateStatement(): void
519    {
520        $faker = Factory::create();
521        $faker->seed(12345);
522        $provider = new SqliteProvider($faker);
523
524        $result = $provider->updateStatement(maxDepth: 6);
525
526        self::assertNotEmpty($result);
527        self::assertStringContainsString('UPDATE', $result);
528    }
529
530    public function testDeleteStatement(): void
531    {
532        $faker = Factory::create();
533        $faker->seed(12345);
534        $provider = new SqliteProvider($faker);
535
536        $result = $provider->deleteStatement(maxDepth: 6);
537
538        self::assertNotEmpty($result);
539        self::assertStringContainsString('DELETE', $result);
540    }
541
542    public function testCreateTableStatement(): void
543    {
544        $faker = Factory::create();
545        $faker->seed(12345);
546        $provider = new SqliteProvider($faker);
547
548        $result = $provider->createTableStatement(maxDepth: 6);
549
550        self::assertNotEmpty($result);
551        self::assertStringContainsString('CREATE', $result);
552        self::assertStringContainsString('TABLE', $result);
553    }
554
555    public function testAlterTableStatement(): void
556    {
557        $faker = Factory::create();
558        $faker->seed(12345);
559        $provider = new SqliteProvider($faker);
560
561        $result = $provider->alterTableStatement(maxDepth: 6);
562
563        self::assertNotEmpty($result);
564        self::assertStringContainsString('ALTER', $result);
565        self::assertStringContainsString('TABLE', $result);
566    }
567
568    public function testDropTableStatement(): void
569    {
570        $faker = Factory::create();
571        $faker->seed(12345);
572        $provider = new SqliteProvider($faker);
573
574        $result = $provider->dropTableStatement(maxDepth: 6);
575
576        self::assertNotEmpty($result);
577        self::assertStringContainsString('DROP', $result);
578        self::assertStringContainsString('TABLE', $result);
579    }
580
581    public function testExpr(): void
582    {
583        $faker = Factory::create();
584        $faker->seed(12345);
585        $provider = new SqliteProvider($faker);
586
587        $result = $provider->expr(maxDepth: 3);
588
589        self::assertNotSame('', $result);
590    }
591
592    public function testTerm(): void
593    {
594        $faker = Factory::create();
595        $faker->seed(12345);
596        $provider = new SqliteProvider($faker);
597
598        $result = $provider->term(maxDepth: 3);
599
600        self::assertNotSame('', $result);
601    }
602
603    public function testWhereClause(): void
604    {
605        $faker = Factory::create();
606        $faker->seed(1);
607        $provider = new SqliteProvider($faker);
608
609        $result = $provider->whereClause(maxDepth: 6);
610
611        self::assertMatchesRegularExpression('/^$|WHERE/', $result);
612    }
613
614    public function testOrderByClause(): void
615    {
616        $faker = Factory::create();
617        $faker->seed(1);
618        $provider = new SqliteProvider($faker);
619
620        $result = $provider->orderByClause(maxDepth: 6);
621
622        self::assertMatchesRegularExpression('/^$|ORDER/', $result);
623    }
624
625    public function testLimitClause(): void
626    {
627        $faker = Factory::create();
628        $faker->seed(1);
629        $provider = new SqliteProvider($faker);
630
631        $result = $provider->limitClause(maxDepth: 6);
632
633        self::assertMatchesRegularExpression('/^$|LIMIT/', $result);
634    }
635
636    public function testGroupByClause(): void
637    {
638        $faker = Factory::create();
639        $faker->seed(1);
640        $provider = new SqliteProvider($faker);
641
642        $result = $provider->groupByClause(maxDepth: 6);
643
644        self::assertMatchesRegularExpression('/^$|GROUP/', $result);
645    }
646
647    public function testHavingClause(): void
648    {
649        $faker = Factory::create();
650        $faker->seed(1);
651        $provider = new SqliteProvider($faker);
652
653        $result = $provider->havingClause(maxDepth: 6);
654
655        self::assertMatchesRegularExpression('/^$|HAVING/', $result);
656    }
657
658    public function testFullname(): void
659    {
660        $faker = Factory::create();
661        $faker->seed(12345);
662        $provider = new SqliteProvider($faker);
663
664        $result = $provider->fullname(maxDepth: 3);
665
666        self::assertNotSame('', $result);
667    }
668
669    public function testWithClause(): void
670    {
671        $faker = Factory::create();
672        $faker->seed(0);
673        $provider = new SqliteProvider($faker);
674
675        $result = $provider->withClause(maxDepth: 6);
676
677        self::assertMatchesRegularExpression('/^$|WITH/', $result);
678    }
679
680    public function testForeignKeyConstraint(): void
681    {
682        $faker = Factory::create();
683        $faker->seed(12345);
684        $provider = new SqliteProvider($faker, 'sqlite-3.47.2');
685
686        $result = $provider->foreignKeyConstraint(1);
687
688        $tokens = array_map(
689            static fn (string $token): string => in_array($token, ['ID', 'STRING', 'INDEXED', 'JOIN_KW'], true) ? 'NAME' : $token,
690            (new LexicalGrammar($faker, 'sqlite-3.47.2'))->tokenize($result),
691        );
692
693        self::assertSame(
694            ['CONSTRAINT', 'NAME', 'FOREIGN', 'KEY', 'LP', 'NAME', 'RP', 'REFERENCES', 'NAME', 'LP', 'NAME', 'RP'],
695            $tokens,
696        );
697
698        $faker->seed(2);
699        $result = $provider->foreignKeyConstraint(20);
700
701        $tokens = array_map(
702            static fn (string $token): string => in_array($token, ['ID', 'STRING', 'INDEXED', 'JOIN_KW'], true) ? 'NAME' : $token,
703            (new LexicalGrammar($faker, 'sqlite-3.47.2'))->tokenize($result),
704        );
705
706        self::assertSame(
707            ['CONSTRAINT', 'NAME', 'FOREIGN', 'KEY'],
708            array_slice($tokens, 0, 4),
709        );
710    }
711
712    public function testIdentifier(): void
713    {
714        $faker = Factory::create();
715        $faker->seed(12345);
716        $provider = new SqliteProvider($faker);
717
718        $result = $provider->identifier(3);
719
720        self::assertNotSame('', $result);
721    }
722
723    public function testQuotedIdentifier(): void
724    {
725        $faker = Factory::create();
726        $faker->seed(12345);
727        $provider = new SqliteProvider($faker);
728
729        $result = $provider->quotedIdentifier();
730
731        self::assertMatchesRegularExpression('/^"[a-z_][a-z0-9_]*"$/', $result);
732    }
733
734    public function testStringLiteral(): void
735    {
736        $faker = Factory::create();
737        $faker->seed(12345);
738        $provider = new SqliteProvider($faker);
739
740        $result = $provider->stringLiteral();
741
742        self::assertMatchesRegularExpression("/^'[a-zA-Z0-9_]{1,255}'$/", $result);
743    }
744
745    public function testIntegerLiteral(): void
746    {
747        $faker = Factory::create();
748        $faker->seed(12345);
749        $provider = new SqliteProvider($faker);
750
751        $result = $provider->integerLiteral();
752
753        self::assertMatchesRegularExpression('/^[1-9]\d*$/', $result);
754    }
755
756    public function testDecimalLiteral(): void
757    {
758        $faker = Factory::create();
759        $faker->seed(12345);
760        $provider = new SqliteProvider($faker);
761
762        $result = $provider->decimalLiteral();
763
764        self::assertMatchesRegularExpression('/^\d+\.\d{2,}$/', $result);
765    }
766
767    public function testQuotedIdentifierDefaultMatchesExplicit(): void
768    {
769        $faker = Factory::create();
770        $p = new SqliteProvider($faker);
771        $faker->seed(42);
772        $a = $p->quotedIdentifier();
773        $faker->seed(42);
774        self::assertSame($a, $p->quotedIdentifier(1, 128));
775    }
776
777    public function testStringLiteralDefaultMatchesExplicit(): void
778    {
779        $faker = Factory::create();
780        $p = new SqliteProvider($faker);
781        $faker->seed(42);
782        $a = $p->stringLiteral();
783        $faker->seed(42);
784        self::assertSame($a, $p->stringLiteral(1, 255));
785    }
786
787    public function testIntegerLiteralDefaultMatchesExplicit(): void
788    {
789        $faker = Factory::create();
790        $p = new SqliteProvider($faker);
791        $faker->seed(42);
792        $a = $p->integerLiteral();
793        $faker->seed(42);
794        self::assertSame($a, $p->integerLiteral(1, PHP_INT_MAX));
795    }
796
797    public function testDecimalLiteralDefaultMatchesExplicit(): void
798    {
799        $faker = Factory::create();
800        $p = new SqliteProvider($faker);
801        $faker->seed(42);
802        $a = $p->decimalLiteral();
803        $faker->seed(42);
804        self::assertSame($a, $p->decimalLiteral(15, 2));
805    }
806
807    public function testQuotedIdentifierCustomLength(): void
808    {
809        $faker = Factory::create();
810        $faker->seed(12345);
811        $provider = new SqliteProvider($faker);
812
813        $result = $provider->quotedIdentifier(5, 10);
814
815        self::assertMatchesRegularExpression('/^"[a-z_][a-z0-9_]{4,9}"$/', $result);
816    }
817
818    public function testStringLiteralCustomLength(): void
819    {
820        $faker = Factory::create();
821        $faker->seed(12345);
822        $provider = new SqliteProvider($faker);
823
824        $result = $provider->stringLiteral(3, 8);
825        $content = substr($result, 1, -1);
826
827        self::assertGreaterThanOrEqual(3, strlen($content));
828        self::assertLessThanOrEqual(8, strlen($content));
829    }
830
831    public function testIntegerLiteralCustomRange(): void
832    {
833        $faker = Factory::create();
834        $faker->seed(12345);
835        $provider = new SqliteProvider($faker);
836
837        $result = $provider->integerLiteral(100, 500);
838
839        self::assertGreaterThanOrEqual(100, (int) $result);
840        self::assertLessThanOrEqual(500, (int) $result);
841    }
842
843    public function testDecimalLiteralCustomPrecision(): void
844    {
845        $faker = Factory::create();
846        $faker->seed(12345);
847        $provider = new SqliteProvider($faker);
848
849        $result = $provider->decimalLiteral(5, 2);
850
851        self::assertMatchesRegularExpression('/^\d+\.\d{2,}$/', $result);
852    }
853
854    public function testSeededGenerationIsReproducible(): void
855    {
856        $faker1 = Factory::create();
857        $provider1 = new SqliteProvider($faker1);
858        $faker1->seed(99999);
859        $sql1 = $provider1->sql(maxDepth: 8);
860
861        $faker2 = Factory::create();
862        $provider2 = new SqliteProvider($faker2);
863        $faker2->seed(99999);
864        $sql2 = $provider2->sql(maxDepth: 8);
865
866        self::assertSame($sql1, $sql2, 'Same seed should produce same output');
867    }
868
869    #[DataProvider('providerStatementTypeValue')]
870    public function testSqlWithAllStatementTypes(StatementType $type): void
871    {
872        $faker = Factory::create();
873        $faker->seed(12345);
874        $provider = new SqliteProvider($faker);
875
876        $result = $provider->sql($type, maxDepth: 6);
877
878        self::assertNotSame('', $result);
879    }
880
881    public function testSelectContainsSelectOrValues(): void
882    {
883        $faker = Factory::create();
884        $faker->seed(12345);
885        $provider = new SqliteProvider($faker);
886
887        $sql = $provider->selectStatement(maxDepth: 6);
888
889        self::assertTrue(
890            str_contains($sql, 'SELECT') || str_contains($sql, 'VALUES'),
891            "select should produce SELECT or VALUES: {$sql}"
892        );
893    }
894
895    public function testUpdateContainsSetClause(): void
896    {
897        $faker = Factory::create();
898        $faker->seed(12345);
899        $provider = new SqliteProvider($faker);
900
901        $result = $provider->updateStatement(maxDepth: 6);
902
903        self::assertStringContainsString('UPDATE', $result);
904        self::assertStringContainsString('SET', $result);
905    }
906
907    public function testDeleteContainsDeleteKeyword(): void
908    {
909        $faker = Factory::create();
910        $faker->seed(12345);
911        $provider = new SqliteProvider($faker);
912
913        $result = $provider->deleteStatement(maxDepth: 6);
914
915        self::assertStringContainsString('DELETE', $result);
916    }
917
918    public function testMultipleGenerationsReturnDifferentResults(): void
919    {
920        $faker1 = Factory::create();
921        $faker1->seed(0);
922        $provider1 = new SqliteProvider($faker1);
923        $sql1 = $provider1->selectStatement(maxDepth: 6);
924
925        $faker2 = Factory::create();
926        $faker2->seed(1);
927        $provider2 = new SqliteProvider($faker2);
928        $sql2 = $provider2->selectStatement(maxDepth: 6);
929
930        self::assertNotSame($sql1, $sql2, 'Different seeds should produce different SQL');
931    }
932
933    public function testGrammarDrivenOutputIsNonEmpty(): void
934    {
935        $faker = Factory::create();
936        $faker->seed(12345);
937        $provider = new SqliteProvider($faker);
938
939        $sql = $provider->sql(maxDepth: 8);
940
941        self::assertNotSame('', $sql, 'Generated SQL must not be empty');
942    }
943
944    public function testSimpleStatementReturnsNonEmpty(): void
945    {
946        $faker = Factory::create();
947        $faker->seed(12345);
948        $provider = new SqliteProvider($faker);
949
950        self::assertNotSame('', $provider->simpleStatement(maxDepth: 6));
951    }
952
953    public function testAlterTableOperations(): void
954    {
955        $faker = Factory::create();
956        $faker->seed(12345);
957        $provider = new SqliteProvider($faker);
958
959        $sql = $provider->alterTableStatement(maxDepth: 6);
960
961        self::assertTrue(
962            str_contains($sql, 'RENAME')
963            || str_contains($sql, 'ADD')
964            || str_contains($sql, 'DROP'),
965            "ALTER TABLE should use RENAME, ADD, or DROP: {$sql}"
966        );
967    }
968
969    public function testDropTableContainsDropTable(): void
970    {
971        $faker = Factory::create();
972        $faker->seed(12345);
973        $provider = new SqliteProvider($faker);
974
975        $sql = $provider->dropTableStatement(maxDepth: 6);
976
977        self::assertSame(
978            ['DROP', 'TABLE'],
979            array_slice((new LexicalGrammar(Factory::create(), 'sqlite-3.47.2'))->tokenize($sql), 0, 2),
980            "DROP TABLE must be present: {$sql}",
981        );
982    }
983
984    public function testInsertContainsInto(): void
985    {
986        $faker = Factory::create();
987        $faker->seed(12345);
988        $provider = new SqliteProvider($faker);
989
990        $sql = $provider->insertStatement(maxDepth: 6);
991
992        self::assertStringContainsString('INTO', $sql, "INSERT must contain INTO: {$sql}");
993    }
994
995    public function testCreateTableContainsTable(): void
996    {
997        $faker = Factory::create();
998        $faker->seed(12345);
999        $provider = new SqliteProvider($faker);
1000
1001        $sql = $provider->createTableStatement(maxDepth: 6);
1002
1003        self::assertStringContainsString('TABLE', $sql, "CREATE TABLE must contain TABLE: {$sql}");
1004    }
1005
1006    /**
1007     * @return iterable<string, array{int}>
1008     */
1009    public static function providerTargetedGenerationSeed(): iterable
1010    {
1011        foreach (range(0, 31) as $seed) {
1012            yield "seed {$seed}" => [$seed];
1013        }
1014    }
1015
1016    /**
1017     * @return iterable<string, array{int, int, string, string}>
1018     */
1019    public static function providerMultiDmlSelection(): iterable
1020    {
1021        yield 'insert' => [0, 0, 'INSERT', 'INSERT'];
1022        yield 'update' => [1, 1, 'UPDATE', 'UPDATE'];
1023        yield 'delete' => [2, 2, 'DELETE', 'DELETE'];
1024    }
1025
1026    /**
1027     * @return iterable<string, array{StatementType}>
1028     */
1029    public static function providerStatementTypeValue(): iterable
1030    {
1031        yield 'Select' => [StatementType::Select];
1032        yield 'Insert' => [StatementType::Insert];
1033        yield 'Update' => [StatementType::Update];
1034        yield 'Delete' => [StatementType::Delete];
1035        yield 'CreateTable' => [StatementType::CreateTable];
1036        yield 'AlterTable' => [StatementType::AlterTable];
1037        yield 'DropTable' => [StatementType::DropTable];
1038    }
1039
1040    public function testPlannerCompilesReusableInstructionsWithoutChangingTheDefaultStart(): void
1041    {
1042        $provider = new SqliteProvider(Factory::create(), 'sqlite-3.47.2');
1043        $plan = (new \SqlFaker\Generation\Choice\BytePlanCompiler())->compile('', $provider->planner());
1044        self::assertNull($plan->startRule());
1045        self::assertSame($provider->generate($plan), $provider->generate($plan));
1046    }
1047}
1048