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