Skip to content

Commit 89f4db4

Browse files
committed
libsql-client: Report batch statement index on error
If a batch statement fails, the SQL over HTTP protocol does report back exactly what step failed. However, we also need to propagate that information to the caller.
1 parent e0b105f commit 89f4db4

File tree

3 files changed

+156
-44
lines changed

3 files changed

+156
-44
lines changed

packages/libsql-client/src/hrana.ts

Lines changed: 52 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type {
66
TransactionMode,
77
InArgs,
88
} from "@libsql/core/api";
9-
import { LibsqlError } from "@libsql/core/api";
9+
import { LibsqlError, LibsqlBatchError } from "@libsql/core/api";
1010
import type { SqlCache } from "./sql_cache.js";
1111
import { transactionModeToBegin, ResultSetImpl } from "@libsql/core/util";
1212

@@ -134,16 +134,33 @@ export abstract class HranaTransaction implements Transaction {
134134
}
135135

136136
const resultSets = [];
137-
for (const rowsPromise of rowsPromises) {
138-
const rows = await rowsPromise;
139-
if (rows === undefined) {
140-
throw new LibsqlError(
141-
"Statement in a transaction was not executed, " +
142-
"probably because the transaction has been rolled back",
143-
"TRANSACTION_CLOSED",
144-
);
137+
for (let i = 0; i < rowsPromises.length; i++) {
138+
try {
139+
const rows = await rowsPromises[i];
140+
if (rows === undefined) {
141+
throw new LibsqlBatchError(
142+
"Statement in a transaction was not executed, " +
143+
"probably because the transaction has been rolled back",
144+
i,
145+
"TRANSACTION_CLOSED",
146+
);
147+
}
148+
resultSets.push(resultSetFromHrana(rows));
149+
} catch (e) {
150+
if (e instanceof LibsqlBatchError) {
151+
throw e;
152+
}
153+
if (e instanceof LibsqlError) {
154+
throw new LibsqlBatchError(
155+
e.message,
156+
i,
157+
e.code,
158+
e.rawCode,
159+
e.cause instanceof Error ? e.cause : undefined,
160+
);
161+
}
162+
throw e;
145163
}
146-
resultSets.push(resultSetFromHrana(rows));
147164
}
148165
return resultSets;
149166
} catch (e) {
@@ -295,15 +312,32 @@ export async function executeHranaBatch(
295312

296313
const resultSets = [];
297314
await beginPromise;
298-
for (const stmtPromise of stmtPromises) {
299-
const hranaRows = await stmtPromise;
300-
if (hranaRows === undefined) {
301-
throw new LibsqlError(
302-
"Statement in a batch was not executed, probably because the transaction has been rolled back",
303-
"TRANSACTION_CLOSED",
304-
);
315+
for (let i = 0; i < stmtPromises.length; i++) {
316+
try {
317+
const hranaRows = await stmtPromises[i];
318+
if (hranaRows === undefined) {
319+
throw new LibsqlBatchError(
320+
"Statement in a batch was not executed, probably because the transaction has been rolled back",
321+
i,
322+
"TRANSACTION_CLOSED",
323+
);
324+
}
325+
resultSets.push(resultSetFromHrana(hranaRows));
326+
} catch (e) {
327+
if (e instanceof LibsqlBatchError) {
328+
throw e;
329+
}
330+
if (e instanceof LibsqlError) {
331+
throw new LibsqlBatchError(
332+
e.message,
333+
i,
334+
e.code,
335+
e.rawCode,
336+
e.cause instanceof Error ? e.cause : undefined,
337+
);
338+
}
339+
throw e;
305340
}
306-
resultSets.push(resultSetFromHrana(hranaRows));
307341
}
308342
await commitPromise;
309343

packages/libsql-client/src/sqlite3.ts

Lines changed: 86 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import type {
1515
InArgs,
1616
Replicated,
1717
} from "@libsql/core/api";
18-
import { LibsqlError } from "@libsql/core/api";
18+
import { LibsqlError, LibsqlBatchError } from "@libsql/core/api";
1919
import type { ExpandedConfig } from "@libsql/core/config";
2020
import { expandConfig, isInMemoryConfig } from "@libsql/core/config";
2121
import {
@@ -148,18 +148,39 @@ export class Sqlite3Client implements Client {
148148
const db = this.#getDb();
149149
try {
150150
executeStmt(db, transactionModeToBegin(mode), this.#intMode);
151-
const resultSets = stmts.map((stmt) => {
152-
if (!db.inTransaction) {
153-
throw new LibsqlError(
154-
"The transaction has been rolled back",
155-
"TRANSACTION_CLOSED",
151+
const resultSets = [];
152+
for (let i = 0; i < stmts.length; i++) {
153+
try {
154+
if (!db.inTransaction) {
155+
throw new LibsqlBatchError(
156+
"The transaction has been rolled back",
157+
i,
158+
"TRANSACTION_CLOSED",
159+
);
160+
}
161+
const stmt = stmts[i];
162+
const normalizedStmt: InStatement = Array.isArray(stmt)
163+
? { sql: stmt[0], args: stmt[1] || [] }
164+
: stmt;
165+
resultSets.push(
166+
executeStmt(db, normalizedStmt, this.#intMode),
156167
);
168+
} catch (e) {
169+
if (e instanceof LibsqlBatchError) {
170+
throw e;
171+
}
172+
if (e instanceof LibsqlError) {
173+
throw new LibsqlBatchError(
174+
e.message,
175+
i,
176+
e.code,
177+
e.rawCode,
178+
e.cause instanceof Error ? e.cause : undefined,
179+
);
180+
}
181+
throw e;
157182
}
158-
const normalizedStmt: InStatement = Array.isArray(stmt)
159-
? { sql: stmt[0], args: stmt[1] || [] }
160-
: stmt;
161-
return executeStmt(db, normalizedStmt, this.#intMode);
162-
});
183+
}
163184
executeStmt(db, "COMMIT", this.#intMode);
164185
return resultSets;
165186
} finally {
@@ -175,15 +196,33 @@ export class Sqlite3Client implements Client {
175196
try {
176197
executeStmt(db, "PRAGMA foreign_keys=off", this.#intMode);
177198
executeStmt(db, transactionModeToBegin("deferred"), this.#intMode);
178-
const resultSets = stmts.map((stmt) => {
179-
if (!db.inTransaction) {
180-
throw new LibsqlError(
181-
"The transaction has been rolled back",
182-
"TRANSACTION_CLOSED",
183-
);
199+
const resultSets = [];
200+
for (let i = 0; i < stmts.length; i++) {
201+
try {
202+
if (!db.inTransaction) {
203+
throw new LibsqlBatchError(
204+
"The transaction has been rolled back",
205+
i,
206+
"TRANSACTION_CLOSED",
207+
);
208+
}
209+
resultSets.push(executeStmt(db, stmts[i], this.#intMode));
210+
} catch (e) {
211+
if (e instanceof LibsqlBatchError) {
212+
throw e;
213+
}
214+
if (e instanceof LibsqlError) {
215+
throw new LibsqlBatchError(
216+
e.message,
217+
i,
218+
e.code,
219+
e.rawCode,
220+
e.cause instanceof Error ? e.cause : undefined,
221+
);
222+
}
223+
throw e;
184224
}
185-
return executeStmt(db, stmt, this.#intMode);
186-
});
225+
}
187226
executeStmt(db, "COMMIT", this.#intMode);
188227
return resultSets;
189228
} finally {
@@ -291,13 +330,34 @@ export class Sqlite3Transaction implements Transaction {
291330
async batch(
292331
stmts: Array<InStatement | [string, InArgs?]>,
293332
): Promise<Array<ResultSet>> {
294-
return stmts.map((stmt) => {
295-
this.#checkNotClosed();
296-
const normalizedStmt: InStatement = Array.isArray(stmt)
297-
? { sql: stmt[0], args: stmt[1] || [] }
298-
: stmt;
299-
return executeStmt(this.#database, normalizedStmt, this.#intMode);
300-
});
333+
const resultSets = [];
334+
for (let i = 0; i < stmts.length; i++) {
335+
try {
336+
this.#checkNotClosed();
337+
const stmt = stmts[i];
338+
const normalizedStmt: InStatement = Array.isArray(stmt)
339+
? { sql: stmt[0], args: stmt[1] || [] }
340+
: stmt;
341+
resultSets.push(
342+
executeStmt(this.#database, normalizedStmt, this.#intMode),
343+
);
344+
} catch (e) {
345+
if (e instanceof LibsqlBatchError) {
346+
throw e;
347+
}
348+
if (e instanceof LibsqlError) {
349+
throw new LibsqlBatchError(
350+
e.message,
351+
i,
352+
e.code,
353+
e.rawCode,
354+
e.cause instanceof Error ? e.cause : undefined,
355+
);
356+
}
357+
throw e;
358+
}
359+
}
360+
return resultSets;
301361
}
302362

303363
async executeMultiple(sql: string): Promise<void> {

packages/libsql-core/src/api.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,3 +504,21 @@ export class LibsqlError extends Error {
504504
this.name = "LibsqlError";
505505
}
506506
}
507+
508+
/** Error thrown by the client during batch operations. */
509+
export class LibsqlBatchError extends LibsqlError {
510+
/** The zero-based index of the statement that failed in the batch. */
511+
statementIndex: number;
512+
513+
constructor(
514+
message: string,
515+
statementIndex: number,
516+
code: string,
517+
rawCode?: number,
518+
cause?: Error,
519+
) {
520+
super(message, code, rawCode, cause);
521+
this.statementIndex = statementIndex;
522+
this.name = "LibsqlBatchError";
523+
}
524+
}

0 commit comments

Comments
 (0)