packages/ztd-query-core/src/Shadow/Mutation/Row/InsertMutation.php
1<?php
2
3declare(strict_types=1);
4
5namespace ZtdQuery\Shadow\Mutation\Row;
6
7use ZtdQuery\Exception\DuplicateKeyException;
8use ZtdQuery\Schema\Key\CandidateKeySet;
9use ZtdQuery\Schema\TableDefinition;
10use ZtdQuery\Shadow\Mutation\ConflictSearch;
11use ZtdQuery\Shadow\Mutation\DataMutation;
12use ZtdQuery\Shadow\Mutation\RowConstraints;
13use ZtdQuery\Shadow\Mutation\UpsertExpression;
14use ZtdQuery\Shadow\ShadowStore;
15
16/**
17 * Applies INSERT result rows to the shadow store.
18 *
19 * @phpstan-import-type Row from TableDefinition
20 */
21final class InsertMutation implements DataMutation
22{
23 /**
24 * Target table to insert into.
25 *
26 * @var string
27 */
28 private string $tableName;
29
30 /**
31 * Whether to ignore duplicate key errors.
32 *
33 * @var bool
34 */
35 private bool $ignore;
36
37 /**
38 * Table definition for constraint validation.
39 *
40 * @var TableDefinition|null
41 */
42 private ?TableDefinition $tableDefinition;
43
44 /**
45 * Original SQL statement for exception messages.
46 *
47 * @var string
48 */
49 private string $sql;
50
51 /**
52 * Whether constraint validation is enabled.
53 *
54 * @var bool
55 */
56 private bool $validateConstraints;
57
58 private CandidateKeySet $candidateKeys;
59
60 private ?UpsertExpression $conflictPredicate;
61
62 private RowConstraints $constraints;
63
64 private ConflictSearch $conflicts;
65
66 /**
67 * @param string $tableName Target table.
68 * @param array<int, string> $primaryKeys Primary key columns.
69 * @param bool $ignore Whether to ignore duplicates (INSERT IGNORE).
70 * @param TableDefinition|null $tableDefinition Table definition for constraint validation.
71 * @param string $sql Original SQL statement for exception messages.
72 * @param bool $validateConstraints Whether to validate constraints.
73 * @param CandidateKeySet|null $candidateKeys Candidate keys used for duplicate detection.
74 * @param UpsertExpression|null $conflictPredicate Condition that controls candidate-key eligibility.
75 */
76 public function __construct(
77 string $tableName,
78 array $primaryKeys = [],
79 bool $ignore = false,
80 ?TableDefinition $tableDefinition = null,
81 string $sql = '',
82 bool $validateConstraints = false,
83 ?CandidateKeySet $candidateKeys = null,
84 ?UpsertExpression $conflictPredicate = null,
85 ) {
86 $this->tableName = $tableName;
87 $this->ignore = $ignore;
88 $this->tableDefinition = $tableDefinition;
89 $this->sql = $sql;
90 $this->validateConstraints = $validateConstraints;
91 $this->candidateKeys = $candidateKeys ?? CandidateKeySet::fromSchema($primaryKeys);
92 $this->conflictPredicate = $conflictPredicate;
93 $this->constraints = new RowConstraints($this->tableDefinition, $this->tableName, $this->sql);
94 $this->conflicts = new ConflictSearch($this->candidateKeys, $this->conflictPredicate, $this->tableName);
95 }
96
97 /**
98 * {@inheritDoc}
99 *
100 * @throws DuplicateKeyException When a row would collide with one already there on a candidate key
101 */
102 public function apply(ShadowStore $store, array $rows): void
103 {
104 $existingRows = $store->get($this->tableName);
105 $filteredRows = [];
106
107 foreach ($rows as $row) {
108 if ($this->validateConstraints && $this->tableDefinition !== null) {
109 $this->constraints->assertNoNullWhereNoneIsAllowed($row);
110 }
111
112 $conflict = $this->conflicts->of($row, $existingRows);
113 if ($conflict !== null) {
114 if ($this->ignore) {
115 continue;
116 }
117
118 if ($this->validateConstraints) {
119 throw new DuplicateKeyException(
120 $this->sql,
121 $this->tableName,
122 $conflict->keyName,
123 $conflict->values
124 );
125 }
126 }
127
128 if ($this->validateConstraints && $this->tableDefinition !== null) {
129 $this->constraints->assertNoDuplicateUniqueKey($row, $existingRows);
130 }
131
132 $filteredRows[] = $row;
133 $existingRows[] = $row;
134 }
135
136 $store->insert($this->tableName, $filteredRows);
137 }
138
139 /**
140 * {@inheritDoc}
141 */
142 public function tableName(): string
143 {
144 return $this->tableName;
145 }
146
147}
148