@crvouga/sqlite-mem 1.6.0 → 1.8.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/COMPATIBILITY-AUDIT.md +41 -16
- package/dist/ast/nodes.d.ts +5 -0
- package/dist/index.js +312 -181
- package/dist/index.js.map +2 -2
- package/dist/unstable.js +47 -20
- 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,21 +1680,24 @@ var Parser = class {
|
|
|
1677
1680
|
parseFkActions() {
|
|
1678
1681
|
let onDelete = null;
|
|
1679
1682
|
let onUpdate = null;
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
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();
|
|
1683
1693
|
while (this.at("ON")) {
|
|
1684
1694
|
if (this.peek().kind === "DELETE") onDelete = this.parseFkAction("DELETE");
|
|
1685
1695
|
else if (this.peek().kind === "UPDATE") onUpdate = this.parseFkAction("UPDATE");
|
|
1686
1696
|
else this.syntaxError("expected ON DELETE or ON UPDATE");
|
|
1687
|
-
|
|
1688
|
-
this.parseIdent();
|
|
1689
|
-
}
|
|
1690
|
-
}
|
|
1691
|
-
while (this.match("MATCH")) {
|
|
1692
|
-
this.parseIdent();
|
|
1697
|
+
readMatch();
|
|
1693
1698
|
}
|
|
1694
|
-
|
|
1699
|
+
readMatch();
|
|
1700
|
+
return { onDelete, onUpdate, match };
|
|
1695
1701
|
}
|
|
1696
1702
|
parseDeferrable() {
|
|
1697
1703
|
let deferrable = false;
|
|
@@ -2566,32 +2572,46 @@ function applyAffinity(value, affinity) {
|
|
|
2566
2572
|
if (value === null) return null;
|
|
2567
2573
|
switch (affinity) {
|
|
2568
2574
|
case "TEXT":
|
|
2569
|
-
if (value instanceof Uint8Array) return
|
|
2575
|
+
if (value instanceof Uint8Array) return value;
|
|
2570
2576
|
if (value instanceof SqlJsonText) return value.value;
|
|
2571
2577
|
if (typeof value === "string") return value;
|
|
2572
|
-
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();
|
|
2573
2581
|
return String(value);
|
|
2574
2582
|
case "INTEGER": {
|
|
2583
|
+
if (value instanceof Uint8Array) return value;
|
|
2575
2584
|
const n = coerceToNumber(value);
|
|
2576
2585
|
if (n === null) return value;
|
|
2577
2586
|
if (typeof value === "bigint") return value;
|
|
2578
2587
|
return Number.isInteger(n) && Number.isSafeInteger(n) ? Math.trunc(n) : canonicalizeNumber(n);
|
|
2579
2588
|
}
|
|
2580
2589
|
case "REAL": {
|
|
2590
|
+
if (value instanceof Uint8Array) return value;
|
|
2581
2591
|
const n = coerceToNumber(value);
|
|
2582
2592
|
return n === null ? value : asSqlReal(n);
|
|
2583
2593
|
}
|
|
2584
2594
|
case "NUMERIC": {
|
|
2595
|
+
if (value instanceof Uint8Array) return value;
|
|
2585
2596
|
const n = coerceToNumber(value);
|
|
2586
2597
|
if (n === null) return value;
|
|
2587
2598
|
if (Number.isInteger(n) && Number.isSafeInteger(n)) return Math.trunc(n);
|
|
2588
2599
|
return canonicalizeNumber(n);
|
|
2589
2600
|
}
|
|
2590
2601
|
case "BLOB":
|
|
2591
|
-
if (typeof value === "number") return canonicalizeNumber(value);
|
|
2592
2602
|
return value;
|
|
2593
2603
|
}
|
|
2594
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
|
+
}
|
|
2595
2615
|
function applyComparisonAffinity(left, right, leftAffinity, rightAffinity) {
|
|
2596
2616
|
const leftNumeric = leftAffinity === "INTEGER" || leftAffinity === "REAL" || leftAffinity === "NUMERIC";
|
|
2597
2617
|
const rightNumeric = rightAffinity === "INTEGER" || rightAffinity === "REAL" || rightAffinity === "NUMERIC";
|
|
@@ -5768,8 +5788,14 @@ function evalExpr(expr, ctx) {
|
|
|
5768
5788
|
return truth === null ? null : booleanValue(!truth);
|
|
5769
5789
|
}
|
|
5770
5790
|
if (value === null) return null;
|
|
5771
|
-
if (expr.op === "+")
|
|
5772
|
-
|
|
5791
|
+
if (expr.op === "+") {
|
|
5792
|
+
const n = asNumber(numberValue(value));
|
|
5793
|
+
return storageClassOf(value) === "real" ? asSqlReal(n) : n;
|
|
5794
|
+
}
|
|
5795
|
+
if (expr.op === "-") {
|
|
5796
|
+
const n = asNumber(-numberValue(value));
|
|
5797
|
+
return storageClassOf(value) === "real" ? asSqlReal(n) : n;
|
|
5798
|
+
}
|
|
5773
5799
|
return ~integerValue(value);
|
|
5774
5800
|
}
|
|
5775
5801
|
case "is_bool": {
|
|
@@ -8402,6 +8428,7 @@ var DatabaseState = class _DatabaseState {
|
|
|
8402
8428
|
refColumns: constraint.columns,
|
|
8403
8429
|
onDelete: constraint.onDelete,
|
|
8404
8430
|
onUpdate: constraint.onUpdate,
|
|
8431
|
+
match: constraint.match,
|
|
8405
8432
|
name: null,
|
|
8406
8433
|
deferrable: constraint.deferrable,
|
|
8407
8434
|
initiallyDeferred: constraint.initiallyDeferred
|
|
@@ -12588,7 +12615,7 @@ function executeInsertCore(stmt, env) {
|
|
|
12588
12615
|
if (view) return executeViewInsert(stmt, view, env, totalBefore);
|
|
12589
12616
|
const fast = tryFastInsert(stmt, env);
|
|
12590
12617
|
if (fast) return fast;
|
|
12591
|
-
|
|
12618
|
+
let table = env.state.getWritableTable(stmt.table);
|
|
12592
12619
|
const columnNames = stmt.columns ?? table.columns.map((column) => column.name);
|
|
12593
12620
|
const rowidIndexes = columnNames.map((name, index) => isRowidName3(name) ? index : -1).filter((index) => index >= 0);
|
|
12594
12621
|
if (rowidIndexes.length > 1) throw new SqliteError("duplicate column name: rowid", "other");
|
|
@@ -12624,116 +12651,141 @@ function executeInsertCore(stmt, env) {
|
|
|
12624
12651
|
env.state.recordChange(changes, last);
|
|
12625
12652
|
return emptyResult(changes, last);
|
|
12626
12653
|
}
|
|
12627
|
-
|
|
12628
|
-
|
|
12629
|
-
|
|
12630
|
-
|
|
12631
|
-
|
|
12632
|
-
|
|
12633
|
-
|
|
12634
|
-
|
|
12635
|
-
|
|
12636
|
-
|
|
12637
|
-
const
|
|
12638
|
-
|
|
12654
|
+
const abortMode = insertAbortResolution(stmt.mode) === "abort";
|
|
12655
|
+
const undoInserted = [];
|
|
12656
|
+
const undoRemoved = [];
|
|
12657
|
+
const undoStatement = () => {
|
|
12658
|
+
if (!abortMode) return;
|
|
12659
|
+
for (let i = undoInserted.length - 1; i >= 0; i--) {
|
|
12660
|
+
const row = table.rows.get(undoInserted[i]);
|
|
12661
|
+
if (row) removeOne(table, row, env);
|
|
12662
|
+
}
|
|
12663
|
+
for (let i = undoRemoved.length - 1; i >= 0; i--) {
|
|
12664
|
+
const row = undoRemoved[i];
|
|
12665
|
+
table = env.state.ensureWritableTable(table);
|
|
12666
|
+
table.rows.set(row.rowid, row);
|
|
12667
|
+
if (table.indexes.length > 0) addIndexes(table, row, env);
|
|
12639
12668
|
}
|
|
12640
|
-
|
|
12641
|
-
|
|
12642
|
-
|
|
12643
|
-
|
|
12644
|
-
|
|
12645
|
-
|
|
12646
|
-
|
|
12647
|
-
|
|
12648
|
-
|
|
12649
|
-
|
|
12650
|
-
|
|
12651
|
-
|
|
12652
|
-
|
|
12653
|
-
|
|
12654
|
-
value: values.get(normalizeColumnName(c.name)) ?? null,
|
|
12655
|
-
affinity: c.affinity,
|
|
12656
|
-
collate: c.collate
|
|
12657
|
-
}
|
|
12658
|
-
];
|
|
12659
|
-
})
|
|
12660
|
-
});
|
|
12661
|
-
const computed = evalExpr(column.generated.expr, genCtx);
|
|
12662
|
-
values.set(normalizeColumnName(column.name), storeColumnValue(table, column, computed));
|
|
12663
|
-
} else {
|
|
12664
|
-
values.set(normalizeColumnName(column.name), null);
|
|
12669
|
+
};
|
|
12670
|
+
try {
|
|
12671
|
+
for (const source of sourceRows) {
|
|
12672
|
+
if (source.length !== columnNames.length)
|
|
12673
|
+
throw new SqliteError(
|
|
12674
|
+
stmt.columns ? `${source.length} values for ${columnNames.length} columns` : `table ${table.name} has ${columnNames.length} columns but ${source.length} values were supplied`,
|
|
12675
|
+
"other"
|
|
12676
|
+
);
|
|
12677
|
+
const values = /* @__PURE__ */ new Map();
|
|
12678
|
+
for (const column of table.columns) {
|
|
12679
|
+
if (column.generated) continue;
|
|
12680
|
+
const suppliedIndex = suppliedIndexes[table.columns.indexOf(column)] ?? -1;
|
|
12681
|
+
const value = suppliedIndex >= 0 ? source[suppliedIndex] ?? null : column.defaultExpr ? evalExpr(column.defaultExpr, env.createEvalContext()) : null;
|
|
12682
|
+
values.set(column.nameLower ?? column.name.toLowerCase(), storeColumnValue(table, column, value));
|
|
12665
12683
|
}
|
|
12666
|
-
|
|
12667
|
-
|
|
12668
|
-
|
|
12669
|
-
|
|
12670
|
-
|
|
12671
|
-
|
|
12672
|
-
|
|
12684
|
+
for (const column of table.columns) {
|
|
12685
|
+
if (!column.generated) continue;
|
|
12686
|
+
if (columnNames.some((name) => name.toLowerCase() === column.name.toLowerCase())) {
|
|
12687
|
+
throw new SqliteError(`cannot INSERT into generated column "${column.name}"`, "misuse");
|
|
12688
|
+
}
|
|
12689
|
+
if (column.generated.stored) {
|
|
12690
|
+
const genCtx = env.createEvalContext({
|
|
12691
|
+
cells: table.columns.filter((c) => !c.generated || c === column).flatMap((c) => {
|
|
12692
|
+
if (c.generated && c !== column) return [];
|
|
12693
|
+
if (c === column) return [];
|
|
12694
|
+
return [
|
|
12695
|
+
{
|
|
12696
|
+
table: table.name,
|
|
12697
|
+
name: c.name,
|
|
12698
|
+
value: values.get(normalizeColumnName(c.name)) ?? null,
|
|
12699
|
+
affinity: c.affinity,
|
|
12700
|
+
collate: c.collate
|
|
12701
|
+
}
|
|
12702
|
+
];
|
|
12703
|
+
})
|
|
12704
|
+
});
|
|
12705
|
+
const computed = evalExpr(column.generated.expr, genCtx);
|
|
12706
|
+
values.set(normalizeColumnName(column.name), storeColumnValue(table, column, computed));
|
|
12707
|
+
} else {
|
|
12708
|
+
values.set(normalizeColumnName(column.name), null);
|
|
12709
|
+
}
|
|
12710
|
+
}
|
|
12711
|
+
if (fireInsertTriggers("BEFORE", table, values, null, env) === "ignore") continue;
|
|
12712
|
+
const conflicts = conflictingRows(table, values, env);
|
|
12713
|
+
if (conflicts.length > 0) {
|
|
12714
|
+
if (stmt.upsert) {
|
|
12715
|
+
const targetConflicts = conflictsForUpsert(table, values, env, stmt.upsert);
|
|
12716
|
+
if (targetConflicts.length === 0) {
|
|
12717
|
+
throw new SqliteError(
|
|
12718
|
+
`UNIQUE constraint failed: ${table.name}`,
|
|
12719
|
+
"constraint_unique",
|
|
12720
|
+
"SQLITE_CONSTRAINT_UNIQUE"
|
|
12721
|
+
);
|
|
12722
|
+
}
|
|
12723
|
+
if (stmt.upsert.action === "nothing") continue;
|
|
12724
|
+
const row = targetConflicts[0];
|
|
12725
|
+
const scope = scopeFor(table, row, stmt.table, env);
|
|
12726
|
+
const excluded = {
|
|
12727
|
+
cells: table.columns.map((column) => ({
|
|
12728
|
+
table: "excluded",
|
|
12729
|
+
name: column.name,
|
|
12730
|
+
value: values.get(normalizeColumnName(column.name)) ?? null
|
|
12731
|
+
}))
|
|
12732
|
+
};
|
|
12733
|
+
const merged = { cells: [...scope.cells, ...excluded.cells], rowid: row.rowid, sourceTable: table.name };
|
|
12734
|
+
const ctx = env.createEvalContext(merged);
|
|
12735
|
+
if (stmt.upsert.action.where && isTruthySql(evalExpr(stmt.upsert.action.where, ctx)) !== true) continue;
|
|
12736
|
+
const updates = evaluateSet(stmt.upsert.action.set, table, ctx);
|
|
12737
|
+
const updated = updateOne(table, row, updates, env);
|
|
12738
|
+
changes += 1 + updated.cascaded;
|
|
12739
|
+
if (stmt.returning.length)
|
|
12740
|
+
returningRows.push(projectReturning(stmt.returning, scopeFor(table, updated.row, stmt.table, env), env));
|
|
12741
|
+
continue;
|
|
12742
|
+
}
|
|
12743
|
+
const mode = stmt.mode;
|
|
12744
|
+
if (mode === "insert_or_ignore") continue;
|
|
12745
|
+
if (mode === "replace" || mode === "insert_or_replace") {
|
|
12746
|
+
for (const row of conflicts) {
|
|
12747
|
+
if (abortMode) undoRemoved.push(cloneRow(row));
|
|
12748
|
+
removeOne(table, row, env);
|
|
12749
|
+
}
|
|
12750
|
+
} else {
|
|
12673
12751
|
throw new SqliteError(
|
|
12674
12752
|
`UNIQUE constraint failed: ${table.name}`,
|
|
12675
12753
|
"constraint_unique",
|
|
12676
12754
|
"SQLITE_CONSTRAINT_UNIQUE"
|
|
12677
12755
|
);
|
|
12678
12756
|
}
|
|
12679
|
-
if (stmt.upsert.action === "nothing") continue;
|
|
12680
|
-
const row = targetConflicts[0];
|
|
12681
|
-
const scope = scopeFor(table, row, stmt.table, env);
|
|
12682
|
-
const excluded = {
|
|
12683
|
-
cells: table.columns.map((column) => ({
|
|
12684
|
-
table: "excluded",
|
|
12685
|
-
name: column.name,
|
|
12686
|
-
value: values.get(normalizeColumnName(column.name)) ?? null
|
|
12687
|
-
}))
|
|
12688
|
-
};
|
|
12689
|
-
const merged = { cells: [...scope.cells, ...excluded.cells], rowid: row.rowid, sourceTable: table.name };
|
|
12690
|
-
const ctx = env.createEvalContext(merged);
|
|
12691
|
-
if (stmt.upsert.action.where && isTruthySql(evalExpr(stmt.upsert.action.where, ctx)) !== true) continue;
|
|
12692
|
-
const updates = evaluateSet(stmt.upsert.action.set, table, ctx);
|
|
12693
|
-
const updated = updateOne(table, row, updates, env);
|
|
12694
|
-
changes += 1 + updated.cascaded;
|
|
12695
|
-
if (stmt.returning.length)
|
|
12696
|
-
returningRows.push(projectReturning(stmt.returning, scopeFor(table, updated.row, stmt.table, env), env));
|
|
12697
|
-
continue;
|
|
12698
12757
|
}
|
|
12699
|
-
|
|
12700
|
-
|
|
12701
|
-
|
|
12702
|
-
|
|
12703
|
-
|
|
12704
|
-
|
|
12705
|
-
|
|
12706
|
-
|
|
12707
|
-
|
|
12708
|
-
)
|
|
12709
|
-
|
|
12710
|
-
|
|
12711
|
-
|
|
12712
|
-
|
|
12713
|
-
|
|
12714
|
-
|
|
12715
|
-
|
|
12716
|
-
|
|
12717
|
-
|
|
12718
|
-
|
|
12719
|
-
|
|
12720
|
-
|
|
12721
|
-
|
|
12722
|
-
|
|
12758
|
+
let insertedRowid;
|
|
12759
|
+
try {
|
|
12760
|
+
const suppliedRowid = rowidIndexes.length ? asExplicitRowid(source[rowidIndexes[0]] ?? null) : void 0;
|
|
12761
|
+
const rowid = table.insert({ values, rowid: suppliedRowid }, { prepared: true });
|
|
12762
|
+
insertedRowid = rowid;
|
|
12763
|
+
const row = table.rows.get(rowid);
|
|
12764
|
+
validateRow(table, row, env);
|
|
12765
|
+
if (table.indexes.length > 0) addIndexes(table, row, env);
|
|
12766
|
+
if (env.state.foreignKeysEnabled) checkForeignKeys(table, row, env);
|
|
12767
|
+
if (!table.withoutRowid) {
|
|
12768
|
+
last = rowid;
|
|
12769
|
+
env.state.lastInsertRowid = rowid;
|
|
12770
|
+
}
|
|
12771
|
+
fireInsertTriggers("AFTER", table, row.values, null, env);
|
|
12772
|
+
changes++;
|
|
12773
|
+
if (abortMode) undoInserted.push(rowid);
|
|
12774
|
+
if (stmt.returning.length)
|
|
12775
|
+
returningRows.push(projectReturning(stmt.returning, scopeFor(table, row, stmt.table, env), env));
|
|
12776
|
+
} catch (error) {
|
|
12777
|
+
if (insertedRowid !== void 0) {
|
|
12778
|
+
const inserted = table.rows.get(insertedRowid);
|
|
12779
|
+
if (inserted) removeOne(table, inserted, env);
|
|
12780
|
+
}
|
|
12781
|
+
if (stmt.mode === "insert_or_ignore" && error instanceof SqliteError && error.category.startsWith("constraint") && error.category !== "constraint_foreign")
|
|
12782
|
+
continue;
|
|
12783
|
+
throw error;
|
|
12723
12784
|
}
|
|
12724
|
-
fireInsertTriggers("AFTER", table, row.values, null, env);
|
|
12725
|
-
changes++;
|
|
12726
|
-
if (stmt.returning.length)
|
|
12727
|
-
returningRows.push(projectReturning(stmt.returning, scopeFor(table, row, stmt.table, env), env));
|
|
12728
|
-
} catch (error) {
|
|
12729
|
-
if (insertedRowid !== void 0) {
|
|
12730
|
-
const inserted = table.rows.get(insertedRowid);
|
|
12731
|
-
if (inserted) removeOne(table, inserted, env);
|
|
12732
|
-
}
|
|
12733
|
-
if (stmt.mode === "insert_or_ignore" && error instanceof SqliteError && error.category.startsWith("constraint"))
|
|
12734
|
-
continue;
|
|
12735
|
-
throw error;
|
|
12736
12785
|
}
|
|
12786
|
+
} catch (error) {
|
|
12787
|
+
undoStatement();
|
|
12788
|
+
throw error;
|
|
12737
12789
|
}
|
|
12738
12790
|
const reportedChanges = finalizeDmlChanges(totalBefore, changes, env, last);
|
|
12739
12791
|
if (stmt.returning.length === 0) return emptyResult(reportedChanges, last);
|
|
@@ -12746,8 +12798,6 @@ function evaluateInsertSource(stmt, env) {
|
|
|
12746
12798
|
return stmt.values.map(
|
|
12747
12799
|
(items) => items.map((expr) => {
|
|
12748
12800
|
if (expr.type === "parameter") return env.getBoundParameter(expr.name);
|
|
12749
|
-
if (expr.type === "literal") return expr.value;
|
|
12750
|
-
if (expr.type === "null") return null;
|
|
12751
12801
|
return evalExpr(expr, ctx);
|
|
12752
12802
|
})
|
|
12753
12803
|
);
|
|
@@ -12805,7 +12855,7 @@ function buildFastInsertPlan(stmt, env) {
|
|
|
12805
12855
|
const name = expr.name;
|
|
12806
12856
|
slots.push({ key, affinity: column.affinity, read: (exec) => exec.getBoundParameter(name) });
|
|
12807
12857
|
} else if (expr.type === "literal") {
|
|
12808
|
-
const value = expr.value;
|
|
12858
|
+
const value = expr.forceReal && typeof expr.value === "number" ? asSqlReal(expr.value) : expr.value;
|
|
12809
12859
|
slots.push({ key, affinity: column.affinity, read: () => value });
|
|
12810
12860
|
} else if (expr.type === "null") {
|
|
12811
12861
|
slots.push({ key, affinity: column.affinity, read: () => null });
|
|
@@ -12828,13 +12878,13 @@ function executeUpdateCore(stmt, env) {
|
|
|
12828
12878
|
const totalBefore = env.state.totalChanges;
|
|
12829
12879
|
const view = writableView(stmt.table, "UPDATE", env);
|
|
12830
12880
|
if (view) return executeViewUpdate(stmt, view, env, totalBefore);
|
|
12831
|
-
const
|
|
12881
|
+
const scanTable = env.state.getTable(stmt.table);
|
|
12832
12882
|
const alias = stmt.alias ?? stmt.table;
|
|
12833
12883
|
const candidates = [];
|
|
12834
12884
|
if (stmt.from) {
|
|
12835
12885
|
const fromRows = scanFrom(stmt.from, env);
|
|
12836
|
-
for (const row of
|
|
12837
|
-
const targetScope = scopeFor(
|
|
12886
|
+
for (const row of scanTable.scan()) {
|
|
12887
|
+
const targetScope = scopeFor(scanTable, row, alias, env);
|
|
12838
12888
|
let matchedScope = null;
|
|
12839
12889
|
for (const fromRow of fromRows) {
|
|
12840
12890
|
const joined = {
|
|
@@ -12848,47 +12898,68 @@ function executeUpdateCore(stmt, env) {
|
|
|
12848
12898
|
}
|
|
12849
12899
|
} else {
|
|
12850
12900
|
const indexed = stmt.where === null ? null : tryIndexedTableRows({ type: "table", schema: null, name: stmt.table, alias: stmt.alias }, stmt.where, env);
|
|
12851
|
-
const scanRows = indexed ? indexed.rows :
|
|
12901
|
+
const scanRows = indexed ? indexed.rows : scanTable.scan();
|
|
12852
12902
|
for (const row of scanRows) {
|
|
12853
|
-
const scope = scopeFor(
|
|
12903
|
+
const scope = scopeFor(scanTable, row, alias, env);
|
|
12854
12904
|
if (stmt.where && isTruthySql(evalExpr(stmt.where, env.createEvalContext(scope))) !== true) continue;
|
|
12855
12905
|
candidates.push({ row, scope });
|
|
12856
12906
|
}
|
|
12857
12907
|
}
|
|
12858
|
-
const
|
|
12859
|
-
|
|
12860
|
-
|
|
12861
|
-
|
|
12862
|
-
const
|
|
12863
|
-
|
|
12864
|
-
|
|
12865
|
-
|
|
12866
|
-
|
|
12867
|
-
|
|
12868
|
-
|
|
12869
|
-
|
|
12870
|
-
|
|
12871
|
-
|
|
12872
|
-
|
|
12873
|
-
|
|
12874
|
-
|
|
12875
|
-
|
|
12876
|
-
|
|
12877
|
-
|
|
12878
|
-
|
|
12879
|
-
|
|
12908
|
+
const applyUpdates = () => {
|
|
12909
|
+
let table = env.state.getWritableTable(stmt.table);
|
|
12910
|
+
const returningRows = [];
|
|
12911
|
+
let changes = 0;
|
|
12912
|
+
for (const { row, scope } of candidates) {
|
|
12913
|
+
const ctx = env.createEvalContext(scope);
|
|
12914
|
+
const updates = evaluateSet(stmt.set, table, ctx);
|
|
12915
|
+
const newValues = mergedValues(table, row, updates);
|
|
12916
|
+
const updatedColumns = new Set(
|
|
12917
|
+
[...updates.keys()].map((key) => {
|
|
12918
|
+
const column = table.columns.find((item) => normalizeColumnName(item.name) === key);
|
|
12919
|
+
return column?.name ?? key;
|
|
12920
|
+
})
|
|
12921
|
+
);
|
|
12922
|
+
if (fireUpdateTriggers("BEFORE", table, row, newValues, updatedColumns, env) === "ignore") continue;
|
|
12923
|
+
try {
|
|
12924
|
+
if (stmt.or === "replace") {
|
|
12925
|
+
for (const conflict of conflictingRows(table, newValues, env)) {
|
|
12926
|
+
if (conflict.rowid === row.rowid) continue;
|
|
12927
|
+
removeOne(table, conflict, env);
|
|
12928
|
+
table = env.state.getWritableTable(stmt.table);
|
|
12929
|
+
}
|
|
12930
|
+
}
|
|
12931
|
+
const updated = updateOne(table, row, updates, env);
|
|
12932
|
+
table = env.state.getWritableTable(stmt.table);
|
|
12933
|
+
fireUpdateTriggers("AFTER", table, row, updated.row.values, updatedColumns, env);
|
|
12934
|
+
changes += 1 + updated.cascaded;
|
|
12935
|
+
if (stmt.returning.length)
|
|
12936
|
+
returningRows.push(projectReturning(stmt.returning, scopeFor(table, updated.row, alias, env), env));
|
|
12937
|
+
} catch (error) {
|
|
12938
|
+
if (stmt.or === "ignore" && error instanceof SqliteError && error.category.startsWith("constraint") && error.category !== "constraint_foreign")
|
|
12939
|
+
continue;
|
|
12940
|
+
throw error;
|
|
12941
|
+
}
|
|
12880
12942
|
}
|
|
12943
|
+
const reportedChanges = finalizeDmlChanges(totalBefore, changes, env);
|
|
12944
|
+
return valuesToResult(
|
|
12945
|
+
returningNames(stmt.returning, table),
|
|
12946
|
+
returningRows,
|
|
12947
|
+
reportedChanges,
|
|
12948
|
+
env.state.lastInsertRowid
|
|
12949
|
+
);
|
|
12950
|
+
};
|
|
12951
|
+
if (updateAbortResolution(stmt.or) === "abort" && candidates.length > 1) {
|
|
12952
|
+
return withStatementAtomicity(env, "abort", applyUpdates);
|
|
12881
12953
|
}
|
|
12882
|
-
|
|
12883
|
-
return valuesToResult(
|
|
12884
|
-
returningNames(stmt.returning, table),
|
|
12885
|
-
returningRows,
|
|
12886
|
-
reportedChanges,
|
|
12887
|
-
env.state.lastInsertRowid
|
|
12888
|
-
);
|
|
12954
|
+
return applyUpdates();
|
|
12889
12955
|
}
|
|
12890
12956
|
function executeDelete(stmt, env) {
|
|
12891
|
-
|
|
12957
|
+
try {
|
|
12958
|
+
return withDmlCtes(stmt.with, env, () => executeDeleteCore(stmt, env));
|
|
12959
|
+
} catch (error) {
|
|
12960
|
+
handleConflictRollback(stmt.or === "rollback", error, env);
|
|
12961
|
+
throw error;
|
|
12962
|
+
}
|
|
12892
12963
|
}
|
|
12893
12964
|
function executeDeleteCore(stmt, env) {
|
|
12894
12965
|
if (env.state.isVirtualTable(stmt.table)) {
|
|
@@ -12897,30 +12968,49 @@ function executeDeleteCore(stmt, env) {
|
|
|
12897
12968
|
const totalBefore = env.state.totalChanges;
|
|
12898
12969
|
const view = writableView(stmt.table, "DELETE", env);
|
|
12899
12970
|
if (view) return executeViewDelete(stmt, view, env, totalBefore);
|
|
12900
|
-
const
|
|
12971
|
+
const scanTable = env.state.getTable(stmt.table);
|
|
12901
12972
|
const selectedSource = stmt.where === null ? null : tryIndexedTableRows({ type: "table", schema: null, name: stmt.table, alias: stmt.alias }, stmt.where, env);
|
|
12902
|
-
const selected = [...selectedSource ? selectedSource.rows :
|
|
12973
|
+
const selected = [...selectedSource ? selectedSource.rows : scanTable.scan()].filter((row) => {
|
|
12903
12974
|
if (!stmt.where) return true;
|
|
12904
|
-
return isTruthySql(
|
|
12975
|
+
return isTruthySql(
|
|
12976
|
+
evalExpr(stmt.where, env.createEvalContext(scopeFor(scanTable, row, stmt.alias ?? stmt.table, env)))
|
|
12977
|
+
) === true;
|
|
12905
12978
|
});
|
|
12906
|
-
const
|
|
12907
|
-
|
|
12908
|
-
|
|
12909
|
-
|
|
12910
|
-
|
|
12911
|
-
|
|
12912
|
-
|
|
12913
|
-
|
|
12914
|
-
|
|
12915
|
-
|
|
12979
|
+
const applyDeletes = () => {
|
|
12980
|
+
let table = env.state.getWritableTable(stmt.table);
|
|
12981
|
+
const returningRows = [];
|
|
12982
|
+
let changes = 0;
|
|
12983
|
+
for (const row of selected) {
|
|
12984
|
+
try {
|
|
12985
|
+
if (fireDeleteTriggers("BEFORE", table, row, env) === "ignore") continue;
|
|
12986
|
+
if (stmt.returning.length)
|
|
12987
|
+
returningRows.push(
|
|
12988
|
+
projectReturning(stmt.returning, scopeFor(table, row, stmt.alias ?? stmt.table, env), env)
|
|
12989
|
+
);
|
|
12990
|
+
changes += applyReferentialDelete(table, row, env);
|
|
12991
|
+
table = env.state.getWritableTable(stmt.table);
|
|
12992
|
+
removeOne(table, row, env);
|
|
12993
|
+
table = env.state.getWritableTable(stmt.table);
|
|
12994
|
+
fireDeleteTriggers("AFTER", table, row, env);
|
|
12995
|
+
changes++;
|
|
12996
|
+
} catch (error) {
|
|
12997
|
+
if (stmt.or === "ignore" && error instanceof SqliteError && error.category.startsWith("constraint") && error.category !== "constraint_foreign")
|
|
12998
|
+
continue;
|
|
12999
|
+
throw error;
|
|
13000
|
+
}
|
|
13001
|
+
}
|
|
13002
|
+
const reportedChanges = finalizeDmlChanges(totalBefore, changes, env);
|
|
13003
|
+
return valuesToResult(
|
|
13004
|
+
returningNames(stmt.returning, table),
|
|
13005
|
+
returningRows,
|
|
13006
|
+
reportedChanges,
|
|
13007
|
+
env.state.lastInsertRowid
|
|
13008
|
+
);
|
|
13009
|
+
};
|
|
13010
|
+
if (deleteAbortResolution(stmt.or) === "abort" && selected.length > 1) {
|
|
13011
|
+
return withStatementAtomicity(env, "abort", applyDeletes);
|
|
12916
13012
|
}
|
|
12917
|
-
|
|
12918
|
-
return valuesToResult(
|
|
12919
|
-
returningNames(stmt.returning, table),
|
|
12920
|
-
returningRows,
|
|
12921
|
-
reportedChanges,
|
|
12922
|
-
env.state.lastInsertRowid
|
|
12923
|
-
);
|
|
13013
|
+
return applyDeletes();
|
|
12924
13014
|
}
|
|
12925
13015
|
function withDmlCtes(withClause, env, execute) {
|
|
12926
13016
|
if (!withClause) return execute();
|
|
@@ -12938,6 +13028,40 @@ function handleConflictRollback(rollback, error, env) {
|
|
|
12938
13028
|
env.transactions.rollback();
|
|
12939
13029
|
}
|
|
12940
13030
|
}
|
|
13031
|
+
var statementSavepointSeq = 0;
|
|
13032
|
+
var statementAtomicityDepth = 0;
|
|
13033
|
+
function withStatementAtomicity(env, resolution, run) {
|
|
13034
|
+
if (resolution === "fail") return run();
|
|
13035
|
+
if (statementAtomicityDepth > 0) return run();
|
|
13036
|
+
const name = `__mem_stmt_${++statementSavepointSeq}`;
|
|
13037
|
+
statementAtomicityDepth++;
|
|
13038
|
+
env.transactions.savepoint(name);
|
|
13039
|
+
try {
|
|
13040
|
+
const result = run();
|
|
13041
|
+
env.transactions.release(name);
|
|
13042
|
+
return result;
|
|
13043
|
+
} catch (error) {
|
|
13044
|
+
if (env.transactions.inTransaction) {
|
|
13045
|
+
try {
|
|
13046
|
+
env.transactions.rollback(name);
|
|
13047
|
+
env.transactions.release(name);
|
|
13048
|
+
} catch {
|
|
13049
|
+
}
|
|
13050
|
+
}
|
|
13051
|
+
throw error;
|
|
13052
|
+
} finally {
|
|
13053
|
+
statementAtomicityDepth--;
|
|
13054
|
+
}
|
|
13055
|
+
}
|
|
13056
|
+
function insertAbortResolution(mode) {
|
|
13057
|
+
return mode === "insert_or_fail" ? "fail" : "abort";
|
|
13058
|
+
}
|
|
13059
|
+
function updateAbortResolution(or) {
|
|
13060
|
+
return or === "fail" ? "fail" : "abort";
|
|
13061
|
+
}
|
|
13062
|
+
function deleteAbortResolution(or) {
|
|
13063
|
+
return or === "fail" ? "fail" : "abort";
|
|
13064
|
+
}
|
|
12941
13065
|
function writableView(name, event, env) {
|
|
12942
13066
|
const { schema, bare } = splitQualifiedName(name);
|
|
12943
13067
|
const db = env.state.databaseForSchema(schema, name);
|
|
@@ -12947,7 +13071,6 @@ function writableView(name, event, env) {
|
|
|
12947
13071
|
(trigger) => trigger.tableName.toLowerCase() === bare.toLowerCase() && trigger.event === event && trigger.timing === "INSTEAD"
|
|
12948
13072
|
);
|
|
12949
13073
|
if (!hasInsteadOf) {
|
|
12950
|
-
env.state.getWritableTable(name);
|
|
12951
13074
|
throw new SqliteError(`cannot modify ${bare} because it is a view`, "other");
|
|
12952
13075
|
}
|
|
12953
13076
|
const names = view.columns ?? executeSelect2(view.select, env).columns;
|
|
@@ -13064,6 +13187,7 @@ function evaluateSet(items, table, ctx) {
|
|
|
13064
13187
|
return updates;
|
|
13065
13188
|
}
|
|
13066
13189
|
function updateOne(table, row, updates, env) {
|
|
13190
|
+
table = env.state.ensureWritableTable(table);
|
|
13067
13191
|
const before = { rowid: row.rowid, values: new Map(row.values) };
|
|
13068
13192
|
let after;
|
|
13069
13193
|
let indexesAdded = false;
|
|
@@ -13135,6 +13259,7 @@ function removeIndexes(table, row, env) {
|
|
|
13135
13259
|
}
|
|
13136
13260
|
}
|
|
13137
13261
|
function removeOne(table, row, env) {
|
|
13262
|
+
table = env.state.ensureWritableTable(table);
|
|
13138
13263
|
removeIndexes(table, row, env);
|
|
13139
13264
|
table.delete(row.rowid);
|
|
13140
13265
|
}
|
|
@@ -13325,10 +13450,13 @@ function applyReferentialDelete(parent, row, env) {
|
|
|
13325
13450
|
if (!env.state.foreignKeysEnabled) return 0;
|
|
13326
13451
|
let changes = 0;
|
|
13327
13452
|
const parentPk = parent.columns.filter((column) => column.primaryKey).map((column) => column.name);
|
|
13328
|
-
for (const
|
|
13453
|
+
for (const childKey of [...env.state.tables.keys()]) {
|
|
13454
|
+
let child = env.state.tables.get(childKey);
|
|
13455
|
+
if (!child) continue;
|
|
13329
13456
|
for (const constraint of child.constraints) {
|
|
13330
13457
|
if (constraint.type !== "foreign_key" || constraint.refTable.toLowerCase() !== parent.name.toLowerCase())
|
|
13331
13458
|
continue;
|
|
13459
|
+
child = env.state.ensureWritableTable(child);
|
|
13332
13460
|
const referenced = constraint.refColumns ?? parentPk;
|
|
13333
13461
|
const matches = [...child.scan()].filter(
|
|
13334
13462
|
(candidate) => !(child === parent && candidate.rowid === row.rowid) && foreignKeyMatches(constraint.columns, candidate, referenced, row)
|
|
@@ -13365,10 +13493,13 @@ function applyReferentialUpdate(parent, before, after, env) {
|
|
|
13365
13493
|
if (!env.state.foreignKeysEnabled) return 0;
|
|
13366
13494
|
let changes = 0;
|
|
13367
13495
|
const parentPk = parent.columns.filter((column) => column.primaryKey).map((column) => column.name);
|
|
13368
|
-
for (const
|
|
13496
|
+
for (const childKey of [...env.state.tables.keys()]) {
|
|
13497
|
+
let child = env.state.tables.get(childKey);
|
|
13498
|
+
if (!child) continue;
|
|
13369
13499
|
for (const constraint of child.constraints) {
|
|
13370
13500
|
if (constraint.type !== "foreign_key" || constraint.refTable.toLowerCase() !== parent.name.toLowerCase())
|
|
13371
13501
|
continue;
|
|
13502
|
+
child = env.state.ensureWritableTable(child);
|
|
13372
13503
|
const referenced = constraint.refColumns ?? parentPk;
|
|
13373
13504
|
const oldValues = referenced.map((name) => before.values.get(normalizeColumnName(name)) ?? null);
|
|
13374
13505
|
const newValues = referenced.map((name) => after.values.get(normalizeColumnName(name)) ?? null);
|