tursodatabase/turso · error · ProgrammingError
batch statement {index} must be a SQL string or a (sql, para
Error message
batch statement {index} must be a SQL string or a (sql, parameters) pair What it means
Serverless Python SDK: each batch statement must be a SQL string or a 2-item (sql, parameters) sequence whose first element is a str. _normalize_batch_statements raises ProgrammingError for anything else, before the HTTP pipeline call is issued.
Source
Thrown at serverless/python/turso_serverless/connection.py:96
def _normalize_batch_statements(
statements: Iterable[Any],
) -> list[tuple[str, Sequence[Any] | Mapping[str, Any]]]:
"""Normalize batch input to (sql, parameters) pairs. Accepts SQL
strings and (sql, parameters) pairs, like the JavaScript driver."""
normalized = []
for index, statement in enumerate(statements):
if isinstance(statement, str):
normalized.append((statement, ()))
continue
if (
isinstance(statement, (tuple, list))
and len(statement) == 2
and isinstance(statement[0], str)
):
normalized.append((statement[0], statement[1]))
continue
raise ProgrammingError(
f"batch statement {index} must be a SQL string or a (sql, parameters) pair"
)
return normalized
def _reject_transaction_control_statements(
statements: list[tuple[str, Sequence[Any] | Mapping[str, Any]]],
) -> None:
for index, (sql, _parameters) in enumerate(statements):
if _first_keyword(sql) in _TRANSACTION_CONTROL_KEYWORDS:
error = ProgrammingError(
"transaction-control SQL is not allowed in a batch with a transaction mode"
)
raise _batch_statement_error(index, error) from error
def _batch_statement_error(
index: int,View on GitHub (pinned to c1e5928725)
Solutions
- Normalize each item to str or (str, params) before calling batch().
- Convert dicts: {"sql": s, "params": p} -> (s, p).
- Drop extra tuple elements; only (sql, parameters) pairs are accepted.
- The message includes the item index — log the list and inspect that exact element.
Example fix
# before
conn.batch([Query("SELECT 1"), ("SELECT ?", (1,), 2)])
# after
conn.batch(["SELECT 1", ("SELECT ?", (1,))]) Defensive patterns
Strategy: validation
Validate before calling
def normalize(items):
out = []
for i, s in enumerate(items):
if isinstance(s, str):
out.append(s)
elif isinstance(s, (tuple, list)) and len(s) == 2 and isinstance(s[0], str):
out.append((s[0], s[1]))
else:
raise TypeError(f"statement {i} must be str or (sql, params)")
return out Type guard
def is_batch_statement(s: object) -> bool:
return isinstance(s, str) or (
isinstance(s, (tuple, list)) and len(s) == 2 and isinstance(s[0], str)
) Try / catch
try:
await conn.batch(stmts)
except ProgrammingError as e:
log.error("malformed batch input: %s", e)
await conn.batch(normalize(stmts)) Prevention
- Normalize statements to str or (sql, params) pairs before every batch call.
- Avoid passing ORM/query-builder objects straight through.
- Keep statement tuples strictly 2-element.
- Add a boundary test mirroring _normalize_batch_statements.
When it happens
Trigger: batch([...]) with a dict entry, a 3-tuple, an entry whose first element isn't a string, None entries, or objects like parsed Statement dataclasses not converted to the expected shape.
Common situations: Reusing statement-building code written for other SDKs (dict-based steps), passing ORM/query-builder objects directly, and code that appends metadata to statement tuples.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- batch mode must be one of {sorted(_BATCH_MODES)} or None, go
- batch mode must be one of {sorted(_BATCH_MODES)} or None, go
- batch statement {index} must be a SQL string or a (sql, para
- Expected first argument to be an array of statements
- batch response does not have one result and one error per st
AI-assisted analysis of tursodatabase/turso@c1e5928725 (2026-08-31).
Data as JSON: /api/errors/d99bc08278050475.
Report an issue: GitHub.