packages/sql-faker/tests/Unit/Sqlite/Generation/GenerationContextTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\SqlFaker\Sqlite\Generation;
6
7use Faker\Factory;
8use PHPUnit\Framework\Attributes\CoversClass;
9use PHPUnit\Framework\Attributes\UsesClass;
10use PHPUnit\Framework\TestCase;
11use SqlFaker\Generation\Exception\GenerationException;
12use SqlFaker\Generation\Exception\LexicalException;
13use SqlFaker\Generation\Plan\GenerationPlan;
14use SqlFaker\Generation\Plan\ProductionPattern;
15use SqlFaker\Generation\SqlGenerator;
16use SqlFaker\Grammar\Model\Grammar;
17use SqlFaker\Grammar\Model\Production;
18use SqlFaker\Grammar\Model\ProductionRule;
19use SqlFaker\Grammar\Model\Terminal;
20use SqlFaker\Sqlite\Generation\GenerationContext;
21
22#[CoversClass(GenerationContext::class)]
23#[UsesClass(Grammar::class)]
24#[UsesClass(Production::class)]
25#[UsesClass(ProductionRule::class)]
26#[UsesClass(Terminal::class)]
27#[UsesClass(SqlGenerator::class)]
28#[UsesClass(GenerationPlan::class)]
29#[UsesClass(ProductionPattern::class)]
30#[UsesClass(GenerationException::class)]
31#[UsesClass(LexicalException::class)]
32#[UsesClass(\SqlFaker\Generation\Derivation\CompletionCosts::class)]
33#[UsesClass(\SqlFaker\Generation\Derivation\Derivation::class)]
34#[UsesClass(\SqlFaker\Generation\Derivation\DerivationTrace::class)]
35#[UsesClass(\SqlFaker\Generation\Derivation\TerminationAnalyzer::class)]
36#[UsesClass(\SqlFaker\Generation\Derivation\TerminationCost::class)]
37#[UsesClass(\SqlFaker\Generation\Candidate\ChoiceLexemeGenerator::class)]
38#[UsesClass(\SqlFaker\Generation\Candidate\FixedLexemeGenerator::class)]
39#[UsesClass(\SqlFaker\Generation\Lexeme\Lexeme::class)]
40#[UsesClass(\SqlFaker\Generation\Lexeme\LexemeCandidates::class)]
41#[UsesClass(\SqlFaker\Generation\Lexeme\LexemeInput::class)]
42#[UsesClass(\SqlFaker\Generation\Lexeme\LexemeSequence::class)]
43#[UsesClass(\SqlFaker\Generation\Candidate\MatchingLexemeGenerator::class)]
44#[UsesClass(\SqlFaker\Generation\Candidate\ValueLexemeGenerator::class)]
45#[UsesClass(\SqlFaker\Generation\Candidate\RegisteredLexemeGenerator::class)]
46#[UsesClass(\SqlFaker\Generation\Output\CandidateResolver::class)]
47#[UsesClass(\SqlFaker\Generation\Lexeme\OutputPart::class)]
48#[UsesClass(\SqlFaker\Generation\Lexeme\ResolvedOutput::class)]
49#[UsesClass(\SqlFaker\Generation\Output\ReverseLexemeGenerator::class)]
50#[UsesClass(\SqlFaker\Generation\Output\SqlSerializer::class)]
51#[UsesClass(\SqlFaker\Generation\Output\CombinedSpacingRule::class)]
52#[UsesClass(\SqlFaker\Generation\Lexeme\SpacingConstraint::class)]
53#[UsesClass(\SqlFaker\Generation\Token\ProductionOccurrence::class)]
54#[UsesClass(\SqlFaker\Generation\Token\TerminalOccurrence::class)]
55#[UsesClass(\SqlFaker\Generation\Token\TerminalSequence::class)]
56#[UsesClass(\SqlFaker\Generation\Derivation\TokenGenerator::class)]
57#[UsesClass(\SqlFaker\Generation\Token\TokenRewriter::class)]
58#[UsesClass(\SqlFaker\Generation\Candidate\VersionCase::class)]
59#[UsesClass(\SqlFaker\Generation\Candidate\VersionedLexemeGenerator::class)]
60#[UsesClass(\SqlFaker\Sqlite\Generation\Tokenization\KeywordIndex::class)]
61#[UsesClass(\SqlFaker\Generation\Value\RandomCharacters::class)]
62#[UsesClass(\SqlFaker\Sqlite\Generation\Value\LiteralGenerator::class)]
63#[UsesClass(\SqlFaker\Grammar\Model\NonTerminal::class)]
64#[UsesClass(\SqlFaker\Grammar\Resource\SqlVersionRegistry::class)]
65#[UsesClass(\SqlFaker\Grammar\Resource\SqlVersion::class)]
66#[UsesClass(\SqlFaker\Sqlite\Generation\Lexeme\DefinitionFactory::class)]
67#[UsesClass(\SqlFaker\Sqlite\Generation\Lexeme\JoinLexemeGenerator::class)]
68#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\IdentifierListRule::class)]
69#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\JoinRule::class)]
70#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\RewriteDefinitions::class)]
71#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\StrictTableRule::class)]
72#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\TableOptionRule::class)]
73#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\WindowFrameRule::class)]
74#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\WithoutRowidRule::class)]
75#[UsesClass(\SqlFaker\Sqlite\Generation\GrammarAdaptation::class)]
76#[UsesClass(\SqlFaker\Sqlite\Grammar\SqliteGrammar::class)]
77#[UsesClass(\SqlFaker\Sqlite\Generation\LexicalGrammar::class)]
78#[UsesClass(\SqlFaker\Sqlite\Generation\Tokenization\SqliteTokenizer::class)]
79#[UsesClass(\SqlFaker\Generation\Lexeme\LexemeBoundary::class)]
80#[UsesClass(\SqlFaker\Sqlite\Generation\Lexeme\JoinModifiers::class)]
81#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\Expression\ExpressionGroupingRule::class)]
82#[UsesClass(\SqlFaker\Sqlite\Generation\Lexeme\WindowNameLexemeGenerator::class)]
83#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\CompoundSelectRule::class)]
84#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\UpsertSourceRule::class)]
85#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\FunctionArgumentRule::class)]
86#[UsesClass(\SqlFaker\Sqlite\Generation\Rewrite\GeneratedColumnRule::class)]
87#[UsesClass(\SqlFaker\Generation\Token\TerminalMappingRule::class)]
88#[UsesClass(\SqlFaker\Generation\Value\CharacterDomain::class)]
89#[UsesClass(\SqlFaker\Generation\Value\ChoiceDomain::class)]
90#[UsesClass(\SqlFaker\Generation\Value\IntegerDomain::class)]
91#[UsesClass(\SqlFaker\Generation\Value\SequenceDomain::class)]
92#[UsesClass(\SqlFaker\MySql\Generation\Value\DollarQuotedDomain::class)]
93#[UsesClass(\SqlFaker\Generation\Output\BoundaryCompletion::class)]
94#[UsesClass(\SqlFaker\Sqlite\Generation\Value\IdentifierDomain::class)]
95#[UsesClass(\SqlFaker\Sqlite\Generation\Value\QuotedDomain::class)]
96#[UsesClass(\SqlFaker\Generation\Value\RepeatDomain::class)]
97#[UsesClass(\SqlFaker\Generation\Value\WordDomain::class)]
98#[UsesClass(\SqlFaker\Generation\Derivation\CompletionState::class)]
99#[UsesClass(\SqlFaker\Generation\Derivation\CompletionFrontier::class)]
100#[UsesClass(\SqlFaker\Generation\Derivation\ConstrainedCompletion::class)]
101#[UsesClass(\SqlFaker\Generation\Value\ValueChoices::class)]
102#[UsesClass(\SqlFaker\Generation\Derivation\CompletionMemo::class)]
103#[UsesClass(\SqlFaker\Generation\Derivation\CompletionReduction::class)]
104#[UsesClass(\SqlFaker\Generation\Derivation\ConstraintDependencies::class)]
105#[UsesClass(\SqlFaker\Generation\Choice\BytePlanCompiler::class)]
106#[UsesClass(\SqlFaker\Generation\Derivation\Completion\PatternProductions::class)]
107#[UsesClass(\SqlFaker\Generation\Derivation\Completion\CompletionWitness::class)]
108#[UsesClass(\SqlFaker\Sqlite\Generation\Lexeme\LexicalDefinition::class)]
109final class GenerationContextTest extends TestCase
110{
111    public function testBindsTheRequestedReleaseAndPreservesTheGrammarStart(): void
112    {
113        $grammar = new Grammar('entry', ['entry' => new ProductionRule('entry', [new Production([new Terminal('SELECT')])])]);
114        $context = new GenerationContext($grammar, Factory::create(), 'sqlite-3.47.2');
115        self::assertSame('sqlite-3.47.2', $context->lexicalGrammar->version());
116        self::assertSame('entry', $context->grammar->startSymbol);
117        self::assertSame($grammar->ruleMap['entry'], $context->grammar->ruleMap['entry']);
118    }
119
120    public function testUsesTheGrammarStartWhenThePlanDoesNotSpecifyOne(): void
121    {
122        $grammar = new Grammar('entry', [
123            'entry' => new ProductionRule('entry', [new Production([new Terminal('SELECT')])]),
124            'fragment' => new ProductionRule('fragment', [new Production([])]),
125        ]);
126        $faker = Factory::create();
127        $context = new GenerationContext($grammar, $faker, 'sqlite-3.47.2');
128        $generator = new SqlGenerator($context->grammar, $faker, $context->lexicalGrammar, $context->rewriter, $context->startSymbol);
129        self::assertSame('SELECT', $generator->generate(GenerationPlan::all()));
130        self::assertSame('', $generator->generate(GenerationPlan::fromRule('fragment')));
131        self::assertSame('SELECT', $generator->generate(GenerationPlan::all()));
132    }
133
134    public function testLeavesThePlanInControlOfNullableProductionChoices(): void
135    {
136        $grammar = new Grammar('entry', ['entry' => new ProductionRule('entry', [
137            new Production([]), new Production([new Terminal('SELECT')]),
138        ])]);
139        $faker = Factory::create();
140        $context = new GenerationContext($grammar, $faker, 'sqlite-3.47.2');
141        $generator = new SqlGenerator($context->grammar, $faker, $context->lexicalGrammar, $context->rewriter, $context->startSymbol);
142        self::assertSame('SELECT', $generator->generate(GenerationPlan::all()->requiringNonEmpty()->withMaxDepth(1)));
143        self::assertSame('', $generator->generate(GenerationPlan::constrained('entry', ['entry' => [ProductionPattern::exactly()]])));
144    }
145
146    public function testReportsMissingLexicalImplementationWhenTheTerminalIsActuallyGenerated(): void
147    {
148        $grammar = new Grammar('entry', [
149            'entry' => new ProductionRule('entry', [new Production([new Terminal('SELECT')])]),
150            'missing' => new ProductionRule('missing', [new Production([new Terminal('UNIMPLEMENTED')])]),
151        ]);
152        $faker = Factory::create();
153        $context = new GenerationContext($grammar, $faker, 'sqlite-3.47.2');
154        $generator = new SqlGenerator($context->grammar, $faker, $context->lexicalGrammar, $context->rewriter, $context->startSymbol);
155        self::assertSame('SELECT', $generator->generate(GenerationPlan::all()));
156        $this->expectException(LexicalException::class);
157        $this->expectExceptionMessage('UNIMPLEMENTED');
158        $generator->generate(GenerationPlan::fromRule('missing'));
159    }
160
161    public function testReportsAnUnknownNonterminalWithoutTreatingItAsSqlText(): void
162    {
163        $grammar = new Grammar('entry', ['entry' => new ProductionRule('entry', [new Production([new Terminal('SELECT')])])]);
164        $faker = Factory::create();
165        $context = new GenerationContext($grammar, $faker, 'sqlite-3.47.2');
166        $generator = new SqlGenerator($context->grammar, $faker, $context->lexicalGrammar, $context->rewriter, $context->startSymbol);
167        $this->expectException(GenerationException::class);
168        $this->expectExceptionMessage('Unknown grammar rule: missing');
169        $generator->generate(GenerationPlan::fromRule('missing'));
170    }
171}
172