Skip to main content

Sync and Async

milvusql ships both from the start — not a sync client with async bolted on later. Both are built on the same translate.ast_to_pymilvus.build_call() dispatch table and the same error-translation layer; the only thing that differs between them is two lines at the call site (await, or not).

Why async is a separate surface, not async def execute()

PEP 249 has no async variant, by definition — Cursor.execute() is a synchronous method, full stop. milvusql.aio is a deliberately separate, explicitly asyncio-native pair of classes (AsyncConnection, AsyncCursor), not a second personality bolted onto Cursor.

Sync

import milvusql

conn = milvusql.connect(uri="./items.db")
cur = conn.cursor()
cur.execute("SELECT id FROM items LIMIT 5")
rows = cur.fetchall()
conn.close()

Backed by pymilvus.MilvusClient.

Async

from milvusql import aio

conn = aio.connect(uri="./items.db")
cur = conn.cursor()
await cur.execute("SELECT id FROM items LIMIT 5")
rows = await cur.fetchall()
await conn.close()

Backed by pymilvus.AsyncMilvusClient — native asyncio (available since pymilvus 2.5.3), not a thread-pool wrapper around the sync client. For a client whose only job is round-tripping gRPC calls to a network service, that's a real, already-common shape (the same reason psycopg and asyncpg both exist for Postgres) — a natural fit for FastAPI/RAG-style backends.

CREATE INDEX against Milvus Lite, over aio

AsyncMilvusClient.create_index waits for completion via an AllocTimestamp RPC that Milvus Lite's async gRPC server doesn't implement — confirmed directly; the sync server handles the equivalent call fine. CREATE INDEX over aio currently only works against a real Milvus server, not Milvus Lite. Everything else on this page still holds unchanged.

What's shared, concretely

PieceShared between sync and async?
translate.ast_to_pymilvus.build_call() — AST → "what to call"Yes, verbatim
dbapi.errors.translate() — exception mappingYes, verbatim
The sqlglot.parse_one() result cacheYes, verbatim
Connection/AsyncConnection, Cursor/AsyncCursor classesNo — one sync, one async, same shape

build_call() never calls the network-touching pymilvus method itself — it returns a small Call(method, kwargs, postprocess, then) describing what to call (then drives the second RPC UPDATE needs, iterator paging, and the relational engine's per-collection chain — both cursors loop on it). Cursor.execute() calls it directly; AsyncCursor.execute() awaits it. Neither implementation needs to know the other exists.