packages/sql-faker/src/MySql/Generation/Rewrite/SubqueryContextRule.php
1<?php
2
3declare(strict_types=1);
4
5namespace SqlFaker\MySql\Generation\Rewrite;
6
7use Override;
8use SqlFaker\Generation\Token\RewriteRule;
9use SqlFaker\Generation\Token\TerminalSequence;
10
11/**
12 * PT_subquery and partition contextualization forbid subqueries in these statement expressions.
13 * @see https://github.com/mysql/mysql-server/blob/mysql-8.4.7/sql/parse_tree_nodes.cc
14 */
15final class SubqueryContextRule implements RewriteRule
16{
17 /**
18 * Replaces only the operand containing a forbidden subquery; ordinary SELECT subqueries remain intact.
19 */
20 #[Override]
21 public function rewrite(TerminalSequence $sequence): TerminalSequence
22 {
23 foreach ($sequence->occurrences('subquery') as $id) {
24 $range = $sequence->range($id);
25 if ($range === null) {
26 continue;
27 }
28 $origin = $sequence->terminals[$range[0]];
29 if (array_intersect(['handler_stmt', 'purge', 'install_stmt', 'part_type_def', 'opt_sub_part'], $origin->rules) === []) {
30 continue;
31 }
32 $position = array_search($id, $origin->ancestors, true);
33 for ($index = ($position === false ? 0 : $position) - 1; $index >= 0; --$index) {
34 if (!in_array($origin->rules[$index], ['expr', 'bool_pri', 'predicate', 'bit_expr', 'simple_expr'], true)) {
35 continue;
36 }
37 $operand = $origin->ancestors[$index];
38 $scope = $sequence->range($operand);
39 if ($scope !== null) {
40 $sequence = $sequence->replace($scope[0], $scope[1] - $scope[0], [
41 $sequence->insertedFor('NUM', $operand, 'sql/parse_tree_nodes.cc:PT_subquery'),
42 ], 'sql/parse_tree_nodes.cc:PT_subquery');
43 }
44 break;
45 }
46 }
47 return $sequence;
48 }
49}
50