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