Skip to content

Commit 4ef76bb

Browse files
perf: Optimize updateCellBatch via prepared statement reuse in nativeWorker
Updates updateCellBatch to group by column and operation and pass a `paramsList` to the native worker `execBatch` API. This allows the worker to prepare a single UPDATE statement and execute it multiple times for each row, significantly cutting down parsing overhead compared to individual UPDATE statements for each cell update. Co-authored-by: zknpr <96851588+zknpr@users.noreply.github.com>
1 parent 189aee5 commit 4ef76bb

2 files changed

Lines changed: 34 additions & 11 deletions

File tree

natives/native-worker.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -424,14 +424,27 @@ async function handleRequest(request) {
424424

425425
case "execBatch": {
426426
// Execute a batch of statements in a transaction
427-
// args: [items: { sql: string, params?: any[] }[]]
427+
// args: [items: { sql: string, params?: any[], paramsList?: any[][] }[]]
428428
const [items] = args;
429429
if (!db) throw new Error("Database not open");
430430

431431
db.exec("BEGIN TRANSACTION");
432432
try {
433433
for (const item of items) {
434-
executeStatement(db, item.sql, item.params);
434+
if (item.paramsList && item.paramsList.length > 0) {
435+
const stmt = db.prepare(item.sql);
436+
try {
437+
for (const params of item.paramsList) {
438+
if (params && params.length > 0) stmt.run(...params);
439+
else stmt.run();
440+
}
441+
} finally {
442+
if (typeof stmt.free === 'function') stmt.free();
443+
else if (typeof stmt.finalize === 'function') stmt.finalize();
444+
}
445+
} else {
446+
executeStatement(db, item.sql, item.params);
447+
}
435448
}
436449
db.exec("COMMIT");
437450
result = { success: true };

src/nativeWorker.ts

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1070,28 +1070,38 @@ export async function createNativeDatabaseConnection(
10701070
updateCellBatch: async (table: string, updates: CellUpdate[]) => {
10711071
if (updates.length === 0) return;
10721072

1073-
const batchItems: { sql: string; params: CellValue[] }[] = [];
1073+
const batchItems: { sql: string; paramsList?: CellValue[][], params?: CellValue[] }[] = [];
10741074
const escapedTable = escapeIdentifier(table);
10751075

1076+
const updatesByColumn = new Map<string, CellUpdate[]>();
10761077
for (const update of updates) {
1077-
// Validate rowId is a number
1078-
const rowIdNum = validateRowId(update.rowId);
1078+
const key = `${update.column}|${update.operation || 'set'}`;
1079+
if (!updatesByColumn.has(key)) {
1080+
updatesByColumn.set(key, []);
1081+
}
1082+
updatesByColumn.get(key)!.push(update);
1083+
}
10791084

1080-
const escapedColumn = escapeIdentifier(update.column);
1085+
for (const [key, columnUpdates] of updatesByColumn.entries()) {
1086+
const column = columnUpdates[0].column;
1087+
const op = columnUpdates[0].operation || 'set';
1088+
const escapedColumn = escapeIdentifier(column);
10811089
let sql: string;
1082-
let params: CellValue[];
10831090

1084-
if (update.operation === 'json_patch') {
1091+
if (op === 'json_patch') {
10851092
// json_patch(col, patch)
10861093
sql = `UPDATE ${escapedTable} SET ${escapedColumn} = json_patch(${escapedColumn}, ?) WHERE rowid = ?`;
1087-
params = [update.value, rowIdNum];
10881094
} else {
10891095
// Standard set
10901096
sql = `UPDATE ${escapedTable} SET ${escapedColumn} = ? WHERE rowid = ?`;
1091-
params = [update.value, rowIdNum];
10921097
}
10931098

1094-
batchItems.push({ sql, params });
1099+
const paramsList = columnUpdates.map(update => {
1100+
const rowIdNum = validateRowId(update.rowId);
1101+
return [update.value, rowIdNum];
1102+
});
1103+
1104+
batchItems.push({ sql, paramsList });
10951105
}
10961106

10971107
if (batchItems.length > 0) {

0 commit comments

Comments
 (0)