tursodatabase/turso · error · DatabaseError

SQL execution failed

Error message

SQL execution failed

What it means

DatabaseError thrown from Statement.iterate() when the cursor stream delivers a step_error or fatal error entry while rows are being yielded from the async generator. Unlike the buffered session path, the error surfaces mid-iteration — some rows may already have been consumed by your for-await loop before the throw; the literal text is the fallback when the entry has no error.message.

Source

Thrown at serverless/javascript/src/statement.ts:298

          }
          break;
        case 'row':
          if (entry.row) {
            const decodedRow = entry.row.map(value => decodeValue(value, this.safeIntegerMode));
            if (this.presentationMode === 'pluck') {
              // In pluck mode, yield only the first column value
              yield decodedRow[0];
            } else if (this.presentationMode === 'raw') {
              // In raw mode, yield arrays of values
              yield decodedRow;
            } else {
              yield createExpandedRow(decodedRow, columns);
            }
          }
          break;
        case 'step_error':
        case 'error':
          throw new DatabaseError(entry.error?.message || 'SQL execution failed');
      }
    }
  }

}

View on GitHub (pinned to bad083fafb)

Solutions

  1. Read the server message and DatabaseError.code to find the real cause; test the same SQL via all() to check whether it fails deterministically
  2. Handle partial results explicitly — iterate() may yield rows before throwing, so track what was processed
  3. For long queries, page through with LIMIT/keyset pagination instead of one giant stream

Example fix

// before
for await (const row of stmt.iterate()) {
  await process(row); // error mid-stream aborts with rows already handled
}

// after
let handled = 0;
try {
  for await (const row of stmt.iterate()) { await process(row); handled++; }
} catch (e) {
  if (e instanceof Error && e.name === "DatabaseError") {
    await resumeFromCheckpoint(handled); // explicit partial-result policy
  } else throw e;
}
Defensive patterns

Strategy: try-catch

Type guard

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

Try / catch

let handled = 0;
try {
  for await (const row of stmt.iterate(args)) {
    await process(row);
    handled++;
  }
} catch (e) {
  if (e instanceof Error && e.name === "DatabaseError") {
    // `handled` rows were already processed — resume from a checkpoint,
    // rerun idempotently, or surface a partial-result error
    await resumeFromCheckpoint(handled);
  } else throw e;
}

Prevention

When it happens

Trigger: for await (const row of stmt.iterate(args)) where the query fails server-side partway through the stream (lock timeout, server restart, kill); message-less error entries producing the fallback wording; errors arriving after the first rows were already yielded and processed.

Common situations: Long-running scans interrupted by server restarts or timeouts; processing pipelines that assume all-or-nothing row delivery; iterating while other connections write conflicting data.

Related errors


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