packages/sql-faker/tests/Unit/PostgreSql/PostgreSqlProviderTest.php

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