packages/ztd-query-mysql/tests/Unit/Rewrite/Transformer/MySqlTransformerTest.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\Platform\MySql\Rewrite\Transformer\DeleteTransformer;
11use ZtdQuery\Platform\MySql\Rewrite\Transformer\InsertTransformer;
12use ZtdQuery\Platform\MySql\Rewrite\Transformer\MySqlTransformer;
13use ZtdQuery\Platform\MySql\Rewrite\Transformer\ReplaceTransformer;
14use ZtdQuery\Platform\MySql\Rewrite\Transformer\SelectTransformer;
15use ZtdQuery\Platform\MySql\Rewrite\Transformer\UpdateTransformer;
16use ZtdQuery\Platform\MySql\Sql\Dml\DmlWhereClauseExtractor;
17use ZtdQuery\Platform\MySql\Sql\Dml\InsertSelectSourceExtractor;
18use ZtdQuery\Platform\MySql\Sql\Dml\UpdateSourceExtractor;
19use ZtdQuery\Platform\MySql\Sql\MySqlIdentifierQuoter;
20use ZtdQuery\Platform\MySql\Sql\MySqlParser;
21use ZtdQuery\Platform\MySql\Sql\Upsert\MySqlUpsertAssignmentExtractor;
22use ZtdQuery\Platform\MySql\Sql\Value\MySqlCastRenderer;
23
24#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Partition\MySqlPartitionSelectionRewriter::class)]
25#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Dml\AssignmentExpression::class)]
26#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Cte\HeaderParser::class)]
27#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Cte\IdentifierReferences::class)]
28#[UsesClass(\ZtdQuery\Platform\MySql\Sql\OptionalInsertIntoNormalizer::class)]
29#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Relation\ExpressionNames::class)]
30#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Relation\ReferenceReader::class)]
31#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Upsert\AssignmentReader::class)]
32#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\FullText\ExpressionEditor::class)]
33#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Partition\SelectionReader::class)]
34#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Partition\SourceProjection::class)]
35#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Upsert\ConflictPredicate::class)]
36#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Upsert\ExpressionBinder::class)]
37#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Upsert\MetadataColumns::class)]
38#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Upsert\QualifiedColumn::class)]
39#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Transformer\Delete\ResultSelect::class)]
40#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Transformer\Delete\TargetProjection::class)]
41#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Transformer\Insert\InsertTarget::class)]
42#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Transformer\Insert\ReplaceStatementConverter::class)]
43#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Transformer\Insert\ResultProjection::class)]
44#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Transformer\Select\ExpressionAliaser::class)]
45#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Transformer\Set\OrderRewriter::class)]
46#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Transformer\Set\ValueNormalizer::class)]
47#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Transformer\Shadow\CteRows::class)]
48#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Transformer\Update\ResultSelect::class)]
49#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Transformer\Update\TargetProjection::class)]
50#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Value\CastTypeResolver::class)]
51#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Type\RankEdits::class)]
52#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Value\ScalarExpression::class)]
53#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Value\StringCoercion::class)]
54#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Dml\UpdateAssignmentExtractor::class)]
55#[CoversClass(MySqlTransformer::class)]
56#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Relation\MySqlSelectRelationParser::class)]
57#[UsesClass(MySqlParser::class)]
58#[UsesClass(MySqlUpsertAssignmentExtractor::class)]
59#[UsesClass(SelectTransformer::class)]
60#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\FullText\MySqlFullTextSearchRewriter::class)]
61#[UsesClass(InsertTransformer::class)]
62#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Transformer\InsertRowRenderer::class)]
63#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Transformer\InsertSelectRenderer::class)]
64#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Transformer\MySqlSelectListAliaser::class)]
65#[UsesClass(InsertSelectSourceExtractor::class)]
66#[UsesClass(UpdateTransformer::class)]
67#[UsesClass(DeleteTransformer::class)]
68#[UsesClass(DmlWhereClauseExtractor::class)]
69#[UsesClass(UpdateSourceExtractor::class)]
70#[UsesClass(ReplaceTransformer::class)]
71#[UsesClass(MySqlCastRenderer::class)]
72#[UsesClass(MySqlIdentifierQuoter::class)]
73#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Value\MySqlValueRenderer::class)]
74#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Type\MySqlTypeSemantics::class)]
75#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Cte\MySqlCteShadowComposer::class)]
76#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Upsert\MySqlNativeUpsertProjector::class)]
77#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\GeneratedColumn\MySqlGeneratedColumnProjector::class)]
78#[UsesClass(\ZtdQuery\Platform\MySql\Sql\MySqlLexerProfile::class)]
79final class MySqlTransformerTest extends TestCase
80{
81    public function testTransformSelectPassthrough(): void
82    {
83        $parser = new MySqlParser();
84        $selectTransformer = new SelectTransformer();
85        $insertTransformer = new InsertTransformer($parser, $selectTransformer);
86        $updateTransformer = new UpdateTransformer($parser, $selectTransformer);
87        $deleteTransformer = new DeleteTransformer($parser, $selectTransformer);
88        $replaceTransformer = new ReplaceTransformer($parser, $selectTransformer);
89
90        $transformer = new MySqlTransformer($parser, $selectTransformer, $insertTransformer, $updateTransformer, $deleteTransformer, $replaceTransformer);
91
92        $sql = 'SELECT 1';
93        $result = $transformer->transform($sql, []);
94        self::assertSame($sql, $result);
95    }
96
97    public function testTransformSelectWithShadowData(): void
98    {
99        $parser = new MySqlParser();
100        $selectTransformer = new SelectTransformer();
101        $insertTransformer = new InsertTransformer($parser, $selectTransformer);
102        $updateTransformer = new UpdateTransformer($parser, $selectTransformer);
103        $deleteTransformer = new DeleteTransformer($parser, $selectTransformer);
104        $replaceTransformer = new ReplaceTransformer($parser, $selectTransformer);
105
106        $transformer = new MySqlTransformer($parser, $selectTransformer, $insertTransformer, $updateTransformer, $deleteTransformer, $replaceTransformer);
107
108        $sql = 'SELECT * FROM users';
109        $tables = [
110            'users' => [
111                'rows' => [['id' => '1', 'name' => 'Alice']],
112                'columns' => ['id', 'name'],
113                'columnTypes' => [],
114            ],
115        ];
116
117        $result = $transformer->transform($sql, $tables);
118        self::assertStringContainsString('WITH', $result);
119    }
120
121    public function testTransformInsertDelegatesToInsertTransformer(): void
122    {
123        $parser = new MySqlParser();
124        $selectTransformer = new SelectTransformer();
125        $insertTransformer = new InsertTransformer($parser, $selectTransformer);
126        $updateTransformer = new UpdateTransformer($parser, $selectTransformer);
127        $deleteTransformer = new DeleteTransformer($parser, $selectTransformer);
128        $replaceTransformer = new ReplaceTransformer($parser, $selectTransformer);
129
130        $transformer = new MySqlTransformer($parser, $selectTransformer, $insertTransformer, $updateTransformer, $deleteTransformer, $replaceTransformer);
131
132        $sql = "INSERT INTO users (id, name) VALUES (1, 'Alice')";
133        $tables = [];
134
135        $result = $transformer->transform($sql, $tables);
136        self::assertStringContainsString('SELECT 1 AS `id`', $result);
137        self::assertStringContainsString('AS `name`', $result);
138    }
139
140    public function testTransformDeleteDelegatesToDeleteTransformer(): void
141    {
142        $parser = new MySqlParser();
143        $selectTransformer = new SelectTransformer();
144        $insertTransformer = new InsertTransformer($parser, $selectTransformer);
145        $updateTransformer = new UpdateTransformer($parser, $selectTransformer);
146        $deleteTransformer = new DeleteTransformer($parser, $selectTransformer);
147        $replaceTransformer = new ReplaceTransformer($parser, $selectTransformer);
148
149        $transformer = new MySqlTransformer($parser, $selectTransformer, $insertTransformer, $updateTransformer, $deleteTransformer, $replaceTransformer);
150
151        $sql = 'DELETE FROM users WHERE id = 1';
152        $tables = [
153            'users' => [
154                'rows' => [['id' => 1]],
155                'columns' => ['id'],
156                'columnTypes' => [],
157            ],
158        ];
159
160        $result = $transformer->transform($sql, $tables);
161        self::assertStringContainsString('SELECT', $result);
162        self::assertStringContainsString('FROM', $result);
163        self::assertStringContainsString('WHERE', $result);
164    }
165
166    public function testTransformReplaceDelegatesToReplaceTransformer(): void
167    {
168        $parser = new MySqlParser();
169        $selectTransformer = new SelectTransformer();
170        $insertTransformer = new InsertTransformer($parser, $selectTransformer);
171        $updateTransformer = new UpdateTransformer($parser, $selectTransformer);
172        $deleteTransformer = new DeleteTransformer($parser, $selectTransformer);
173        $replaceTransformer = new ReplaceTransformer($parser, $selectTransformer);
174
175        $transformer = new MySqlTransformer($parser, $selectTransformer, $insertTransformer, $updateTransformer, $deleteTransformer, $replaceTransformer);
176
177        $sql = "REPLACE INTO users (id, name) VALUES (1, 'Bob')";
178        $tables = [];
179
180        $result = $transformer->transform($sql, $tables);
181        self::assertStringContainsString('SELECT 1 AS `id`', $result);
182    }
183
184    public function testCommitRewriteState(): void
185    {
186        $parser = new MySqlParser();
187        $store = new \ZtdQuery\Shadow\ShadowStore();
188        $registry = new \ZtdQuery\Schema\TableDefinitionRegistry();
189        $select = new SelectTransformer();
190        $insert = new InsertTransformer($parser, $select);
191        $update = new UpdateTransformer($parser, $select);
192        $delete = new DeleteTransformer($parser, $select);
193        $replace = new ReplaceTransformer($parser, $select);
194        $transformer = new MySqlTransformer($parser, $select, $insert, $update, $delete, $replace);
195        $tables = ['users' => ['rows' => [], 'columns' => ['id', 'name'], 'columnTypes' => [], 'identityStrategies' => ['id' => \ZtdQuery\Schema\Key\IdentityGenerationStrategy::MaxValue]]];
196        self::assertSame("SELECT 1 AS `id`, 'a' AS `name`", $transformer->transform("INSERT INTO users (name) VALUES ('a')", $tables));
197        self::assertSame("SELECT 1 AS `id`, 'a' AS `name`", $transformer->transform("REPLACE INTO users (name) VALUES ('a')", $tables));
198        $transformer->commitRewriteState();
199        self::assertSame("SELECT 2 AS `id`, 'b' AS `name`", $transformer->transform("INSERT INTO users (name) VALUES ('b')", $tables));
200        self::assertSame("SELECT 2 AS `id`, 'b' AS `name`", $transformer->transform("REPLACE INTO users (name) VALUES ('b')", $tables));
201    }
202}
203