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.setterView on GitHub (pinned to bad083fafb)
Solutions
- Use cursor.executescript(sql) for any multi-statement script — it iterates prepare_first until exhausted
- Split the script into single statements and call execute() per statement when you need per-statement results or error attribution
- 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
- Default to executescript() for anything read from a file or built by concatenation
- Reserve execute() for single, known statements with parameter binding
- Note that a single trailing semicolon is fine — only a second real statement triggers it
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
- no SQL statements to execute
- executemany() requires a single DML statement
- Sequence execution failed
- Cannot operate on a closed cursor
- Named parameters are not supported; use positional parameter
AI-assisted analysis of tursodatabase/turso@bad083fafb (2026-08-16).
Data as JSON: /api/errors/590c6784b9b12ccf.
Report an issue: GitHub.