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