packages/ztd-query-sqlite/src/Rewrite/Transformer/InsertTransformer.php
1<?php
2
3declare(strict_types=1);
4
5namespace ZtdQuery\Platform\Sqlite\Rewrite\Transformer;
6
7use ZtdQuery\Exception\UnsupportedSqlException;
8use ZtdQuery\Platform\CastRenderer;
9use ZtdQuery\Platform\Sqlite\Rewrite\Cte\SqliteCteShadowComposer;
10use ZtdQuery\Platform\Sqlite\Rewrite\Upsert\SqliteNativeUpsertProjector;
11use ZtdQuery\Platform\Sqlite\Sql\SqliteParser;
12use ZtdQuery\Platform\Sqlite\Sql\Value\SqliteCastRenderer;
13use ZtdQuery\Rewrite\ShadowIdentityAllocator;
14use ZtdQuery\Rewrite\SqlTransformer;
15
16/**
17 * Transforms INSERT/REPLACE statements into SELECT queries that return the inserted rows.
18 * Applies CTE shadowing via the SelectTransformer delegate.
19 *
20 * Handles:
21 * - INSERT INTO ... VALUES (...)
22 * - INSERT OR REPLACE INTO ... VALUES (...)
23 * - REPLACE INTO ... VALUES (...)
24 * - INSERT INTO ... SELECT ...
25 * - INSERT INTO ... ON CONFLICT ... DO UPDATE SET ...
26 */
27final class InsertTransformer implements SqlTransformer
28{
29 private SqliteParser $parser;
30 private SelectTransformer $selectTransformer;
31 private CastRenderer $castRenderer;
32 private InsertRowRenderer $rowRenderer;
33 private ShadowIdentityAllocator $identityAllocator;
34 private InsertSelectRenderer $insertSelectRenderer;
35 private SqliteCteShadowComposer $cteComposer;
36 private SqliteNativeUpsertProjector $upsertProjector;
37
38 /**
39 * Binds the dependencies used by this operation.
40 */
41 public function __construct(
42 SqliteParser $parser,
43 SelectTransformer $selectTransformer,
44 ?CastRenderer $castRenderer = null,
45 ) {
46 $this->parser = $parser;
47 $this->selectTransformer = $selectTransformer;
48 $this->castRenderer = $castRenderer ?? new SqliteCastRenderer();
49 $this->rowRenderer = new InsertRowRenderer();
50 $this->identityAllocator = new ShadowIdentityAllocator();
51 $this->insertSelectRenderer = new InsertSelectRenderer();
52 $this->cteComposer = new SqliteCteShadowComposer();
53 $this->upsertProjector = new SqliteNativeUpsertProjector();
54 }
55
56 /**
57 * {@inheritDoc}
58 * @throws UnsupportedSqlException
59 */
60 public function transform(string $sql, array $tables): string
61 {
62 $this->identityAllocator->beginProjection();
63 $type = $this->parser->classifyStatement($sql);
64 if ($type !== 'INSERT') {
65 throw new UnsupportedSqlException($sql, 'Expected INSERT/REPLACE statement');
66 }
67
68 $tableName = $this->parser->extractTargetTable($sql);
69 if ($tableName === null) {
70 throw new UnsupportedSqlException($sql, 'Cannot resolve INSERT target');
71 }
72
73 $insertColumns = Insert\InsertProjectionBuilder::orderedValues($this->parser->extractInsertColumns($sql));
74 $tableColumns = Insert\InsertProjectionBuilder::orderedValues($tables[$tableName]['columns'] ?? $insertColumns);
75 if ($tableColumns === []) {
76 throw new UnsupportedSqlException($sql, 'Cannot determine columns');
77 }
78
79 $columnTypes = $tables[$tableName]['columnTypes'] ?? [];
80 $columnDefaults = $tables[$tableName]['columnDefaults'] ?? [];
81 $identityStrategies = $tables[$tableName]['identityStrategies'] ?? [];
82 $existingRows = $tables[$tableName]['rows'] ?? [];
83 $selectSql = (new Insert\InsertProjectionBuilder($this->castRenderer, $this->identityAllocator, $this->insertSelectRenderer, $this->parser, $this->rowRenderer))->buildInsertSelect(
84 $sql,
85 $tableName,
86 $tableColumns,
87 $insertColumns,
88 $columnTypes,
89 $columnDefaults,
90 $identityStrategies,
91 $existingRows,
92 );
93 $selectSql = $this->upsertProjector->project(
94 $selectSql,
95 $tableName,
96 $tableColumns,
97 isset($tables[$tableName]['candidateKeys']) ? $tables[$tableName]['candidateKeys'] : [],
98 $this->parser->extractOnConflictUpdates($sql),
99 $this->parser->extractOnConflictUpdateWhere($sql),
100 );
101
102 return $this->selectTransformer->transform(
103 $this->cteComposer->carryPrefix($sql, $selectSql),
104 $tables,
105 );
106 }
107
108 /**
109 * Commits the transformer state after a rewrite completes.
110 */
111 public function commitRewriteState(): void
112 {
113 $this->identityAllocator->commitProjection();
114 }
115
116}
117