@crvouga/sqlite-mem 1.5.0 → 1.7.0
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.
- package/compat/scenarios.ts +5 -0
- package/dist/ast/nodes.d.ts +5 -0
- package/dist/index.js +342 -182
- package/dist/index.js.map +2 -2
- package/dist/unstable.js +39 -9
- package/dist/unstable.js.map +2 -2
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -1037,7 +1037,7 @@ var Parser = class {
|
|
|
1037
1037
|
if (this.at("SELECT") || this.at("WITH") || this.at("VALUES")) {
|
|
1038
1038
|
const select = this.at("SELECT") || this.at("WITH") ? this.parseSelectStmt() : this.parseValuesAsSelect();
|
|
1039
1039
|
this.expect("RPAREN");
|
|
1040
|
-
const alias3 = this.optionalAlias() ?? this.
|
|
1040
|
+
const alias3 = this.optionalAlias() ?? `__subq_${this.pos}`;
|
|
1041
1041
|
return { type: "subquery", select, alias: alias3 };
|
|
1042
1042
|
}
|
|
1043
1043
|
const item = this.parseFromItem();
|
|
@@ -1264,13 +1264,15 @@ var Parser = class {
|
|
|
1264
1264
|
// ── DELETE ──────────────────────────────────────────────────────────────
|
|
1265
1265
|
parseDeleteStmt(withClause = this.parseOptionalWith()) {
|
|
1266
1266
|
this.expect("DELETE");
|
|
1267
|
+
const or = this.parseOrConflict();
|
|
1267
1268
|
this.expect("FROM");
|
|
1268
1269
|
const table = this.parseTableName();
|
|
1269
1270
|
const alias = this.optionalAlias();
|
|
1270
1271
|
let where = null;
|
|
1271
1272
|
if (this.match("WHERE")) where = this.parseExpr();
|
|
1272
1273
|
const returning = this.parseReturning();
|
|
1273
|
-
|
|
1274
|
+
const orMode = or === null ? null : or === "REPLACE" ? "replace" : or === "IGNORE" ? "ignore" : or === "ABORT" ? "abort" : or === "ROLLBACK" ? "rollback" : "fail";
|
|
1275
|
+
return { type: "delete", with: withClause, or: orMode, table, alias, where, returning };
|
|
1274
1276
|
}
|
|
1275
1277
|
// ── CREATE ──────────────────────────────────────────────────────────────
|
|
1276
1278
|
parseCreateStmt() {
|
|
@@ -1559,9 +1561,9 @@ var Parser = class {
|
|
|
1559
1561
|
} while (this.match("COMMA"));
|
|
1560
1562
|
this.expect("RPAREN");
|
|
1561
1563
|
}
|
|
1562
|
-
const { onDelete, onUpdate } = this.parseFkActions();
|
|
1564
|
+
const { onDelete, onUpdate, match } = this.parseFkActions();
|
|
1563
1565
|
const { deferrable, initiallyDeferred } = this.parseDeferrable();
|
|
1564
|
-
return { type: "references", table, columns, onDelete, onUpdate, deferrable, initiallyDeferred };
|
|
1566
|
+
return { type: "references", table, columns, onDelete, onUpdate, match, deferrable, initiallyDeferred };
|
|
1565
1567
|
}
|
|
1566
1568
|
parseTableConstraint() {
|
|
1567
1569
|
let name = null;
|
|
@@ -1607,7 +1609,7 @@ var Parser = class {
|
|
|
1607
1609
|
} while (this.match("COMMA"));
|
|
1608
1610
|
this.expect("RPAREN");
|
|
1609
1611
|
}
|
|
1610
|
-
const { onDelete, onUpdate } = this.parseFkActions();
|
|
1612
|
+
const { onDelete, onUpdate, match } = this.parseFkActions();
|
|
1611
1613
|
const { deferrable, initiallyDeferred } = this.parseDeferrable();
|
|
1612
1614
|
return {
|
|
1613
1615
|
type: "foreign_key",
|
|
@@ -1616,6 +1618,7 @@ var Parser = class {
|
|
|
1616
1618
|
refColumns,
|
|
1617
1619
|
onDelete,
|
|
1618
1620
|
onUpdate,
|
|
1621
|
+
match,
|
|
1619
1622
|
name,
|
|
1620
1623
|
deferrable,
|
|
1621
1624
|
initiallyDeferred
|
|
@@ -1677,12 +1680,24 @@ var Parser = class {
|
|
|
1677
1680
|
parseFkActions() {
|
|
1678
1681
|
let onDelete = null;
|
|
1679
1682
|
let onUpdate = null;
|
|
1683
|
+
let match = "SIMPLE";
|
|
1684
|
+
const readMatch = () => {
|
|
1685
|
+
while (this.match("MATCH")) {
|
|
1686
|
+
const word = this.parseIdent().toUpperCase();
|
|
1687
|
+
if (word === "FULL") match = "FULL";
|
|
1688
|
+
else if (word === "PARTIAL") match = "PARTIAL";
|
|
1689
|
+
else match = "SIMPLE";
|
|
1690
|
+
}
|
|
1691
|
+
};
|
|
1692
|
+
readMatch();
|
|
1680
1693
|
while (this.at("ON")) {
|
|
1681
1694
|
if (this.peek().kind === "DELETE") onDelete = this.parseFkAction("DELETE");
|
|
1682
1695
|
else if (this.peek().kind === "UPDATE") onUpdate = this.parseFkAction("UPDATE");
|
|
1683
1696
|
else this.syntaxError("expected ON DELETE or ON UPDATE");
|
|
1697
|
+
readMatch();
|
|
1684
1698
|
}
|
|
1685
|
-
|
|
1699
|
+
readMatch();
|
|
1700
|
+
return { onDelete, onUpdate, match };
|
|
1686
1701
|
}
|
|
1687
1702
|
parseDeferrable() {
|
|
1688
1703
|
let deferrable = false;
|
|
@@ -2557,32 +2572,46 @@ function applyAffinity(value, affinity) {
|
|
|
2557
2572
|
if (value === null) return null;
|
|
2558
2573
|
switch (affinity) {
|
|
2559
2574
|
case "TEXT":
|
|
2560
|
-
if (value instanceof Uint8Array) return
|
|
2575
|
+
if (value instanceof Uint8Array) return value;
|
|
2561
2576
|
if (value instanceof SqlJsonText) return value.value;
|
|
2562
2577
|
if (typeof value === "string") return value;
|
|
2563
|
-
if (value instanceof SqlReal) return
|
|
2578
|
+
if (value instanceof SqlReal) return formatRealAsText(value.value);
|
|
2579
|
+
if (typeof value === "number") return String(canonicalizeNumber(value));
|
|
2580
|
+
if (typeof value === "bigint") return value.toString();
|
|
2564
2581
|
return String(value);
|
|
2565
2582
|
case "INTEGER": {
|
|
2583
|
+
if (value instanceof Uint8Array) return value;
|
|
2566
2584
|
const n = coerceToNumber(value);
|
|
2567
2585
|
if (n === null) return value;
|
|
2568
2586
|
if (typeof value === "bigint") return value;
|
|
2569
2587
|
return Number.isInteger(n) && Number.isSafeInteger(n) ? Math.trunc(n) : canonicalizeNumber(n);
|
|
2570
2588
|
}
|
|
2571
2589
|
case "REAL": {
|
|
2590
|
+
if (value instanceof Uint8Array) return value;
|
|
2572
2591
|
const n = coerceToNumber(value);
|
|
2573
2592
|
return n === null ? value : asSqlReal(n);
|
|
2574
2593
|
}
|
|
2575
2594
|
case "NUMERIC": {
|
|
2595
|
+
if (value instanceof Uint8Array) return value;
|
|
2576
2596
|
const n = coerceToNumber(value);
|
|
2577
2597
|
if (n === null) return value;
|
|
2578
2598
|
if (Number.isInteger(n) && Number.isSafeInteger(n)) return Math.trunc(n);
|
|
2579
2599
|
return canonicalizeNumber(n);
|
|
2580
2600
|
}
|
|
2581
2601
|
case "BLOB":
|
|
2582
|
-
if (typeof value === "number") return canonicalizeNumber(value);
|
|
2583
2602
|
return value;
|
|
2584
2603
|
}
|
|
2585
2604
|
}
|
|
2605
|
+
function formatRealAsText(value) {
|
|
2606
|
+
const n = canonicalizeNumber(value);
|
|
2607
|
+
if (Object.is(n, -0) || n === 0) return "0.0";
|
|
2608
|
+
if (Number.isInteger(n) && Number.isSafeInteger(n)) return `${n}.0`;
|
|
2609
|
+
const abs = Math.abs(n);
|
|
2610
|
+
if (abs >= 1e16 || abs > 0 && abs < 1e-4) {
|
|
2611
|
+
return n.toExponential(1).replace(/e([+-])(\d)$/, "e$10$2").replace(/e\+/, "e+");
|
|
2612
|
+
}
|
|
2613
|
+
return String(n);
|
|
2614
|
+
}
|
|
2586
2615
|
function applyComparisonAffinity(left, right, leftAffinity, rightAffinity) {
|
|
2587
2616
|
const leftNumeric = leftAffinity === "INTEGER" || leftAffinity === "REAL" || leftAffinity === "NUMERIC";
|
|
2588
2617
|
const rightNumeric = rightAffinity === "INTEGER" || rightAffinity === "REAL" || rightAffinity === "NUMERIC";
|
|
@@ -8393,6 +8422,7 @@ var DatabaseState = class _DatabaseState {
|
|
|
8393
8422
|
refColumns: constraint.columns,
|
|
8394
8423
|
onDelete: constraint.onDelete,
|
|
8395
8424
|
onUpdate: constraint.onUpdate,
|
|
8425
|
+
match: constraint.match,
|
|
8396
8426
|
name: null,
|
|
8397
8427
|
deferrable: constraint.deferrable,
|
|
8398
8428
|
initiallyDeferred: constraint.initiallyDeferred
|
|
@@ -9899,11 +9929,13 @@ function pragmaTableInfo(args, env, xinfo) {
|
|
|
9899
9929
|
const tableName = requireNameArg(args, "table_info");
|
|
9900
9930
|
const table = env.state.tables.get(tableName.toLowerCase());
|
|
9901
9931
|
if (!table) return { columns: xinfoColumns(xinfo), rows: [] };
|
|
9902
|
-
const
|
|
9932
|
+
const visible = xinfo ? table.columns : table.columns.filter((column) => !column.generated);
|
|
9933
|
+
const rows = visible.map((column, cid) => {
|
|
9903
9934
|
const pkIndex = table.columns.filter((c) => c.primaryKey).findIndex((c) => c.name === column.name);
|
|
9904
9935
|
const pk = column.primaryKey ? pkIndex >= 0 ? pkIndex + 1 : 1 : 0;
|
|
9905
9936
|
const dflt = column.defaultExpr ? defaultLiteral(column.defaultExpr) : null;
|
|
9906
|
-
const
|
|
9937
|
+
const notNull = column.notNull || table.withoutRowid && column.primaryKey ? 1 : 0;
|
|
9938
|
+
const base = [cid, column.name, column.typeName ?? "", notNull, dflt, pk];
|
|
9907
9939
|
if (xinfo) {
|
|
9908
9940
|
let hidden = 0;
|
|
9909
9941
|
if (column.generated && !column.generated.stored) hidden = 2;
|
|
@@ -11362,10 +11394,20 @@ function scanFrom(item, env, parent) {
|
|
|
11362
11394
|
if (item.joinType === "RIGHT" || item.joinType === "FULL") {
|
|
11363
11395
|
right.forEach((rhs, rightIndex) => {
|
|
11364
11396
|
if (matchedRight.has(rightIndex)) return;
|
|
11365
|
-
|
|
11366
|
-
|
|
11367
|
-
|
|
11368
|
-
|
|
11397
|
+
if (using) {
|
|
11398
|
+
const leftCells = nullLeft.map((cell) => {
|
|
11399
|
+
const shared = using.some((name) => name.toLowerCase() === cell.name.toLowerCase());
|
|
11400
|
+
if (!shared) return cell;
|
|
11401
|
+
const fromRight = rhs.cells.find((c) => c.name.toLowerCase() === cell.name.toLowerCase());
|
|
11402
|
+
return { ...cell, value: fromRight?.value ?? null, hiddenByUsing: false };
|
|
11403
|
+
});
|
|
11404
|
+
const rightOnly = rhs.cells.filter(
|
|
11405
|
+
(cell) => !using.some((name) => name.toLowerCase() === cell.name.toLowerCase())
|
|
11406
|
+
);
|
|
11407
|
+
result.push({ cells: [...leftCells, ...rightOnly] });
|
|
11408
|
+
} else {
|
|
11409
|
+
result.push({ cells: [...nullLeft, ...rhs.cells] });
|
|
11410
|
+
}
|
|
11369
11411
|
});
|
|
11370
11412
|
}
|
|
11371
11413
|
return result;
|
|
@@ -12567,7 +12609,7 @@ function executeInsertCore(stmt, env) {
|
|
|
12567
12609
|
if (view) return executeViewInsert(stmt, view, env, totalBefore);
|
|
12568
12610
|
const fast = tryFastInsert(stmt, env);
|
|
12569
12611
|
if (fast) return fast;
|
|
12570
|
-
|
|
12612
|
+
let table = env.state.getWritableTable(stmt.table);
|
|
12571
12613
|
const columnNames = stmt.columns ?? table.columns.map((column) => column.name);
|
|
12572
12614
|
const rowidIndexes = columnNames.map((name, index) => isRowidName3(name) ? index : -1).filter((index) => index >= 0);
|
|
12573
12615
|
if (rowidIndexes.length > 1) throw new SqliteError("duplicate column name: rowid", "other");
|
|
@@ -12603,116 +12645,141 @@ function executeInsertCore(stmt, env) {
|
|
|
12603
12645
|
env.state.recordChange(changes, last);
|
|
12604
12646
|
return emptyResult(changes, last);
|
|
12605
12647
|
}
|
|
12606
|
-
|
|
12607
|
-
|
|
12608
|
-
|
|
12609
|
-
|
|
12610
|
-
|
|
12611
|
-
|
|
12612
|
-
|
|
12613
|
-
|
|
12614
|
-
|
|
12615
|
-
|
|
12616
|
-
const
|
|
12617
|
-
|
|
12648
|
+
const abortMode = insertAbortResolution(stmt.mode) === "abort";
|
|
12649
|
+
const undoInserted = [];
|
|
12650
|
+
const undoRemoved = [];
|
|
12651
|
+
const undoStatement = () => {
|
|
12652
|
+
if (!abortMode) return;
|
|
12653
|
+
for (let i = undoInserted.length - 1; i >= 0; i--) {
|
|
12654
|
+
const row = table.rows.get(undoInserted[i]);
|
|
12655
|
+
if (row) removeOne(table, row, env);
|
|
12656
|
+
}
|
|
12657
|
+
for (let i = undoRemoved.length - 1; i >= 0; i--) {
|
|
12658
|
+
const row = undoRemoved[i];
|
|
12659
|
+
table = env.state.ensureWritableTable(table);
|
|
12660
|
+
table.rows.set(row.rowid, row);
|
|
12661
|
+
if (table.indexes.length > 0) addIndexes(table, row, env);
|
|
12618
12662
|
}
|
|
12619
|
-
|
|
12620
|
-
|
|
12621
|
-
|
|
12622
|
-
|
|
12623
|
-
|
|
12624
|
-
|
|
12625
|
-
|
|
12626
|
-
|
|
12627
|
-
|
|
12628
|
-
|
|
12629
|
-
|
|
12630
|
-
|
|
12631
|
-
|
|
12632
|
-
|
|
12633
|
-
value: values.get(normalizeColumnName(c.name)) ?? null,
|
|
12634
|
-
affinity: c.affinity,
|
|
12635
|
-
collate: c.collate
|
|
12636
|
-
}
|
|
12637
|
-
];
|
|
12638
|
-
})
|
|
12639
|
-
});
|
|
12640
|
-
const computed = evalExpr(column.generated.expr, genCtx);
|
|
12641
|
-
values.set(normalizeColumnName(column.name), storeColumnValue(table, column, computed));
|
|
12642
|
-
} else {
|
|
12643
|
-
values.set(normalizeColumnName(column.name), null);
|
|
12663
|
+
};
|
|
12664
|
+
try {
|
|
12665
|
+
for (const source of sourceRows) {
|
|
12666
|
+
if (source.length !== columnNames.length)
|
|
12667
|
+
throw new SqliteError(
|
|
12668
|
+
stmt.columns ? `${source.length} values for ${columnNames.length} columns` : `table ${table.name} has ${columnNames.length} columns but ${source.length} values were supplied`,
|
|
12669
|
+
"other"
|
|
12670
|
+
);
|
|
12671
|
+
const values = /* @__PURE__ */ new Map();
|
|
12672
|
+
for (const column of table.columns) {
|
|
12673
|
+
if (column.generated) continue;
|
|
12674
|
+
const suppliedIndex = suppliedIndexes[table.columns.indexOf(column)] ?? -1;
|
|
12675
|
+
const value = suppliedIndex >= 0 ? source[suppliedIndex] ?? null : column.defaultExpr ? evalExpr(column.defaultExpr, env.createEvalContext()) : null;
|
|
12676
|
+
values.set(column.nameLower ?? column.name.toLowerCase(), storeColumnValue(table, column, value));
|
|
12644
12677
|
}
|
|
12645
|
-
|
|
12646
|
-
|
|
12647
|
-
|
|
12648
|
-
|
|
12649
|
-
|
|
12650
|
-
|
|
12651
|
-
|
|
12678
|
+
for (const column of table.columns) {
|
|
12679
|
+
if (!column.generated) continue;
|
|
12680
|
+
if (columnNames.some((name) => name.toLowerCase() === column.name.toLowerCase())) {
|
|
12681
|
+
throw new SqliteError(`cannot INSERT into generated column "${column.name}"`, "misuse");
|
|
12682
|
+
}
|
|
12683
|
+
if (column.generated.stored) {
|
|
12684
|
+
const genCtx = env.createEvalContext({
|
|
12685
|
+
cells: table.columns.filter((c) => !c.generated || c === column).flatMap((c) => {
|
|
12686
|
+
if (c.generated && c !== column) return [];
|
|
12687
|
+
if (c === column) return [];
|
|
12688
|
+
return [
|
|
12689
|
+
{
|
|
12690
|
+
table: table.name,
|
|
12691
|
+
name: c.name,
|
|
12692
|
+
value: values.get(normalizeColumnName(c.name)) ?? null,
|
|
12693
|
+
affinity: c.affinity,
|
|
12694
|
+
collate: c.collate
|
|
12695
|
+
}
|
|
12696
|
+
];
|
|
12697
|
+
})
|
|
12698
|
+
});
|
|
12699
|
+
const computed = evalExpr(column.generated.expr, genCtx);
|
|
12700
|
+
values.set(normalizeColumnName(column.name), storeColumnValue(table, column, computed));
|
|
12701
|
+
} else {
|
|
12702
|
+
values.set(normalizeColumnName(column.name), null);
|
|
12703
|
+
}
|
|
12704
|
+
}
|
|
12705
|
+
if (fireInsertTriggers("BEFORE", table, values, null, env) === "ignore") continue;
|
|
12706
|
+
const conflicts = conflictingRows(table, values, env);
|
|
12707
|
+
if (conflicts.length > 0) {
|
|
12708
|
+
if (stmt.upsert) {
|
|
12709
|
+
const targetConflicts = conflictsForUpsert(table, values, env, stmt.upsert);
|
|
12710
|
+
if (targetConflicts.length === 0) {
|
|
12711
|
+
throw new SqliteError(
|
|
12712
|
+
`UNIQUE constraint failed: ${table.name}`,
|
|
12713
|
+
"constraint_unique",
|
|
12714
|
+
"SQLITE_CONSTRAINT_UNIQUE"
|
|
12715
|
+
);
|
|
12716
|
+
}
|
|
12717
|
+
if (stmt.upsert.action === "nothing") continue;
|
|
12718
|
+
const row = targetConflicts[0];
|
|
12719
|
+
const scope = scopeFor(table, row, stmt.table, env);
|
|
12720
|
+
const excluded = {
|
|
12721
|
+
cells: table.columns.map((column) => ({
|
|
12722
|
+
table: "excluded",
|
|
12723
|
+
name: column.name,
|
|
12724
|
+
value: values.get(normalizeColumnName(column.name)) ?? null
|
|
12725
|
+
}))
|
|
12726
|
+
};
|
|
12727
|
+
const merged = { cells: [...scope.cells, ...excluded.cells], rowid: row.rowid, sourceTable: table.name };
|
|
12728
|
+
const ctx = env.createEvalContext(merged);
|
|
12729
|
+
if (stmt.upsert.action.where && isTruthySql(evalExpr(stmt.upsert.action.where, ctx)) !== true) continue;
|
|
12730
|
+
const updates = evaluateSet(stmt.upsert.action.set, table, ctx);
|
|
12731
|
+
const updated = updateOne(table, row, updates, env);
|
|
12732
|
+
changes += 1 + updated.cascaded;
|
|
12733
|
+
if (stmt.returning.length)
|
|
12734
|
+
returningRows.push(projectReturning(stmt.returning, scopeFor(table, updated.row, stmt.table, env), env));
|
|
12735
|
+
continue;
|
|
12736
|
+
}
|
|
12737
|
+
const mode = stmt.mode;
|
|
12738
|
+
if (mode === "insert_or_ignore") continue;
|
|
12739
|
+
if (mode === "replace" || mode === "insert_or_replace") {
|
|
12740
|
+
for (const row of conflicts) {
|
|
12741
|
+
if (abortMode) undoRemoved.push(cloneRow(row));
|
|
12742
|
+
removeOne(table, row, env);
|
|
12743
|
+
}
|
|
12744
|
+
} else {
|
|
12652
12745
|
throw new SqliteError(
|
|
12653
12746
|
`UNIQUE constraint failed: ${table.name}`,
|
|
12654
12747
|
"constraint_unique",
|
|
12655
12748
|
"SQLITE_CONSTRAINT_UNIQUE"
|
|
12656
12749
|
);
|
|
12657
12750
|
}
|
|
12658
|
-
if (stmt.upsert.action === "nothing") continue;
|
|
12659
|
-
const row = targetConflicts[0];
|
|
12660
|
-
const scope = scopeFor(table, row, stmt.table, env);
|
|
12661
|
-
const excluded = {
|
|
12662
|
-
cells: table.columns.map((column) => ({
|
|
12663
|
-
table: "excluded",
|
|
12664
|
-
name: column.name,
|
|
12665
|
-
value: values.get(normalizeColumnName(column.name)) ?? null
|
|
12666
|
-
}))
|
|
12667
|
-
};
|
|
12668
|
-
const merged = { cells: [...scope.cells, ...excluded.cells], rowid: row.rowid, sourceTable: table.name };
|
|
12669
|
-
const ctx = env.createEvalContext(merged);
|
|
12670
|
-
if (stmt.upsert.action.where && isTruthySql(evalExpr(stmt.upsert.action.where, ctx)) !== true) continue;
|
|
12671
|
-
const updates = evaluateSet(stmt.upsert.action.set, table, ctx);
|
|
12672
|
-
const updated = updateOne(table, row, updates, env);
|
|
12673
|
-
changes += 1 + updated.cascaded;
|
|
12674
|
-
if (stmt.returning.length)
|
|
12675
|
-
returningRows.push(projectReturning(stmt.returning, scopeFor(table, updated.row, stmt.table, env), env));
|
|
12676
|
-
continue;
|
|
12677
12751
|
}
|
|
12678
|
-
|
|
12679
|
-
|
|
12680
|
-
|
|
12681
|
-
|
|
12682
|
-
|
|
12683
|
-
|
|
12684
|
-
|
|
12685
|
-
|
|
12686
|
-
|
|
12687
|
-
)
|
|
12752
|
+
let insertedRowid;
|
|
12753
|
+
try {
|
|
12754
|
+
const suppliedRowid = rowidIndexes.length ? asExplicitRowid(source[rowidIndexes[0]] ?? null) : void 0;
|
|
12755
|
+
const rowid = table.insert({ values, rowid: suppliedRowid }, { prepared: true });
|
|
12756
|
+
insertedRowid = rowid;
|
|
12757
|
+
const row = table.rows.get(rowid);
|
|
12758
|
+
validateRow(table, row, env);
|
|
12759
|
+
if (table.indexes.length > 0) addIndexes(table, row, env);
|
|
12760
|
+
if (env.state.foreignKeysEnabled) checkForeignKeys(table, row, env);
|
|
12761
|
+
if (!table.withoutRowid) {
|
|
12762
|
+
last = rowid;
|
|
12763
|
+
env.state.lastInsertRowid = rowid;
|
|
12764
|
+
}
|
|
12765
|
+
fireInsertTriggers("AFTER", table, row.values, null, env);
|
|
12766
|
+
changes++;
|
|
12767
|
+
if (abortMode) undoInserted.push(rowid);
|
|
12768
|
+
if (stmt.returning.length)
|
|
12769
|
+
returningRows.push(projectReturning(stmt.returning, scopeFor(table, row, stmt.table, env), env));
|
|
12770
|
+
} catch (error) {
|
|
12771
|
+
if (insertedRowid !== void 0) {
|
|
12772
|
+
const inserted = table.rows.get(insertedRowid);
|
|
12773
|
+
if (inserted) removeOne(table, inserted, env);
|
|
12774
|
+
}
|
|
12775
|
+
if (stmt.mode === "insert_or_ignore" && error instanceof SqliteError && error.category.startsWith("constraint") && error.category !== "constraint_foreign")
|
|
12776
|
+
continue;
|
|
12777
|
+
throw error;
|
|
12688
12778
|
}
|
|
12689
12779
|
}
|
|
12690
|
-
|
|
12691
|
-
|
|
12692
|
-
|
|
12693
|
-
const rowid = table.insert({ values, rowid: suppliedRowid }, { prepared: true });
|
|
12694
|
-
insertedRowid = rowid;
|
|
12695
|
-
const row = table.rows.get(rowid);
|
|
12696
|
-
validateRow(table, row, env);
|
|
12697
|
-
if (table.indexes.length > 0) addIndexes(table, row, env);
|
|
12698
|
-
if (env.state.foreignKeysEnabled) checkForeignKeys(table, row, env);
|
|
12699
|
-
if (!table.withoutRowid) {
|
|
12700
|
-
last = rowid;
|
|
12701
|
-
env.state.lastInsertRowid = rowid;
|
|
12702
|
-
}
|
|
12703
|
-
fireInsertTriggers("AFTER", table, row.values, null, env);
|
|
12704
|
-
changes++;
|
|
12705
|
-
if (stmt.returning.length)
|
|
12706
|
-
returningRows.push(projectReturning(stmt.returning, scopeFor(table, row, stmt.table, env), env));
|
|
12707
|
-
} catch (error) {
|
|
12708
|
-
if (insertedRowid !== void 0) {
|
|
12709
|
-
const inserted = table.rows.get(insertedRowid);
|
|
12710
|
-
if (inserted) removeOne(table, inserted, env);
|
|
12711
|
-
}
|
|
12712
|
-
if (stmt.mode === "insert_or_ignore" && error instanceof SqliteError && error.category.startsWith("constraint"))
|
|
12713
|
-
continue;
|
|
12714
|
-
throw error;
|
|
12715
|
-
}
|
|
12780
|
+
} catch (error) {
|
|
12781
|
+
undoStatement();
|
|
12782
|
+
throw error;
|
|
12716
12783
|
}
|
|
12717
12784
|
const reportedChanges = finalizeDmlChanges(totalBefore, changes, env, last);
|
|
12718
12785
|
if (stmt.returning.length === 0) return emptyResult(reportedChanges, last);
|
|
@@ -12725,8 +12792,6 @@ function evaluateInsertSource(stmt, env) {
|
|
|
12725
12792
|
return stmt.values.map(
|
|
12726
12793
|
(items) => items.map((expr) => {
|
|
12727
12794
|
if (expr.type === "parameter") return env.getBoundParameter(expr.name);
|
|
12728
|
-
if (expr.type === "literal") return expr.value;
|
|
12729
|
-
if (expr.type === "null") return null;
|
|
12730
12795
|
return evalExpr(expr, ctx);
|
|
12731
12796
|
})
|
|
12732
12797
|
);
|
|
@@ -12784,7 +12849,7 @@ function buildFastInsertPlan(stmt, env) {
|
|
|
12784
12849
|
const name = expr.name;
|
|
12785
12850
|
slots.push({ key, affinity: column.affinity, read: (exec) => exec.getBoundParameter(name) });
|
|
12786
12851
|
} else if (expr.type === "literal") {
|
|
12787
|
-
const value = expr.value;
|
|
12852
|
+
const value = expr.forceReal && typeof expr.value === "number" ? asSqlReal(expr.value) : expr.value;
|
|
12788
12853
|
slots.push({ key, affinity: column.affinity, read: () => value });
|
|
12789
12854
|
} else if (expr.type === "null") {
|
|
12790
12855
|
slots.push({ key, affinity: column.affinity, read: () => null });
|
|
@@ -12807,13 +12872,13 @@ function executeUpdateCore(stmt, env) {
|
|
|
12807
12872
|
const totalBefore = env.state.totalChanges;
|
|
12808
12873
|
const view = writableView(stmt.table, "UPDATE", env);
|
|
12809
12874
|
if (view) return executeViewUpdate(stmt, view, env, totalBefore);
|
|
12810
|
-
const
|
|
12875
|
+
const scanTable = env.state.getTable(stmt.table);
|
|
12811
12876
|
const alias = stmt.alias ?? stmt.table;
|
|
12812
12877
|
const candidates = [];
|
|
12813
12878
|
if (stmt.from) {
|
|
12814
12879
|
const fromRows = scanFrom(stmt.from, env);
|
|
12815
|
-
for (const row of
|
|
12816
|
-
const targetScope = scopeFor(
|
|
12880
|
+
for (const row of scanTable.scan()) {
|
|
12881
|
+
const targetScope = scopeFor(scanTable, row, alias, env);
|
|
12817
12882
|
let matchedScope = null;
|
|
12818
12883
|
for (const fromRow of fromRows) {
|
|
12819
12884
|
const joined = {
|
|
@@ -12827,47 +12892,69 @@ function executeUpdateCore(stmt, env) {
|
|
|
12827
12892
|
}
|
|
12828
12893
|
} else {
|
|
12829
12894
|
const indexed = stmt.where === null ? null : tryIndexedTableRows({ type: "table", schema: null, name: stmt.table, alias: stmt.alias }, stmt.where, env);
|
|
12830
|
-
const scanRows = indexed ? indexed.rows :
|
|
12895
|
+
const scanRows = indexed ? indexed.rows : scanTable.scan();
|
|
12831
12896
|
for (const row of scanRows) {
|
|
12832
|
-
const scope = scopeFor(
|
|
12897
|
+
const scope = scopeFor(scanTable, row, alias, env);
|
|
12833
12898
|
if (stmt.where && isTruthySql(evalExpr(stmt.where, env.createEvalContext(scope))) !== true) continue;
|
|
12834
12899
|
candidates.push({ row, scope });
|
|
12835
12900
|
}
|
|
12836
12901
|
}
|
|
12837
|
-
const
|
|
12838
|
-
|
|
12839
|
-
|
|
12840
|
-
|
|
12841
|
-
const
|
|
12842
|
-
|
|
12843
|
-
|
|
12844
|
-
|
|
12845
|
-
|
|
12846
|
-
|
|
12847
|
-
|
|
12848
|
-
|
|
12849
|
-
|
|
12850
|
-
|
|
12851
|
-
|
|
12852
|
-
|
|
12853
|
-
|
|
12854
|
-
|
|
12855
|
-
|
|
12856
|
-
|
|
12857
|
-
|
|
12858
|
-
|
|
12902
|
+
const applyUpdates = () => {
|
|
12903
|
+
let table = env.state.getWritableTable(stmt.table);
|
|
12904
|
+
const returningRows = [];
|
|
12905
|
+
let changes = 0;
|
|
12906
|
+
for (const { row, scope } of candidates) {
|
|
12907
|
+
const ctx = env.createEvalContext(scope);
|
|
12908
|
+
const updates = evaluateSet(stmt.set, table, ctx);
|
|
12909
|
+
const newValues = mergedValues(table, row, updates);
|
|
12910
|
+
const updatedColumns = new Set(
|
|
12911
|
+
[...updates.keys()].map((key) => {
|
|
12912
|
+
const column = table.columns.find((item) => normalizeColumnName(item.name) === key);
|
|
12913
|
+
return column?.name ?? key;
|
|
12914
|
+
})
|
|
12915
|
+
);
|
|
12916
|
+
if (fireUpdateTriggers("BEFORE", table, row, newValues, updatedColumns, env) === "ignore") continue;
|
|
12917
|
+
try {
|
|
12918
|
+
if (stmt.or === "replace") {
|
|
12919
|
+
for (const conflict of conflictingRows(table, newValues, env)) {
|
|
12920
|
+
if (conflict.rowid === row.rowid) continue;
|
|
12921
|
+
removeOne(table, conflict, env);
|
|
12922
|
+
table = env.state.getWritableTable(stmt.table);
|
|
12923
|
+
changes++;
|
|
12924
|
+
}
|
|
12925
|
+
}
|
|
12926
|
+
const updated = updateOne(table, row, updates, env);
|
|
12927
|
+
table = env.state.getWritableTable(stmt.table);
|
|
12928
|
+
fireUpdateTriggers("AFTER", table, row, updated.row.values, updatedColumns, env);
|
|
12929
|
+
changes += 1 + updated.cascaded;
|
|
12930
|
+
if (stmt.returning.length)
|
|
12931
|
+
returningRows.push(projectReturning(stmt.returning, scopeFor(table, updated.row, alias, env), env));
|
|
12932
|
+
} catch (error) {
|
|
12933
|
+
if (stmt.or === "ignore" && error instanceof SqliteError && error.category.startsWith("constraint") && error.category !== "constraint_foreign")
|
|
12934
|
+
continue;
|
|
12935
|
+
throw error;
|
|
12936
|
+
}
|
|
12859
12937
|
}
|
|
12938
|
+
const reportedChanges = finalizeDmlChanges(totalBefore, changes, env);
|
|
12939
|
+
return valuesToResult(
|
|
12940
|
+
returningNames(stmt.returning, table),
|
|
12941
|
+
returningRows,
|
|
12942
|
+
reportedChanges,
|
|
12943
|
+
env.state.lastInsertRowid
|
|
12944
|
+
);
|
|
12945
|
+
};
|
|
12946
|
+
if (updateAbortResolution(stmt.or) === "abort" && candidates.length > 1) {
|
|
12947
|
+
return withStatementAtomicity(env, "abort", applyUpdates);
|
|
12860
12948
|
}
|
|
12861
|
-
|
|
12862
|
-
return valuesToResult(
|
|
12863
|
-
returningNames(stmt.returning, table),
|
|
12864
|
-
returningRows,
|
|
12865
|
-
reportedChanges,
|
|
12866
|
-
env.state.lastInsertRowid
|
|
12867
|
-
);
|
|
12949
|
+
return applyUpdates();
|
|
12868
12950
|
}
|
|
12869
12951
|
function executeDelete(stmt, env) {
|
|
12870
|
-
|
|
12952
|
+
try {
|
|
12953
|
+
return withDmlCtes(stmt.with, env, () => executeDeleteCore(stmt, env));
|
|
12954
|
+
} catch (error) {
|
|
12955
|
+
handleConflictRollback(stmt.or === "rollback", error, env);
|
|
12956
|
+
throw error;
|
|
12957
|
+
}
|
|
12871
12958
|
}
|
|
12872
12959
|
function executeDeleteCore(stmt, env) {
|
|
12873
12960
|
if (env.state.isVirtualTable(stmt.table)) {
|
|
@@ -12876,30 +12963,49 @@ function executeDeleteCore(stmt, env) {
|
|
|
12876
12963
|
const totalBefore = env.state.totalChanges;
|
|
12877
12964
|
const view = writableView(stmt.table, "DELETE", env);
|
|
12878
12965
|
if (view) return executeViewDelete(stmt, view, env, totalBefore);
|
|
12879
|
-
const
|
|
12966
|
+
const scanTable = env.state.getTable(stmt.table);
|
|
12880
12967
|
const selectedSource = stmt.where === null ? null : tryIndexedTableRows({ type: "table", schema: null, name: stmt.table, alias: stmt.alias }, stmt.where, env);
|
|
12881
|
-
const selected = [...selectedSource ? selectedSource.rows :
|
|
12968
|
+
const selected = [...selectedSource ? selectedSource.rows : scanTable.scan()].filter((row) => {
|
|
12882
12969
|
if (!stmt.where) return true;
|
|
12883
|
-
return isTruthySql(
|
|
12970
|
+
return isTruthySql(
|
|
12971
|
+
evalExpr(stmt.where, env.createEvalContext(scopeFor(scanTable, row, stmt.alias ?? stmt.table, env)))
|
|
12972
|
+
) === true;
|
|
12884
12973
|
});
|
|
12885
|
-
const
|
|
12886
|
-
|
|
12887
|
-
|
|
12888
|
-
|
|
12889
|
-
|
|
12890
|
-
|
|
12891
|
-
|
|
12892
|
-
|
|
12893
|
-
|
|
12894
|
-
|
|
12974
|
+
const applyDeletes = () => {
|
|
12975
|
+
let table = env.state.getWritableTable(stmt.table);
|
|
12976
|
+
const returningRows = [];
|
|
12977
|
+
let changes = 0;
|
|
12978
|
+
for (const row of selected) {
|
|
12979
|
+
try {
|
|
12980
|
+
if (fireDeleteTriggers("BEFORE", table, row, env) === "ignore") continue;
|
|
12981
|
+
if (stmt.returning.length)
|
|
12982
|
+
returningRows.push(
|
|
12983
|
+
projectReturning(stmt.returning, scopeFor(table, row, stmt.alias ?? stmt.table, env), env)
|
|
12984
|
+
);
|
|
12985
|
+
changes += applyReferentialDelete(table, row, env);
|
|
12986
|
+
table = env.state.getWritableTable(stmt.table);
|
|
12987
|
+
removeOne(table, row, env);
|
|
12988
|
+
table = env.state.getWritableTable(stmt.table);
|
|
12989
|
+
fireDeleteTriggers("AFTER", table, row, env);
|
|
12990
|
+
changes++;
|
|
12991
|
+
} catch (error) {
|
|
12992
|
+
if (stmt.or === "ignore" && error instanceof SqliteError && error.category.startsWith("constraint") && error.category !== "constraint_foreign")
|
|
12993
|
+
continue;
|
|
12994
|
+
throw error;
|
|
12995
|
+
}
|
|
12996
|
+
}
|
|
12997
|
+
const reportedChanges = finalizeDmlChanges(totalBefore, changes, env);
|
|
12998
|
+
return valuesToResult(
|
|
12999
|
+
returningNames(stmt.returning, table),
|
|
13000
|
+
returningRows,
|
|
13001
|
+
reportedChanges,
|
|
13002
|
+
env.state.lastInsertRowid
|
|
13003
|
+
);
|
|
13004
|
+
};
|
|
13005
|
+
if (deleteAbortResolution(stmt.or) === "abort" && selected.length > 1) {
|
|
13006
|
+
return withStatementAtomicity(env, "abort", applyDeletes);
|
|
12895
13007
|
}
|
|
12896
|
-
|
|
12897
|
-
return valuesToResult(
|
|
12898
|
-
returningNames(stmt.returning, table),
|
|
12899
|
-
returningRows,
|
|
12900
|
-
reportedChanges,
|
|
12901
|
-
env.state.lastInsertRowid
|
|
12902
|
-
);
|
|
13008
|
+
return applyDeletes();
|
|
12903
13009
|
}
|
|
12904
13010
|
function withDmlCtes(withClause, env, execute) {
|
|
12905
13011
|
if (!withClause) return execute();
|
|
@@ -12917,6 +13023,40 @@ function handleConflictRollback(rollback, error, env) {
|
|
|
12917
13023
|
env.transactions.rollback();
|
|
12918
13024
|
}
|
|
12919
13025
|
}
|
|
13026
|
+
var statementSavepointSeq = 0;
|
|
13027
|
+
var statementAtomicityDepth = 0;
|
|
13028
|
+
function withStatementAtomicity(env, resolution, run) {
|
|
13029
|
+
if (resolution === "fail") return run();
|
|
13030
|
+
if (statementAtomicityDepth > 0) return run();
|
|
13031
|
+
const name = `__mem_stmt_${++statementSavepointSeq}`;
|
|
13032
|
+
statementAtomicityDepth++;
|
|
13033
|
+
env.transactions.savepoint(name);
|
|
13034
|
+
try {
|
|
13035
|
+
const result = run();
|
|
13036
|
+
env.transactions.release(name);
|
|
13037
|
+
return result;
|
|
13038
|
+
} catch (error) {
|
|
13039
|
+
if (env.transactions.inTransaction) {
|
|
13040
|
+
try {
|
|
13041
|
+
env.transactions.rollback(name);
|
|
13042
|
+
env.transactions.release(name);
|
|
13043
|
+
} catch {
|
|
13044
|
+
}
|
|
13045
|
+
}
|
|
13046
|
+
throw error;
|
|
13047
|
+
} finally {
|
|
13048
|
+
statementAtomicityDepth--;
|
|
13049
|
+
}
|
|
13050
|
+
}
|
|
13051
|
+
function insertAbortResolution(mode) {
|
|
13052
|
+
return mode === "insert_or_fail" ? "fail" : "abort";
|
|
13053
|
+
}
|
|
13054
|
+
function updateAbortResolution(or) {
|
|
13055
|
+
return or === "fail" ? "fail" : "abort";
|
|
13056
|
+
}
|
|
13057
|
+
function deleteAbortResolution(or) {
|
|
13058
|
+
return or === "fail" ? "fail" : "abort";
|
|
13059
|
+
}
|
|
12920
13060
|
function writableView(name, event, env) {
|
|
12921
13061
|
const { schema, bare } = splitQualifiedName(name);
|
|
12922
13062
|
const db = env.state.databaseForSchema(schema, name);
|
|
@@ -12926,7 +13066,6 @@ function writableView(name, event, env) {
|
|
|
12926
13066
|
(trigger) => trigger.tableName.toLowerCase() === bare.toLowerCase() && trigger.event === event && trigger.timing === "INSTEAD"
|
|
12927
13067
|
);
|
|
12928
13068
|
if (!hasInsteadOf) {
|
|
12929
|
-
env.state.getWritableTable(name);
|
|
12930
13069
|
throw new SqliteError(`cannot modify ${bare} because it is a view`, "other");
|
|
12931
13070
|
}
|
|
12932
13071
|
const names = view.columns ?? executeSelect2(view.select, env).columns;
|
|
@@ -13043,6 +13182,7 @@ function evaluateSet(items, table, ctx) {
|
|
|
13043
13182
|
return updates;
|
|
13044
13183
|
}
|
|
13045
13184
|
function updateOne(table, row, updates, env) {
|
|
13185
|
+
table = env.state.ensureWritableTable(table);
|
|
13046
13186
|
const before = { rowid: row.rowid, values: new Map(row.values) };
|
|
13047
13187
|
let after;
|
|
13048
13188
|
let indexesAdded = false;
|
|
@@ -13114,6 +13254,7 @@ function removeIndexes(table, row, env) {
|
|
|
13114
13254
|
}
|
|
13115
13255
|
}
|
|
13116
13256
|
function removeOne(table, row, env) {
|
|
13257
|
+
table = env.state.ensureWritableTable(table);
|
|
13117
13258
|
removeIndexes(table, row, env);
|
|
13118
13259
|
table.delete(row.rowid);
|
|
13119
13260
|
}
|
|
@@ -13266,17 +13407,28 @@ function checkForeignKeys(table, row, env) {
|
|
|
13266
13407
|
function fkIsDeferred(constraint, env) {
|
|
13267
13408
|
return env.transactions.inTransaction && constraint.initiallyDeferred;
|
|
13268
13409
|
}
|
|
13269
|
-
function assertForeignKeySatisfied(row, constraint, env) {
|
|
13410
|
+
function assertForeignKeySatisfied(row, constraint, env, excludeParent = null) {
|
|
13270
13411
|
const values = constraint.columns.map((name) => row.values.get(normalizeColumnName(name)) ?? null);
|
|
13412
|
+
assertForeignKeyValues(values, constraint, env, excludeParent);
|
|
13413
|
+
}
|
|
13414
|
+
function assertForeignKeySatisfiedWithUpdates(row, constraint, updates, env, excludeParent) {
|
|
13415
|
+
const values = constraint.columns.map((name) => {
|
|
13416
|
+
const key = normalizeColumnName(name);
|
|
13417
|
+
return updates.has(key) ? updates.get(key) ?? null : row.values.get(key) ?? null;
|
|
13418
|
+
});
|
|
13419
|
+
assertForeignKeyValues(values, constraint, env, excludeParent);
|
|
13420
|
+
}
|
|
13421
|
+
function assertForeignKeyValues(values, constraint, env, excludeParent) {
|
|
13271
13422
|
if (values.some((value) => value === null)) return;
|
|
13272
13423
|
const parent = env.state.getTable(constraint.refTable);
|
|
13273
13424
|
const parentColumns = constraint.refColumns ?? parent.columns.filter((column) => column.primaryKey).map((column) => column.name);
|
|
13274
|
-
if (![...parent.scan()].some(
|
|
13275
|
-
(candidate
|
|
13425
|
+
if (![...parent.scan()].some((candidate) => {
|
|
13426
|
+
if (excludeParent && candidate.rowid === excludeParent.rowid) return false;
|
|
13427
|
+
return values.every((value, index) => {
|
|
13276
13428
|
const parentColumn = parentColumns[index];
|
|
13277
13429
|
return parentColumn !== void 0 && compareSql(value, candidate.values.get(normalizeColumnName(parentColumn)) ?? null) === 0;
|
|
13278
|
-
})
|
|
13279
|
-
)) {
|
|
13430
|
+
});
|
|
13431
|
+
})) {
|
|
13280
13432
|
throw new SqliteError("FOREIGN KEY constraint failed", "constraint_foreign", "SQLITE_CONSTRAINT_FOREIGNKEY");
|
|
13281
13433
|
}
|
|
13282
13434
|
}
|
|
@@ -13293,10 +13445,13 @@ function applyReferentialDelete(parent, row, env) {
|
|
|
13293
13445
|
if (!env.state.foreignKeysEnabled) return 0;
|
|
13294
13446
|
let changes = 0;
|
|
13295
13447
|
const parentPk = parent.columns.filter((column) => column.primaryKey).map((column) => column.name);
|
|
13296
|
-
for (const
|
|
13448
|
+
for (const childKey of [...env.state.tables.keys()]) {
|
|
13449
|
+
let child = env.state.tables.get(childKey);
|
|
13450
|
+
if (!child) continue;
|
|
13297
13451
|
for (const constraint of child.constraints) {
|
|
13298
13452
|
if (constraint.type !== "foreign_key" || constraint.refTable.toLowerCase() !== parent.name.toLowerCase())
|
|
13299
13453
|
continue;
|
|
13454
|
+
child = env.state.ensureWritableTable(child);
|
|
13300
13455
|
const referenced = constraint.refColumns ?? parentPk;
|
|
13301
13456
|
const matches = [...child.scan()].filter(
|
|
13302
13457
|
(candidate) => !(child === parent && candidate.rowid === row.rowid) && foreignKeyMatches(constraint.columns, candidate, referenced, row)
|
|
@@ -13317,7 +13472,9 @@ function applyReferentialDelete(parent, row, env) {
|
|
|
13317
13472
|
);
|
|
13318
13473
|
changes += 1 + updated.cascaded;
|
|
13319
13474
|
} else if (constraint.onDelete === "SET DEFAULT") {
|
|
13320
|
-
const
|
|
13475
|
+
const updates = defaultUpdates(child, constraint.columns, env);
|
|
13476
|
+
assertForeignKeySatisfiedWithUpdates(candidate, constraint, updates, env, row);
|
|
13477
|
+
const updated = updateOne(child, candidate, updates, env);
|
|
13321
13478
|
changes += 1 + updated.cascaded;
|
|
13322
13479
|
} else if (constraint.onDelete === "RESTRICT" || !fkIsDeferred(constraint, env)) {
|
|
13323
13480
|
throw new SqliteError("FOREIGN KEY constraint failed", "constraint_foreign", "SQLITE_CONSTRAINT_FOREIGNKEY");
|
|
@@ -13331,10 +13488,13 @@ function applyReferentialUpdate(parent, before, after, env) {
|
|
|
13331
13488
|
if (!env.state.foreignKeysEnabled) return 0;
|
|
13332
13489
|
let changes = 0;
|
|
13333
13490
|
const parentPk = parent.columns.filter((column) => column.primaryKey).map((column) => column.name);
|
|
13334
|
-
for (const
|
|
13491
|
+
for (const childKey of [...env.state.tables.keys()]) {
|
|
13492
|
+
let child = env.state.tables.get(childKey);
|
|
13493
|
+
if (!child) continue;
|
|
13335
13494
|
for (const constraint of child.constraints) {
|
|
13336
13495
|
if (constraint.type !== "foreign_key" || constraint.refTable.toLowerCase() !== parent.name.toLowerCase())
|
|
13337
13496
|
continue;
|
|
13497
|
+
child = env.state.ensureWritableTable(child);
|
|
13338
13498
|
const referenced = constraint.refColumns ?? parentPk;
|
|
13339
13499
|
const oldValues = referenced.map((name) => before.values.get(normalizeColumnName(name)) ?? null);
|
|
13340
13500
|
const newValues = referenced.map((name) => after.values.get(normalizeColumnName(name)) ?? null);
|