{"record":{"id":"d99bc08278050475","repo":"tursodatabase/turso","slug":"batch-statement-index-must-be-a-sql-string-or-a-d99bc0","errorCode":null,"errorMessage":"batch statement {index} must be a SQL string or a (sql, parameters) pair","messagePattern":"batch statement (.+?) must be a SQL string or a \\(sql, parameters\\) pair","errorType":"validation","errorClass":"ProgrammingError","httpStatus":null,"severity":"error","filePath":"serverless/python/turso_serverless/connection.py","lineNumber":96,"sourceCode":"\ndef _normalize_batch_statements(\n    statements: Iterable[Any],\n) -> list[tuple[str, Sequence[Any] | Mapping[str, Any]]]:\n    \"\"\"Normalize batch input to (sql, parameters) pairs. Accepts SQL\n    strings and (sql, parameters) pairs, like the JavaScript driver.\"\"\"\n    normalized = []\n    for index, statement in enumerate(statements):\n        if isinstance(statement, str):\n            normalized.append((statement, ()))\n            continue\n        if (\n            isinstance(statement, (tuple, list))\n            and len(statement) == 2\n            and isinstance(statement[0], str)\n        ):\n            normalized.append((statement[0], statement[1]))\n            continue\n        raise ProgrammingError(\n            f\"batch statement {index} must be a SQL string or a (sql, parameters) pair\"\n        )\n    return normalized\n\n\ndef _reject_transaction_control_statements(\n    statements: list[tuple[str, Sequence[Any] | Mapping[str, Any]]],\n) -> None:\n    for index, (sql, _parameters) in enumerate(statements):\n        if _first_keyword(sql) in _TRANSACTION_CONTROL_KEYWORDS:\n            error = ProgrammingError(\n                \"transaction-control SQL is not allowed in a batch with a transaction mode\"\n            )\n            raise _batch_statement_error(index, error) from error\n\n\ndef _batch_statement_error(\n    index: int,","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/tursodatabase/turso/blob/c1e59287258d99b309e362a63f48822256e2f65f/serverless/python/turso_serverless/connection.py#L78-L114","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before\nconn.batch([Query(\"SELECT 1\"), (\"SELECT ?\", (1,), 2)])\n# after\nconn.batch([\"SELECT 1\", (\"SELECT ?\", (1,))])","handlingStrategy":"validation","validationCode":"def normalize(items):\n    out = []\n    for i, s in enumerate(items):\n        if isinstance(s, str):\n            out.append(s)\n        elif isinstance(s, (tuple, list)) and len(s) == 2 and isinstance(s[0], str):\n            out.append((s[0], s[1]))\n        else:\n            raise TypeError(f\"statement {i} must be str or (sql, params)\")\n    return out","typeGuard":"def is_batch_statement(s: object) -> bool:\n    return isinstance(s, str) or (\n        isinstance(s, (tuple, list)) and len(s) == 2 and isinstance(s[0], str)\n    )","tryCatchPattern":"try:\n    await conn.batch(stmts)\nexcept ProgrammingError as e:\n    log.error(\"malformed batch input: %s\", e)\n    await conn.batch(normalize(stmts))","preventionTips":["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."],"tags":["python","api-misuse","validation","batch","serverless"],"backgroundTag":"invalid-argument-value","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"}