tursodatabase/turso · error · ProgrammingError

You can only execute one statement at a time

Error message

You can only execute one statement at a time

What it means

Cursor.execute() prepares exactly one statement: after preparing the first, _raise_if_multiple_statements prepares the remainder and raises ProgrammingError if another real statement exists. This matches stdlib sqlite3, whose execute() also rejects multi-statement strings. A trailing semicolon, whitespace, or comments after the single statement are skipped and do not trigger it.

Source

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

        has_cols = len(columns) > 0
        return _Prepared(stmt=stmt, tail_index=tail_idx, has_columns=has_cols, column_names=columns)

    def _raise_if_multiple_statements(self, sql: str, tail_index: int) -> None:
        """
        Ensure there is no second statement after the first one; otherwise raise ProgrammingError.
        """
        # Skip any trailing whitespace/comments after tail_index, and check if another statement exists.
        rest = sql[tail_index:]
        try:
            nxt = self._conn.prepare_first(rest)
            if nxt is not None:
                # Clean-up the prepared second statement immediately
                second_stmt, _ = nxt
                try:
                    second_stmt.finalize()
                except Exception:
                    pass
                raise ProgrammingError("You can only execute one statement at a time")
        except ProgrammingError:
            raise
        except Exception as exc:  # noqa: BLE001
            raise _map_turso_exception(exc)

    @property
    def in_transaction(self) -> bool:
        try:
            return not self._conn.get_auto_commit()
        except Exception as exc:  # noqa: BLE001
            raise _map_turso_exception(exc)

    # Provide autocommit property for sqlite3-like API (optional)
    @property
    def autocommit(self) -> object | bool:
        return self._autocommit_mode

    @autocommit.setter

View on GitHub (pinned to bad083fafb)

Solutions

  1. Use cursor.executescript(sql) for any multi-statement script — it iterates prepare_first until exhausted
  2. Split the script into single statements and call execute() per statement when you need per-statement results or error attribution
  3. If you intended one statement, remove the accidental second one (often a duplicated line or an embedded ';' inside a string literal built by hand)

Example fix

# before
cur.execute("""
    CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT);
    CREATE INDEX idx_users_name ON users(name);
""")

# after
cur.executescript("""
    CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT);
    CREATE INDEX idx_users_name ON users(name);
""")
Defensive patterns

Strategy: validation

Validate before calling

def split_statements(sql: str) -> list[str]:
    """Naive splitter for scripts without ';' inside strings — use executescript otherwise."""
    return [s for s in sql.split(";") if s.strip()]

stmts = split_statements(sql)
if len(stmts) > 1:
    cur.executescript(sql)      # multi-statement path
else:
    cur.execute(sql)

Try / catch

try:
    cur.execute(sql)
except ProgrammingError as e:
    if "one statement at a time" in str(e):
        cur.executescript(sql)  # intentional fallback to the script path
    else:
        raise

Prevention

When it happens

Trigger: `cur.execute("INSERT INTO t VALUES (1); INSERT INTO t VALUES (2);")`, `cur.execute("CREATE TABLE a(...); CREATE INDEX ...")`, or feeding a multi-statement .sql dump/migration through execute() instead of executescript().

Common situations: Running schema migrations or seed scripts built by concatenating statements; code ported from drivers that permit multi-statement execute (e.g. some MySQL/postgres configs); iterating over a file read as one string.

Related errors


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