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