tursodatabase/turso · error · ProgrammingError
Named parameters are not supported; use positional parameter
Error message
Named parameters are not supported; use positional parameters with '?'
What it means
Cursor._to_positional_params raises ProgrammingError when it receives a Mapping (dict-style parameters), because the positional binding path only supports `?` placeholders fed by sequences. In the current code the public execute()/executemany() route Mapping parameters through _bind_named_params (which binds :name/@name/$name/?N placeholders), so this message comes from the positional conversion path — direct use of the private helper, or call paths/versions that bypass the named binder.
Source
Thrown at bindings/python/turso/lib.py:596
def _reset_last_result(self) -> None:
# Ensure any previous statement is finalized to not leak resources
if self._active_stmt is not None:
try:
self._active_stmt.finalize()
except Exception:
pass
self._active_stmt = None
self._active_has_rows = False
self._description = None
self._rowcount = -1
# Do not reset lastrowid here; sqlite3 preserves lastrowid until next insert.
@staticmethod
def _to_positional_params(parameters: Sequence[Any] | Mapping[str, Any]) -> tuple[Any, ...]:
if isinstance(parameters, Mapping):
# Named placeholders are not supported
raise ProgrammingError("Named parameters are not supported; use positional parameters with '?'")
if parameters is None:
return ()
if isinstance(parameters, tuple):
return parameters
# Convert arbitrary sequences to tuple efficiently
return tuple(parameters)
@staticmethod
def _bind_named_params(stmt: PyTursoStatement, parameters: Mapping[str, Any]) -> None:
"""
Bind mapping-style parameters to a prepared SQLite statement, emulating
the behavior of Python's ``sqlite3`` module for named parameters.
SQLite supports the following parameter syntaxes:
:name
@name
$nameView on GitHub (pinned to bad083fafb)
Solutions
- For `?`-style SQL pass a sequence: cur.execute("SELECT * FROM t WHERE id = ?", (1,))
- For dict parameters keep named placeholders, which the public execute() supports: cur.execute("SELECT * FROM t WHERE id = :id", {"id": 1})
- If you maintain a wrapper, route Mapping inputs to the named style and sequences to the positional style instead of forcing everything positional
Example fix
# before
cur.execute("SELECT * FROM t WHERE id = ?", {"id": 1}) # dict with ? placeholder
# after (positional)
cur.execute("SELECT * FROM t WHERE id = ?", (1,))
# after (named)
cur.execute("SELECT * FROM t WHERE id = :id", {"id": 1}) Defensive patterns
Strategy: validation
Validate before calling
import re
from collections.abc import Mapping
def execute_with_params(cur, sql: str, params):
"""Route dict params to named placeholders, sequences to positional."""
if isinstance(params, Mapping):
if not re.search(r"[:@$]\w+", sql):
raise ValueError("dict parameters require :name/@name/$name placeholders, not '?'")
return cur.execute(sql, params) Type guard
from collections.abc import Mapping, Sequence
def is_positional_params(params) -> bool:
"""True when params can feed '?' placeholders (a plain sequence, not a mapping)."""
return isinstance(params, Sequence) and not isinstance(params, (str, bytes)) and not isinstance(params, Mapping) Prevention
- Pick one placeholder style per call site: '?' with tuples/lists, or :name with dicts
- Public execute() binds named params via :name/@name/$name — keep dicts away from '?' SQL
- In wrapper libraries, branch on Mapping vs Sequence instead of forcing one path
When it happens
Trigger: Passing a dict to a binding path that only handles sequences, e.g. calling Cursor._to_positional_params({'id': 1}) directly, or helper code that forwards parameters into a sequence-only bind. With `?`-style SQL you must pass a tuple/list: execute("... WHERE id = ?", (1,)).
Common situations: Wrapper libraries that normalize parameters by routing everything through one positional binder; porting code that mixes dict parameters with ? placeholders; internal utilities calling private Cursor helpers.
Related errors
- no SQL statements to execute
- You can only execute one statement at a time
- Cannot operate on a closed cursor
- executemany() requires a single DML (INSERT/UPDATE/DELETE/RE
- Cannot operate on a closed connection
AI-assisted analysis of tursodatabase/turso@bad083fafb (2026-08-16).
Data as JSON: /api/errors/fa55229498a8b442.
Report an issue: GitHub.