{"record":{"id":"44503c956087fec8","repo":"tursodatabase/turso","slug":"batch-statement-index-failed-keyword-is-not-44503c","errorCode":null,"errorMessage":"batch statement ${index} failed: ${keyword} is not allowed in an atomic batch","messagePattern":"batch statement (.+?) failed: (.+?) is not allowed in an atomic batch","errorType":"validation","errorClass":"DatabaseError","httpStatus":null,"severity":"error","filePath":"serverless/javascript/src/session.ts","lineNumber":539,"sourceCode":"        throw batchInputError(index, e?.message ?? String(e));\n      }\n      return {\n        stmt: {\n          sql: statement.sql,\n          args: encodedArgs.args,\n          named_args: encodedArgs.namedArgs,\n          want_rows: true,\n        },\n      };\n    });\n\n    if (mode !== undefined) {\n      for (let index = 0; index < statements.length; index++) {\n        const statement = statements[index];\n        const sql = typeof statement === 'string' ? statement : statement.sql;\n        const keyword = firstSqlKeyword(sql);\n        if (keyword !== undefined && TRANSACTION_CONTROL_KEYWORDS.has(keyword)) {\n          throw batchInputError(index, `${keyword} is not allowed in an atomic batch`);\n        }\n      }\n    }\n\n    let steps: BatchStep[];\n    let firstUserStepIdx = 0;\n    let beginIdx = -1;\n    let commitIdx = -1;\n    let rollbackIdx = -1;\n    if (mode === undefined) {\n      // Each statement is gated on its predecessor succeeding, so\n      // execution stops at the first failure (matching the Rust and\n      // Python drivers and the `sequence` request).\n      steps = userSteps.map((step, i) =>\n        i === 0 ? step : { ...step, condition: { type: 'ok' as const, step: i - 1 } },\n      );\n    } else {\n      // Atomic batch: BEGIN <mode>, then each user step gated on its","sourceCodeStart":521,"sourceCodeEnd":557,"githubUrl":"https://github.com/tursodatabase/turso/blob/c1e59287258d99b309e362a63f48822256e2f65f/serverless/javascript/src/session.ts#L521-L557","documentation":"session.batch() with an explicit BatchMode runs atomically: the library emits its own BEGIN <mode> ... COMMIT ... ROLLBACK steps. Statements starting with a transaction-control keyword (BEGIN, COMMIT, ROLLBACK, SAVEPOINT, RELEASE, END) would corrupt that structure, so they are rejected before the request is sent, reported as batchInputError(index, ...) naming the keyword.","triggerScenarios":"Calling session.batch(statements, mode) where any statement string (or statement.sql) begins with a TRANSACTION_CONTROL_KEYWORDS entry — e.g. a SQL dump containing BEGIN;...COMMIT; passed as batch statements with mode 'write'.","commonSituations":"Importing .sql dump files via batch(); converting imperative execute('BEGIN') code to batch with a mode; template-generated SQL that includes transaction frames; applying a mode parameter where previously none was passed.","solutions":["Strip transaction-control statements; pass the batch mode and let the library manage BEGIN/COMMIT/ROLLBACK.","If manual control is required, omit the mode argument so no atomic wrap is added, or send those statements via execute().","Pre-process SQL scripts to split on statement and filter out keywords before batching."],"exampleFix":"// before\nawait session.batch([\"BEGIN\", \"UPDATE t SET x=1\", \"COMMIT\"], \"write\");\n// after\nawait session.batch([\"UPDATE t SET x=1\"], \"write\");","handlingStrategy":"validation","validationCode":"const TXN = new Set([\"BEGIN\",\"COMMIT\",\"END\",\"ROLLBACK\",\"SAVEPOINT\",\"RELEASE\"]);\nconst rejectTxnInBatch = (statements) => statements.forEach((s, i) => {\n  const sql = typeof s === 'string' ? s : s.sql;\n  const kw = sql.trim().split(/\\s+/)[0]?.toUpperCase();\n  if (TXN.has(kw)) throw new Error(`statement ${i}: ${kw} not allowed in atomic batch`);\n});","typeGuard":"const isTxnControl = (sql: string): boolean =>\n  TXN.has(sql.trim().split(/\\s+/)[0]?.toUpperCase() ?? \"\");","tryCatchPattern":"try {\n  await session.batch(statements, mode);\n} catch (e) {\n  if (/not allowed in an atomic batch/.test(e?.message ?? \"\")) {\n    const filtered = statements.filter(s => !isTxnControl(typeof s === 'string' ? s : s.sql));\n    return session.batch(filtered, mode);\n  }\n  throw e;\n}","preventionTips":["Pass a BatchMode instead of writing BEGIN/COMMIT yourself","Strip transaction keywords from SQL dumps before batching","Use execute() for statements that must manage their own transactions","Add a pre-flight keyword check when building statements dynamically"],"tags":["javascript","serverless","batch","transactions"],"backgroundTag":"transaction-control-in-batch","analyzedSha":"c1e59287258d99b309e362a63f48822256e2f65f","analyzedAt":"2026-08-31T11:17:35.598Z","contentChangedAt":"2026-08-31T11:17:35.598Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}