tursodatabase/turso · error · ProgrammingError

Named parameters are not supported; use positional…

Error message

Named parameters are not supported; use positional parameters with '?'

What it means

Cursor._to_positional_params() is the static helper that converts a parameter argument into a positional tuple; its Mapping branch raises ProgrammingError to signal that named placeholders cannot be expressed positionally. In the current code, execute() and executemany() route Mapping arguments through _bind_params, which dispatches dicts to _bind_named_params() and binds them as :name/@name/$name parameters. The guard only fires when the private helper itself is handed a mapping - i.e. code that pre-converts parameters instead of passing the dict straight to execute().

Solutions

  1. Pass the mapping directly to cur.execute(sql, {'name': value}) - named parameters are supported through _bind_named_params
  2. If you must pre-convert, build a tuple yourself ordered to match the '?' placeholders
  3. Upgrade to a current pyturso release if an old execute() raised this for plain dicts

Example fix

# before
params = Cursor._to_positional_params({"id": 1})  # ProgrammingError
cur.execute("SELECT * FROM t WHERE id = :id", params)

# after
cur.execute("SELECT * FROM t WHERE id = :id", {"id": 1})
Defensive patterns

Strategy: validation

Validate before calling

from collections.abc import Mapping

def normalize_params(parameters):
    # Pass mappings straight through; execute() binds them as named params.
    if isinstance(parameters, Mapping):
        return parameters
    return tuple(parameters or ())

cur.execute(sql, normalize_params(user_params))

Try / catch

from turso.lib import ProgrammingError

try:
    cur.execute(sql, params)
except ProgrammingError as e:
    if "Named parameters" in str(e):
        placeholders = ", ".join("?" for _ in range(len(params)))
        cur.execute(sql.replace(":name", "?"), tuple(params.values()))
    else:
        raise

Prevention

When it happens

Trigger: Calling Cursor._to_positional_params({...}) directly (private API misuse); older pyturso versions where execute() routed through this helper before _bind_named_params() was added; helper code that tries to normalize parameters into a tuple before calling execute().

Common situations: A wrapper layer that 'normalizes' DB-API parameters before passing them down; upgrading a codebase that worked around an old pyturso build that did not accept dicts.

Related errors


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

Appendix: source

Thrown at bindings/python/turso/lib.py:842

    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 c1e5928725)