@crvouga/sqlite-mem 1.6.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/dist/ast/nodes.d.ts +5 -0
- package/dist/index.js +305 -179
- package/dist/index.js.map +2 -2
- package/dist/unstable.js +39 -18
- 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";
|
|
@@ -8402,6 +8422,7 @@ var DatabaseState = class _DatabaseState {
|
|
|
8402
8422
|
refColumns: constraint.columns,
|
|
8403
8423
|
onDelete: constraint.onDelete,
|
|
8404
8424
|
onUpdate: constraint.onUpdate,
|
|
8425
|
+
match: constraint.match,
|
|
8405
8426
|
name: null,
|
|
8406
8427
|
deferrable: constraint.deferrable,
|
|
8407
8428
|
initiallyDeferred: constraint.initiallyDeferred
|
|
@@ -12588,7 +12609,7 @@ function executeInsertCore(stmt, env) {
|
|
|
12588
12609
|
if (view) return executeViewInsert(stmt, view, env, totalBefore);
|
|
12589
12610
|
const fast = tryFastInsert(stmt, env);
|
|
12590
12611
|
if (fast) return fast;
|
|
12591
|
-
|
|
12612
|
+
let table = env.state.getWritableTable(stmt.table);
|
|
12592
12613
|
const columnNames = stmt.columns ?? table.columns.map((column) => column.name);
|
|
12593
12614
|
const rowidIndexes = columnNames.map((name, index) => isRowidName3(name) ? index : -1).filter((index) => index >= 0);
|
|
12594
12615
|
if (rowidIndexes.length > 1) throw new SqliteError("duplicate column name: rowid", "other");
|
|
@@ -12624,116 +12645,141 @@ function executeInsertCore(stmt, env) {
|
|
|
12624
12645
|
env.state.recordChange(changes, last);
|
|
12625
12646
|
return emptyResult(changes, last);
|
|
12626
12647
|
}
|
|
12627
|
-
|
|
12628
|
-
|
|
12629
|
-
|
|
12630
|
-
|
|
12631
|
-
|
|
12632
|
-
|
|
12633
|
-
|
|
12634
|
-
|
|
12635
|
-
|
|
12636
|
-
|
|
12637
|
-
const
|
|
12638
|
-
|
|
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);
|
|
12639
12662
|
}
|
|
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);
|
|
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));
|
|
12665
12677
|
}
|
|
12666
|
-
|
|
12667
|
-
|
|
12668
|
-
|
|
12669
|
-
|
|
12670
|
-
|
|
12671
|
-
|
|
12672
|
-
|
|
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 {
|
|
12673
12745
|
throw new SqliteError(
|
|
12674
12746
|
`UNIQUE constraint failed: ${table.name}`,
|
|
12675
12747
|
"constraint_unique",
|
|
12676
12748
|
"SQLITE_CONSTRAINT_UNIQUE"
|
|
12677
12749
|
);
|
|
12678
12750
|
}
|
|
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
12751
|
}
|
|
12699
|
-
|
|
12700
|
-
|
|
12701
|
-
|
|
12702
|
-
|
|
12703
|
-
|
|
12704
|
-
|
|
12705
|
-
|
|
12706
|
-
|
|
12707
|
-
|
|
12708
|
-
)
|
|
12709
|
-
|
|
12710
|
-
|
|
12711
|
-
|
|
12712
|
-
|
|
12713
|
-
|
|
12714
|
-
|
|
12715
|
-
|
|
12716
|
-
|
|
12717
|
-
|
|
12718
|
-
|
|
12719
|
-
|
|
12720
|
-
|
|
12721
|
-
|
|
12722
|
-
|
|
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;
|
|
12723
12778
|
}
|
|
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
12779
|
}
|
|
12780
|
+
} catch (error) {
|
|
12781
|
+
undoStatement();
|
|
12782
|
+
throw error;
|
|
12737
12783
|
}
|
|
12738
12784
|
const reportedChanges = finalizeDmlChanges(totalBefore, changes, env, last);
|
|
12739
12785
|
if (stmt.returning.length === 0) return emptyResult(reportedChanges, last);
|
|
@@ -12746,8 +12792,6 @@ function evaluateInsertSource(stmt, env) {
|
|
|
12746
12792
|
return stmt.values.map(
|
|
12747
12793
|
(items) => items.map((expr) => {
|
|
12748
12794
|
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
12795
|
return evalExpr(expr, ctx);
|
|
12752
12796
|
})
|
|
12753
12797
|
);
|
|
@@ -12805,7 +12849,7 @@ function buildFastInsertPlan(stmt, env) {
|
|
|
12805
12849
|
const name = expr.name;
|
|
12806
12850
|
slots.push({ key, affinity: column.affinity, read: (exec) => exec.getBoundParameter(name) });
|
|
12807
12851
|
} else if (expr.type === "literal") {
|
|
12808
|
-
const value = expr.value;
|
|
12852
|
+
const value = expr.forceReal && typeof expr.value === "number" ? asSqlReal(expr.value) : expr.value;
|
|
12809
12853
|
slots.push({ key, affinity: column.affinity, read: () => value });
|
|
12810
12854
|
} else if (expr.type === "null") {
|
|
12811
12855
|
slots.push({ key, affinity: column.affinity, read: () => null });
|
|
@@ -12828,13 +12872,13 @@ function executeUpdateCore(stmt, env) {
|
|
|
12828
12872
|
const totalBefore = env.state.totalChanges;
|
|
12829
12873
|
const view = writableView(stmt.table, "UPDATE", env);
|
|
12830
12874
|
if (view) return executeViewUpdate(stmt, view, env, totalBefore);
|
|
12831
|
-
const
|
|
12875
|
+
const scanTable = env.state.getTable(stmt.table);
|
|
12832
12876
|
const alias = stmt.alias ?? stmt.table;
|
|
12833
12877
|
const candidates = [];
|
|
12834
12878
|
if (stmt.from) {
|
|
12835
12879
|
const fromRows = scanFrom(stmt.from, env);
|
|
12836
|
-
for (const row of
|
|
12837
|
-
const targetScope = scopeFor(
|
|
12880
|
+
for (const row of scanTable.scan()) {
|
|
12881
|
+
const targetScope = scopeFor(scanTable, row, alias, env);
|
|
12838
12882
|
let matchedScope = null;
|
|
12839
12883
|
for (const fromRow of fromRows) {
|
|
12840
12884
|
const joined = {
|
|
@@ -12848,47 +12892,69 @@ function executeUpdateCore(stmt, env) {
|
|
|
12848
12892
|
}
|
|
12849
12893
|
} else {
|
|
12850
12894
|
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 :
|
|
12895
|
+
const scanRows = indexed ? indexed.rows : scanTable.scan();
|
|
12852
12896
|
for (const row of scanRows) {
|
|
12853
|
-
const scope = scopeFor(
|
|
12897
|
+
const scope = scopeFor(scanTable, row, alias, env);
|
|
12854
12898
|
if (stmt.where && isTruthySql(evalExpr(stmt.where, env.createEvalContext(scope))) !== true) continue;
|
|
12855
12899
|
candidates.push({ row, scope });
|
|
12856
12900
|
}
|
|
12857
12901
|
}
|
|
12858
|
-
const
|
|
12859
|
-
|
|
12860
|
-
|
|
12861
|
-
|
|
12862
|
-
const
|
|
12863
|
-
|
|
12864
|
-
|
|
12865
|
-
|
|
12866
|
-
|
|
12867
|
-
|
|
12868
|
-
|
|
12869
|
-
|
|
12870
|
-
|
|
12871
|
-
|
|
12872
|
-
|
|
12873
|
-
|
|
12874
|
-
|
|
12875
|
-
|
|
12876
|
-
|
|
12877
|
-
|
|
12878
|
-
|
|
12879
|
-
|
|
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
|
+
}
|
|
12880
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);
|
|
12881
12948
|
}
|
|
12882
|
-
|
|
12883
|
-
return valuesToResult(
|
|
12884
|
-
returningNames(stmt.returning, table),
|
|
12885
|
-
returningRows,
|
|
12886
|
-
reportedChanges,
|
|
12887
|
-
env.state.lastInsertRowid
|
|
12888
|
-
);
|
|
12949
|
+
return applyUpdates();
|
|
12889
12950
|
}
|
|
12890
12951
|
function executeDelete(stmt, env) {
|
|
12891
|
-
|
|
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
|
+
}
|
|
12892
12958
|
}
|
|
12893
12959
|
function executeDeleteCore(stmt, env) {
|
|
12894
12960
|
if (env.state.isVirtualTable(stmt.table)) {
|
|
@@ -12897,30 +12963,49 @@ function executeDeleteCore(stmt, env) {
|
|
|
12897
12963
|
const totalBefore = env.state.totalChanges;
|
|
12898
12964
|
const view = writableView(stmt.table, "DELETE", env);
|
|
12899
12965
|
if (view) return executeViewDelete(stmt, view, env, totalBefore);
|
|
12900
|
-
const
|
|
12966
|
+
const scanTable = env.state.getTable(stmt.table);
|
|
12901
12967
|
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 :
|
|
12968
|
+
const selected = [...selectedSource ? selectedSource.rows : scanTable.scan()].filter((row) => {
|
|
12903
12969
|
if (!stmt.where) return true;
|
|
12904
|
-
return isTruthySql(
|
|
12970
|
+
return isTruthySql(
|
|
12971
|
+
evalExpr(stmt.where, env.createEvalContext(scopeFor(scanTable, row, stmt.alias ?? stmt.table, env)))
|
|
12972
|
+
) === true;
|
|
12905
12973
|
});
|
|
12906
|
-
const
|
|
12907
|
-
|
|
12908
|
-
|
|
12909
|
-
|
|
12910
|
-
|
|
12911
|
-
|
|
12912
|
-
|
|
12913
|
-
|
|
12914
|
-
|
|
12915
|
-
|
|
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);
|
|
12916
13007
|
}
|
|
12917
|
-
|
|
12918
|
-
return valuesToResult(
|
|
12919
|
-
returningNames(stmt.returning, table),
|
|
12920
|
-
returningRows,
|
|
12921
|
-
reportedChanges,
|
|
12922
|
-
env.state.lastInsertRowid
|
|
12923
|
-
);
|
|
13008
|
+
return applyDeletes();
|
|
12924
13009
|
}
|
|
12925
13010
|
function withDmlCtes(withClause, env, execute) {
|
|
12926
13011
|
if (!withClause) return execute();
|
|
@@ -12938,6 +13023,40 @@ function handleConflictRollback(rollback, error, env) {
|
|
|
12938
13023
|
env.transactions.rollback();
|
|
12939
13024
|
}
|
|
12940
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
|
+
}
|
|
12941
13060
|
function writableView(name, event, env) {
|
|
12942
13061
|
const { schema, bare } = splitQualifiedName(name);
|
|
12943
13062
|
const db = env.state.databaseForSchema(schema, name);
|
|
@@ -12947,7 +13066,6 @@ function writableView(name, event, env) {
|
|
|
12947
13066
|
(trigger) => trigger.tableName.toLowerCase() === bare.toLowerCase() && trigger.event === event && trigger.timing === "INSTEAD"
|
|
12948
13067
|
);
|
|
12949
13068
|
if (!hasInsteadOf) {
|
|
12950
|
-
env.state.getWritableTable(name);
|
|
12951
13069
|
throw new SqliteError(`cannot modify ${bare} because it is a view`, "other");
|
|
12952
13070
|
}
|
|
12953
13071
|
const names = view.columns ?? executeSelect2(view.select, env).columns;
|
|
@@ -13064,6 +13182,7 @@ function evaluateSet(items, table, ctx) {
|
|
|
13064
13182
|
return updates;
|
|
13065
13183
|
}
|
|
13066
13184
|
function updateOne(table, row, updates, env) {
|
|
13185
|
+
table = env.state.ensureWritableTable(table);
|
|
13067
13186
|
const before = { rowid: row.rowid, values: new Map(row.values) };
|
|
13068
13187
|
let after;
|
|
13069
13188
|
let indexesAdded = false;
|
|
@@ -13135,6 +13254,7 @@ function removeIndexes(table, row, env) {
|
|
|
13135
13254
|
}
|
|
13136
13255
|
}
|
|
13137
13256
|
function removeOne(table, row, env) {
|
|
13257
|
+
table = env.state.ensureWritableTable(table);
|
|
13138
13258
|
removeIndexes(table, row, env);
|
|
13139
13259
|
table.delete(row.rowid);
|
|
13140
13260
|
}
|
|
@@ -13325,10 +13445,13 @@ function applyReferentialDelete(parent, row, env) {
|
|
|
13325
13445
|
if (!env.state.foreignKeysEnabled) return 0;
|
|
13326
13446
|
let changes = 0;
|
|
13327
13447
|
const parentPk = parent.columns.filter((column) => column.primaryKey).map((column) => column.name);
|
|
13328
|
-
for (const
|
|
13448
|
+
for (const childKey of [...env.state.tables.keys()]) {
|
|
13449
|
+
let child = env.state.tables.get(childKey);
|
|
13450
|
+
if (!child) continue;
|
|
13329
13451
|
for (const constraint of child.constraints) {
|
|
13330
13452
|
if (constraint.type !== "foreign_key" || constraint.refTable.toLowerCase() !== parent.name.toLowerCase())
|
|
13331
13453
|
continue;
|
|
13454
|
+
child = env.state.ensureWritableTable(child);
|
|
13332
13455
|
const referenced = constraint.refColumns ?? parentPk;
|
|
13333
13456
|
const matches = [...child.scan()].filter(
|
|
13334
13457
|
(candidate) => !(child === parent && candidate.rowid === row.rowid) && foreignKeyMatches(constraint.columns, candidate, referenced, row)
|
|
@@ -13365,10 +13488,13 @@ function applyReferentialUpdate(parent, before, after, env) {
|
|
|
13365
13488
|
if (!env.state.foreignKeysEnabled) return 0;
|
|
13366
13489
|
let changes = 0;
|
|
13367
13490
|
const parentPk = parent.columns.filter((column) => column.primaryKey).map((column) => column.name);
|
|
13368
|
-
for (const
|
|
13491
|
+
for (const childKey of [...env.state.tables.keys()]) {
|
|
13492
|
+
let child = env.state.tables.get(childKey);
|
|
13493
|
+
if (!child) continue;
|
|
13369
13494
|
for (const constraint of child.constraints) {
|
|
13370
13495
|
if (constraint.type !== "foreign_key" || constraint.refTable.toLowerCase() !== parent.name.toLowerCase())
|
|
13371
13496
|
continue;
|
|
13497
|
+
child = env.state.ensureWritableTable(child);
|
|
13372
13498
|
const referenced = constraint.refColumns ?? parentPk;
|
|
13373
13499
|
const oldValues = referenced.map((name) => before.values.get(normalizeColumnName(name)) ?? null);
|
|
13374
13500
|
const newValues = referenced.map((name) => after.values.get(normalizeColumnName(name)) ?? null);
|