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