-
Notifications
You must be signed in to change notification settings - Fork 76
Description
Bug description
query() always returns response["result"][0]["result"] — only the first statement's result. For multi-statement queries and BEGIN TRANSACTION blocks, all results after the first statement are silently discarded.
This causes silent data loss when using transactions: the server executes all statements (mutations apply), but the caller receives None or an empty result because [0] points to the BEGIN TRANSACTION acknowledgment (which has no result).
Reproduction
import asyncio
from surrealdb import AsyncSurreal
async def main():
db = AsyncSurreal("ws://127.0.0.1:8000")
await db.connect()
await db.use("test", "test")
await db.signin({"username": "root", "password": "root"})
# Setup
await db.query("DELETE test_tbl")
await db.query("CREATE test_tbl:1 SET name = 'alice', status = 'pending'")
# Multi-statement query — only first result returned
result = await db.query("SELECT * FROM test_tbl; SELECT count() FROM test_tbl GROUP ALL")
print("Multi-statement:", result)
# Expected: both results
# Actual: only the SELECT * result (first statement)
# Transaction — returns None (BEGIN has no result)
result = await db.query(
'BEGIN TRANSACTION;'
' LET $rec = (SELECT * FROM test_tbl:1);'
' UPDATE test_tbl:1 SET status = "running";'
' COMMIT TRANSACTION;'
)
print("Transaction:", result)
# Expected: the UPDATE result
# Actual: None (response["result"][0] is the BEGIN acknowledgment)
# Verify the UPDATE did execute server-side
check = await db.query("SELECT * FROM test_tbl:1")
print("Server state:", check)
# Shows status = "running" — mutation applied, but caller never saw the result
await db.close()
asyncio.run(main())Root cause
return response["result"][0]["result"] # hard-coded [0]The server returns N result objects (one per statement), but the SDK only extracts the first. The same pattern exists in blocking_ws.py.
Impact
This is a silent failure — no error is raised, no warning logged. The server correctly executes all statements and returns all results, but the SDK discards everything after index 0. Users who rely on query() return values for transactions or multi-statement queries will get phantom writes: mutations apply server-side but the caller sees None.
In our case, this caused a job queue to leak thousands of "running" jobs that were never processed — the claim_next_job transaction successfully set status = "running" in the DB, but returned None to the worker, which assumed no job was available.
Suggested fix
query() should return all results for multi-statement queries. Options:
- Return a list when the query contains multiple statements (breaking change to return type)
- Return the last non-null result (least surprise, matches SQL clients)
- At minimum, document this limitation clearly and recommend
query_raw()for multi-statement use
Environment
- Python SDK:
mainbranch @1ff4470e(also verified on currentmain) - SurrealDB server: v3.0.0-beta.4
- Python: 3.13+/3.14