toga-ai 1.0.308 → 1.0.309
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -6,7 +6,7 @@ project: Database Changes
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: architecture
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-07-
|
|
9
|
+
updated: 2026-07-09
|
|
10
10
|
owners: [jcardinal, mhammontree, bala]
|
|
11
11
|
files:
|
|
12
12
|
- Core/
|
|
@@ -168,35 +168,62 @@ its own header.)
|
|
|
168
168
|
5. Never edit or re-date an already-applied file — add a new dated file instead. Never put new
|
|
169
169
|
work in a `HISTORIC` folder.
|
|
170
170
|
|
|
171
|
-
## Bulk data loads —
|
|
171
|
+
## Bulk data loads — batch, and stage large sets in a temp table
|
|
172
172
|
|
|
173
|
-
When a change inserts many rows (reference-data / code-table loads), write it as
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
173
|
+
When a change inserts many rows (reference-data / code-table loads), write it as **batched
|
|
174
|
+
statements**, not one `INSERT` per row. The database is billed per query and the external
|
|
175
|
+
executor runs each statement as its own round-trip, so N separate inserts cost N times the
|
|
176
|
+
round-trips (and query cost) of a batched load.
|
|
177
177
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
178
|
+
To stay **idempotent** without a unique key to `INSERT IGNORE` against, anti-join the source
|
|
179
|
+
set to the target and insert only the misses.
|
|
180
|
+
|
|
181
|
+
**Small sets (a few hundred rows):** a single `INSERT ... VALUES (...),(...),...`, or an
|
|
182
|
+
`INSERT ... SELECT` over a `UNION ALL` derived row set, is fine:
|
|
182
183
|
|
|
183
184
|
```sql
|
|
184
185
|
INSERT INTO TargetTable (uuid, keyColumn, otherColumn)
|
|
185
|
-
SELECT
|
|
186
|
-
src.uuid,
|
|
187
|
-
src.keyColumn,
|
|
188
|
-
src.otherColumn
|
|
186
|
+
SELECT src.uuid, src.keyColumn, src.otherColumn
|
|
189
187
|
FROM (
|
|
190
188
|
SELECT '<uuid>' AS uuid, '<key>' AS keyColumn, '<value>' AS otherColumn
|
|
191
189
|
UNION ALL SELECT ...
|
|
192
190
|
) AS src
|
|
193
191
|
LEFT JOIN TargetTable existing ON existing.keyColumn = src.keyColumn
|
|
194
|
-
WHERE
|
|
195
|
-
|
|
192
|
+
WHERE existing.id IS NULL;
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**⚠ Large sets — never build them as a long `UNION ALL` chain.** MySQL parses a `UNION ALL`
|
|
196
|
+
chain **recursively**, one parser-stack frame per link. Around ~2,000+ links overflow the
|
|
197
|
+
default 256 KB per-thread stack and the statement dies at **parse time** (before a single row
|
|
198
|
+
is processed) with:
|
|
199
|
+
|
|
200
|
+
ERROR 1436 (HY000): Thread stack overrun: <n> bytes used of a 262144 byte stack, and
|
|
201
|
+
20000 bytes needed. Use 'mysqld --thread_stack=#' to specify a bigger stack.
|
|
202
|
+
|
|
203
|
+
This is a parser-recursion limit — **not** a row-volume, `max_allowed_packet`, or
|
|
204
|
+
server-misconfiguration issue, and **do not** raise `thread_stack` on the server for a one-off
|
|
205
|
+
load. Instead, stage the rows in a **session-scoped temp table via a flat multi-row `VALUES`
|
|
206
|
+
list** (which the parser reads as a flat list, no recursion), then run the anti-join once from
|
|
207
|
+
the staging table:
|
|
208
|
+
|
|
209
|
+
```sql
|
|
210
|
+
CREATE TEMPORARY TABLE _stage (
|
|
211
|
+
uuid VARCHAR(36) NOT NULL, keyColumn VARCHAR(255) NOT NULL, otherColumn TEXT NULL);
|
|
212
|
+
INSERT INTO _stage (uuid, keyColumn, otherColumn)
|
|
213
|
+
VALUES ('<uuid>','<key>','<value>'), ('<uuid>','<key>','<value>'), ... ; -- flat, thousands of rows
|
|
214
|
+
INSERT INTO TargetTable (uuid, keyColumn, otherColumn)
|
|
215
|
+
SELECT src.uuid, src.keyColumn, src.otherColumn
|
|
216
|
+
FROM _stage AS src
|
|
217
|
+
LEFT JOIN TargetTable existing ON existing.keyColumn = src.keyColumn
|
|
218
|
+
WHERE existing.id IS NULL;
|
|
219
|
+
DROP TEMPORARY TABLE _stage;
|
|
196
220
|
```
|
|
197
221
|
|
|
198
|
-
|
|
199
|
-
|
|
222
|
+
The `TEMPORARY` table is session-scoped (safe on production, auto-dropped on disconnect even if
|
|
223
|
+
the run aborts). A multi-row `VALUES` load of a few thousand rows is a few hundred KB — well
|
|
224
|
+
under the 64 MB `max_allowed_packet` default. First hit in
|
|
225
|
+
`Client_Aig/2026-06-18a - TRUE-79534 AIG SaleItem codes.sql` (2,200 AIG SaleItem codes); see
|
|
226
|
+
`clients/aig/features/entitlement-intake.md` → *Uploading new codes*.
|
|
200
227
|
|
|
201
228
|
## Self-referencing DELETE — wrap the subquery in a derived table
|
|
202
229
|
|
|
@@ -88,17 +88,27 @@ When AIG sends a new "Active SaleItemID" spreadsheet (columns `SaleItemID`, `Des
|
|
|
88
88
|
3. Generate **fully-random UUIDs** (e.g. `uuid4`), never the time-based MySQL `UUID()` —
|
|
89
89
|
per the 2.0 standard. Bake literal UUIDs into the SQL since a migration can't call PHP's
|
|
90
90
|
`_String::generateUuid()`.
|
|
91
|
-
4.
|
|
92
|
-
|
|
93
|
-
can't help)
|
|
91
|
+
4. Feed the rows through a **session-scoped temp table populated by a flat multi-row
|
|
92
|
+
`VALUES` list**, then anti-join once into `Items` — idempotent because `Items` has **no
|
|
93
|
+
unique key on `partNumber`** (so `INSERT IGNORE` can't help). Do **not** assemble the rows
|
|
94
|
+
as a long `UNION ALL` derived table: at AIG's ~2,200-code scale that overflows MySQL's
|
|
95
|
+
parser stack and fails at **parse time** with **ERROR 1436 (HY000) Thread stack overrun**
|
|
96
|
+
(see the gotcha below and the dbchanges2 architecture bulk-load rule). A flat `VALUES`
|
|
97
|
+
list does not recurse and stays well under `max_allowed_packet`:
|
|
94
98
|
```sql
|
|
99
|
+
CREATE TEMPORARY TABLE _aig_saleitem_stage (
|
|
100
|
+
uuid VARCHAR(36) NOT NULL, partNumber VARCHAR(255) NOT NULL, description TEXT NULL);
|
|
101
|
+
INSERT INTO _aig_saleitem_stage (uuid, partNumber, description)
|
|
102
|
+
VALUES ('<uuid>','<part>','<desc>'), ('<uuid>','<part>','<desc>'), ... ; -- flat, ~2,200 rows
|
|
95
103
|
INSERT INTO Items (uuid, partNumber, description)
|
|
96
104
|
SELECT src.uuid, src.partNumber, src.description
|
|
97
|
-
FROM
|
|
98
|
-
UNION ALL SELECT ... ) AS src
|
|
105
|
+
FROM _aig_saleitem_stage AS src
|
|
99
106
|
LEFT JOIN Items existing ON existing.partNumber = src.partNumber
|
|
100
107
|
WHERE existing.id IS NULL;
|
|
108
|
+
DROP TEMPORARY TABLE _aig_saleitem_stage;
|
|
101
109
|
```
|
|
110
|
+
The `TEMPORARY` table is session-scoped (safe on production, auto-dropped on disconnect
|
|
111
|
+
even if the run aborts). Do **not** raise `thread_stack` on the server for a one-off load.
|
|
102
112
|
|
|
103
113
|
## Client variations
|
|
104
114
|
|
|
@@ -121,6 +131,13 @@ this interceptor or use this dual-purpose Items pattern.
|
|
|
121
131
|
codes are **not** in the SaleItemID spreadsheet and must be sourced separately if missing.
|
|
122
132
|
- **No unique key on `Items.partNumber`** — use an anti-join for idempotent loads, not
|
|
123
133
|
`INSERT IGNORE`.
|
|
134
|
+
- **A long `UNION ALL` derived table fails at ~2,200 rows with ERROR 1436 "Thread stack
|
|
135
|
+
overrun."** The AIG SaleItem load is exactly this scale. MySQL builds the `UNION ALL`
|
|
136
|
+
chain recursively, so ~2,200 links overflow the default 256 KB per-thread parser stack and
|
|
137
|
+
the statement dies at **parse time** (before any row is processed) — it is a parser-recursion
|
|
138
|
+
limit, **not** a row-volume, `max_allowed_packet`, or server-config problem. Stage the rows
|
|
139
|
+
in a temp table via a flat multi-row `VALUES` list and anti-join from there (see *Uploading
|
|
140
|
+
new codes* step 4). Do not raise `thread_stack` for a one-off load.
|
|
124
141
|
- **An `entitlementFulfillmentType` EV-12 VALIDATION error is NOT a bad client payload —
|
|
125
142
|
it's this interceptor's injected field failing to resolve.** AIG never sends that field;
|
|
126
143
|
the interceptor injects it as `{name: "Repair"}`. The error means the V2 resolver could not
|
|
@@ -136,6 +153,11 @@ this interceptor or use this dual-purpose Items pattern.
|
|
|
136
153
|
|
|
137
154
|
## Change history
|
|
138
155
|
|
|
156
|
+
- 2026-07-09 — TRUE-79534 SaleItem load reworked: the 2,200-code `INSERT ... SELECT FROM
|
|
157
|
+
(<2,199 `UNION ALL` links>)` derived table failed at parse time with ERROR 1436 (Thread
|
|
158
|
+
stack overrun). Rebuilt as a `TEMPORARY` staging table filled by a flat multi-row `VALUES`
|
|
159
|
+
list + the same anti-join into `Items` (identical rows/UUIDs, 0 duplicate partNumbers).
|
|
160
|
+
(mhammontree)
|
|
139
161
|
- 2026-07-09 — TRUE-79978: root-caused an `entitlementFulfillmentType` EV-12 on beta/QA. The
|
|
140
162
|
field is injected (name-only) by `prePost`; the beta `Client_Aig.Apis_RecordFields` override
|
|
141
163
|
row (apiId=2, recordFieldId=1202) had `overrideIsIdentifier=NULL` + `overrideChildPolicy=MATCH_CREATE`,
|
package/package.json
CHANGED