packages/sql-faker/tests/Unit/PostgreSqlProviderTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\SqlFaker;
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\ProductionPattern;
17use SqlFaker\Generation\SqlGenerator;
18use SqlFaker\Grammar\Model\Grammar;
19use SqlFaker\Grammar\Model\NonTerminal;
20use SqlFaker\Grammar\Model\Production;
21use SqlFaker\Grammar\Model\ProductionRule;
22use SqlFaker\Grammar\Model\Terminal;
23use SqlFaker\Grammar\Model\TerminalInventory;
24use SqlFaker\Grammar\Resource\SqlVersion;
25use SqlFaker\PostgreSql\Generation\GenerationPlans;
26use SqlFaker\PostgreSql\Generation\LexicalGrammar;
27use SqlFaker\PostgreSql\Generation\Value\LiteralGenerator;
28use SqlFaker\PostgreSql\Grammar\PgGrammar;
29use SqlFaker\PostgreSql\StatementType;
30use SqlFaker\PostgreSqlProvider;
31
32#[CoversClass(PostgreSqlProvider::class)]
33#[CoversClass(LiteralGenerator::class)]
34#[CoversClass(SqlGenerator::class)]
35#[CoversClass(PgGrammar::class)]
36#[CoversClass(Grammar::class)]
37#[CoversClass(NonTerminal::class)]
38#[CoversClass(Production::class)]
39#[CoversClass(ProductionRule::class)]
40#[CoversClass(Terminal::class)]
41#[CoversClass(TerminationAnalyzer::class)]
42#[CoversClass(StatementType::class)]
43#[CoversClass(LexicalGrammar::class)]
44#[UsesClass(GenerationPlan::class)]
45#[UsesClass(ProductionPattern::class)]
46#[UsesClass(SqlVersion::class)]
47#[UsesClass(TerminalInventory::class)]
48#[UsesClass(GenerationPlans::class)]
49#[Medium]
50#[UsesClass(\SqlFaker\Provider\SqlGeneratorFactory::class)]
51#[UsesClass(\SqlFaker\Generation\Derivation\CompletionCosts::class)]
52#[UsesClass(\SqlFaker\Generation\Derivation\Derivation::class)]
53#[UsesClass(\SqlFaker\Generation\Derivation\DerivationTrace::class)]
54#[UsesClass(\SqlFaker\Generation\Derivation\TerminationCost::class)]
55#[UsesClass(\SqlFaker\Generation\Exception\GenerationException::class)]
56#[UsesClass(\SqlFaker\Generation\Candidate\ChoiceLexemeGenerator::class)]
57#[UsesClass(\SqlFaker\Generation\Candidate\FixedLexemeGenerator::class)]
58#[UsesClass(\SqlFaker\Generation\Candidate\IntegerLexemeGenerator::class)]
59#[UsesClass(\SqlFaker\Generation\Lexeme\Lexeme::class)]
60#[UsesClass(\SqlFaker\Generation\Lexeme\LexemeCandidates::class)]
61#[UsesClass(\SqlFaker\Generation\Lexeme\LexemeInput::class)]
62#[UsesClass(\SqlFaker\Generation\Lexeme\LexemeSequence::class)]
63#[UsesClass(\SqlFaker\Generation\Candidate\MatchingLexemeGenerator::class)]
64#[UsesClass(\SqlFaker\Generation\Candidate\ValueLexemeGenerator::class)]
65#[UsesClass(\SqlFaker\Generation\Candidate\RegisteredLexemeGenerator::class)]
66#[UsesClass(\SqlFaker\Generation\Candidate\SequenceLexemeGenerator::class)]
67#[UsesClass(\SqlFaker\Generation\Output\CandidateResolver::class)]
68#[UsesClass(\SqlFaker\Generation\Lexeme\OutputPart::class)]
69#[UsesClass(\SqlFaker\Generation\Lexeme\ResolvedOutput::class)]
70#[UsesClass(\SqlFaker\Generation\Output\ReverseLexemeGenerator::class)]
71#[UsesClass(\SqlFaker\Generation\Output\SqlSerializer::class)]
72#[UsesClass(\SqlFaker\Generation\Output\CombinedSpacingRule::class)]
73#[UsesClass(\SqlFaker\MySql\Generation\Spacing\KeywordPhraseSpacingRule::class)]
74#[UsesClass(\SqlFaker\Generation\Lexeme\LexemeBoundary::class)]
75#[UsesClass(\SqlFaker\Generation\Lexeme\SpacingConstraint::class)]
76#[UsesClass(\SqlFaker\Generation\Token\ProductionOccurrence::class)]
77#[UsesClass(\SqlFaker\Generation\Token\TerminalMappingRule::class)]
78#[UsesClass(\SqlFaker\Generation\Token\TerminalOccurrence::class)]
79#[UsesClass(\SqlFaker\Generation\Token\TerminalSequence::class)]
80#[UsesClass(\SqlFaker\Generation\Derivation\TokenGenerator::class)]
81#[UsesClass(\SqlFaker\Generation\Token\TokenRewriter::class)]
82#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Option\UniqueOptionRule::class)]
83#[UsesClass(\SqlFaker\Generation\Candidate\VersionCase::class)]
84#[UsesClass(\SqlFaker\Generation\Candidate\VersionedLexemeGenerator::class)]
85#[UsesClass(\SqlFaker\Generation\Exception\LexicalException::class)]
86#[UsesClass(\SqlFaker\PostgreSql\Generation\Tokenization\KeywordIndex::class)]
87#[UsesClass(\SqlFaker\Generation\Value\RandomCharacters::class)]
88#[UsesClass(\SqlFaker\Grammar\Resource\SqlVersionRegistry::class)]
89#[UsesClass(\SqlFaker\MySql\Generation\GenerationContext::class)]
90#[UsesClass(\SqlFaker\MySql\Generation\Lexeme\DefinitionFactory::class)]
91#[UsesClass(\SqlFaker\MySql\Generation\Lexeme\KeywordLexemeGenerator::class)]
92#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\AlterDatabaseRule::class)]
93#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\ConstraintEnforcementRule::class)]
94#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\FlushExportRule::class)]
95#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\InstanceActionRule::class)]
96#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\IntegerContextRule::class)]
97#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\LoadSourceCountRule::class)]
98#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\RequiredAliasRule::class)]
99#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\RewriteDefinitions::class)]
100#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\RoleGrantRule::class)]
101#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\SetNamesRule::class)]
102#[UsesClass(\SqlFaker\MySql\Generation\Spacing\CloneAddressSpacingRule::class)]
103#[UsesClass(\SqlFaker\MySql\Generation\Spacing\FunctionSpacingRule::class)]
104#[UsesClass(\SqlFaker\MySql\Generation\Spacing\QualifiedNameSpacingRule::class)]
105#[UsesClass(\SqlFaker\MySql\Generation\Spacing\VariableSpacingRule::class)]
106#[UsesClass(\SqlFaker\MySql\Grammar\MySqlGrammar::class)]
107#[UsesClass(\SqlFaker\MySql\Generation\LexicalGrammar::class)]
108#[UsesClass(\SqlFaker\MySql\Generation\Tokenization\MySqlTokenizer::class)]
109#[UsesClass(\SqlFaker\MySql\Generation\StartRuleResolver::class)]
110#[UsesClass(\SqlFaker\PostgreSql\Generation\GenerationContext::class)]
111#[UsesClass(\SqlFaker\PostgreSql\Generation\Lexeme\DefinitionFactory::class)]
112#[UsesClass(\SqlFaker\PostgreSql\Generation\Lexeme\HashBoundLexemeGenerator::class)]
113#[UsesClass(\SqlFaker\PostgreSql\Generation\Lexeme\KeywordLexemeGenerator::class)]
114#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\CopySourceRule::class)]
115#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\FetchWithTiesRule::class)]
116#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Name\FunctionNameRule::class)]
117#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\HashPartitionBoundRule::class)]
118#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\LimitOffsetRule::class)]
119#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\LookaheadRule::class)]
120#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\OperatorArgumentsRule::class)]
121#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\OverlapsArgumentsRule::class)]
122#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\PublicationObjectRule::class)]
123#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Name\RelationNameRule::class)]
124#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\RewriteDefinitions::class)]
125#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\TimeZoneIntervalRule::class)]
126#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\WindowFrameRule::class)]
127#[UsesClass(\SqlFaker\PostgreSql\Generation\Lookahead\PgLookahead::class)]
128#[UsesClass(\SqlFaker\PostgreSql\Generation\Tokenization\PgTokenizer::class)]
129#[UsesClass(\SqlFaker\Sqlite\Generation\GenerationContext::class)]
130#[UsesClass(\SqlFaker\Sqlite\Generation\Lexeme\DefinitionFactory::class)]
131#[UsesClass(\SqlFaker\Sqlite\Generation\Lexeme\JoinLexemeGenerator::class)]
132#[UsesClass(\SqlFaker\Sqlite\Generation\Lexeme\JoinModifiers::class)]
133#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\IdentifierListRule::class)]
134#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\JoinRule::class)]
135#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\RewriteDefinitions::class)]
136#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\StrictTableRule::class)]
137#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\TableOptionRule::class)]
138#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\WindowFrameRule::class)]
139#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\WithoutRowidRule::class)]
140#[UsesClass(\SqlFaker\Sqlite\Generation\GrammarAdaptation::class)]
141#[UsesClass(\SqlFaker\Sqlite\Grammar\SqliteGrammar::class)]
142#[UsesClass(\SqlFaker\Sqlite\Generation\LexicalGrammar::class)]
143#[UsesClass(\SqlFaker\Sqlite\Generation\Tokenization\SqliteTokenizer::class)]
144#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Expression\ExpressionGroupingRule::class)]
145#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\AlterEventRule::class)]
146#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\SubqueryContextRule::class)]
147#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\ConstraintAttributesRule::class)]
148#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Name\AnyRelationNameRule::class)]
149#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Name\IndirectionStarRule::class)]
150#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\IntoClauseRule::class)]
151#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\GeneratedColumnRule::class)]
152#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\TransactionCompletionRule::class)]
153#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\GeneratedColumnRule::class)]
154#[UsesClass(\SqlFaker\Sqlite\Generation\Lexeme\WindowNameLexemeGenerator::class)]
155#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Alter\OrderByRule::class)]
156#[UsesClass(\SqlFaker\MySql\Generation\Lexeme\BoundedIntegerLexemeGenerator::class)]
157#[UsesClass(\SqlFaker\MySql\Generation\Lexeme\ReplicationTablePatternLexemeGenerator::class)]
158#[UsesClass(\SqlFaker\MySql\Generation\Lexeme\SizeNumberLexemeGenerator::class)]
159#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\CompoundSelectRule::class)]
160#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\UpsertSourceRule::class)]
161#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\FunctionArgumentRule::class)]
162#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\GeneratedColumnRule::class)]
163#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Replication\StartRule::class)]
164#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Query\SelectOptionsRule::class)]
165#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Routine\TableFunctionRule::class)]
166#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Routine\TypeModifierRule::class)]
167#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Column\IdentityOptionRule::class)]
168#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Query\IntoClauseRule::class)]
169#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Partition\FieldListRule::class)]
170#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Replication\TablePatternRule::class)]
171#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Expression\QuantifiedComparisonRule::class)]
172#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Expression\TableValueConstructorRule::class)]
173#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Query\QueryContextRule::class)]
174#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Query\JoinGroupingRule::class)]
175#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Routine\WithinGroupRule::class)]
176#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Column\FieldLengthRule::class)]
177#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Routine\ReturnRule::class)]
178#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Name\ColumnNameRule::class)]
179#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Name\SystemVariableRule::class)]
180#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Column\AutoIncrementRule::class)]
181#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Partition\DefinitionRule::class)]
182#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Partition\ListValueRule::class)]
183#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Routine\LanguageRule::class)]
184#[UsesClass(\SqlFaker\MySql\Generation\Lexeme\FactorLexemeGenerator::class)]
185#[UsesClass(\SqlFaker\MySql\Generation\Lexeme\PrecisionLexemeGenerator::class)]
186#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Name\AliasRule::class)]
187#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Partition\ValueArityRule::class)]
188#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Partition\ValueShape::class)]
189#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Routine\SubstringRule::class)]
190#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Routine\RangeFunctionOrdinalityRule::class)]
191#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Query\WindowFrameRule::class)]
192#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Routine\JsonOptionsRule::class)]
193#[UsesClass(\SqlFaker\Generation\Value\CharacterDomain::class)]
194#[UsesClass(\SqlFaker\Generation\Value\ChoiceDomain::class)]
195#[UsesClass(\SqlFaker\Generation\Value\IntegerDomain::class)]
196#[UsesClass(\SqlFaker\Generation\Value\SequenceDomain::class)]
197#[UsesClass(\SqlFaker\PostgreSql\Generation\Value\DollarQuotedDomain::class)]
198#[UsesClass(\SqlFaker\Generation\Output\BoundaryCompletion::class)]
199#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Column\ConstraintCapabilitiesRule::class)]
200#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Column\ForeignKeyActionRule::class)]
201#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Query\SchemaElementsRule::class)]
202#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Query\ParserOptionsRule::class)]
203#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Routine\JsonTablePathRule::class)]
204#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Routine\AggregateArgumentRule::class)]
205#[UsesClass(\SqlFaker\Generation\Derivation\CompletionState::class)]
206#[UsesClass(\SqlFaker\Generation\Derivation\CompletionFrontier::class)]
207#[UsesClass(\SqlFaker\Generation\Derivation\ConstrainedCompletion::class)]
208#[UsesClass(\SqlFaker\Generation\Value\ValueChoices::class)]
209#[UsesClass(\SqlFaker\Generation\Derivation\CompletionMemo::class)]
210#[UsesClass(\SqlFaker\Generation\Derivation\CompletionReduction::class)]
211#[UsesClass(\SqlFaker\Generation\Derivation\ConstraintDependencies::class)]
212#[UsesClass(\SqlFaker\Generation\Choice\BytePlanCompiler::class)]
213#[UsesClass(\SqlFaker\MySql\Generation\Rewrite\Name\HostNameRule::class)]
214#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Name\ParserNameRule::class)]
215#[UsesClass(\SqlFaker\PostgreSql\Generation\Rewrite\Column\NumericContextRule::class)]
216#[UsesClass(\SqlFaker\MySql\Generation\Lexeme\CharsetLexemeGenerator::class)]
217#[UsesClass(\SqlFaker\MySql\Generation\Lexeme\CharsetValueLexemeGenerator::class)]
218#[UsesClass(\SqlFaker\PostgreSql\Generation\Value\IdentifierDomain::class)]
219#[UsesClass(\SqlFaker\PostgreSql\Generation\Value\QuotedDomain::class)]
220#[UsesClass(\SqlFaker\PostgreSql\Generation\Value\OperatorDomain::class)]
221#[UsesClass(\SqlFaker\Generation\Value\WordDomain::class)]
222#[UsesClass(\SqlFaker\Generation\Derivation\Completion\PatternProductions::class)]
223#[UsesClass(\SqlFaker\Generation\Derivation\Completion\CompletionWitness::class)]
224#[UsesClass(\SqlFaker\MySql\Generation\Value\RadixDomain::class)]
225#[UsesClass(\SqlFaker\Generation\Value\RepeatDomain::class)]
226#[UsesClass(\SqlFaker\Generation\Value\Utf8::class)]
227#[UsesClass(\SqlFaker\PostgreSql\Generation\Lexeme\LexicalDefinition::class)]
228final class PostgreSqlProviderTest extends TestCase
229{
230 #[DataProvider('providerTargetedGenerationSeed')]
231 public function testPartitionOfStatementGeneratesRangeChildDdl(int $seed): void
232 {
233 $faker = Factory::create();
234 $faker->seed($seed);
235 $provider = new PostgreSqlProvider($faker);
236 $sql = $provider->partitionOfStatement();
237 $faker->seed($seed);
238
239 $tokens = (new LexicalGrammar($faker, 'pg-17.2'))->tokenize($sql);
240
241 self::assertSame($sql, $provider->partitionOfStatement(40));
242 self::assertContains('PARTITION', $tokens);
243 self::assertContains('FROM', $tokens);
244 self::assertContains('TO', $tokens);
245 }
246
247 #[DataProvider('providerTargetedGenerationSeed')]
248 public function testInsertFunctionUpsertStatementDerivesFunctionExpressionFromGrammar(int $seed): void
249 {
250 $faker = Factory::create();
251 $faker->seed($seed);
252 $provider = new PostgreSqlProvider($faker);
253 $sql = $provider->insertFunctionUpsertStatement();
254 $faker->seed($seed);
255
256 $tokens = (new LexicalGrammar($faker, 'pg-17.2'))
257 ->tokenize($sql);
258 $values = array_search('VALUES', $tokens, true);
259 $conflict = array_search('CONFLICT', $tokens, true);
260 $set = array_search('SET', $tokens, true);
261 $functionOpen = array_search('(', array_slice($tokens, (int) $set, null, true), true);
262
263 self::assertSame($sql, $provider->insertFunctionUpsertStatement(40));
264 self::assertIsInt($values);
265 self::assertIsInt($conflict);
266 self::assertIsInt($set);
267 self::assertIsInt($functionOpen);
268 self::assertLessThan($conflict, $values);
269 self::assertGreaterThan($set, $functionOpen);
270 self::assertContains('UPDATE', $tokens);
271 }
272
273 #[DataProvider('providerTargetedGenerationSeed')]
274 public function testPartialIndexUpsertStatementIncludesArbiterPredicate(int $seed): void
275 {
276 $faker = Factory::create();
277 $faker->seed($seed);
278 $provider = new PostgreSqlProvider($faker);
279 $sql = $provider->partialIndexUpsertStatement();
280 $faker->seed($seed);
281 $tokens = (new LexicalGrammar($faker, 'pg-17.2'))
282 ->tokenize($sql);
283
284 self::assertSame($sql, $provider->partialIndexUpsertStatement(40));
285 $conflict = array_search('CONFLICT', $tokens, true);
286 $where = array_search('WHERE', $tokens, true);
287 $update = array_search('UPDATE', $tokens, true);
288 self::assertIsInt($conflict);
289 self::assertIsInt($where);
290 self::assertIsInt($update);
291 self::assertGreaterThan($conflict, $where);
292 self::assertGreaterThan($where, $update);
293 }
294
295 #[DataProvider('providerTargetedGenerationSeed')]
296 public function testFullTextSearchStatementDerivesMatchOperatorFromGrammarAndLexer(int $seed): void
297 {
298 $faker = Factory::create();
299 $faker->seed($seed);
300 $provider = new PostgreSqlProvider($faker);
301 $sql = $provider->fullTextSearchStatement();
302 $faker->seed($seed);
303 $tokens = (new LexicalGrammar($faker, 'pg-17.2'))->tokenize($sql);
304 $where = array_search('WHERE', $tokens, true);
305
306 self::assertSame($sql, $provider->fullTextSearchStatement(40));
307 self::assertSame('SELECT', $tokens[0]);
308 self::assertContains('FROM', $tokens);
309 self::assertIsInt($where);
310 self::assertSame(['IDENT', 'Op', 'IDENT'], array_slice($tokens, $where + 1, 3));
311 self::assertStringContainsString('@@', $sql);
312 }
313
314 #[DataProvider('providerTargetedGenerationSeed')]
315 public function testTemporaryTableStatement(int $seed): void
316 {
317 $faker = Factory::create();
318 $faker->seed($seed);
319 $provider = new PostgreSqlProvider($faker);
320 $sql = $provider->temporaryTableStatement();
321 $faker->seed($seed);
322
323 $tokens = (new LexicalGrammar($faker, 'pg-17.2'))
324 ->tokenize($sql);
325
326 self::assertSame($sql, $provider->temporaryTableStatement(40));
327 self::assertSame('CREATE', $tokens[0]);
328 self::assertContains('TEMP', $tokens);
329 self::assertContains('TABLE', $tokens);
330 }
331
332 #[DataProvider('providerTargetedGenerationSeed')]
333 public function testViewStatement(int $seed): void
334 {
335 $faker = Factory::create();
336 $faker->seed($seed);
337 $provider = new PostgreSqlProvider($faker);
338 $sql = $provider->viewStatement();
339 $faker->seed($seed);
340 $tokens = (new LexicalGrammar($faker, 'pg-17.2'))
341 ->tokenize($sql);
342
343 self::assertSame($sql, $provider->viewStatement(40));
344 self::assertSame('CREATE', $tokens[0]);
345 self::assertContains('VIEW', $tokens);
346 self::assertNotSame([], array_intersect(['SELECT', 'VALUES', 'TABLE'], $tokens));
347 }
348
349 #[DataProvider('providerTargetedGenerationSeed')]
350 public function testGeneratedColumnStatement(int $seed): void
351 {
352 $faker = Factory::create();
353 $faker->seed($seed);
354 $provider = new PostgreSqlProvider($faker);
355 $sql = $provider->generatedColumnStatement();
356 $faker->seed($seed);
357 $tokens = (new LexicalGrammar($faker, 'pg-17.2'))
358 ->tokenize($sql);
359
360 self::assertSame($sql, $provider->generatedColumnStatement(40));
361 self::assertContains('GENERATED', $tokens);
362 self::assertContains('STORED', $tokens);
363 self::assertContains('AS', $tokens);
364 }
365
366 #[DataProvider('providerTargetedGenerationSeed')]
367 public function testForeignKeyCascadeStatement(int $seed): void
368 {
369 $faker = Factory::create();
370 $faker->seed($seed);
371 $provider = new PostgreSqlProvider($faker);
372 $sql = $provider->foreignKeyCascadeStatement();
373 $faker->seed($seed);
374 $tokens = (new LexicalGrammar($faker, 'pg-17.2'))
375 ->tokenize($sql);
376
377 self::assertSame($sql, $provider->foreignKeyCascadeStatement(40));
378 self::assertContains('FOREIGN', $tokens);
379 self::assertContains('REFERENCES', $tokens);
380 self::assertStringContainsString('ON UPDATE CASCADE', implode(' ', $tokens));
381 self::assertStringContainsString('ON DELETE_P CASCADE', implode(' ', $tokens));
382 }
383
384 #[Override]
385 protected function setUp(): void
386 {
387 parent::setUp();
388 gc_collect_cycles();
389 }
390
391 public function testRegistersItselfWithTheFakerGenerator(): void
392 {
393 $faker = Factory::create();
394 $provider = new PostgreSqlProvider($faker);
395
396 /** @var list<object> $providers */
397 $providers = $faker->getProviders();
398 self::assertContains($provider, $providers);
399
400 $identifier = $provider->identifier(3);
401 self::assertNotSame('', $identifier);
402 }
403
404 public function testSql(): void
405 {
406 $faker = Factory::create();
407 $faker->seed(12345);
408 $provider = new PostgreSqlProvider($faker);
409
410 $result = $provider->sql(maxDepth: 6);
411
412 self::assertNotSame('', $result);
413 }
414
415 public function testSqlWithStatementType(): void
416 {
417 $faker = Factory::create();
418 $faker->seed(12345);
419 $provider = new PostgreSqlProvider($faker);
420
421 $result = $provider->sql(StatementType::Select, maxDepth: 6);
422
423 self::assertMatchesRegularExpression('/SELECT|VALUES|TABLE/', $result);
424 }
425
426 public function testSqlWithNullStatementTypeUsesRandom(): void
427 {
428 $faker = Factory::create();
429 $faker->seed(12345);
430 $provider = new PostgreSqlProvider($faker);
431
432 $result = $provider->sql(null, maxDepth: 6);
433
434 self::assertNotSame('', $result);
435 }
436
437 public function testSqlWithMaxDepth(): void
438 {
439 $faker = Factory::create();
440 $faker->seed(12345);
441 $provider = new PostgreSqlProvider($faker);
442
443 $result = $provider->sql(maxDepth: 8);
444
445 self::assertNotSame('', $result);
446 }
447
448 public function testSelectStatement(): void
449 {
450 $faker = Factory::create();
451 $faker->seed(12345);
452 $provider = new PostgreSqlProvider($faker);
453
454 $result = $provider->selectStatement(maxDepth: 6);
455
456 self::assertNotEmpty($result);
457 self::assertMatchesRegularExpression('/SELECT|VALUES|TABLE/', $result);
458 }
459
460 public function testInsertStatement(): void
461 {
462 $faker = Factory::create();
463 $faker->seed(12345);
464 $provider = new PostgreSqlProvider($faker);
465
466 $result = $provider->insertStatement(maxDepth: 6);
467
468 self::assertNotEmpty($result);
469 self::assertStringContainsString('INSERT', $result);
470 }
471
472 public function testUpdateStatement(): void
473 {
474 $faker = Factory::create();
475 $faker->seed(12345);
476 $provider = new PostgreSqlProvider($faker);
477
478 $result = $provider->updateStatement(maxDepth: 6);
479
480 self::assertNotEmpty($result);
481 self::assertStringContainsString('UPDATE', $result);
482 }
483
484 public function testDeleteStatement(): void
485 {
486 $faker = Factory::create();
487 $faker->seed(12345);
488 $provider = new PostgreSqlProvider($faker);
489
490 $result = $provider->deleteStatement(maxDepth: 6);
491
492 self::assertNotEmpty($result);
493 self::assertStringContainsString('DELETE', $result);
494 }
495
496 public function testCreateTableStatement(): void
497 {
498 $faker = Factory::create();
499 $faker->seed(12345);
500 $provider = new PostgreSqlProvider($faker);
501
502 $result = $provider->createTableStatement(maxDepth: 6);
503
504 self::assertNotEmpty($result);
505 self::assertStringContainsString('CREATE', $result);
506 self::assertStringContainsString('TABLE', $result);
507 }
508
509 public function testCreateTableAsStatement(): void
510 {
511 $faker = Factory::create();
512 $faker->seed(12345);
513 $provider = new PostgreSqlProvider($faker);
514
515 $result = $provider->createTableAsStatement(maxDepth: 8);
516
517 self::assertStringContainsString('CREATE', $result);
518 self::assertStringContainsString('TABLE', $result);
519 self::assertStringContainsString('AS', $result);
520 self::assertMatchesRegularExpression('/\b(?:SELECT|VALUES|TABLE)\b/', $result);
521 }
522
523 public function testCreateDomainStatement(): void
524 {
525 $faker = Factory::create();
526 $faker->seed(12345);
527 $provider = new PostgreSqlProvider($faker);
528
529 $result = $provider->createDomainStatement(maxDepth: 8);
530
531 self::assertStringContainsString('CREATE', $result);
532 self::assertStringContainsString('DOMAIN', $result);
533 }
534
535 public function testAlterTableStatement(): void
536 {
537 $faker = Factory::create();
538 $faker->seed(12345);
539 $provider = new PostgreSqlProvider($faker);
540
541 $result = $provider->alterTableStatement(maxDepth: 6);
542
543 self::assertNotEmpty($result);
544 self::assertStringContainsString('ALTER', $result);
545 }
546
547 public function testDropTableStatement(): void
548 {
549 $faker = Factory::create();
550 $faker->seed(12345);
551 $provider = new PostgreSqlProvider($faker);
552
553 $result = $provider->dropTableStatement(maxDepth: 6);
554
555 self::assertNotEmpty($result);
556 self::assertStringContainsString('DROP', $result);
557 }
558
559 public function testTruncateStatement(): void
560 {
561 $faker = Factory::create();
562 $faker->seed(12345);
563 $provider = new PostgreSqlProvider($faker);
564
565 $result = $provider->truncateStatement(maxDepth: 6);
566
567 self::assertNotEmpty($result);
568 self::assertStringContainsString('TRUNCATE', $result);
569 }
570
571 public function testCreateIndexStatement(): void
572 {
573 $faker = Factory::create();
574 $faker->seed(12345);
575 $provider = new PostgreSqlProvider($faker);
576
577 $result = $provider->createIndexStatement(maxDepth: 6);
578
579 self::assertNotEmpty($result);
580 self::assertStringContainsString('CREATE', $result);
581 self::assertStringContainsString('INDEX', $result);
582 }
583
584 public function testTransactionStatement(): void
585 {
586 $faker = Factory::create();
587 $faker->seed(12345);
588 $provider = new PostgreSqlProvider($faker);
589
590 $result = $provider->transactionStatement(maxDepth: 6);
591
592 self::assertNotEmpty($result);
593 self::assertMatchesRegularExpression('/BEGIN|COMMIT|ROLLBACK|ABORT|END|START|SAVEPOINT|RELEASE|PREPARE/', $result);
594 }
595
596 public function testExpr(): void
597 {
598 $faker = Factory::create();
599 $faker->seed(12345);
600 $provider = new PostgreSqlProvider($faker);
601
602 $result = $provider->expr(maxDepth: 3);
603
604 self::assertNotSame('', $result);
605 }
606
607 public function testSimpleExpr(): void
608 {
609 $faker = Factory::create();
610 $faker->seed(12345);
611 $provider = new PostgreSqlProvider($faker);
612
613 $result = $provider->simpleExpr(maxDepth: 3);
614
615 self::assertNotSame('', $result);
616 }
617
618 public function testLiteral(): void
619 {
620 $faker = Factory::create();
621 $faker->seed(12345);
622 $provider = new PostgreSqlProvider($faker);
623
624 $result = $provider->literal(maxDepth: 3);
625
626 self::assertNotSame('', $result);
627 }
628
629 public function testWhereClause(): void
630 {
631 $faker = Factory::create();
632 $faker->seed(0);
633 $provider = new PostgreSqlProvider($faker);
634
635 $result = $provider->whereClause(maxDepth: 1);
636
637 self::assertSame('', $result);
638 }
639
640 public function testSortClause(): void
641 {
642 $faker = Factory::create();
643 $faker->seed(12345);
644 $provider = new PostgreSqlProvider($faker);
645
646 $result = $provider->sortClause(maxDepth: 3);
647
648 self::assertSame(['ORDER', 'BY'], array_slice((new LexicalGrammar($faker, 'pg-17.2'))->tokenize($result), 0, 2));
649 }
650
651 public function testSelectLimit(): void
652 {
653 $faker = Factory::create();
654 $faker->seed(12345);
655 $provider = new PostgreSqlProvider($faker);
656
657 $result = $provider->selectLimit(maxDepth: 3);
658
659 self::assertMatchesRegularExpression('/LIMIT|OFFSET|FETCH/', $result);
660 }
661
662 public function testTableRef(): void
663 {
664 $faker = Factory::create();
665 $faker->seed(12345);
666 $provider = new PostgreSqlProvider($faker);
667
668 $result = $provider->tableRef(maxDepth: 3);
669
670 self::assertNotSame('', $result);
671 }
672
673 public function testJoinedTable(): void
674 {
675 $faker = Factory::create();
676 $faker->seed(12345);
677 $provider = new PostgreSqlProvider($faker);
678
679 $result = $provider->joinedTable(maxDepth: 3);
680
681 self::assertStringContainsString('JOIN', $result);
682 }
683
684 public function testQualifiedName(): void
685 {
686 $faker = Factory::create();
687 $faker->seed(12345);
688 $provider = new PostgreSqlProvider($faker);
689
690 $result = $provider->qualifiedName(maxDepth: 3);
691
692 self::assertNotSame('', $result);
693 }
694
695 public function testSubquery(): void
696 {
697 $faker = Factory::create();
698 $faker->seed(12345);
699 $provider = new PostgreSqlProvider($faker);
700
701 $result = $provider->subquery(maxDepth: 3);
702
703 self::assertStringContainsString('(', $result);
704 self::assertStringContainsString(')', $result);
705 }
706
707 public function testWithClause(): void
708 {
709 $faker = Factory::create();
710 $faker->seed(12345);
711 $provider = new PostgreSqlProvider($faker);
712
713 $result = $provider->withClause(maxDepth: 3);
714
715 self::assertStringContainsString('WITH', $result);
716 }
717
718 public function testForeignKeyConstraint(): void
719 {
720 $faker = Factory::create();
721 $faker->seed(12345);
722 $provider = new PostgreSqlProvider($faker, 'pg-17.2');
723
724 $result = $provider->foreignKeyConstraint(1);
725
726 self::assertSame(
727 ['CONSTRAINT', 'IDENT', 'FOREIGN', 'KEY', '(', 'IDENT', ')', 'REFERENCES', 'IDENT', '(', 'IDENT', ')'],
728 (new LexicalGrammar($faker, 'pg-17.2'))->tokenize($result),
729 );
730 }
731
732 public function testIdentifier(): void
733 {
734 $faker = Factory::create();
735 $faker->seed(12345);
736 $provider = new PostgreSqlProvider($faker);
737
738 $result = $provider->identifier(3);
739
740 self::assertNotSame('', $result);
741 }
742
743 public function testQuotedIdentifier(): void
744 {
745 $faker = Factory::create();
746 $faker->seed(12345);
747 $provider = new PostgreSqlProvider($faker);
748
749 $result = $provider->quotedIdentifier();
750
751 self::assertMatchesRegularExpression('/^"[a-z_][a-z0-9_]*"$/', $result);
752 }
753
754 public function testStringLiteral(): void
755 {
756 $faker = Factory::create();
757 $faker->seed(12345);
758 $provider = new PostgreSqlProvider($faker);
759
760 $result = $provider->stringLiteral();
761
762 self::assertMatchesRegularExpression("/^'[a-zA-Z0-9_]{1,255}'$/", $result);
763 }
764
765 public function testIntegerLiteral(): void
766 {
767 $faker = Factory::create();
768 $faker->seed(12345);
769 $provider = new PostgreSqlProvider($faker);
770
771 $result = $provider->integerLiteral();
772
773 self::assertMatchesRegularExpression('/^[1-9]\d*$/', $result);
774 }
775
776 public function testDecimalLiteral(): void
777 {
778 $faker = Factory::create();
779 $faker->seed(12345);
780 $provider = new PostgreSqlProvider($faker);
781
782 $result = $provider->decimalLiteral();
783
784 self::assertMatchesRegularExpression('/^\d+\.\d{2,}$/', $result);
785 }
786
787 public function testFloatLiteral(): void
788 {
789 $faker = Factory::create();
790 $faker->seed(12345);
791 $provider = new PostgreSqlProvider($faker);
792
793 $result = $provider->floatLiteral();
794
795 self::assertMatchesRegularExpression('/^\d+\.\d+e-?\d+$/', $result);
796 }
797
798 public function testHexLiteral(): void
799 {
800 $faker = Factory::create();
801 $faker->seed(12345);
802 $provider = new PostgreSqlProvider($faker);
803
804 $result = $provider->hexLiteral();
805
806 self::assertMatchesRegularExpression("/^X'[0-9a-f]{1,16}'$/", $result);
807 }
808
809 public function testBinaryLiteral(): void
810 {
811 $faker = Factory::create();
812 $faker->seed(12345);
813 $provider = new PostgreSqlProvider($faker);
814
815 $result = $provider->binaryLiteral();
816
817 self::assertMatchesRegularExpression("/^B'[01]{1,64}'$/", $result);
818 }
819
820 public function testDollarQuotedString(): void
821 {
822 $faker = Factory::create();
823 $faker->seed(12345);
824 $provider = new PostgreSqlProvider($faker);
825
826 $result = $provider->dollarQuotedString();
827
828 self::assertMatchesRegularExpression('/^\$\$[a-zA-Z0-9_]{1,255}\$\$$/', $result);
829 }
830
831 public function testParameterMarker(): void
832 {
833 $faker = Factory::create();
834 $faker->seed(12345);
835 $provider = new PostgreSqlProvider($faker);
836
837 $result = $provider->parameterMarker();
838
839 self::assertMatchesRegularExpression('/^\$\d+$/', $result);
840 }
841
842 public function testQuotedIdentifierDefaultMatchesExplicit(): void
843 {
844 $faker = Factory::create();
845 $p = new PostgreSqlProvider($faker);
846 $faker->seed(42);
847 $a = $p->quotedIdentifier();
848 $faker->seed(42);
849 self::assertSame($a, $p->quotedIdentifier(1, 63));
850 }
851
852 public function testStringLiteralDefaultMatchesExplicit(): void
853 {
854 $faker = Factory::create();
855 $p = new PostgreSqlProvider($faker);
856 $faker->seed(42);
857 $a = $p->stringLiteral();
858 $faker->seed(42);
859 self::assertSame($a, $p->stringLiteral(1, 255));
860 }
861
862 public function testIntegerLiteralDefaultMatchesExplicit(): void
863 {
864 $faker = Factory::create();
865 $p = new PostgreSqlProvider($faker);
866 $faker->seed(42);
867 $a = $p->integerLiteral();
868 $faker->seed(42);
869 self::assertSame($a, $p->integerLiteral(1, 2147483647));
870 }
871
872 public function testDecimalLiteralDefaultMatchesExplicit(): void
873 {
874 $faker = Factory::create();
875 $p = new PostgreSqlProvider($faker);
876 $faker->seed(42);
877 $a = $p->decimalLiteral();
878 $faker->seed(42);
879 self::assertSame($a, $p->decimalLiteral(10, 2));
880 }
881
882 public function testFloatLiteralDefaultMatchesExplicit(): void
883 {
884 $faker = Factory::create();
885 $p = new PostgreSqlProvider($faker);
886 $faker->seed(42);
887 $a = $p->floatLiteral();
888 $faker->seed(42);
889 self::assertSame($a, $p->floatLiteral(10, 2, -307, 308));
890 }
891
892 public function testHexLiteralDefaultMatchesExplicit(): void
893 {
894 $faker = Factory::create();
895 $p = new PostgreSqlProvider($faker);
896 $faker->seed(42);
897 $a = $p->hexLiteral();
898 $faker->seed(42);
899 self::assertSame($a, $p->hexLiteral(1, 16));
900 }
901
902 public function testBinaryLiteralDefaultMatchesExplicit(): void
903 {
904 $faker = Factory::create();
905 $p = new PostgreSqlProvider($faker);
906 $faker->seed(42);
907 $a = $p->binaryLiteral();
908 $faker->seed(42);
909 self::assertSame($a, $p->binaryLiteral(1, 64));
910 }
911
912 public function testDollarQuotedStringDefaultMatchesExplicit(): void
913 {
914 $faker = Factory::create();
915 $p = new PostgreSqlProvider($faker);
916 $faker->seed(42);
917 $a = $p->dollarQuotedString();
918 $faker->seed(42);
919 self::assertSame($a, $p->dollarQuotedString(1, 255));
920 }
921
922 public function testParameterMarkerDefaultMatchesExplicit(): void
923 {
924 $faker = Factory::create();
925 $p = new PostgreSqlProvider($faker);
926 $faker->seed(42);
927 $a = $p->parameterMarker();
928 $faker->seed(42);
929 self::assertSame($a, $p->parameterMarker(1, 99));
930 }
931
932 public function testQuotedIdentifierCustomLength(): void
933 {
934 $faker = Factory::create();
935 $faker->seed(12345);
936 $provider = new PostgreSqlProvider($faker);
937
938 $result = $provider->quotedIdentifier(5, 10);
939
940 self::assertMatchesRegularExpression('/^"[a-z_][a-z0-9_]{4,9}"$/', $result);
941 }
942
943 public function testStringLiteralCustomLength(): void
944 {
945 $faker = Factory::create();
946 $faker->seed(12345);
947 $provider = new PostgreSqlProvider($faker);
948
949 $result = $provider->stringLiteral(3, 8);
950 $content = substr($result, 1, -1);
951
952 self::assertGreaterThanOrEqual(3, strlen($content));
953 self::assertLessThanOrEqual(8, strlen($content));
954 }
955
956 public function testIntegerLiteralCustomRange(): void
957 {
958 $faker = Factory::create();
959 $faker->seed(12345);
960 $provider = new PostgreSqlProvider($faker);
961
962 $result = $provider->integerLiteral(100, 500);
963
964 self::assertGreaterThanOrEqual(100, (int) $result);
965 self::assertLessThanOrEqual(500, (int) $result);
966 }
967
968 public function testDecimalLiteralCustomPrecision(): void
969 {
970 $faker = Factory::create();
971 $faker->seed(12345);
972 $provider = new PostgreSqlProvider($faker);
973
974 $result = $provider->decimalLiteral(5, 2);
975
976 self::assertMatchesRegularExpression('/^\d+\.\d{2,}$/', $result);
977 }
978
979 public function testFloatLiteralCustomParams(): void
980 {
981 $faker = Factory::create();
982 $faker->seed(12345);
983 $provider = new PostgreSqlProvider($faker);
984
985 $result = $provider->floatLiteral(5, 2, -10, 10);
986
987 self::assertMatchesRegularExpression('/^\d+\.\d+e-?\d+$/', $result);
988 }
989
990 public function testHexLiteralCustomLength(): void
991 {
992 $faker = Factory::create();
993 $faker->seed(12345);
994 $provider = new PostgreSqlProvider($faker);
995
996 $result = $provider->hexLiteral(4, 8);
997
998 self::assertMatchesRegularExpression("/^X'[0-9a-f]{4,8}'$/", $result);
999 }
1000
1001 public function testBinaryLiteralCustomLength(): void
1002 {
1003 $faker = Factory::create();
1004 $faker->seed(12345);
1005 $provider = new PostgreSqlProvider($faker);
1006
1007 $result = $provider->binaryLiteral(8, 16);
1008
1009 self::assertMatchesRegularExpression("/^B'[01]{8,16}'$/", $result);
1010 }
1011
1012 public function testDollarQuotedStringCustomLength(): void
1013 {
1014 $faker = Factory::create();
1015 $faker->seed(12345);
1016 $provider = new PostgreSqlProvider($faker);
1017
1018 $result = $provider->dollarQuotedString(2, 6);
1019 $content = substr($result, 2, -2);
1020
1021 self::assertGreaterThanOrEqual(2, strlen($content));
1022 self::assertLessThanOrEqual(6, strlen($content));
1023 }
1024
1025 public function testParameterMarkerCustomRange(): void
1026 {
1027 $faker = Factory::create();
1028 $faker->seed(12345);
1029 $provider = new PostgreSqlProvider($faker);
1030
1031 $result = $provider->parameterMarker(1, 5);
1032
1033 self::assertMatchesRegularExpression('/^\$[1-5]$/', $result);
1034 }
1035
1036 public function testSeededGenerationIsReproducible(): void
1037 {
1038 $faker1 = Factory::create();
1039 $provider1 = new PostgreSqlProvider($faker1);
1040 $faker1->seed(99999);
1041 $sql1 = $provider1->sql(maxDepth: 8);
1042
1043 $faker2 = Factory::create();
1044 $provider2 = new PostgreSqlProvider($faker2);
1045 $faker2->seed(99999);
1046 $sql2 = $provider2->sql(maxDepth: 8);
1047
1048 self::assertSame($sql1, $sql2, 'Same seed should produce same output');
1049 }
1050
1051 #[DataProvider('providerStatementTypeValue')]
1052 public function testSqlWithAllStatementTypes(StatementType $type): void
1053 {
1054 $faker = Factory::create();
1055 $faker->seed(12345);
1056 $provider = new PostgreSqlProvider($faker);
1057
1058 $result = $provider->sql($type, maxDepth: 6);
1059
1060 self::assertNotSame('', $result);
1061 }
1062
1063 public function testSelectContainsSelectOrValuesOrTable(): void
1064 {
1065 $faker = Factory::create();
1066 $faker->seed(12345);
1067 $provider = new PostgreSqlProvider($faker);
1068
1069 $sql = $provider->selectStatement(maxDepth: 6);
1070
1071 self::assertMatchesRegularExpression('/SELECT|VALUES|TABLE/', $sql);
1072 }
1073
1074 public function testUpdateContainsSetClause(): void
1075 {
1076 $faker = Factory::create();
1077 $faker->seed(12345);
1078 $provider = new PostgreSqlProvider($faker);
1079
1080 $result = $provider->updateStatement(maxDepth: 6);
1081
1082 self::assertStringContainsString('UPDATE', $result);
1083 self::assertStringContainsString('SET', $result);
1084 }
1085
1086 public function testDeleteContainsFromKeyword(): void
1087 {
1088 $faker = Factory::create();
1089 $faker->seed(12345);
1090 $provider = new PostgreSqlProvider($faker);
1091
1092 $result = $provider->deleteStatement(maxDepth: 6);
1093
1094 self::assertStringContainsString('DELETE', $result);
1095 }
1096
1097 public function testMultipleGenerationsReturnDifferentResults(): void
1098 {
1099 $faker1 = Factory::create();
1100 $faker1->seed(1);
1101 $provider1 = new PostgreSqlProvider($faker1);
1102 $sql1 = $provider1->selectStatement(maxDepth: 3);
1103
1104 $faker2 = Factory::create();
1105 $faker2->seed(2);
1106 $provider2 = new PostgreSqlProvider($faker2);
1107 $sql2 = $provider2->selectStatement(maxDepth: 3);
1108
1109 self::assertNotSame($sql1, $sql2, 'Different seeds should produce different SQL');
1110 }
1111
1112 public function testGrammarDrivenOutputIsNonEmpty(): void
1113 {
1114 $faker = Factory::create();
1115 $faker->seed(12345);
1116 $provider = new PostgreSqlProvider($faker);
1117
1118 $result = $provider->sql(maxDepth: 8);
1119
1120 self::assertNotSame('', $result);
1121 }
1122
1123 #[DataProvider('providerTargetedGenerationSeed')]
1124 public function testTableSampleStatementDerivesSamplingClauseFromGrammar(int $seed): void
1125 {
1126 $faker = Factory::create();
1127 $faker->seed($seed);
1128 $provider = new PostgreSqlProvider($faker);
1129 $sql = $provider->tableSampleStatement();
1130 $faker->seed($seed);
1131
1132 $tokens = (new LexicalGrammar($faker, 'pg-17.2'))->tokenize($sql);
1133
1134 self::assertSame($sql, $provider->tableSampleStatement(40));
1135 self::assertSame('SELECT', $tokens[0]);
1136 self::assertContains('FROM', $tokens);
1137 self::assertContains('TABLESAMPLE', $tokens);
1138 }
1139
1140 #[DataProvider('providerTargetedGenerationSeed')]
1141 public function testDoStatementDerivesAnonymousBlockFromGrammar(int $seed): void
1142 {
1143 $faker = Factory::create();
1144 $faker->seed($seed);
1145 $provider = new PostgreSqlProvider($faker);
1146 $sql = $provider->doStatement();
1147 $faker->seed($seed);
1148
1149 self::assertSame($sql, $provider->doStatement(40));
1150 self::assertSame(
1151 ['DO', 'SCONST'],
1152 (new LexicalGrammar($faker, 'pg-17.2'))->tokenize($sql),
1153 );
1154 }
1155
1156 #[DataProvider('providerTargetedGenerationSeed')]
1157 public function testMergeStatementDerivesEveryActionFromGrammar(int $seed): void
1158 {
1159 $faker = Factory::create();
1160 $faker->seed($seed);
1161 $provider = new PostgreSqlProvider($faker);
1162 $sql = $provider->mergeStatement();
1163 $faker->seed($seed);
1164 $tokens = (new LexicalGrammar($faker, 'pg-17.2'))->tokenize($sql);
1165 $normalized = implode(' ', $tokens);
1166 $delete = strpos($normalized, 'WHEN MATCHED THEN DELETE_P');
1167 $nothing = strpos($normalized, 'WHEN MATCHED THEN DO NOTHING');
1168 $update = strpos($normalized, 'WHEN MATCHED THEN UPDATE');
1169 $insert = strpos($normalized, 'WHEN NOT MATCHED THEN INSERT');
1170
1171 self::assertSame($sql, $provider->mergeStatement(40));
1172 self::assertContains('MERGE', $tokens);
1173 self::assertGreaterThanOrEqual(
1174 4,
1175 count(array_filter($tokens, static fn (string $token): bool => $token === 'WHEN')),
1176 );
1177 self::assertIsInt($delete);
1178 self::assertIsInt($nothing);
1179 self::assertIsInt($update);
1180 self::assertIsInt($insert);
1181 self::assertLessThan($nothing, $delete);
1182 self::assertLessThan($update, $nothing);
1183 self::assertLessThan($insert, $update);
1184 }
1185
1186 public function testCopyStatementUsesOfficialGrammarRule(): void
1187 {
1188 $faker = Factory::create();
1189 $faker->seed(12345);
1190 $provider = new PostgreSqlProvider($faker);
1191
1192 self::assertStringContainsString('COPY', $provider->copyStatement(maxDepth: 8));
1193 }
1194
1195 #[DataProvider('providerNullableSimpleStatementSeed')]
1196 public function testSimpleStatementReturnsNonEmpty(int $seed): void
1197 {
1198 $faker = Factory::create();
1199 $faker->seed($seed);
1200 $provider = new PostgreSqlProvider($faker);
1201
1202 self::assertNotSame('', $provider->simpleStatement(maxDepth: 20));
1203 }
1204
1205 public function testSimpleStatementReturnsNonEmptyAtMinimumDepth(): void
1206 {
1207 $faker = Factory::create();
1208 $faker->seed(0);
1209 $provider = new PostgreSqlProvider($faker);
1210
1211 self::assertNotSame('', $provider->simpleStatement(maxDepth: 1));
1212 }
1213
1214 /**
1215 * @return iterable<string, array{int}>
1216 */
1217 public static function providerNullableSimpleStatementSeed(): iterable
1218 {
1219 yield 'PHP 8.1 and 8.2 random mode' => [252];
1220 yield 'PHP 8.3 and later random mode' => [68];
1221 }
1222
1223 /**
1224 * @return iterable<string, array{int}>
1225 */
1226 public static function providerTargetedGenerationSeed(): iterable
1227 {
1228 foreach (range(0, 31) as $seed) {
1229 yield "seed {$seed}" => [$seed];
1230 }
1231 }
1232
1233 /**
1234 * @return iterable<string, array{StatementType}>
1235 */
1236 public static function providerStatementTypeValue(): iterable
1237 {
1238 yield 'Select' => [StatementType::Select];
1239 yield 'Insert' => [StatementType::Insert];
1240 yield 'Update' => [StatementType::Update];
1241 yield 'Delete' => [StatementType::Delete];
1242 yield 'CreateTable' => [StatementType::CreateTable];
1243 yield 'CreateTableAs' => [StatementType::CreateTableAs];
1244 yield 'CreateDomain' => [StatementType::CreateDomain];
1245 yield 'AlterTable' => [StatementType::AlterTable];
1246 yield 'DropTable' => [StatementType::DropTable];
1247 }
1248
1249 #[DataProvider('providerTargetedGenerationSeed')]
1250 public function testDomainDmlStatementDerivesDmlFromGrammar(int $seed): void
1251 {
1252 $faker = Factory::create();
1253 $faker->seed($seed);
1254 $provider = new PostgreSqlProvider($faker);
1255 $sql = $provider->domainDmlStatement();
1256 $faker->seed($seed);
1257 $lexer = new LexicalGrammar($faker, 'pg-17.2');
1258
1259 $tokens = $lexer->tokenize($sql);
1260
1261 self::assertSame($sql, $provider->domainDmlStatement(40));
1262 self::assertContains($tokens[0], ['INSERT', 'UPDATE', 'DELETE_P']);
1263 }
1264 public function testPlannerCompilesReusableInstructionsWithoutChangingTheDefaultStart(): void
1265 {
1266 $provider = new PostgreSqlProvider(Factory::create(), 'pg-17.2');
1267 $plan = (new \SqlFaker\Generation\Choice\BytePlanCompiler())->compile('', $provider->planner());
1268 self::assertNull($plan->startRule());
1269 self::assertSame($provider->generate($plan), $provider->generate($plan));
1270 }
1271}
1272