packages/ztd-query-sqlite/tests/Unit/Rewrite/Transformer/SqliteTransformerTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Rewrite\Transformer;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\UsesClass;
9use PHPUnit\Framework\TestCase;
10use ZtdQuery\Exception\UnsupportedSqlException;
11use ZtdQuery\Platform\Sqlite\Rewrite\Transformer\DeleteTransformer;
12use ZtdQuery\Platform\Sqlite\Rewrite\Transformer\InsertTransformer;
13use ZtdQuery\Platform\Sqlite\Rewrite\Transformer\SelectTransformer;
14use ZtdQuery\Platform\Sqlite\Rewrite\Transformer\SqliteTransformer;
15use ZtdQuery\Platform\Sqlite\Rewrite\Transformer\UpdateTransformer;
16use ZtdQuery\Platform\Sqlite\Sql\SqliteIdentifierQuoter;
17use ZtdQuery\Platform\Sqlite\Sql\SqliteLexicalMasker;
18use ZtdQuery\Platform\Sqlite\Sql\SqliteParser;
19use ZtdQuery\Platform\Sqlite\Sql\Value\SqliteCastRenderer;
20
21#[CoversClass(SqliteTransformer::class)]
22#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Dml\Expression\AssignmentColumnParser::class)]
23#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Dml\Expression\AssignmentParser::class)]
24#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Dml\Expression\ValueListParser::class)]
25#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Dml\Insert\InsertClauseParser::class)]
26#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\ExpressionSpan::class)]
27#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\IdentifierDecoder::class)]
28#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\LiteralMasker::class)]
29#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\OpaqueSqlSpan::class)]
30#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\QuotedSpan::class)]
31#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Lexing\TopLevelKeywordScanner::class)]
32#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Relation\RelationSourceParser::class)]
33#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Statement\StatementClassifier::class)]
34#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Statement\StatementStructure::class)]
35#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Statement\TargetTableParser::class)]
36#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Dml\Update\UpdateClauseParser::class)]
37#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\Cte\CteDependencies::class)]
38#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\Cte\CteHeaderParser::class)]
39#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\Cte\CtePrefixMerger::class)]
40#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\Cte\CteReferences::class)]
41#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\FullText\FullTextColumns::class)]
42#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\FullText\MatchExpressionRewriter::class)]
43#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\Index\IndexHintTokens::class)]
44#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Value\CastTypeMapper::class)]
45#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Value\ValueExpressionRenderer::class)]
46#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Value\ValueLiteralRenderer::class)]
47#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\SqlEdits::class)]
48#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\Upsert\UpsertConflictPredicate::class)]
49#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\Upsert\UpsertExpressionBinder::class)]
50#[UsesClass(SqliteCastRenderer::class)]
51#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\Cte\SqliteCteShadowComposer::class)]
52#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\FullText\SqliteFullTextSearchRewriter::class)]
53#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\GeneratedColumn\SqliteGeneratedColumnProjector::class)]
54#[UsesClass(SqliteIdentifierQuoter::class)]
55#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\Index\SqliteIndexHintStripper::class)]
56#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\SqliteLexerProfile::class)]
57#[UsesClass(SqliteLexicalMasker::class)]
58#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\Upsert\SqliteNativeUpsertProjector::class)]
59#[UsesClass(SqliteParser::class)]
60#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Relation\SqliteSelectRelationParser::class)]
61#[UsesClass(\ZtdQuery\Platform\Sqlite\Sql\Value\SqliteValueRenderer::class)]
62#[UsesClass(DeleteTransformer::class)]
63#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\Transformer\InsertRowRenderer::class)]
64#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\Transformer\InsertSelectRenderer::class)]
65#[UsesClass(InsertTransformer::class)]
66#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\Transformer\Insert\InsertProjectionBuilder::class)]
67#[UsesClass(SelectTransformer::class)]
68#[UsesClass(\ZtdQuery\Platform\Sqlite\Rewrite\Transformer\Select\ShadowCteRenderer::class)]
69#[UsesClass(UpdateTransformer::class)]
70final class SqliteTransformerTest extends TestCase
71{
72 public function testTransformSelect(): void
73 {
74 $parser = new SqliteParser();
75 $selectTransformer = new SelectTransformer();
76 $insertTransformer = new InsertTransformer($parser, $selectTransformer);
77 $updateTransformer = new UpdateTransformer($parser, $selectTransformer);
78 $deleteTransformer = new DeleteTransformer($parser, $selectTransformer);
79 $transformer = new SqliteTransformer($parser, $selectTransformer, $insertTransformer, $updateTransformer, $deleteTransformer);
80
81 $tables = [
82 'users' => [
83 'rows' => [['id' => 1, 'name' => 'Alice']],
84 'columns' => ['id', 'name'],
85 'columnTypes' => [],
86 ],
87 ];
88
89 $result = $transformer->transform('SELECT * FROM users', $tables);
90 self::assertStringContainsString('WITH', $result);
91 }
92
93 public function testTransformInsert(): void
94 {
95 $parser = new SqliteParser();
96 $selectTransformer = new SelectTransformer();
97 $insertTransformer = new InsertTransformer($parser, $selectTransformer);
98 $updateTransformer = new UpdateTransformer($parser, $selectTransformer);
99 $deleteTransformer = new DeleteTransformer($parser, $selectTransformer);
100 $transformer = new SqliteTransformer($parser, $selectTransformer, $insertTransformer, $updateTransformer, $deleteTransformer);
101
102 $tables = [
103 'users' => [
104 'rows' => [],
105 'columns' => ['id', 'name'],
106 'columnTypes' => [],
107 ],
108 ];
109
110 $result = $transformer->transform("INSERT INTO users (id, name) VALUES (1, 'Alice')", $tables);
111 self::assertStringContainsString('SELECT', $result);
112 }
113
114 public function testTransformUpdate(): void
115 {
116 $parser = new SqliteParser();
117 $selectTransformer = new SelectTransformer();
118 $insertTransformer = new InsertTransformer($parser, $selectTransformer);
119 $updateTransformer = new UpdateTransformer($parser, $selectTransformer);
120 $deleteTransformer = new DeleteTransformer($parser, $selectTransformer);
121 $transformer = new SqliteTransformer($parser, $selectTransformer, $insertTransformer, $updateTransformer, $deleteTransformer);
122
123 $tables = [
124 'users' => [
125 'rows' => [],
126 'columns' => ['id', 'name'],
127 'columnTypes' => [],
128 ],
129 ];
130
131 $result = $transformer->transform("UPDATE users SET name = 'Bob' WHERE id = 1", $tables);
132 self::assertStringContainsString('SELECT', $result);
133 }
134
135 public function testTransformDelete(): void
136 {
137 $parser = new SqliteParser();
138 $selectTransformer = new SelectTransformer();
139 $insertTransformer = new InsertTransformer($parser, $selectTransformer);
140 $updateTransformer = new UpdateTransformer($parser, $selectTransformer);
141 $deleteTransformer = new DeleteTransformer($parser, $selectTransformer);
142 $transformer = new SqliteTransformer($parser, $selectTransformer, $insertTransformer, $updateTransformer, $deleteTransformer);
143
144 $tables = [
145 'users' => [
146 'rows' => [],
147 'columns' => ['id', 'name'],
148 'columnTypes' => [],
149 ],
150 ];
151
152 $result = $transformer->transform('DELETE FROM users WHERE id = 1', $tables);
153 self::assertStringContainsString('SELECT', $result);
154 }
155
156 public function testTransformUnsupportedThrows(): void
157 {
158 $parser = new SqliteParser();
159 $selectTransformer = new SelectTransformer();
160 $insertTransformer = new InsertTransformer($parser, $selectTransformer);
161 $updateTransformer = new UpdateTransformer($parser, $selectTransformer);
162 $deleteTransformer = new DeleteTransformer($parser, $selectTransformer);
163 $transformer = new SqliteTransformer($parser, $selectTransformer, $insertTransformer, $updateTransformer, $deleteTransformer);
164
165 $this->expectException(UnsupportedSqlException::class);
166 $transformer->transform('CREATE TABLE t (id INTEGER)', []);
167 }
168
169 public function testTransformEmptyThrows(): void
170 {
171 $parser = new SqliteParser();
172 $selectTransformer = new SelectTransformer();
173 $insertTransformer = new InsertTransformer($parser, $selectTransformer);
174 $updateTransformer = new UpdateTransformer($parser, $selectTransformer);
175 $deleteTransformer = new DeleteTransformer($parser, $selectTransformer);
176 $transformer = new SqliteTransformer($parser, $selectTransformer, $insertTransformer, $updateTransformer, $deleteTransformer);
177
178 $this->expectException(UnsupportedSqlException::class);
179 $transformer->transform('', []);
180 }
181
182 public function testTransformWithEmptyTablesReturnsOriginal(): void
183 {
184 $parser = new SqliteParser();
185 $selectTransformer = new SelectTransformer();
186 $insertTransformer = new InsertTransformer($parser, $selectTransformer);
187 $updateTransformer = new UpdateTransformer($parser, $selectTransformer);
188 $deleteTransformer = new DeleteTransformer($parser, $selectTransformer);
189 $transformer = new SqliteTransformer($parser, $selectTransformer, $insertTransformer, $updateTransformer, $deleteTransformer);
190
191 $result = $transformer->transform('SELECT * FROM users', []);
192 self::assertSame('SELECT * FROM users', $result);
193 }
194
195 public function testCommitRewriteStateKeepsAllocatedIdentitiesAcrossProjections(): void
196 {
197 $parser = new SqliteParser();
198 $select = new SelectTransformer();
199 $transformer = new SqliteTransformer(
200 $parser,
201 $select,
202 new InsertTransformer($parser, $select),
203 new UpdateTransformer($parser, $select),
204 new DeleteTransformer($parser, $select),
205 );
206 $tables = ['users' => ['rows' => [], 'columns' => ['id', 'name'], 'columnTypes' => [], 'identityStrategies' => ['id' => \ZtdQuery\Schema\Key\IdentityGenerationStrategy::MaxValue]]];
207 $first = $transformer->transform("INSERT INTO users(name) VALUES ('Alice')", $tables);
208 $transformer->commitRewriteState();
209 $second = $transformer->transform("INSERT INTO users(name) VALUES ('Bob')", $tables);
210 self::assertStringContainsString('1 AS "id"', $first);
211 self::assertStringContainsString('2 AS "id"', $second);
212 }
213
214}
215