packages/ztd-query-mysql/src/Rewrite/Transformer/Insert/InsertTarget.php
1<?php
2
3declare(strict_types=1);
4
5namespace ZtdQuery\Platform\MySql\Rewrite\Transformer\Insert;
6
7use PhpMyAdmin\SqlParser\Statements\InsertStatement;
8use ZtdQuery\Exception\UnsupportedSqlException;
9use ZtdQuery\Schema\ColumnDeclaration;
10use ZtdQuery\Schema\Key\IdentityGenerationStrategy;
11
12/**
13 * Validated target metadata and identity input for an INSERT projection.
14 *
15 * Accepts the public transformer context and narrows fixture cells to the only
16 * values the identity allocator recognizes: integers and numeric strings.
17 */
18final class InsertTarget
19{
20 /**
21 * tableName used to construct the projected row.
22 */
23 public readonly string $tableName;
24
25 /**
26 * tableColumns used to construct the projected row.
27 * @var list<string>
28 */
29 public readonly array $tableColumns;
30
31 /**
32 * insertColumns used to construct the projected row.
33 * @var list<string>
34 */
35 public readonly array $insertColumns;
36
37 /**
38 * columnTypes used to construct the projected row.
39 * @var array<string, ColumnDeclaration>
40 */
41 public readonly array $columnTypes;
42
43 /**
44 * columnDefaults used to construct the projected row.
45 * @var array<string, string>
46 */
47 public readonly array $columnDefaults;
48
49 /**
50 * identityStrategies used to construct the projected row.
51 * @var array<string, IdentityGenerationStrategy>
52 */
53 public readonly array $identityStrategies;
54
55 /**
56 * existingRows used to construct the projected row.
57 * @var array<int, array<string, int|string>>
58 */
59 public readonly array $existingRows;
60
61 /**
62 * candidateKeys used to construct the projected row.
63 * @var array<string, array<int, string>>
64 */
65 public readonly array $candidateKeys;
66
67 /**
68 * Create a target from already validated column and identity metadata.
69 * @param string $tableName
70 * @param list<string> $tableColumns
71 * @param list<string> $insertColumns
72 * @param array<string, ColumnDeclaration> $columnTypes
73 * @param array<string, string> $columnDefaults
74 * @param array<string, IdentityGenerationStrategy> $identityStrategies
75 * @param array<int, array<string, int|string>> $existingRows
76 * @param array<string, array<int, string>> $candidateKeys
77 */
78 public function __construct(
79 string $tableName,
80 array $tableColumns,
81 array $insertColumns,
82 array $columnTypes,
83 array $columnDefaults,
84 array $identityStrategies,
85 array $existingRows,
86 array $candidateKeys,
87 ) {
88 $this->tableName = $tableName;
89 $this->tableColumns = $tableColumns;
90 $this->insertColumns = $insertColumns;
91 $this->columnTypes = $columnTypes;
92 $this->columnDefaults = $columnDefaults;
93 $this->identityStrategies = $identityStrategies;
94 $this->existingRows = $existingRows;
95 $this->candidateKeys = $candidateKeys;
96 }
97
98 /**
99 * @param array<string, array{viewSql: string}|array{
100 * rows: array<int, array<string, mixed>>,
101 * columns: array<int, string>,
102 * columnTypes: array<string, ColumnDeclaration>,
103 * columnDefaults?: array<string, string>,
104 * identityStrategies?: array<string, IdentityGenerationStrategy>,
105 * candidateKeys?: array<string, array<int, string>>
106 * }> $tables
107 * @throws UnsupportedSqlException
108 */
109 public static function fromStatement(InsertStatement $statement, array $tables, string $sql): self
110 {
111 if ($statement->into === null || $statement->into->dest === null) {
112 throw new UnsupportedSqlException($sql, 'Cannot resolve INSERT target');
113 }
114
115 $dest = $statement->into->dest;
116 $tableName = is_string($dest) ? $dest : ($dest->table ?? null);
117 if ($tableName === null) {
118 throw new UnsupportedSqlException($sql, 'Cannot resolve table name');
119 }
120
121 $insertColumns = ResultProjection::orderedValues($statement->into->columns ?? []);
122 $tableColumns = ResultProjection::orderedValues($tables[$tableName]['columns'] ?? $insertColumns);
123 if ($tableColumns === []) {
124 throw new UnsupportedSqlException($sql, 'Cannot determine columns');
125 }
126
127 $columnTypes = $tables[$tableName]['columnTypes'] ?? [];
128 $columnDefaults = $tables[$tableName]['columnDefaults'] ?? [];
129 $identityStrategies = $tables[$tableName]['identityStrategies'] ?? [];
130 $existingRows = [];
131 foreach ($tables[$tableName]['rows'] ?? [] as $row) {
132 $identityRow = [];
133 foreach ($row as $column => $value) {
134 if (is_int($value) || is_string($value)) {
135 $identityRow[$column] = $value;
136 }
137 }
138 $existingRows[] = $identityRow;
139 }
140 $candidateKeys = $tables[$tableName]['candidateKeys'] ?? [];
141 return new self($tableName, $tableColumns, $insertColumns, $columnTypes, $columnDefaults, $identityStrategies, $existingRows, $candidateKeys);
142 }
143}
144