packages/ztd-query-mysql/tests/Unit/Rewrite/Transformer/UpdateTransformerTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Rewrite\Transformer;
6
7use PhpMyAdmin\SqlParser\Parser;
8use PHPUnit\Framework\Attributes\CoversClass;
9use PHPUnit\Framework\Attributes\UsesClass;
10use PHPUnit\Framework\TestCase;
11use ZtdQuery\Platform\MySql\Rewrite\Transformer\SelectTransformer;
12use ZtdQuery\Platform\MySql\Rewrite\Transformer\UpdateTransformer;
13use ZtdQuery\Platform\MySql\Sql\Dml\DmlWhereClauseExtractor;
14use ZtdQuery\Platform\MySql\Sql\Dml\UpdateAssignmentExtractor;
15use ZtdQuery\Platform\MySql\Sql\Dml\UpdateSourceExtractor;
16use ZtdQuery\Platform\MySql\Sql\MySqlIdentifierQuoter;
17use ZtdQuery\Platform\MySql\Sql\MySqlParser;
18use ZtdQuery\Platform\MySql\Sql\Value\MySqlCastRenderer;
19
20#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Partition\MySqlPartitionSelectionRewriter::class)]
21#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Dml\AssignmentExpression::class)]
22#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Cte\HeaderParser::class)]
23#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Cte\IdentifierReferences::class)]
24#[UsesClass(\ZtdQuery\Platform\MySql\Sql\OptionalInsertIntoNormalizer::class)]
25#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Relation\ReferenceReader::class)]
26#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\FullText\ExpressionEditor::class)]
27#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Partition\SelectionReader::class)]
28#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Partition\SourceProjection::class)]
29#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Transformer\Set\OrderRewriter::class)]
30#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Transformer\Set\ValueNormalizer::class)]
31#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Transformer\Shadow\CteRows::class)]
32
33
34#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Value\CastTypeResolver::class)]
35#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Type\RankEdits::class)]
36#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Value\ScalarExpression::class)]
37#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Value\StringCoercion::class)]
38#[CoversClass(UpdateTransformer::class)]
39#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Relation\MySqlSelectRelationParser::class)]
40#[UsesClass(MySqlParser::class)]
41#[UsesClass(SelectTransformer::class)]
42#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\FullText\MySqlFullTextSearchRewriter::class)]
43#[UsesClass(MySqlCastRenderer::class)]
44#[UsesClass(MySqlIdentifierQuoter::class)]
45#[UsesClass(DmlWhereClauseExtractor::class)]
46#[UsesClass(UpdateAssignmentExtractor::class)]
47#[UsesClass(UpdateSourceExtractor::class)]
48#[UsesClass(\ZtdQuery\Platform\MySql\Sql\Value\MySqlValueRenderer::class)]
49#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Type\MySqlTypeSemantics::class)]
50#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\Cte\MySqlCteShadowComposer::class)]
51#[UsesClass(\ZtdQuery\Platform\MySql\Rewrite\GeneratedColumn\MySqlGeneratedColumnProjector::class)]
52#[UsesClass(\ZtdQuery\Platform\MySql\Sql\MySqlLexerProfile::class)]
53#[CoversClass(\ZtdQuery\Platform\MySql\Rewrite\Transformer\Update\ResultSelect::class)]
54#[CoversClass(\ZtdQuery\Platform\MySql\Rewrite\Transformer\Update\TargetProjection::class)]
55final class UpdateTransformerTest extends TestCase
56{
57 public function testBuildProjectionPreservesDerivedJoinSource(): void
58 {
59 $sql = 'UPDATE summary s JOIN (SELECT category, COUNT(*) AS cnt, MIN(price) AS mn FROM products GROUP BY category) p ON s.category = p.category SET s.min_price = p.mn, s.item_count = p.cnt';
60 $parser = new Parser($sql);
61 $statement = $parser->statements[0];
62 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
63 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
64
65 $result = $transformer->buildProjection(
66 $statement,
67 ['id', 'category', 'min_price', 'item_count'],
68 ['id'],
69 [],
70 ['p.mn', 'p.cnt'],
71 null,
72 (new UpdateSourceExtractor())->extract($sql),
73 );
74
75 self::assertSame(
76 'SELECT p.mn AS `min_price`, p.cnt AS `item_count`, `s`.`id`, `s`.`category`, `s`.`id` AS `__ztd_original_id` FROM summary s JOIN (SELECT category, COUNT(*) AS cnt, MIN(price) AS mn FROM products GROUP BY category) p ON s.category = p.category',
77 $result['sql'],
78 );
79 }
80
81 public function testTransformPreservesIntroducedHexLiteral(): void
82 {
83 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
84 $tables = [
85 'data' => [
86 'rows' => [['id' => 1, 'payload' => 'Hello']],
87 'columns' => ['id', 'payload'],
88 'columnTypes' => [],
89 ],
90 ];
91
92 $result = $transformer->transform("UPDATE data SET payload = X'576F726C64' WHERE id = 1", $tables);
93
94 self::assertStringContainsString("X'576F726C64' AS `payload`", $result);
95 self::assertStringNotContainsString('SELECT X AS `payload`', $result);
96 }
97
98 public function testTransformPreservesIntroducedLiteralsForEveryUpdateTarget(): void
99 {
100 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
101 $tables = [
102 'data' => [
103 'rows' => [['id' => 1, 'payload' => 'old']],
104 'columns' => ['id', 'payload'],
105 'columnTypes' => [],
106 'primaryKeys' => ['id'],
107 ],
108 'audit' => [
109 'rows' => [['id' => 1, 'bits' => '0']],
110 'columns' => ['id', 'bits'],
111 'columnTypes' => [],
112 'primaryKeys' => ['id'],
113 ],
114 ];
115
116 $result = $transformer->transform(
117 "UPDATE data d, audit a SET d.payload = X'00', a.bits = B'01' WHERE d.id = a.id",
118 $tables,
119 );
120
121 self::assertStringContainsString("X'00' AS `__ztd_multi_0_value_1`", $result);
122 self::assertStringContainsString("B'01' AS `__ztd_multi_1_value_1`", $result);
123 }
124
125 public function testTransformPreservesIntervalUnitInAssignment(): void
126 {
127 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
128 $tables = [
129 'tasks' => [
130 'rows' => [['id' => 1, 'created_at' => '2025-01-01 10:00:00', 'due_at' => null]],
131 'columns' => ['id', 'created_at', 'due_at'],
132 'columnTypes' => [],
133 ],
134 ];
135
136 $result = $transformer->transform(
137 'UPDATE tasks SET due_at = created_at + INTERVAL 30 DAY WHERE id = 1',
138 $tables,
139 );
140
141 self::assertStringContainsString('created_at + INTERVAL 30 DAY AS `due_at`', $result);
142 }
143
144 public function testTransformPreservesCaseWhereExpression(): void
145 {
146 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
147 $tables = [
148 't' => [
149 'rows' => [['id' => 1, 'score' => 85], ['id' => 2, 'score' => 60]],
150 'columns' => ['id', 'score'],
151 'columnTypes' => [],
152 ],
153 ];
154
155 $result = $transformer->transform(
156 'UPDATE t SET score = 0 WHERE CASE WHEN score > 80 THEN 1 ELSE 0 END = 1',
157 $tables,
158 );
159
160 self::assertStringContainsString('WHERE CASE WHEN score > 80 THEN 1 ELSE 0 END = 1', $result);
161 }
162
163 public function testBuildUpdateSelectUsesAliasAndColumns(): void
164 {
165 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
166 $sql = "UPDATE users u SET name = 'Bob' WHERE id = 1";
167 $parser = new Parser($sql);
168 $statement = $parser->statements[0];
169 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
170
171 $result = $transformer->buildProjection($statement, ['id', 'name']);
172
173 self::assertStringContainsString("SELECT 'Bob' AS `name`", $result['sql']);
174 self::assertStringContainsString('`u`.`id`', $result['sql']);
175 self::assertStringContainsString('FROM `users` AS u', $result['sql']);
176 self::assertStringContainsString('WHERE id = 1', $result['sql']);
177 self::assertSame('users', $result['table']);
178 self::assertCount(1, $result['tables']);
179 }
180
181 public function testBuildUpdateSelectWithQualifiedColumnName(): void
182 {
183 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
184 $sql = "UPDATE products SET `products`.`name` = 'Widget' WHERE id = 1";
185 $parser = new Parser($sql);
186 $statement = $parser->statements[0];
187 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
188
189 $result = $transformer->buildProjection($statement, ['id', 'name', 'price']);
190
191 self::assertStringContainsString('AS `name`', $result['sql']);
192 self::assertStringNotContainsString('``name``', $result['sql']);
193 self::assertStringContainsString('`products`.`id`', $result['sql']);
194 self::assertStringContainsString('`products`.`price`', $result['sql']);
195 }
196
197 public function testBuildUpdateSelectWithUnqualifiedColumnName(): void
198 {
199 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
200 $sql = "UPDATE products SET name = 'Widget' WHERE id = 1";
201 $parser = new Parser($sql);
202 $statement = $parser->statements[0];
203 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
204
205 $result = $transformer->buildProjection($statement, ['id', 'name', 'price']);
206
207 self::assertStringContainsString('AS `name`', $result['sql']);
208 self::assertStringNotContainsString('``', $result['sql']);
209 self::assertStringContainsString('`products`.`id`', $result['sql']);
210 self::assertStringContainsString('`products`.`price`', $result['sql']);
211 }
212
213 public function testBuildUpdateSelectWithBacktickedUnqualifiedColumn(): void
214 {
215 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
216 $sql = "UPDATE products SET `name` = 'Widget' WHERE id = 1";
217 $parser = new Parser($sql);
218 $statement = $parser->statements[0];
219 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
220
221 $result = $transformer->buildProjection($statement, ['id', 'name']);
222
223 self::assertStringContainsString('AS `name`', $result['sql']);
224 self::assertStringNotContainsString('``name``', $result['sql']);
225 }
226
227 public function testBuildUpdateSelectWithJoin(): void
228 {
229 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
230 $sql = "UPDATE users u JOIN orders o ON u.id = o.user_id SET u.name = 'Updated' WHERE o.amount > 100";
231 $parser = new Parser($sql);
232 $statement = $parser->statements[0];
233 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
234
235 $result = $transformer->buildProjection($statement, ['id', 'name']);
236
237 self::assertStringContainsString("SELECT 'Updated' AS `name`", $result['sql']);
238 self::assertStringContainsString('`u`.`id`', $result['sql']);
239 self::assertStringContainsString('FROM `users` AS u', $result['sql']);
240 self::assertStringContainsString('JOIN `orders` AS o', $result['sql']);
241 self::assertStringContainsString('ON u.id = o.user_id', $result['sql']);
242 self::assertStringContainsString('WHERE o.amount > 100', $result['sql']);
243 }
244
245 public function testBuildUpdateSelectWithLeftJoin(): void
246 {
247 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
248 $sql = "UPDATE users u LEFT JOIN orders o ON u.id = o.user_id SET u.status = 'inactive' WHERE o.id IS NULL";
249 $parser = new Parser($sql);
250 $statement = $parser->statements[0];
251 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
252
253 $result = $transformer->buildProjection($statement, ['id', 'status']);
254
255 self::assertStringContainsString("SELECT 'inactive' AS `status`", $result['sql']);
256 self::assertStringContainsString('LEFT JOIN `orders` AS o', $result['sql']);
257 self::assertStringContainsString('ON u.id = o.user_id', $result['sql']);
258 }
259
260 public function testBuildUpdateSelectWithMultipleJoins(): void
261 {
262 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
263 $sql = "UPDATE users u JOIN orders o ON u.id = o.user_id JOIN products p ON o.product_id = p.id SET u.name = 'VIP' WHERE p.price > 1000";
264 $parser = new Parser($sql);
265 $statement = $parser->statements[0];
266 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
267
268 $result = $transformer->buildProjection($statement, ['id', 'name']);
269
270 self::assertStringContainsString('JOIN `orders` AS o', $result['sql']);
271 self::assertStringContainsString('JOIN `products` AS p', $result['sql']);
272 self::assertStringContainsString('ON u.id = o.user_id', $result['sql']);
273 self::assertStringContainsString('ON o.product_id = p.id', $result['sql']);
274 }
275
276 public function testBuildMultiTableUpdate(): void
277 {
278 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
279 $sql = "UPDATE users u, orders o SET u.name = 'Updated', o.status = 'processed' WHERE u.id = o.user_id";
280 $parser = new Parser($sql);
281 $statement = $parser->statements[0];
282 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
283
284 $result = $transformer->buildProjection($statement, ['id', 'name']);
285
286 self::assertStringContainsString('FROM `users`', $result['sql']);
287 self::assertStringContainsString('`orders`', $result['sql']);
288 self::assertSame('users', $result['table']);
289 self::assertCount(2, $result['tables']);
290 self::assertArrayHasKey('users', $result['tables']);
291 self::assertArrayHasKey('orders', $result['tables']);
292 }
293
294 public function testTransformThrowsForNonUpdateStatement(): void
295 {
296 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
297
298 self::expectException(\ZtdQuery\Exception\UnsupportedSqlException::class);
299 $transformer->transform('SELECT 1', []);
300 }
301
302 public function testTransformSimpleUpdateReturnsSelect(): void
303 {
304 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
305 $sql = "UPDATE users SET name = 'Bob' WHERE id = 1";
306 $tables = [
307 'users' => [
308 'rows' => [],
309 'columns' => ['id', 'name'],
310 'columnTypes' => [],
311 'primaryKeys' => ['id'],
312 ],
313 ];
314
315 $result = $transformer->transform($sql, $tables);
316 self::assertStringContainsString('SELECT', $result);
317 self::assertStringContainsString("'Bob' AS `name`", $result);
318 self::assertStringContainsString('`users`.`id` AS `__ztd_original_id`', $result);
319 }
320
321 public function testTransformUpdateWithPartitionThrows(): void
322 {
323 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
324
325 self::expectException(\ZtdQuery\Exception\UnsupportedSqlException::class);
326 self::expectExceptionMessage('PARTITION');
327 $transformer->transform("UPDATE users PARTITION (p0) SET name = 'Bob' WHERE id = 1", []);
328 }
329
330 public function testBuildUpdateSelectWithOrderByAndLimit(): void
331 {
332 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
333 $sql = "UPDATE users SET name = 'Bob' WHERE id > 5 ORDER BY id LIMIT 10";
334 $parser = new Parser($sql);
335 $statement = $parser->statements[0];
336 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
337
338 $result = $transformer->buildProjection($statement, ['id', 'name']);
339
340 self::assertStringContainsString('ORDER BY', $result['sql']);
341 self::assertStringContainsString('LIMIT', $result['sql']);
342 }
343
344 public function testBuildUpdateSelectWithOnlyOrderBySelectsRowsBeforeProjecting(): void
345 {
346 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
347 $parser = new Parser("UPDATE users SET name = 'Bob' ORDER BY id");
348 $statement = $parser->statements[0];
349 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
350
351 $result = $transformer->buildProjection($statement, ['id', 'name']);
352
353 self::assertSame(
354 "SELECT 'Bob' AS `name`, `users`.`id` FROM (SELECT * FROM `users` ORDER BY id ASC) AS `users`",
355 $result['sql'],
356 );
357 }
358
359 public function testBuildUpdateSelectWithOnlyLimitSelectsRowsBeforeProjecting(): void
360 {
361 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
362 $parser = new Parser("UPDATE users SET name = 'Bob' LIMIT 2");
363 $statement = $parser->statements[0];
364 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
365
366 $result = $transformer->buildProjection($statement, ['id', 'name']);
367
368 self::assertSame(
369 "SELECT 'Bob' AS `name`, `users`.`id` FROM (SELECT * FROM `users` LIMIT 0, 2) AS `users`",
370 $result['sql'],
371 );
372 }
373
374 public function testBuildUpdateSelectWithJoinAndLimitKeepsJoinInResultSelect(): void
375 {
376 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
377 $parser = new Parser("UPDATE users JOIN orders ON users.id = orders.user_id SET users.name = 'Bob' LIMIT 2");
378 $statement = $parser->statements[0];
379 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
380
381 $result = $transformer->buildProjection($statement, ['id', 'name']);
382
383 self::assertSame(
384 "SELECT 'Bob' AS `name`, `users`.`id` FROM `users` JOIN `orders` ON users.id = orders.user_id LIMIT 0, 2",
385 $result['sql'],
386 );
387 }
388
389 public function testBuildUpdateSelectWithEmptyColumnsUsesWildcard(): void
390 {
391 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
392 $sql = "UPDATE users SET name = 'Bob' WHERE id = 1";
393 $parser = new Parser($sql);
394 $statement = $parser->statements[0];
395 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
396
397 $result = $transformer->buildProjection($statement, []);
398
399 self::assertStringContainsString("'Bob' AS `name`", $result['sql']);
400 }
401
402 public function testTransformUpdateWithShadowDataAddsCte(): void
403 {
404 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
405 $sql = "UPDATE users SET name = 'Bob' WHERE id = 1";
406 $tables = [
407 'users' => [
408 'rows' => [['id' => 1, 'name' => 'Alice']],
409 'columns' => ['id', 'name'],
410 'columnTypes' => [],
411 ],
412 ];
413
414 $result = $transformer->transform($sql, $tables);
415 self::assertStringContainsString('WITH', $result);
416 }
417
418 public function testBuildUpdateSelectExactFormat(): void
419 {
420 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
421 $sql = 'UPDATE t SET a = 1 WHERE b = 2';
422 $parser = new Parser($sql);
423 $statement = $parser->statements[0];
424 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
425
426 $result = $transformer->buildProjection($statement, ['a', 'b']);
427 self::assertStringStartsWith('SELECT 1 AS `a`, `t`.`b` FROM `t`', $result['sql']);
428 self::assertStringContainsString('WHERE b = 2', $result['sql']);
429 }
430
431 public function testBuildUpdateSelectWithoutColumnsIncludesSetValuesOnly(): void
432 {
433 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
434 $sql = 'UPDATE t SET a = 1';
435 $parser = new Parser($sql);
436 $statement = $parser->statements[0];
437 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
438
439 $result = $transformer->buildProjection($statement, []);
440 self::assertStringStartsWith('SELECT 1 AS `a` FROM `t`', $result['sql']);
441 }
442
443 public function testBuildUpdateSelectAliasClause(): void
444 {
445 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
446 $sql = "UPDATE users u SET u.name = 'X' WHERE u.id = 1";
447 $parser = new Parser($sql);
448 $statement = $parser->statements[0];
449 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
450
451 $result = $transformer->buildProjection($statement, ['id', 'name']);
452 self::assertStringContainsString('FROM `users` AS u', $result['sql']);
453 self::assertStringContainsString('`u`.`id`', $result['sql']);
454 }
455
456 public function testBuildMultiTableUpdateAdditionalTableAlias(): void
457 {
458 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
459 $sql = "UPDATE users u, orders o SET u.name = 'X' WHERE u.id = o.user_id";
460 $parser = new Parser($sql);
461 $statement = $parser->statements[0];
462 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
463
464 $result = $transformer->buildProjection($statement, ['id', 'name']);
465 self::assertStringContainsString(', `orders` AS o', $result['sql']);
466 self::assertSame('users', $result['table']);
467 }
468
469 public function testBuildUpdateSelectCoveredColNotDuplicated(): void
470 {
471 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
472 $sql = 'UPDATE t SET a = 1, b = 2 WHERE c = 3';
473 $parser = new Parser($sql);
474 $statement = $parser->statements[0];
475 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
476
477 $result = $transformer->buildProjection($statement, ['a', 'b', 'c']);
478 self::assertSame(1, substr_count($result['sql'], 'AS `a`'));
479 self::assertSame(1, substr_count($result['sql'], 'AS `b`'));
480 self::assertStringContainsString('`t`.`c`', $result['sql']);
481 }
482
483 public function testBuildUpdateWithJoinUsingClause(): void
484 {
485 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
486 $sql = "UPDATE users JOIN orders USING (id) SET users.name = 'X' WHERE orders.amount > 100";
487 $parser = new Parser($sql);
488 $statement = $parser->statements[0];
489 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
490
491 $result = $transformer->buildProjection($statement, ['id', 'name']);
492 self::assertStringContainsString('JOIN', $result['sql']);
493 }
494
495 public function testBuildUpdateSelectOrderByFormatContainsKeyword(): void
496 {
497 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
498 $sql = 'UPDATE t SET a = 1 ORDER BY b DESC LIMIT 5';
499 $parser = new Parser($sql);
500 $statement = $parser->statements[0];
501 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
502
503 $result = $transformer->buildProjection($statement, ['a', 'b']);
504 self::assertStringContainsString('ORDER BY', $result['sql']);
505 self::assertStringContainsString('b DESC', $result['sql']);
506 self::assertStringContainsString('LIMIT', $result['sql']);
507 }
508
509 public function testTransformWithLowercasePartitionThrows(): void
510 {
511 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
512
513 self::expectException(\ZtdQuery\Exception\UnsupportedSqlException::class);
514 self::expectExceptionMessage('PARTITION');
515 $transformer->transform("UPDATE users partition (p0) SET name = 'Bob' WHERE id = 1", []);
516 }
517
518 public function testBuildProjectionOrderByExactFormat(): void
519 {
520 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
521 $sql = 'UPDATE t SET a = 1 ORDER BY b ASC LIMIT 3';
522 $parser = new Parser($sql);
523 $statement = $parser->statements[0];
524 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
525
526 $result = $transformer->buildProjection($statement, ['a', 'b']);
527 self::assertStringContainsString(' ORDER BY b ASC', $result['sql']);
528 self::assertStringContainsString(' LIMIT ', $result['sql']);
529 self::assertMatchesRegularExpression('/ORDER BY b ASC LIMIT/', $result['sql']);
530 }
531
532 public function testBuildProjectionColumnsNotEmpty(): void
533 {
534 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
535 $sql = 'UPDATE t SET a = 1 WHERE id = 1';
536 $parser = new Parser($sql);
537 $statement = $parser->statements[0];
538 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
539
540 $tables = ['t' => ['columns' => ['a', 'b'], 'rows' => [], 'columnTypes' => []]];
541 $result = $transformer->transform($sql, $tables);
542 self::assertStringContainsString('AS `a`', $result);
543 self::assertStringContainsString('`t`.`b`', $result);
544 }
545
546 public function testBuildProjectionCoveredColIsNotDuplicatedWhenTrue(): void
547 {
548 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
549 $sql = 'UPDATE t SET a = 99 WHERE id = 1';
550 $parser = new Parser($sql);
551 $statement = $parser->statements[0];
552 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
553
554 $result = $transformer->buildProjection($statement, ['a', 'b']);
555 self::assertSame(1, substr_count($result['sql'], '`a`'));
556 }
557
558 public function testBuildProjectionMultiTableReturnsAllTables(): void
559 {
560 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
561 $sql = "UPDATE users u, orders o SET u.name = 'X' WHERE u.id = o.user_id";
562 $parser = new Parser($sql);
563 $statement = $parser->statements[0];
564 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
565
566 $result = $transformer->buildProjection($statement, ['id', 'name']);
567 self::assertSame('u', $result['tables']['users']['alias']);
568 self::assertSame('o', $result['tables']['orders']['alias']);
569 }
570
571 public function testBuildProjectionJoinOnClause(): void
572 {
573 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
574 $sql = "UPDATE users u JOIN orders o ON u.id = o.user_id SET u.name = 'X'";
575 $parser = new Parser($sql);
576 $statement = $parser->statements[0];
577 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
578
579 $result = $transformer->buildProjection($statement, ['id', 'name']);
580 self::assertStringContainsString('ON u.id = o.user_id', $result['sql']);
581 self::assertStringContainsString('JOIN `orders`', $result['sql']);
582 }
583
584 public function testBuildProjectionJoinTypeIsIncluded(): void
585 {
586 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
587 $sql = "UPDATE users u LEFT JOIN orders o ON u.id = o.user_id SET u.name = 'X'";
588 $parser = new Parser($sql);
589 $statement = $parser->statements[0];
590 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
591
592 $result = $transformer->buildProjection($statement, ['id', 'name']);
593 self::assertStringContainsString('LEFT JOIN', $result['sql']);
594 }
595
596 public function testBuildProjectionWithNoWhereReturnsNoWhereClause(): void
597 {
598 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
599 $sql = 'UPDATE t SET a = 1';
600 $parser = new Parser($sql);
601 $statement = $parser->statements[0];
602 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
603
604 $result = $transformer->buildProjection($statement, ['a']);
605 self::assertStringNotContainsString('WHERE', $result['sql']);
606 }
607
608 public function testBuildProjectionWithNoOrderByReturnsNoOrderClause(): void
609 {
610 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
611 $sql = 'UPDATE t SET a = 1 WHERE id = 1';
612 $parser = new Parser($sql);
613 $statement = $parser->statements[0];
614 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
615
616 $result = $transformer->buildProjection($statement, ['a']);
617 self::assertStringNotContainsString('ORDER BY', $result['sql']);
618 self::assertStringNotContainsString('LIMIT', $result['sql']);
619 }
620
621 public function testBuildProjectionCoveredColIsTrueNotFalse(): void
622 {
623 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
624 $sql = 'UPDATE t SET a = 99 WHERE id = 1';
625 $parser = new Parser($sql);
626 $statement = $parser->statements[0];
627 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
628
629 $result = $transformer->buildProjection($statement, ['a', 'b']);
630 self::assertStringNotContainsString('`t`.`a`', $result['sql']);
631 self::assertStringContainsString('99 AS `a`', $result['sql']);
632 self::assertStringContainsString('`t`.`b`', $result['sql']);
633 }
634
635 public function testBuildProjectionLimitContainsActualValue(): void
636 {
637 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
638 $sql = 'UPDATE t SET a = 1 ORDER BY b LIMIT 5';
639 $parser = new Parser($sql);
640 $statement = $parser->statements[0];
641 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
642
643 $result = $transformer->buildProjection($statement, ['a', 'b']);
644 self::assertMatchesRegularExpression('/LIMIT\s+\d/', $result['sql']);
645 }
646
647 public function testBuildProjectionSingleTableNoAdditionalTables(): void
648 {
649 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
650 $sql = 'UPDATE t SET a = 1 WHERE id = 1';
651 $parser = new Parser($sql);
652 $statement = $parser->statements[0];
653 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
654
655 $result = $transformer->buildProjection($statement, ['a']);
656 $fromPos = strpos($result['sql'], 'FROM `t`');
657 self::assertNotFalse($fromPos);
658 $afterFrom = substr($result['sql'], $fromPos + strlen('FROM `t`'));
659 self::assertStringNotContainsString('`t`', $afterFrom);
660 }
661
662 public function testBuildProjectionMultiTableAdditionalTableSameNameNoAlias(): void
663 {
664 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
665 $sql = "UPDATE users, orders SET users.name = 'X' WHERE users.id = orders.user_id";
666 $parser = new Parser($sql);
667 $statement = $parser->statements[0];
668 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
669
670 $result = $transformer->buildProjection($statement, ['id', 'name']);
671 self::assertStringContainsString('`orders`', $result['sql']);
672 }
673
674 public function testBuildProjectionJoinUsingClauseExactFormat(): void
675 {
676 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
677 $sql = "UPDATE users JOIN orders USING (id) SET users.name = 'X'";
678 $parser = new Parser($sql);
679 $statement = $parser->statements[0];
680 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
681
682 $result = $transformer->buildProjection($statement, ['id', 'name']);
683 self::assertStringContainsString('USING', $result['sql']);
684 self::assertStringContainsString('(', $result['sql']);
685 self::assertStringContainsString(')', $result['sql']);
686 }
687
688 public function testBuildProjectionJoinOnConditionFormatted(): void
689 {
690 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
691 $sql = "UPDATE users u JOIN orders o ON u.id = o.user_id SET u.name = 'X' WHERE o.amount > 50";
692 $parser = new Parser($sql);
693 $statement = $parser->statements[0];
694 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
695
696 $result = $transformer->buildProjection($statement, ['id', 'name']);
697 self::assertStringContainsString(' ON ', $result['sql']);
698 self::assertStringContainsString('u.id = o.user_id', $result['sql']);
699 }
700
701 public function testBuildProjectionJoinAliasPresentInOutput(): void
702 {
703 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
704 $sql = "UPDATE users u JOIN orders o ON u.id = o.user_id SET u.name = 'X'";
705 $parser = new Parser($sql);
706 $statement = $parser->statements[0];
707 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
708
709 $result = $transformer->buildProjection($statement, ['id', 'name']);
710 self::assertStringContainsString('AS o', $result['sql']);
711 }
712
713 public function testBuildProjectionExactSqlForSimpleUpdate(): void
714 {
715 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
716 $sql = "UPDATE users SET name = 'Bob' WHERE id = 1";
717 $parser = new Parser($sql);
718 $statement = $parser->statements[0];
719 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
720
721 $result = $transformer->buildProjection($statement, ['id', 'name']);
722 self::assertSame("SELECT 'Bob' AS `name`, `users`.`id` FROM `users` WHERE id = 1", $result['sql']);
723 self::assertSame('users', $result['table']);
724 self::assertSame(['users' => ['alias' => 'users']], $result['tables']);
725 }
726
727 public function testBuildProjectionExactSqlForUpdateWithAlias(): void
728 {
729 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
730 $sql = "UPDATE users u SET u.name = 'Bob' WHERE u.id = 1";
731 $parser = new Parser($sql);
732 $statement = $parser->statements[0];
733 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
734
735 $result = $transformer->buildProjection($statement, ['id', 'name']);
736 self::assertSame("SELECT 'Bob' AS `name`, `u`.`id` FROM `users` AS u WHERE u.id = 1", $result['sql']);
737 self::assertSame('users', $result['table']);
738 self::assertSame(['users' => ['alias' => 'u']], $result['tables']);
739 }
740
741 public function testBuildProjectionExactSqlForMultiTableUpdate(): void
742 {
743 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
744 $sql = "UPDATE users u, orders o SET u.name = 'X', o.status = 'done' WHERE u.id = o.user_id";
745 $parser = new Parser($sql);
746 $statement = $parser->statements[0];
747 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
748
749 $result = $transformer->buildProjection($statement, ['id', 'name']);
750 self::assertSame("SELECT 'X' AS `name`, 'done' AS `status`, `u`.`id` FROM `users` AS u, `orders` AS o WHERE u.id = o.user_id", $result['sql']);
751 self::assertSame('users', $result['table']);
752 self::assertSame(['users' => ['alias' => 'u'], 'orders' => ['alias' => 'o']], $result['tables']);
753 }
754
755 public function testBuildProjectionExactSqlForUpdateWithOrderByAndLimit(): void
756 {
757 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
758 $sql = "UPDATE users SET name = 'X' ORDER BY id LIMIT 5";
759 $parser = new Parser($sql);
760 $statement = $parser->statements[0];
761 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
762
763 $result = $transformer->buildProjection($statement, ['id', 'name']);
764 self::assertSame("SELECT 'X' AS `name`, `users`.`id` FROM (SELECT * FROM `users` ORDER BY id ASC LIMIT 0, 5) AS `users`", $result['sql']);
765 }
766
767 public function testBuildProjectionExactSqlForUpdateWithJoin(): void
768 {
769 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
770 $sql = "UPDATE users u JOIN orders o ON u.id = o.user_id SET u.name = 'X' WHERE o.status = 'pending'";
771 $parser = new Parser($sql);
772 $statement = $parser->statements[0];
773 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
774
775 $result = $transformer->buildProjection($statement, ['id', 'name']);
776 self::assertSame("SELECT 'X' AS `name`, `u`.`id` FROM `users` AS u JOIN `orders` AS o ON u.id = o.user_id WHERE o.status = 'pending'", $result['sql']);
777 self::assertSame('users', $result['table']);
778 self::assertSame(['users' => ['alias' => 'u']], $result['tables']);
779 }
780
781 public function testTransformExactOutputWithShadowData(): void
782 {
783 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
784 $sql = "UPDATE users SET name = 'Bob' WHERE id = 1";
785 $tables = [
786 'users' => [
787 'rows' => [['id' => 1, 'name' => 'Alice']],
788 'columns' => ['id', 'name'],
789 'columnTypes' => [],
790 ],
791 ];
792
793 $result = $transformer->transform($sql, $tables);
794 self::assertSame(
795 'WITH `users` AS (SELECT CAST(1 AS SIGNED) AS `id`, CAST(' . "'" . 'Alice' . "'" . ' AS CHAR) AS `name`)' . "\n"
796 . "SELECT 'Bob' AS `name`, `users`.`id` FROM users WHERE id = 1",
797 $result
798 );
799 }
800
801 public function testTransformExactOutputWithEmptyRows(): void
802 {
803 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
804 $sql = "UPDATE users SET name = 'Bob' WHERE id = 1";
805 $tables = [
806 'users' => [
807 'rows' => [],
808 'columns' => ['id', 'name'],
809 'columnTypes' => [],
810 ],
811 ];
812
813 $result = $transformer->transform($sql, $tables);
814 self::assertSame(
815 'WITH `users` AS (SELECT CAST(NULL AS CHAR) AS `id`, CAST(NULL AS CHAR) AS `name` FROM DUAL WHERE 0)' . "\n"
816 . "SELECT 'Bob' AS `name`, `users`.`id` FROM users WHERE id = 1",
817 $result
818 );
819 }
820
821 public function testBuildProjectionWithNoColumnsUsesWildcard(): void
822 {
823 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
824 $sql = "UPDATE users SET name = 'Bob' WHERE id = 1";
825 $parser = new Parser($sql);
826 $statement = $parser->statements[0];
827 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
828
829 $result = $transformer->buildProjection($statement, []);
830 self::assertSame("SELECT 'Bob' AS `name` FROM `users` WHERE id = 1", $result['sql']);
831 }
832
833 public function testBuildProjectionJoinUsingExactColumnInOutput(): void
834 {
835 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
836 $sql = "UPDATE users JOIN orders USING (user_id) SET users.name = 'X'";
837 $parser = new Parser($sql);
838 $statement = $parser->statements[0];
839 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
840
841 $result = $transformer->buildProjection($statement, ['user_id', 'name']);
842 self::assertStringContainsString('USING (user_id)', $result['sql']);
843 }
844
845 public function testBuildProjectionMultiTableNoAliasSameNameNotDuplicated(): void
846 {
847 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
848 $sql = "UPDATE users, orders SET users.name = 'X' WHERE users.id = orders.user_id";
849 $parser = new Parser($sql);
850 $statement = $parser->statements[0];
851 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
852
853 $result = $transformer->buildProjection($statement, ['id', 'name']);
854 self::assertStringNotContainsString('AS orders', $result['sql']);
855 self::assertStringContainsString('`orders`', $result['sql']);
856 }
857
858 public function testBuildProjectionMultiTableWithAliasDifferentFromTableName(): void
859 {
860 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
861 $sql = "UPDATE users u, orders o SET u.name = 'Y' WHERE u.id = o.user_id";
862 $parser = new Parser($sql);
863 $statement = $parser->statements[0];
864 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
865
866 $result = $transformer->buildProjection($statement, ['id', 'name']);
867 self::assertStringContainsString('`orders` AS o', $result['sql']);
868 }
869
870 public function testBuildProjectionExactSqlForUpdateWithJoinUsing(): void
871 {
872 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
873 $sql = "UPDATE users JOIN orders USING (id) SET users.name = 'Z' WHERE orders.amount > 10";
874 $parser = new Parser($sql);
875 $statement = $parser->statements[0];
876 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
877
878 $result = $transformer->buildProjection($statement, ['id', 'name']);
879 self::assertStringContainsString('JOIN `orders`', $result['sql']);
880 self::assertStringContainsString('USING (id)', $result['sql']);
881 self::assertStringContainsString("'Z' AS `name`", $result['sql']);
882 self::assertStringContainsString('WHERE orders.amount > 10', $result['sql']);
883 }
884
885 public function testBuildProjectionExactSqlForUpdateWithLeftJoinOn(): void
886 {
887 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
888 $sql = 'UPDATE t1 LEFT JOIN t2 ON t1.id = t2.t1_id SET t1.a = 99';
889 $parser = new Parser($sql);
890 $statement = $parser->statements[0];
891 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
892
893 $result = $transformer->buildProjection($statement, ['id', 'a']);
894 self::assertStringContainsString('LEFT JOIN `t2`', $result['sql']);
895 self::assertStringContainsString('ON t1.id = t2.t1_id', $result['sql']);
896 self::assertStringContainsString('99 AS `a`', $result['sql']);
897 }
898
899 public function testTransformExactOutputForUpdateWithJoinUsing(): void
900 {
901 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
902 $sql = "UPDATE users JOIN orders USING (id) SET users.name = 'Z'";
903 $tables = [
904 'users' => [
905 'rows' => [],
906 'columns' => ['id', 'name'],
907 'columnTypes' => [],
908 ],
909 ];
910
911 $result = $transformer->transform($sql, $tables);
912 self::assertStringContainsString('USING (id)', $result);
913 self::assertStringContainsString("'Z' AS `name`", $result);
914 }
915
916 public function testBuildProjectionCoveredColBlocksColumnFromRemainder(): void
917 {
918 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
919 $sql = 'UPDATE t SET x = 10, y = 20 WHERE id = 1';
920 $parser = new Parser($sql);
921 $statement = $parser->statements[0];
922 self::assertInstanceOf(\PhpMyAdmin\SqlParser\Statements\UpdateStatement::class, $statement);
923
924 $result = $transformer->buildProjection($statement, ['x', 'y', 'z']);
925 self::assertStringContainsString('10 AS `x`', $result['sql']);
926 self::assertStringContainsString('20 AS `y`', $result['sql']);
927 self::assertStringContainsString('`t`.`z`', $result['sql']);
928 self::assertStringNotContainsString('`t`.`x`', $result['sql']);
929 self::assertStringNotContainsString('`t`.`y`', $result['sql']);
930 }
931
932 public function testTransformMultiUpdateProjectsValuesAndOriginalIdentityPerTarget(): void
933 {
934 $transformer = new UpdateTransformer(new MySqlParser(), new SelectTransformer());
935 $tables = [
936 'users' => [
937 'rows' => [['id' => 2, 'name' => 'Bob']],
938 'columns' => ['id', 'name'],
939 'columnTypes' => [],
940 'primaryKeys' => ['id'],
941 ],
942 'orders' => [
943 'rows' => [['order_id' => 9, 'user_id' => 2]],
944 'columns' => ['order_id', 'user_id'],
945 'columnTypes' => [],
946 'primaryKeys' => ['order_id'],
947 ],
948 ];
949
950 $result = $transformer->transform(
951 "UPDATE users u, orders o SET `u`.`name` = 'Updated', `o`.`user_id` = 7 WHERE u.id = o.user_id",
952 $tables,
953 );
954
955 self::assertStringContainsString(
956 "SELECT `u`.`id` AS `__ztd_multi_0_value_0`, 'Updated' AS `__ztd_multi_0_value_1`, `u`.`id` AS `__ztd_multi_0_identity_0`",
957 $result,
958 );
959 self::assertStringContainsString(
960 '`o`.`order_id` AS `__ztd_multi_1_value_0`, 7 AS `__ztd_multi_1_value_1`, `o`.`order_id` AS `__ztd_multi_1_identity_0`',
961 $result,
962 );
963 }
964}
965