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
Each entry passed to Connection.batch() must be either a plain SQL string or a 2-item (sql, parameters) tuple/list whose first element is a string. _normalize_batch_statements raises this ProgrammingError for any item that doesn't fit those shapes. It fails fast client-side, before any network or native call.
Source
Thrown at bindings/python/turso/lib.py:249
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."""
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
- Ensure every item is either a str or a 2-tuple/list (sql_string, parameters).
- Convert dict-style statements: {"sql": s, "params": p} -> (s, p).
- Trim extra elements from tuples; parameters must be the single second element.
- Print/type-check the offending item at the given index in the message; the error names the exact index.
Example fix
# before
conn.batch([{"sql": "SELECT 1"}, ("SELECT ?", (1,), "extra")])
# 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:
conn.batch(stmts)
except ProgrammingError as e:
log.error("malformed batch input: %s", e)
conn.batch(normalize(stmts)) Prevention
- Keep statements as str or (sql, params) 2-tuples end to end.
- Convert query-builder/ORM objects to SQL strings before batching.
- Never append extra metadata elements to statement tuples.
- Unit-test batch inputs with the same normalization the SDK applies.
When it happens
Trigger: batch([...]) with items like a dict ({'sql': ...}), a 3-element tuple, a tuple whose first element is not a string (e.g. (123, params)), a bare int/None, or a (sql, params, extra) triple.
Common situations: Migrating from drivers that accept dicts for statements, accidentally passing Cursor.execute-style kwargs, building statements programmatically and appending an extra element, or passing parameter objects instead of strings.
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
- SQL command cannot be null
AI-assisted analysis of tursodatabase/turso@c1e5928725 (2026-08-31).
Data as JSON: /api/errors/0fd4f04aa3eddbe4.
Report an issue: GitHub.