{"record":{"id":"060a796f219161cb","repo":"tursodatabase/turso","slug":"no-sql-in-batch-step","errorCode":null,"errorMessage":"No SQL in batch step","messagePattern":"No SQL in batch step","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"cli/sync_server.rs","lineNumber":431,"sourceCode":"            }\n            BatchCond::And(list) => list\n                .conds\n                .iter()\n                .all(|c| Self::evaluate_condition(c, step_results, step_errors, conn)),\n            BatchCond::Or(list) => list\n                .conds\n                .iter()\n                .any(|c| Self::evaluate_condition(c, step_results, step_errors, conn)),\n            BatchCond::IsAutocommit {} => conn.get_auto_commit(),\n        }\n    }\n\n    fn execute_batch_step(&self, conn: &Arc<Connection>, step: &BatchStep) -> Result<StmtResult> {\n        let sql = step\n            .stmt\n            .sql\n            .as_ref()\n            .ok_or_else(|| anyhow!(\"No SQL in batch step\"))?;\n\n        debug!(\"Executing batch step SQL: {}\", sql);\n\n        let mut stmt = conn.prepare(sql)?;\n\n        for (i, arg) in step.stmt.args.iter().enumerate() {\n            let core_value = convert_value_to_core(arg);\n            stmt.bind_at(std::num::NonZero::new(i + 1).unwrap(), core_value)?;\n        }\n\n        let want_rows = step.stmt.want_rows.unwrap_or(true);\n\n        if want_rows {\n            let rows = stmt.run_collect_rows()?;\n\n            let cols: Vec<Col> = (0..stmt.num_columns())\n                .map(|i| Col {\n                    name: Some(stmt.get_column_name(i).to_string()),","sourceCodeStart":413,"sourceCodeEnd":449,"githubUrl":"https://github.com/tursodatabase/turso/blob/bad083fafbefdeae9a42ec19bdaaad8918dcf411/cli/sync_server.rs#L413-L449","documentation":"Thrown by execute_batch_step when a batch step in a POST /v2/pipeline Batch request has no `sql` on its statement. The wire type makes stmt.sql optional (so steps could theoretically reference prepared statements), but this sync server only executes literal SQL. Unlike most errors in this file it does not become an HTTP 500: execute_batch catches it and returns it inside a 200 response as that step's entry in step_errors with code BATCH_STEP_ERROR.","triggerScenarios":"POST /v2/pipeline with StreamRequest::Batch where a step's `stmt` object omits `sql` or sends only `args`/`want_rows`; or a client written for the sqld/Hrana v2 API that sends named/stored statements, which cli/sync_server.rs does not implement.","commonSituations":"Hand-written JSON pipeline payloads during debugging; porting a client from the production sqld HTTP API that relies on named statements; serde deserializers that default the sql field to None.","solutions":["Set a non-empty \"sql\" string on every step.stmt in the batch request body.","Replace named/stored statement references with inline SQL literals when targeting this server.","Find the failing step by scanning the 200 response's step_errors array for code=BATCH_STEP_ERROR and matching the message.","If stored statements are required, run a server that implements them instead of cli/sync_server.rs."],"exampleFix":"// before\n{\"requests\":[{\"type\":\"batch\",\"batch\":{\"steps\":[{\"stmt\":{\"args\":[{\"type\":\"integer\",\"value\":1}]}}]}}]}\n\n// after\n{\"requests\":[{\"type\":\"batch\",\"batch\":{\"steps\":[{\"stmt\":{\"sql\":\"SELECT * FROM t WHERE id = ?\",\"args\":[{\"type\":\"integer\",\"value\":1}]}}]}}]}","handlingStrategy":"validation","validationCode":"fn validate_batch_steps(req: &BatchStreamReq) -> Result<(), String> {\n    for (i, step) in req.batch.steps.iter().enumerate() {\n        let has_sql = step.stmt.sql.as_deref().is_some_and(|s| !s.trim().is_empty());\n        if !has_sql {\n            return Err(format!(\"batch step {i} has no sql\"));\n        }\n    }\n    Ok(()\n)}","typeGuard":"function stepHasSql(step: BatchStep | undefined): step is BatchStep & { stmt: { sql: string } } {\n  return typeof step?.stmt?.sql === \"string\" && step.stmt.sql.trim().length > 0;\n}","tryCatchPattern":"The endpoint returns HTTP 200 even on step failure: after each batch, iterate step_errors and treat any entry with code=BATCH_STEP_ERROR (message 'No SQL in batch step') as a failed step, mapping its array index back to the request step that lacks sql.","preventionTips":["Set stmt.sql on every batch step; this server supports only literal SQL, no named or stored statements.","Validate the pipeline request body locally with the same schema before POSTing to /v2/pipeline.","Treat any non-null step_errors entry as a failed step and stop processing dependent conditional steps."],"tags":["sync","hrana","batch","pipeline","request-validation"],"backgroundTag":"missing-required-field","analyzedSha":"bad083fafbefdeae9a42ec19bdaaad8918dcf411","analyzedAt":"2026-08-16T23:12:11.798Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}