tursodatabase/turso · error · DatabaseError

Batch execution failed

Error message

Batch execution failed

What it means

DatabaseError thrown from Session.batch() when a step of a batch fails on the server. In atomic mode the first error from BEGIN, any user step, or COMMIT is captured (deferredError) and re-thrown after the stream drains — so ROLLBACK and the trailing autocommit probe are still observed — while a fatal stream 'error' entry throws immediately. The literal text only appears for message-less errors.

Source

Thrown at serverless/javascript/src/session.ts:625

            nextNonAtomicIdx = idx + 1;
          }
          currentResultIdx = undefined;
          break;
        }
        case 'step_error':
          // Capture the first error from BEGIN, any user step, or COMMIT
          // and keep draining so the trailing probe (and, in atomic mode,
          // ROLLBACK) is still observed. Errors on the synthetic ROLLBACK
          // step are suppressed — by the time it runs the transaction has
          // already been undone and surfacing a ROLLBACK error would mask
          // the real cause we already captured.
          if (deferredError === null && entry.step !== rollbackIdx) {
            deferredError = new DatabaseError(entry.error?.message || 'Batch execution failed', entry.error?.code);
          }
          currentResultIdx = undefined;
          break;
        case 'error':
          throw new DatabaseError(entry.error?.message || 'Batch execution failed', entry.error?.code);
      }
    }

    if (deferredError !== null) {
      throw deferredError;
    }

    return results;
  }

  /**
   * Execute a sequence of SQL statements separated by semicolons.
   * 
   * @param sql - SQL string containing multiple statements separated by semicolons
   * @returns Promise resolving when all statements are executed
   */
  async sequence(sql: string, queryOptions?: QueryOptions): Promise<void> {
    const request: PipelineRequest = {

View on GitHub (pinned to bad083fafb)

Solutions

  1. Identify the failing statement from the server message/code and fix it or its data (ON CONFLICT, pre-validation)
  2. Decide atomicity deliberately: pass a mode ('immediate', 'deferred', ...) for all-or-nothing, omit it when partial progress is acceptable
  3. For constraint-heavy bulk loads, pre-check uniqueness/FK validity or use INSERT OR IGNORE/REPLACE explicitly

Example fix

// before
await db.batch([
  "INSERT INTO kv(k) VALUES ('a')",
  "INSERT INTO kv(k) VALUES ('a')", // UNIQUE violation -> Batch execution failed, atomic mode rolls back
]);

// after
await db.batch([
  "INSERT INTO kv(k) VALUES ('a')",
  "INSERT INTO kv(k) VALUES ('a') ON CONFLICT(k) DO NOTHING",
], "immediate");
Defensive patterns

Strategy: try-catch

Type guard

const isDatabaseError = (e: unknown): e is Error & { code?: string } =>
  e instanceof Error && e.name === "DatabaseError";

Try / catch

try {
  await db.batch(statements, "immediate");
} catch (e) {
  if (e instanceof Error && e.name === "DatabaseError") {
    // atomic mode: nothing was committed — safe to fix and rerun the batch.
    // non-atomic mode: earlier statements ARE committed — reconcile before rerun.
    console.error("batch failed:", (e as { code?: string }).code, e.message);
  }
  throw e;
}

Prevention

When it happens

Trigger: db.batch([...], 'immediate') where a mid-batch statement violates a constraint; any statement of a non-atomic batch failing; COMMIT itself failing in atomic mode (e.g. deferred foreign-key violations), after which earlier statements were rolled back.

Common situations: Bulk inserts hitting duplicate keys; batched migrations containing one bad statement; FK constraints enforced at COMMIT surprising 'atomic' batches; non-atomic batches leaving earlier statements committed after the failure (by design).

Related errors


AI-assisted analysis of tursodatabase/turso@bad083fafb (2026-08-16). Data as JSON: /api/errors/6c5c897b6b243f85. Report an issue: GitHub.