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

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