{"record":{"id":"0fd4f04aa3eddbe4","repo":"tursodatabase/turso","slug":"batch-statement-index-must-be-a-sql-string-or-a","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":"bindings/python/turso/lib.py","lineNumber":249,"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.\"\"\"\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":231,"sourceCodeEnd":267,"githubUrl":"https://github.com/tursodatabase/turso/blob/c1e59287258d99b309e362a63f48822256e2f65f/bindings/python/turso/lib.py#L231-L267","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before\nconn.batch([{\"sql\": \"SELECT 1\"}, (\"SELECT ?\", (1,), \"extra\")])\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    conn.batch(stmts)\nexcept ProgrammingError as e:\n    log.error(\"malformed batch input: %s\", e)\n    conn.batch(normalize(stmts))","preventionTips":["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."],"tags":["python","api-misuse","validation","batch"],"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-14T00:17:10.932Z"}