packages/sql-faker/tests/Unit/Sqlite/Generation/GenerationPlansTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\SqlFaker\Sqlite\Generation;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\UsesClass;
9use PHPUnit\Framework\TestCase;
10use SqlFaker\Generation\Plan\GenerationPlan;
11use SqlFaker\Generation\Plan\ProductionPattern;
12use SqlFaker\Sqlite\Generation\GenerationPlans;
13
14#[CoversClass(GenerationPlans::class)]
15#[UsesClass(GenerationPlan::class)]
16#[UsesClass(ProductionPattern::class)]
17#[UsesClass(\SqlFaker\Generation\Choice\BytePlanCompiler::class)]
18final class GenerationPlansTest extends TestCase
19{
20 public function testMultiDmlStatementDirectsBothStatementOccurrences(): void
21 {
22 $plan = GenerationPlans::multiDmlStatement(0, 2);
23
24 self::assertSame('input', $plan->startRule());
25 self::assertTrue($plan->patternAt('cmdlist', 0)?->matches(['cmdlist', 'ecmd']) ?? false);
26 self::assertTrue($plan->patternAt('cmdlist', 1)?->matches(['ecmd']) ?? false);
27 self::assertTrue($plan->patternAt('ecmd', 0)?->matches(['cmdx', 'SEMI']) ?? false);
28 self::assertTrue($plan->patternAt('ecmd', 1)?->matches(['cmdx', 'SEMI']) ?? false);
29 self::assertTrue($plan->patternAt('cmd', 0)?->matches(['insert_cmd']) ?? false);
30 self::assertTrue($plan->patternAt('cmd', 1)?->matches(['DELETE']) ?? false);
31 self::assertTrue($plan->patternAt('insert_cmd', 0)?->matches(['INSERT']) ?? false);
32 self::assertTrue($plan->patternAt('insert_cmd', 1)?->matches(['INSERT']) ?? false);
33 self::assertTrue($plan->patternAt('with', 0)?->matches([]) ?? false);
34 self::assertTrue($plan->patternAt('with', 1)?->matches([]) ?? false);
35 }
36
37
38
39 public function testFullTextSearchStatementRequiresMatchGrammar(): void
40 {
41 $plan = GenerationPlans::fullTextSearchStatement();
42
43 self::assertSame('select', $plan->startRule());
44 self::assertTrue($plan->patternAt('select', 0)?->matches(['selectnowith']) ?? false);
45 self::assertTrue(
46 $plan->patternAt('oneselect', 0)?->matches(['SELECT', 'selcollist', 'from', 'where_opt']) ?? false,
47 );
48 self::assertTrue($plan->patternAt('selcollist', 0)?->matches(['sclp', 'scanpt', 'STAR']) ?? false);
49 self::assertTrue($plan->patternAt('sclp', 0)?->matches([]) ?? false);
50 self::assertTrue($plan->patternAt('from', 0)?->matches(['seltablist']) ?? false);
51 self::assertTrue(
52 $plan->patternAt('seltablist', 0)?->matches(['stl_prefix', 'nm', 'dbnm', 'as', 'on_using']) ?? false,
53 );
54 self::assertTrue($plan->patternAt('stl_prefix', 0)?->matches([]) ?? false);
55 self::assertTrue($plan->patternAt('on_using', 0)?->matches([]) ?? false);
56 self::assertTrue($plan->patternAt('where_opt', 0)?->matches(['expr']) ?? false);
57 self::assertTrue($plan->patternAt('expr', 0)?->matches(['expr', 'likeop', 'expr']) ?? false);
58 self::assertTrue($plan->patternAt('expr', 1)?->matches(['term']) ?? false);
59 self::assertTrue($plan->patternAt('expr', 2)?->matches(['term']) ?? false);
60 self::assertTrue($plan->patternAt('likeop', 0)?->matches(['MATCH']) ?? false);
61 }
62
63 public function testForeignKeyConstraintRestrictsTheTableConstraintGrammar(): void
64 {
65 $plan = GenerationPlans::foreignKeyConstraint();
66
67 self::assertSame('conslist', $plan->startRule());
68 self::assertTrue(
69 $plan->patternAt('conslist', 0)?->matches(['conslist', 'tconscomma', 'tcons']) ?? false,
70 );
71 self::assertTrue($plan->patternAt('conslist', 1)?->matches(['tcons']) ?? false);
72 self::assertTrue($plan->patternAt('tcons', 0)?->matches(['CONSTRAINT']) ?? false);
73 self::assertTrue($plan->patternAt('tcons', 1)?->matches(['FOREIGN', 'KEY']) ?? false);
74 self::assertTrue($plan->patternAt('tconscomma', 0)?->matches([]) ?? false);
75 self::assertTrue($plan->patternAt('eidlist_opt', 0)?->matches(['column']) ?? false);
76 }
77
78 public function testInsertFunctionUpsertStatementRestrictsTheConflictFunctionGrammar(): void
79 {
80 $plan = GenerationPlans::insertFunctionUpsertStatement();
81
82 self::assertSame('cmd', $plan->startRule());
83 self::assertTrue($plan->patternAt('cmd', 0)?->matches(['insert_cmd', 'select', 'upsert']) ?? false);
84 self::assertTrue($plan->patternAt('with', 0)?->matches([]) ?? false);
85 self::assertTrue($plan->patternAt('insert_cmd', 0)?->matches(['INSERT']) ?? false);
86 self::assertTrue($plan->patternAt('select', 0)?->matches(['selectnowith']) ?? false);
87 self::assertTrue($plan->patternAt('selectnowith', 0)?->matches(['oneselect']) ?? false);
88 self::assertTrue($plan->patternAt('oneselect', 0)?->matches(['values']) ?? false);
89 self::assertTrue(
90 $plan->patternAt('values', 0)?->matches(['VALUES', 'LP', 'nexprlist', 'RP']) ?? false,
91 );
92 self::assertTrue($plan->patternAt('nexprlist', 0)?->matches(['expr']) ?? false);
93 self::assertTrue(
94 $plan->patternAt('upsert', 0)?->matches([
95 'ON',
96 'CONFLICT',
97 'DO',
98 'UPDATE',
99 'SET',
100 'setlist',
101 'where_opt',
102 'returning',
103 ]) ?? false,
104 );
105 self::assertTrue($plan->patternAt('expr', 0)?->matches(['term']) ?? false);
106 self::assertTrue($plan->patternAt('expr', 1)?->matches(['idj', 'LP', 'RP']) ?? false);
107 }
108
109 public function testTemporaryTableStatementRequiresTheTemporaryProduction(): void
110 {
111 $plan = GenerationPlans::temporaryTableStatement();
112
113 self::assertSame('cmd', $plan->startRule());
114 self::assertTrue($plan->patternAt('cmd', 0)?->matches(['create_table', 'create_table_args']) ?? false);
115 self::assertTrue($plan->patternAt('temp', 0)?->matches(['TEMP']) ?? false);
116 }
117
118 public function testViewStatementRestrictsTheCreateGrammar(): void
119 {
120 $plan = GenerationPlans::viewStatement();
121
122 self::assertSame('cmd', $plan->startRule());
123 self::assertTrue($plan->patternAt('cmd', 0)?->matches(['createkw', 'VIEW', 'select']) ?? false);
124 self::assertTrue($plan->patternAt('oneselect', 0)?->matches(['SELECT']) ?? false);
125 }
126
127 public function testGeneratedColumnStatementRequiresTheGeneratedConstraint(): void
128 {
129 $plan = GenerationPlans::generatedColumnStatement();
130
131 self::assertSame('cmd', $plan->startRule());
132 self::assertTrue($plan->patternAt('cmd', 0)?->matches(['create_table', 'create_table_args']) ?? false);
133 self::assertTrue($plan->patternAt('create_table_args', 0)?->matches(['columnlist']) ?? false);
134 self::assertTrue($plan->patternAt('carglist', 0)?->matches(['carglist', 'ccons']) ?? false);
135 self::assertTrue($plan->patternAt('carglist', 1)?->matches([]) ?? false);
136 self::assertTrue($plan->patternAt('ccons', 0)?->matches(['GENERATED', 'generated']) ?? false);
137 }
138
139 public function testForeignKeyCascadeStatementRequiresBothCascadeActions(): void
140 {
141 $plan = GenerationPlans::foreignKeyCascadeStatement();
142
143 self::assertSame('cmd', $plan->startRule());
144 self::assertTrue($plan->patternAt('cmd', 0)?->matches(['create_table', 'create_table_args']) ?? false);
145 self::assertTrue(
146 $plan->patternAt('create_table_args', 0)?->matches(['columnlist', 'conslist_opt']) ?? false,
147 );
148 self::assertTrue($plan->patternAt('conslist_opt', 0)?->matches(['tcons']) ?? false);
149 self::assertTrue($plan->patternAt('tcons', 0)?->matches(['FOREIGN', 'KEY', 'REFERENCES']) ?? false);
150 self::assertTrue($plan->patternAt('refargs', 0)?->matches(['refargs', 'refarg']) ?? false);
151 self::assertTrue($plan->patternAt('refargs', 1)?->matches(['refargs', 'refarg']) ?? false);
152 self::assertTrue($plan->patternAt('refargs', 2)?->matches([]) ?? false);
153 self::assertTrue($plan->patternAt('refarg', 0)?->matches(['ON', 'DELETE']) ?? false);
154 self::assertTrue($plan->patternAt('refarg', 1)?->matches(['ON', 'UPDATE']) ?? false);
155 self::assertTrue($plan->patternAt('refact', 0)?->matches(['CASCADE']) ?? false);
156 self::assertTrue($plan->patternAt('refact', 1)?->matches(['CASCADE']) ?? false);
157 }
158
159 public function testQuotedIdentifierPlansThatLexeme(): void
160 {
161 $plan = GenerationPlans::quotedIdentifier(1, 2);
162
163 self::assertSame('quoted_identifier', $plan->lexicalTarget());
164 self::assertSame(['minLength' => 1, 'maxLength' => 2], $plan->parameters());
165 }
166
167 public function testStringLiteralPlansThatLexeme(): void
168 {
169 $plan = GenerationPlans::stringLiteral(1, 2);
170
171 self::assertSame('string_literal', $plan->lexicalTarget());
172 self::assertSame(['minLength' => 1, 'maxLength' => 2], $plan->parameters());
173 }
174
175 public function testIntegerLiteralPlansThatLexeme(): void
176 {
177 $plan = GenerationPlans::integerLiteral(1, 2);
178
179 self::assertSame('integer_literal', $plan->lexicalTarget());
180 self::assertSame(['min' => 1, 'max' => 2], $plan->parameters());
181 }
182
183 public function testDecimalLiteralPlansThatLexeme(): void
184 {
185 $plan = GenerationPlans::decimalLiteral(1, 2);
186
187 self::assertSame('decimal_literal', $plan->lexicalTarget());
188 self::assertSame(['precision' => 1, 'scale' => 2], $plan->parameters());
189 }
190
191 public function testStatementBoundsTheWalkAtTheRuleItIsGrownFrom(): void
192 {
193 $plan = GenerationPlans::statement('select_stmt', 12);
194
195 self::assertSame('select_stmt', $plan->startRule());
196 self::assertSame(12, $plan->maxDepth());
197 }
198
199 public function testStatementOfTypePreservesAnExplicitTypeWithoutConsumingRandomness(): void
200 {
201 $faker = \Faker\Factory::create();
202 $faker->seed(17);
203 $plan = GenerationPlans::statementOfType($faker, \SqlFaker\Sqlite\StatementType::Select, 6);
204 $next = $faker->randomNumber();
205 $faker->seed(17);
206
207 self::assertSame($faker->randomNumber(), $next);
208 self::assertSame(\SqlFaker\Sqlite\StatementType::Select->value, $plan->startRule());
209 self::assertSame(6, $plan->maxDepth());
210 self::assertSame(true, $plan->usesStepBudget());
211 }
212
213 public function testStatementOfTypeUsesTheSeededFakerChoiceWhenNoTypeIsSpecified(): void
214 {
215 $faker = \Faker\Factory::create();
216 $faker->seed(17);
217 $plan = GenerationPlans::statementOfType($faker, null, 8);
218 $faker->seed(17);
219 /** @var \SqlFaker\Sqlite\StatementType $expected */
220 $expected = $faker->randomElement(\SqlFaker\Sqlite\StatementType::cases());
221
222 self::assertSame($expected->value, $plan->startRule());
223 self::assertSame(8, $plan->maxDepth());
224 }
225}
226