packages/sql-faker/tests/Unit/PostgreSql/PostgreSqlProviderTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\PostgreSql;
6
7use Faker\Factory;
8use Override;
9use PHPUnit\Framework\Attributes\CoversClass;
10use PHPUnit\Framework\Attributes\DataProvider;
11use PHPUnit\Framework\Attributes\Medium;
12use PHPUnit\Framework\Attributes\UsesClass;
13use PHPUnit\Framework\TestCase;
14use SqlFaker\Generation\Derivation\TerminationAnalyzer;
15use SqlFaker\Generation\Plan\GenerationPlan;
16use SqlFaker\Generation\Plan\LexemeConstraint;
17use SqlFaker\Generation\Plan\ProductionPattern;
18use SqlFaker\Generation\Plan\RulePlan;
19use SqlFaker\Generation\SqlGenerator;
20use SqlFaker\Grammar\Model\Grammar;
21use SqlFaker\Grammar\Model\NonTerminal;
22use SqlFaker\Grammar\Model\Production;
23use SqlFaker\Grammar\Model\ProductionRule;
24use SqlFaker\Grammar\Model\Terminal;
25use SqlFaker\Grammar\Model\TerminalInventory;
26use SqlFaker\Grammar\Resource\SqlVersion;
27use SqlFaker\PostgreSql\Generation\GenerationPlans;
28use SqlFaker\PostgreSql\Generation\LexicalGrammar;
29use SqlFaker\PostgreSql\Generation\StatementRule;
30use SqlFaker\PostgreSql\Generation\Value\LiteralGenerator;
31use SqlFaker\PostgreSql\Grammar\PgGrammar;
32use SqlFaker\PostgreSql\PostgreSqlProvider;
33
34#[CoversClass(PostgreSqlProvider::class)]
35#[CoversClass(LiteralGenerator::class)]
36#[CoversClass(SqlGenerator::class)]
37#[CoversClass(PgGrammar::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\PostgreSql\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\PostgreSql\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\PostgreSql\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\Generation\Lookahead\PgLookahead::class)]
130#[UsesClass(\SqlFaker\PostgreSql\Generation\Tokenization\PgTokenizer::class)]
131#[UsesClass(\SqlFaker\Sqlite\Generation\GenerationContext::class)]
132#[UsesClass(\SqlFaker\Sqlite\Generation\Lexeme\DefinitionFactory::class)]
133#[UsesClass(\SqlFaker\Sqlite\Generation\Lexeme\JoinLexemeGenerator::class)]
134#[UsesClass(\SqlFaker\Sqlite\Generation\Lexeme\JoinModifiers::class)]
135#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\IdentifierListRule::class)]
136#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\JoinRule::class)]
137#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\RewriteDefinitions::class)]
138#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\StrictTableRule::class)]
139#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\TableOptionRule::class)]
140#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\WindowFrameRule::class)]
141#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\WithoutRowidRule::class)]
142#[UsesClass(\SqlFaker\Sqlite\Generation\GrammarAdaptation::class)]
143#[UsesClass(\SqlFaker\Sqlite\Grammar\SqliteGrammar::class)]
144#[UsesClass(\SqlFaker\Sqlite\Generation\LexicalGrammar::class)]
145#[UsesClass(\SqlFaker\Sqlite\Generation\Tokenization\SqliteTokenizer::class)]
146#[UsesClass(\SqlFaker\PostgreSql\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\PostgreSql\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\PostgreSql\Generation\Value\IdentifierDomain::class)]
223#[UsesClass(\SqlFaker\PostgreSql\Generation\Value\QuotedDomain::class)]
224#[UsesClass(\SqlFaker\PostgreSql\Generation\Value\OperatorDomain::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\MySql\Generation\Value\RadixDomain::class)]
229#[UsesClass(\SqlFaker\Generation\Value\RepeatDomain::class)]
230#[UsesClass(\SqlFaker\Generation\Value\Utf8::class)]
231#[UsesClass(\SqlFaker\PostgreSql\Generation\Lexeme\LexicalDefinition::class)]
232#[UsesClass(RulePlan::class)]
233#[UsesClass(LexemeConstraint::class)]
234#[UsesClass(\SqlFaker\Generation\Plan\Compilation\GrammarCompiler::class)]
235#[UsesClass(\SqlFaker\Generation\Plan\Compilation\Scope::class)]
236#[UsesClass(\SqlFaker\Generation\Plan\Compilation\PreparedGrammar::class)]
237#[UsesClass(\SqlFaker\Generation\Plan\Compilation\ScopedGeneration::class)]
238final class PostgreSqlProviderTest extends TestCase
239{
240 #[DataProvider('providerTargetedGenerationSeed')]
241 public function testPartitionOfStatementGeneratesRangeChildDdl(int $seed): void
242 {
243 $faker = Factory::create();
244 $faker->seed($seed);
245 $provider = new PostgreSqlProvider($faker);
246 $sql = $provider->partitionOfStatement();
247 $faker->seed($seed);
248
249 $tokens = (new LexicalGrammar($faker, 'pg-17.2'))->tokenize($sql);
250
251 self::assertSame($sql, $provider->partitionOfStatement(40));
252 self::assertContains('PARTITION', $tokens);
253 self::assertContains('FROM', $tokens);
254 self::assertContains('TO', $tokens);
255 }
256
257 #[DataProvider('providerTargetedGenerationSeed')]
258 public function testInsertFunctionUpsertStatementDerivesFunctionExpressionFromGrammar(int $seed): void
259 {
260 $faker = Factory::create();
261 $faker->seed($seed);
262 $provider = new PostgreSqlProvider($faker);
263 $sql = $provider->insertFunctionUpsertStatement();
264 $faker->seed($seed);
265
266 $tokens = (new LexicalGrammar($faker, 'pg-17.2'))
267 ->tokenize($sql);
268 $values = array_search('VALUES', $tokens, true);
269 $conflict = array_search('CONFLICT', $tokens, true);
270 $set = array_search('SET', $tokens, true);
271 $functionOpen = array_search('(', array_slice($tokens, (int) $set, null, true), true);
272
273 self::assertSame($sql, $provider->insertFunctionUpsertStatement(40));
274 self::assertIsInt($values);
275 self::assertIsInt($conflict);
276 self::assertIsInt($set);
277 self::assertIsInt($functionOpen);
278 self::assertLessThan($conflict, $values);
279 self::assertGreaterThan($set, $functionOpen);
280 self::assertContains('UPDATE', $tokens);
281 }
282
283 #[DataProvider('providerTargetedGenerationSeed')]
284 public function testPartialIndexUpsertStatementIncludesArbiterPredicate(int $seed): void
285 {
286 $faker = Factory::create();
287 $faker->seed($seed);
288 $provider = new PostgreSqlProvider($faker);
289 $sql = $provider->partialIndexUpsertStatement();
290 $faker->seed($seed);
291 $tokens = (new LexicalGrammar($faker, 'pg-17.2'))
292 ->tokenize($sql);
293
294 self::assertSame($sql, $provider->partialIndexUpsertStatement(40));
295 $conflict = array_search('CONFLICT', $tokens, true);
296 $where = array_search('WHERE', $tokens, true);
297 $update = array_search('UPDATE', $tokens, true);
298 self::assertIsInt($conflict);
299 self::assertIsInt($where);
300 self::assertIsInt($update);
301 self::assertGreaterThan($conflict, $where);
302 self::assertGreaterThan($where, $update);
303 }
304
305 #[DataProvider('providerTargetedGenerationSeed')]
306 public function testFullTextSearchStatementDerivesMatchOperatorFromGrammarAndLexer(int $seed): void
307 {
308 $faker = Factory::create();
309 $faker->seed($seed);
310 $provider = new PostgreSqlProvider($faker);
311 $sql = $provider->fullTextSearchStatement();
312 $faker->seed($seed);
313 $tokens = (new LexicalGrammar($faker, 'pg-17.2'))->tokenize($sql);
314 $where = array_search('WHERE', $tokens, true);
315
316 self::assertSame($sql, $provider->fullTextSearchStatement(40));
317 self::assertSame('SELECT', $tokens[0]);
318 self::assertContains('FROM', $tokens);
319 self::assertIsInt($where);
320 self::assertSame(['IDENT', 'Op', 'IDENT'], array_slice($tokens, $where + 1, 3));
321 self::assertStringContainsString('@@', $sql);
322 }
323
324 #[DataProvider('providerTargetedGenerationSeed')]
325 public function testTemporaryTableStatement(int $seed): void
326 {
327 $faker = Factory::create();
328 $faker->seed($seed);
329 $provider = new PostgreSqlProvider($faker);
330 $sql = $provider->temporaryTableStatement();
331 $faker->seed($seed);
332
333 $tokens = (new LexicalGrammar($faker, 'pg-17.2'))
334 ->tokenize($sql);
335
336 self::assertSame($sql, $provider->temporaryTableStatement(40));
337 self::assertSame('CREATE', $tokens[0]);
338 self::assertContains('TEMP', $tokens);
339 self::assertContains('TABLE', $tokens);
340 }
341
342 #[DataProvider('providerTargetedGenerationSeed')]
343 public function testViewStatement(int $seed): void
344 {
345 $faker = Factory::create();
346 $faker->seed($seed);
347 $provider = new PostgreSqlProvider($faker);
348 $sql = $provider->viewStatement();
349 $faker->seed($seed);
350 $tokens = (new LexicalGrammar($faker, 'pg-17.2'))
351 ->tokenize($sql);
352
353 self::assertSame($sql, $provider->viewStatement(40));
354 self::assertSame('CREATE', $tokens[0]);
355 self::assertContains('VIEW', $tokens);
356 self::assertNotSame([], array_intersect(['SELECT', 'VALUES', 'TABLE'], $tokens));
357 }
358
359 #[DataProvider('providerTargetedGenerationSeed')]
360 public function testGeneratedColumnStatement(int $seed): void
361 {
362 $faker = Factory::create();
363 $faker->seed($seed);
364 $provider = new PostgreSqlProvider($faker);
365 $sql = $provider->generatedColumnStatement();
366 $faker->seed($seed);
367 $tokens = (new LexicalGrammar($faker, 'pg-17.2'))
368 ->tokenize($sql);
369
370 self::assertSame($sql, $provider->generatedColumnStatement(40));
371 self::assertContains('GENERATED', $tokens);
372 self::assertContains('STORED', $tokens);
373 self::assertContains('AS', $tokens);
374 }
375
376 #[DataProvider('providerTargetedGenerationSeed')]
377 public function testForeignKeyCascadeStatement(int $seed): void
378 {
379 $faker = Factory::create();
380 $faker->seed($seed);
381 $provider = new PostgreSqlProvider($faker);
382 $sql = $provider->foreignKeyCascadeStatement();
383 $faker->seed($seed);
384 $tokens = (new LexicalGrammar($faker, 'pg-17.2'))
385 ->tokenize($sql);
386
387 self::assertSame($sql, $provider->foreignKeyCascadeStatement(40));
388 self::assertContains('FOREIGN', $tokens);
389 self::assertContains('REFERENCES', $tokens);
390 self::assertStringContainsString('ON UPDATE CASCADE', implode(' ', $tokens));
391 self::assertStringContainsString('ON DELETE_P CASCADE', implode(' ', $tokens));
392 }
393
394 #[Override]
395 protected function setUp(): void
396 {
397 parent::setUp();
398 gc_collect_cycles();
399 }
400
401 public function testRegistersItselfWithTheFakerGenerator(): void
402 {
403 $faker = Factory::create();
404 $provider = new PostgreSqlProvider($faker);
405
406 /** @var list<object> $providers */
407 $providers = $faker->getProviders();
408 self::assertContains($provider, $providers);
409
410 $identifier = $provider->identifier(3);
411 self::assertNotSame('', $identifier);
412 }
413
414 public function testSql(): void
415 {
416 $faker = Factory::create();
417 $faker->seed(12345);
418 $provider = new PostgreSqlProvider($faker);
419
420 $result = $provider->sql(maxDepth: 6);
421
422 self::assertNotSame('', $result);
423 }
424
425 public function testSqlWithStatementRule(): void
426 {
427 $faker = Factory::create();
428 $faker->seed(12345);
429 $provider = new PostgreSqlProvider($faker);
430
431 $result = $provider->sql(StatementRule::Select, maxDepth: 6);
432
433 self::assertMatchesRegularExpression('/SELECT|VALUES|TABLE/', $result);
434 }
435
436 public function testSqlWithNullStatementRuleUsesRandom(): void
437 {
438 $faker = Factory::create();
439 $faker->seed(12345);
440 $provider = new PostgreSqlProvider($faker);
441
442 $result = $provider->sql(null, maxDepth: 6);
443
444 self::assertNotSame('', $result);
445 }
446
447 public function testSqlWithMaxDepth(): void
448 {
449 $faker = Factory::create();
450 $faker->seed(12345);
451 $provider = new PostgreSqlProvider($faker);
452
453 $result = $provider->sql(maxDepth: 8);
454
455 self::assertNotSame('', $result);
456 }
457
458 public function testSelectStatement(): void
459 {
460 $faker = Factory::create();
461 $faker->seed(12345);
462 $provider = new PostgreSqlProvider($faker);
463
464 $result = $provider->selectStatement(maxDepth: 6);
465
466 self::assertNotEmpty($result);
467 self::assertMatchesRegularExpression('/SELECT|VALUES|TABLE/', $result);
468 }
469
470 public function testInsertStatement(): void
471 {
472 $faker = Factory::create();
473 $faker->seed(12345);
474 $provider = new PostgreSqlProvider($faker);
475
476 $result = $provider->insertStatement(maxDepth: 6);
477
478 self::assertNotEmpty($result);
479 self::assertStringContainsString('INSERT', $result);
480 }
481
482 public function testUpdateStatement(): void
483 {
484 $faker = Factory::create();
485 $faker->seed(12345);
486 $provider = new PostgreSqlProvider($faker);
487
488 $result = $provider->updateStatement(maxDepth: 6);
489
490 self::assertNotEmpty($result);
491 self::assertStringContainsString('UPDATE', $result);
492 }
493
494 public function testDeleteStatement(): void
495 {
496 $faker = Factory::create();
497 $faker->seed(12345);
498 $provider = new PostgreSqlProvider($faker);
499
500 $result = $provider->deleteStatement(maxDepth: 6);
501
502 self::assertNotEmpty($result);
503 self::assertStringContainsString('DELETE', $result);
504 }
505
506 public function testCreateTableStatement(): void
507 {
508 $faker = Factory::create();
509 $faker->seed(12345);
510 $provider = new PostgreSqlProvider($faker);
511
512 $result = $provider->createTableStatement(maxDepth: 6);
513
514 self::assertNotEmpty($result);
515 self::assertStringContainsString('CREATE', $result);
516 self::assertStringContainsString('TABLE', $result);
517 }
518
519 public function testCreateTableAsStatement(): void
520 {
521 $faker = Factory::create();
522 $faker->seed(12345);
523 $provider = new PostgreSqlProvider($faker);
524
525 $result = $provider->createTableAsStatement(maxDepth: 8);
526
527 self::assertStringContainsString('CREATE', $result);
528 self::assertStringContainsString('TABLE', $result);
529 self::assertStringContainsString('AS', $result);
530 self::assertMatchesRegularExpression('/\b(?:SELECT|VALUES|TABLE)\b/', $result);
531 }
532
533 public function testCreateDomainStatement(): void
534 {
535 $faker = Factory::create();
536 $faker->seed(12345);
537 $provider = new PostgreSqlProvider($faker);
538
539 $result = $provider->createDomainStatement(maxDepth: 8);
540
541 self::assertStringContainsString('CREATE', $result);
542 self::assertStringContainsString('DOMAIN', $result);
543 }
544
545 public function testAlterTableStatement(): void
546 {
547 $faker = Factory::create();
548 $faker->seed(12345);
549 $provider = new PostgreSqlProvider($faker);
550
551 $result = $provider->alterTableStatement(maxDepth: 6);
552
553 self::assertNotEmpty($result);
554 self::assertStringContainsString('ALTER', $result);
555 }
556
557 public function testDropTableStatement(): void
558 {
559 $faker = Factory::create();
560 $faker->seed(12345);
561 $provider = new PostgreSqlProvider($faker);
562
563 $result = $provider->dropTableStatement(maxDepth: 6);
564
565 self::assertNotEmpty($result);
566 self::assertStringContainsString('DROP', $result);
567 }
568
569 public function testTruncateStatement(): void
570 {
571 $faker = Factory::create();
572 $faker->seed(12345);
573 $provider = new PostgreSqlProvider($faker);
574
575 $result = $provider->truncateStatement(maxDepth: 6);
576
577 self::assertNotEmpty($result);
578 self::assertStringContainsString('TRUNCATE', $result);
579 }
580
581 public function testCreateIndexStatement(): void
582 {
583 $faker = Factory::create();
584 $faker->seed(12345);
585 $provider = new PostgreSqlProvider($faker);
586
587 $result = $provider->createIndexStatement(maxDepth: 6);
588
589 self::assertNotEmpty($result);
590 self::assertStringContainsString('CREATE', $result);
591 self::assertStringContainsString('INDEX', $result);
592 }
593
594 public function testTransactionStatement(): void
595 {
596 $faker = Factory::create();
597 $faker->seed(12345);
598 $provider = new PostgreSqlProvider($faker);
599
600 $result = $provider->transactionStatement(maxDepth: 6);
601
602 self::assertNotEmpty($result);
603 self::assertMatchesRegularExpression('/BEGIN|COMMIT|ROLLBACK|ABORT|END|START|SAVEPOINT|RELEASE|PREPARE/', $result);
604 }
605
606 public function testExpr(): void
607 {
608 $faker = Factory::create();
609 $faker->seed(12345);
610 $provider = new PostgreSqlProvider($faker);
611
612 $result = $provider->expr(maxDepth: 3);
613
614 self::assertNotSame('', $result);
615 }
616
617 public function testSimpleExpr(): void
618 {
619 $faker = Factory::create();
620 $faker->seed(12345);
621 $provider = new PostgreSqlProvider($faker);
622
623 $result = $provider->simpleExpr(maxDepth: 3);
624
625 self::assertNotSame('', $result);
626 }
627
628 public function testLiteral(): void
629 {
630 $faker = Factory::create();
631 $faker->seed(12345);
632 $provider = new PostgreSqlProvider($faker);
633
634 $result = $provider->literal(maxDepth: 3);
635
636 self::assertNotSame('', $result);
637 }
638
639 public function testWhereClause(): void
640 {
641 $faker = Factory::create();
642 $faker->seed(0);
643 $provider = new PostgreSqlProvider($faker);
644
645 $result = $provider->whereClause(maxDepth: 1);
646
647 self::assertSame('', $result);
648 }
649
650 public function testSortClause(): void
651 {
652 $faker = Factory::create();
653 $faker->seed(12345);
654 $provider = new PostgreSqlProvider($faker);
655
656 $result = $provider->sortClause(maxDepth: 3);
657
658 self::assertSame(['ORDER', 'BY'], array_slice((new LexicalGrammar($faker, 'pg-17.2'))->tokenize($result), 0, 2));
659 }
660
661 public function testSelectLimit(): void
662 {
663 $faker = Factory::create();
664 $faker->seed(12345);
665 $provider = new PostgreSqlProvider($faker);
666
667 $result = $provider->selectLimit(maxDepth: 3);
668
669 self::assertMatchesRegularExpression('/LIMIT|OFFSET|FETCH/', $result);
670 }
671
672 public function testTableRef(): void
673 {
674 $faker = Factory::create();
675 $faker->seed(12345);
676 $provider = new PostgreSqlProvider($faker);
677
678 $result = $provider->tableRef(maxDepth: 3);
679
680 self::assertNotSame('', $result);
681 }
682
683 public function testJoinedTable(): void
684 {
685 $faker = Factory::create();
686 $faker->seed(12345);
687 $provider = new PostgreSqlProvider($faker);
688
689 $result = $provider->joinedTable(maxDepth: 3);
690
691 self::assertStringContainsString('JOIN', $result);
692 }
693
694 public function testQualifiedName(): void
695 {
696 $faker = Factory::create();
697 $faker->seed(12345);
698 $provider = new PostgreSqlProvider($faker);
699
700 $result = $provider->qualifiedName(maxDepth: 3);
701
702 self::assertNotSame('', $result);
703 }
704
705 public function testSubquery(): void
706 {
707 $faker = Factory::create();
708 $faker->seed(12345);
709 $provider = new PostgreSqlProvider($faker);
710
711 $result = $provider->subquery(maxDepth: 3);
712
713 self::assertStringContainsString('(', $result);
714 self::assertStringContainsString(')', $result);
715 }
716
717 public function testWithClause(): void
718 {
719 $faker = Factory::create();
720 $faker->seed(12345);
721 $provider = new PostgreSqlProvider($faker);
722
723 $result = $provider->withClause(maxDepth: 3);
724
725 self::assertStringContainsString('WITH', $result);
726 }
727
728 public function testForeignKeyConstraint(): void
729 {
730 $faker = Factory::create();
731 $faker->seed(12345);
732 $provider = new PostgreSqlProvider($faker, 'pg-17.2');
733
734 $result = $provider->foreignKeyConstraint(1);
735
736 self::assertSame(
737 ['CONSTRAINT', 'IDENT', 'FOREIGN', 'KEY', '(', 'IDENT', ')', 'REFERENCES', 'IDENT', '(', 'IDENT', ')'],
738 (new LexicalGrammar($faker, 'pg-17.2'))->tokenize($result),
739 );
740 }
741
742 public function testIdentifier(): void
743 {
744 $faker = Factory::create();
745 $faker->seed(12345);
746 $provider = new PostgreSqlProvider($faker);
747
748 $result = $provider->identifier(3);
749
750 self::assertNotSame('', $result);
751 }
752
753 public function testQuotedIdentifier(): void
754 {
755 $faker = Factory::create();
756 $faker->seed(12345);
757 $provider = new PostgreSqlProvider($faker);
758
759 $result = $provider->quotedIdentifier();
760
761 self::assertMatchesRegularExpression('/^"[a-z_][a-z0-9_]*"$/', $result);
762 }
763
764 public function testStringLiteral(): void
765 {
766 $faker = Factory::create();
767 $faker->seed(12345);
768 $provider = new PostgreSqlProvider($faker);
769
770 $result = $provider->stringLiteral();
771
772 self::assertMatchesRegularExpression("/^'[a-zA-Z0-9_]{1,255}'$/", $result);
773 }
774
775 public function testIntegerLiteral(): void
776 {
777 $faker = Factory::create();
778 $faker->seed(12345);
779 $provider = new PostgreSqlProvider($faker);
780
781 $result = $provider->integerLiteral();
782
783 self::assertMatchesRegularExpression('/^[1-9]\d*$/', $result);
784 }
785
786 public function testDecimalLiteral(): void
787 {
788 $faker = Factory::create();
789 $faker->seed(12345);
790 $provider = new PostgreSqlProvider($faker);
791
792 $result = $provider->decimalLiteral();
793
794 self::assertMatchesRegularExpression('/^\d+\.\d{2,}$/', $result);
795 }
796
797 public function testFloatLiteral(): void
798 {
799 $faker = Factory::create();
800 $faker->seed(12345);
801 $provider = new PostgreSqlProvider($faker);
802
803 $result = $provider->floatLiteral();
804
805 self::assertMatchesRegularExpression('/^\d+\.\d+e-?\d+$/', $result);
806 }
807
808 public function testHexLiteral(): void
809 {
810 $faker = Factory::create();
811 $faker->seed(12345);
812 $provider = new PostgreSqlProvider($faker);
813
814 $result = $provider->hexLiteral();
815
816 self::assertMatchesRegularExpression("/^X'[0-9a-f]{1,16}'$/", $result);
817 }
818
819 public function testBinaryLiteral(): void
820 {
821 $faker = Factory::create();
822 $faker->seed(12345);
823 $provider = new PostgreSqlProvider($faker);
824
825 $result = $provider->binaryLiteral();
826
827 self::assertMatchesRegularExpression("/^B'[01]{1,64}'$/", $result);
828 }
829
830 public function testDollarQuotedString(): void
831 {
832 $faker = Factory::create();
833 $faker->seed(12345);
834 $provider = new PostgreSqlProvider($faker);
835
836 $result = $provider->dollarQuotedString();
837
838 self::assertMatchesRegularExpression('/^\$\$[a-zA-Z0-9_]{1,255}\$\$$/', $result);
839 }
840
841 public function testParameterMarker(): void
842 {
843 $faker = Factory::create();
844 $faker->seed(12345);
845 $provider = new PostgreSqlProvider($faker);
846
847 $result = $provider->parameterMarker();
848
849 self::assertMatchesRegularExpression('/^\$\d+$/', $result);
850 }
851
852 public function testQuotedIdentifierDefaultMatchesExplicit(): void
853 {
854 $faker = Factory::create();
855 $p = new PostgreSqlProvider($faker);
856 $faker->seed(42);
857 $a = $p->quotedIdentifier();
858 $faker->seed(42);
859 self::assertSame($a, $p->quotedIdentifier(1, 63));
860 }
861
862 public function testStringLiteralDefaultMatchesExplicit(): void
863 {
864 $faker = Factory::create();
865 $p = new PostgreSqlProvider($faker);
866 $faker->seed(42);
867 $a = $p->stringLiteral();
868 $faker->seed(42);
869 self::assertSame($a, $p->stringLiteral(1, 255));
870 }
871
872 public function testIntegerLiteralDefaultMatchesExplicit(): void
873 {
874 $faker = Factory::create();
875 $p = new PostgreSqlProvider($faker);
876 $faker->seed(42);
877 $a = $p->integerLiteral();
878 $faker->seed(42);
879 self::assertSame($a, $p->integerLiteral(1, 2147483647));
880 }
881
882 public function testDecimalLiteralDefaultMatchesExplicit(): void
883 {
884 $faker = Factory::create();
885 $p = new PostgreSqlProvider($faker);
886 $faker->seed(42);
887 $a = $p->decimalLiteral();
888 $faker->seed(42);
889 self::assertSame($a, $p->decimalLiteral(10, 2));
890 }
891
892 public function testFloatLiteralDefaultMatchesExplicit(): void
893 {
894 $faker = Factory::create();
895 $p = new PostgreSqlProvider($faker);
896 $faker->seed(42);
897 $a = $p->floatLiteral();
898 $faker->seed(42);
899 self::assertSame($a, $p->floatLiteral(10, 2, -307, 308));
900 }
901
902 public function testHexLiteralDefaultMatchesExplicit(): void
903 {
904 $faker = Factory::create();
905 $p = new PostgreSqlProvider($faker);
906 $faker->seed(42);
907 $a = $p->hexLiteral();
908 $faker->seed(42);
909 self::assertSame($a, $p->hexLiteral(1, 16));
910 }
911
912 public function testBinaryLiteralDefaultMatchesExplicit(): void
913 {
914 $faker = Factory::create();
915 $p = new PostgreSqlProvider($faker);
916 $faker->seed(42);
917 $a = $p->binaryLiteral();
918 $faker->seed(42);
919 self::assertSame($a, $p->binaryLiteral(1, 64));
920 }
921
922 public function testDollarQuotedStringDefaultMatchesExplicit(): void
923 {
924 $faker = Factory::create();
925 $p = new PostgreSqlProvider($faker);
926 $faker->seed(42);
927 $a = $p->dollarQuotedString();
928 $faker->seed(42);
929 self::assertSame($a, $p->dollarQuotedString(1, 255));
930 }
931
932 public function testParameterMarkerDefaultMatchesExplicit(): void
933 {
934 $faker = Factory::create();
935 $p = new PostgreSqlProvider($faker);
936 $faker->seed(42);
937 $a = $p->parameterMarker();
938 $faker->seed(42);
939 self::assertSame($a, $p->parameterMarker(1, 99));
940 }
941
942 public function testQuotedIdentifierCustomLength(): void
943 {
944 $faker = Factory::create();
945 $faker->seed(12345);
946 $provider = new PostgreSqlProvider($faker);
947
948 $result = $provider->quotedIdentifier(5, 10);
949
950 self::assertMatchesRegularExpression('/^"[a-z_][a-z0-9_]{4,9}"$/', $result);
951 }
952
953 public function testStringLiteralCustomLength(): void
954 {
955 $faker = Factory::create();
956 $faker->seed(12345);
957 $provider = new PostgreSqlProvider($faker);
958
959 $result = $provider->stringLiteral(3, 8);
960 $content = substr($result, 1, -1);
961
962 self::assertGreaterThanOrEqual(3, strlen($content));
963 self::assertLessThanOrEqual(8, strlen($content));
964 }
965
966 public function testIntegerLiteralCustomRange(): void
967 {
968 $faker = Factory::create();
969 $faker->seed(12345);
970 $provider = new PostgreSqlProvider($faker);
971
972 $result = $provider->integerLiteral(100, 500);
973
974 self::assertGreaterThanOrEqual(100, (int) $result);
975 self::assertLessThanOrEqual(500, (int) $result);
976 }
977
978 public function testDecimalLiteralCustomPrecision(): void
979 {
980 $faker = Factory::create();
981 $faker->seed(12345);
982 $provider = new PostgreSqlProvider($faker);
983
984 $result = $provider->decimalLiteral(5, 2);
985
986 self::assertMatchesRegularExpression('/^\d+\.\d{2,}$/', $result);
987 }
988
989 public function testFloatLiteralCustomParams(): void
990 {
991 $faker = Factory::create();
992 $faker->seed(12345);
993 $provider = new PostgreSqlProvider($faker);
994
995 $result = $provider->floatLiteral(5, 2, -10, 10);
996
997 self::assertMatchesRegularExpression('/^\d+\.\d+e-?\d+$/', $result);
998 }
999
1000 public function testHexLiteralCustomLength(): void
1001 {
1002 $faker = Factory::create();
1003 $faker->seed(12345);
1004 $provider = new PostgreSqlProvider($faker);
1005
1006 $result = $provider->hexLiteral(4, 8);
1007
1008 self::assertMatchesRegularExpression("/^X'[0-9a-f]{4,8}'$/", $result);
1009 }
1010
1011 public function testBinaryLiteralCustomLength(): void
1012 {
1013 $faker = Factory::create();
1014 $faker->seed(12345);
1015 $provider = new PostgreSqlProvider($faker);
1016
1017 $result = $provider->binaryLiteral(8, 16);
1018
1019 self::assertMatchesRegularExpression("/^B'[01]{8,16}'$/", $result);
1020 }
1021
1022 public function testDollarQuotedStringCustomLength(): void
1023 {
1024 $faker = Factory::create();
1025 $faker->seed(12345);
1026 $provider = new PostgreSqlProvider($faker);
1027
1028 $result = $provider->dollarQuotedString(2, 6);
1029 $content = substr($result, 2, -2);
1030
1031 self::assertGreaterThanOrEqual(2, strlen($content));
1032 self::assertLessThanOrEqual(6, strlen($content));
1033 }
1034
1035 public function testParameterMarkerCustomRange(): void
1036 {
1037 $faker = Factory::create();
1038 $faker->seed(12345);
1039 $provider = new PostgreSqlProvider($faker);
1040
1041 $result = $provider->parameterMarker(1, 5);
1042
1043 self::assertMatchesRegularExpression('/^\$[1-5]$/', $result);
1044 }
1045
1046 public function testSeededGenerationIsReproducible(): void
1047 {
1048 $faker1 = Factory::create();
1049 $provider1 = new PostgreSqlProvider($faker1);
1050 $faker1->seed(99999);
1051 $sql1 = $provider1->sql(maxDepth: 8);
1052
1053 $faker2 = Factory::create();
1054 $provider2 = new PostgreSqlProvider($faker2);
1055 $faker2->seed(99999);
1056 $sql2 = $provider2->sql(maxDepth: 8);
1057
1058 self::assertSame($sql1, $sql2, 'Same seed should produce same output');
1059 }
1060
1061 #[DataProvider('providerStatementRuleValue')]
1062 public function testSqlWithAllStatementRules(StatementRule $type): void
1063 {
1064 $faker = Factory::create();
1065 $faker->seed(12345);
1066 $provider = new PostgreSqlProvider($faker);
1067
1068 $result = $provider->sql($type, maxDepth: 6);
1069
1070 self::assertNotSame('', $result);
1071 }
1072
1073 public function testSelectContainsSelectOrValuesOrTable(): void
1074 {
1075 $faker = Factory::create();
1076 $faker->seed(12345);
1077 $provider = new PostgreSqlProvider($faker);
1078
1079 $sql = $provider->selectStatement(maxDepth: 6);
1080
1081 self::assertMatchesRegularExpression('/SELECT|VALUES|TABLE/', $sql);
1082 }
1083
1084 public function testUpdateContainsSetClause(): void
1085 {
1086 $faker = Factory::create();
1087 $faker->seed(12345);
1088 $provider = new PostgreSqlProvider($faker);
1089
1090 $result = $provider->updateStatement(maxDepth: 6);
1091
1092 self::assertStringContainsString('UPDATE', $result);
1093 self::assertStringContainsString('SET', $result);
1094 }
1095
1096 public function testDeleteContainsFromKeyword(): void
1097 {
1098 $faker = Factory::create();
1099 $faker->seed(12345);
1100 $provider = new PostgreSqlProvider($faker);
1101
1102 $result = $provider->deleteStatement(maxDepth: 6);
1103
1104 self::assertStringContainsString('DELETE', $result);
1105 }
1106
1107 public function testMultipleGenerationsReturnDifferentResults(): void
1108 {
1109 $faker1 = Factory::create();
1110 $faker1->seed(1);
1111 $provider1 = new PostgreSqlProvider($faker1);
1112 $sql1 = $provider1->selectStatement(maxDepth: 3);
1113
1114 $faker2 = Factory::create();
1115 $faker2->seed(2);
1116 $provider2 = new PostgreSqlProvider($faker2);
1117 $sql2 = $provider2->selectStatement(maxDepth: 3);
1118
1119 self::assertNotSame($sql1, $sql2, 'Different seeds should produce different SQL');
1120 }
1121
1122 public function testGrammarDrivenOutputIsNonEmpty(): void
1123 {
1124 $faker = Factory::create();
1125 $faker->seed(12345);
1126 $provider = new PostgreSqlProvider($faker);
1127
1128 $result = $provider->sql(maxDepth: 8);
1129
1130 self::assertNotSame('', $result);
1131 }
1132
1133 #[DataProvider('providerTargetedGenerationSeed')]
1134 public function testTableSampleStatementDerivesSamplingClauseFromGrammar(int $seed): void
1135 {
1136 $faker = Factory::create();
1137 $faker->seed($seed);
1138 $provider = new PostgreSqlProvider($faker);
1139 $sql = $provider->tableSampleStatement();
1140 $faker->seed($seed);
1141
1142 $tokens = (new LexicalGrammar($faker, 'pg-17.2'))->tokenize($sql);
1143
1144 self::assertSame($sql, $provider->tableSampleStatement(40));
1145 self::assertSame('SELECT', $tokens[0]);
1146 self::assertContains('FROM', $tokens);
1147 self::assertContains('TABLESAMPLE', $tokens);
1148 }
1149
1150 #[DataProvider('providerTargetedGenerationSeed')]
1151 public function testDoStatementDerivesAnonymousBlockFromGrammar(int $seed): void
1152 {
1153 $faker = Factory::create();
1154 $faker->seed($seed);
1155 $provider = new PostgreSqlProvider($faker);
1156 $sql = $provider->doStatement();
1157 $faker->seed($seed);
1158
1159 self::assertSame($sql, $provider->doStatement(40));
1160 self::assertSame(
1161 ['DO', 'SCONST'],
1162 (new LexicalGrammar($faker, 'pg-17.2'))->tokenize($sql),
1163 );
1164 }
1165
1166 #[DataProvider('providerTargetedGenerationSeed')]
1167 public function testMergeStatementDerivesEveryActionFromGrammar(int $seed): void
1168 {
1169 $faker = Factory::create();
1170 $faker->seed($seed);
1171 $provider = new PostgreSqlProvider($faker);
1172 $sql = $provider->mergeStatement();
1173 $faker->seed($seed);
1174 $tokens = (new LexicalGrammar($faker, 'pg-17.2'))->tokenize($sql);
1175 $normalized = implode(' ', $tokens);
1176 $delete = strpos($normalized, 'WHEN MATCHED THEN DELETE_P');
1177 $nothing = strpos($normalized, 'WHEN MATCHED THEN DO NOTHING');
1178 $update = strpos($normalized, 'WHEN MATCHED THEN UPDATE');
1179 $insert = strpos($normalized, 'WHEN NOT MATCHED THEN INSERT');
1180
1181 self::assertSame($sql, $provider->mergeStatement(40));
1182 self::assertContains('MERGE', $tokens);
1183 self::assertGreaterThanOrEqual(
1184 4,
1185 count(array_filter($tokens, static fn (string $token): bool => $token === 'WHEN')),
1186 );
1187 self::assertIsInt($delete);
1188 self::assertIsInt($nothing);
1189 self::assertIsInt($update);
1190 self::assertIsInt($insert);
1191 self::assertLessThan($nothing, $delete);
1192 self::assertLessThan($update, $nothing);
1193 self::assertLessThan($insert, $update);
1194 }
1195
1196 public function testCopyStatementUsesOfficialGrammarRule(): void
1197 {
1198 $faker = Factory::create();
1199 $faker->seed(12345);
1200 $provider = new PostgreSqlProvider($faker);
1201
1202 self::assertStringContainsString('COPY', $provider->copyStatement(maxDepth: 8));
1203 }
1204
1205 #[DataProvider('providerNullableSimpleStatementSeed')]
1206 public function testSimpleStatementReturnsNonEmpty(int $seed): void
1207 {
1208 $faker = Factory::create();
1209 $faker->seed($seed);
1210 $provider = new PostgreSqlProvider($faker);
1211
1212 self::assertNotSame('', $provider->simpleStatement(maxDepth: 20));
1213 }
1214
1215 public function testSimpleStatementReturnsNonEmptyAtMinimumDepth(): void
1216 {
1217 $faker = Factory::create();
1218 $faker->seed(0);
1219 $provider = new PostgreSqlProvider($faker);
1220
1221 self::assertNotSame('', $provider->simpleStatement(maxDepth: 1));
1222 }
1223
1224 /**
1225 * @return iterable<string, array{int}>
1226 */
1227 public static function providerNullableSimpleStatementSeed(): iterable
1228 {
1229 yield 'PHP 8.1 and 8.2 random mode' => [252];
1230 yield 'PHP 8.3 and later random mode' => [68];
1231 }
1232
1233 /**
1234 * @return iterable<string, array{int}>
1235 */
1236 public static function providerTargetedGenerationSeed(): iterable
1237 {
1238 foreach (range(0, 31) as $seed) {
1239 yield "seed {$seed}" => [$seed];
1240 }
1241 }
1242
1243 /**
1244 * @return iterable<string, array{StatementRule}>
1245 */
1246 public static function providerStatementRuleValue(): iterable
1247 {
1248 yield 'Select' => [StatementRule::Select];
1249 yield 'Insert' => [StatementRule::Insert];
1250 yield 'Update' => [StatementRule::Update];
1251 yield 'Delete' => [StatementRule::Delete];
1252 yield 'CreateTable' => [StatementRule::CreateTable];
1253 yield 'CreateTableAs' => [StatementRule::CreateTableAs];
1254 yield 'CreateDomain' => [StatementRule::CreateDomain];
1255 yield 'AlterTable' => [StatementRule::AlterTable];
1256 yield 'DropTable' => [StatementRule::DropTable];
1257 }
1258
1259 #[DataProvider('providerTargetedGenerationSeed')]
1260 public function testDomainDmlStatementDerivesDmlFromGrammar(int $seed): void
1261 {
1262 $faker = Factory::create();
1263 $faker->seed($seed);
1264 $provider = new PostgreSqlProvider($faker);
1265 $sql = $provider->domainDmlStatement();
1266 $faker->seed($seed);
1267 $lexer = new LexicalGrammar($faker, 'pg-17.2');
1268
1269 $tokens = $lexer->tokenize($sql);
1270
1271 self::assertSame($sql, $provider->domainDmlStatement(40));
1272 self::assertContains($tokens[0], ['INSERT', 'UPDATE', 'DELETE_P']);
1273 }
1274 public function testPlannerCompilesReusableInstructionsWithoutChangingTheDefaultStart(): void
1275 {
1276 $provider = new PostgreSqlProvider(Factory::create(), 'pg-17.2');
1277 $plan = (new \SqlFaker\Generation\Choice\BytePlanCompiler())->compile('', $provider->planner());
1278 self::assertNull($plan->startRule());
1279 self::assertSame($provider->generate($plan), $provider->generate($plan));
1280 }
1281
1282 /**
1283 * @param GenerationPlan<true> $constraints
1284 */
1285 #[DataProvider('providerScopedInsertPlan')]
1286 public function testGenerateSchemaBoundInsertAndFreezeChoices(GenerationPlan $constraints, int $seed): void
1287 {
1288 $faker = Factory::create();
1289 $faker->seed($seed);
1290 $provider = new PostgreSqlProvider($faker, 'pg-17.2');
1291 self::assertMatchesRegularExpression("/^INSERT INTO users VALUES \( (?:1[0-9]|20) , '(?:Alice|Bob)' \)$/", $provider->generate($constraints));
1292 $frozen = (new \SqlFaker\Generation\Choice\BytePlanCompiler())->compile(str_repeat(chr($seed), 64), $provider->planner(), $constraints);
1293 $sql = $provider->generate($frozen);
1294 $faker->seed(999);
1295 self::assertSame($sql, $provider->generate($frozen));
1296 self::assertMatchesRegularExpression("/^INSERT INTO users VALUES \( (?:1[0-9]|20) , '(?:Alice|Bob)' \)$/", $sql);
1297 }
1298
1299 /**
1300 * @return iterable<string, array{GenerationPlan<true>, int}>
1301 */
1302 public static function providerScopedInsertPlan(): iterable
1303 {
1304 $integer = RulePlan::any()->allowing(ProductionPattern::exactly('c_expr'))->withRule('c_expr', RulePlan::any()->allowing(ProductionPattern::exactly('AexprConst')))->withRule('AexprConst', RulePlan::any()->allowing(ProductionPattern::exactly('Iconst')))->withLexeme('ICONST', LexemeConstraint::integers(10, 20));
1305 $string = RulePlan::any()->allowing(ProductionPattern::exactly('c_expr'))->withRule('c_expr', RulePlan::any()->allowing(ProductionPattern::exactly('AexprConst')))->withRule('AexprConst', RulePlan::any()->allowing(ProductionPattern::exactly('Sconst')))->withLexeme('SCONST', LexemeConstraint::oneOf("'Alice'", "'Bob'"));
1306 $plan = GenerationPlan::fromRule('InsertStmt')->withRule('insert_target', RulePlan::any()->allowing(ProductionPattern::exactly('qualified_name'))->withLexeme('IDENT', LexemeConstraint::oneOf('users')))
1307 ->withRule('expr_list', RulePlan::any()->withItems(RulePlan::any()->withRule('a_expr', $integer), RulePlan::any()->withRule('a_expr', $string)))
1308 ->requiringNonEmpty()->withExpansionBudget(100);
1309 foreach (['opt_with_clause' => [],'opt_on_conflict' => [],'returning_clause' => [],'qualified_name' => ['ColId'],'ColId' => ['IDENT'],'insert_rest' => ['SelectStmt'],'SelectStmt' => ['select_no_parens'],'select_no_parens' => ['simple_select'],'simple_select' => ['values_clause'],'values_clause' => ['VALUES','(','expr_list',')']] as $name => $symbols) {
1310 $plan = $plan->withRule($name, RulePlan::any()->allowing(ProductionPattern::exactly(...$symbols)));
1311 }
1312
1313 foreach ([0, 1, 7, 31] as $seed) {
1314 yield 'seed ' . $seed => [$plan, $seed];
1315 }
1316 }
1317
1318}
1319