tursodatabase/turso · error · TypeError

batch statement ${index} failed: ${keyword} is not allowed i

Error message

batch statement ${index} failed: ${keyword} is not allowed in an atomic batch

What it means

In atomic batch mode the library itself wraps statements in BEGIN/COMMIT, so any user statement starting with a transaction-control keyword (BEGIN, COMMIT, ROLLBACK, SAVEPOINT, etc.) is rejected up front with batchInputError(index, ...) naming the offending keyword. Nested or interleaved transaction control would break the atomicity guarantee the mode promises.

Source

Thrown at bindings/javascript/packages/common/promise.ts:808

    }
    try {
      const args = Array.isArray(statement.args)
        ? statement.args.map(normalizeBatchBindValue)
        : Object.fromEntries(
          Object.entries(statement.args).map(([name, value]) => [name, normalizeBatchBindValue(value)]),
        );
      return { sql: statement.sql, args };
    } catch (error) {
      throw batchInputError(index, error);
    }
  });
  if (wrap) {
    for (let index = 0; index < normalizedStatements.length; index++) {
      const statement = normalizedStatements[index];
      const sql = typeof statement === "string" ? statement : statement.sql;
      const keyword = firstSqlKeyword(sql);
      if (keyword !== undefined && TRANSACTION_CONTROL_KEYWORDS.has(keyword)) {
        throw batchInputError(index, new Error(`${keyword} is not allowed in an atomic batch`));
      }
    }
  }
  if (wrap) {
    await runRawSql(`BEGIN ${normalizeBatchMode(mode!)}`);
  }

  const results: ResultSet[] = [];
  const executeStatement = async (
    statement: BatchStatement,
  ): Promise<ResultSet> => {
    const sql = typeof statement === "string" ? statement : statement.sql;
    const args = typeof statement === "string" ? undefined : statement.args;

    let nativeStmt: NativeStatement;
    try {
      nativeStmt = native.prepare(sql);
    } catch (err) {

View on GitHub (pinned to c1e5928725)

Solutions

  1. Remove BEGIN/COMMIT/ROLLBACK/SAVEPOINT statements from the batch; the library handles the transaction when a mode is given.
  2. If you need manual transaction control, call batch() without a mode (no wrap) or use execute() for the transaction statements separately.
  3. Split the script so each batch contains only DML/DDL statements.

Example fix

// before
await db.batch(["BEGIN", "INSERT INTO t VALUES (1)", "COMMIT"], "write");
// after
await db.batch(["INSERT INTO t VALUES (1)"], "write"); // library wraps in BEGIN/COMMIT
Defensive patterns

Strategy: validation

Validate before calling

const TXN_KEYWORDS = new Set(["BEGIN","COMMIT","END","ROLLBACK","SAVEPOINT","RELEASE"]);
const assertNoTxnControl = (statements) => statements.forEach((s, i) => {
  const sql = typeof s === "string" ? s : s.sql;
  const kw = sql.trim().split(/\s+/)[0]?.toUpperCase();
  if (TXN_KEYWORDS.has(kw)) throw new Error(`statement ${i}: ${kw} not allowed in atomic batch`);
});

Type guard

const isTransactionControl = (sql: string): boolean =>
  TXN_KEYWORDS.has(sql.trim().split(/\s+/)[0]?.toUpperCase() ?? "");

Try / catch

try {
  await db.batch(statements, "write");
} catch (e) {
  if (/not allowed in an atomic batch/.test(e.message ?? "")) {
    // strip txn statements and retry without a mode, or drop them
    await db.batch(statements.filter(s => !isTransactionControl(typeof s === 'string' ? s : s.sql)), "write");
  } else throw e;
}

Prevention

When it happens

Trigger: Calling batch(statements, 'write'|'read'|<BatchMode>) where a statement's SQL starts with BEGIN/COMMIT/END/ROLLBACK/SAVEPOINT/RELEASE and the library is not already inside a transaction (so it adds its own wrap).

Common situations: Copy-pasting a SQL script (with BEGIN...COMMIT) into an atomic batch; dynamically generated SQL that includes transaction statements; switching an existing execute() script to batch() without removing transaction statements.

Related errors


AI-assisted analysis of tursodatabase/turso@c1e5928725 (2026-08-31). Data as JSON: /api/errors/78d24b055924de89. Report an issue: GitHub.