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
            $name

View on GitHub (pinned to bad083fafb)

Solutions

  1. For `?`-style SQL pass a sequence: cur.execute("SELECT * FROM t WHERE id = ?", (1,))
  2. For dict parameters keep named placeholders, which the public execute() supports: cur.execute("SELECT * FROM t WHERE id = :id", {"id": 1})
  3. 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

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


AI-assisted analysis of tursodatabase/turso@bad083fafb (2026-08-16). Data as JSON: /api/errors/fa55229498a8b442. Report an issue: GitHub.