Overview
milvusql is the PEP 249 layer everything else in this project is built on. It owns three things:
- The DBAPI surface —
connect(),Cursor, the module-levelapilevel/threadsafety/paramstyleattributes every DBAPI module is expected to expose. - The dispatch table —
translate.ast_to_pymilvus, one function turning a parsed MilvusQL AST into "what to call on apymilvusclient." Shared, unchanged, by the syncCursorand the asyncaioclient. - The error hierarchy — the full PEP 249 exception tree, and the translation from
sqlglot/pymilvus/grpcexceptions onto it (see Errors).
Connecting
import milvusql
# Milvus Lite -- a local file, embedded, no server
conn = milvusql.connect(uri="./items.db")
# a real server
conn = milvusql.connect(
uri="http://localhost:19530",
token="root:Milvus", # or user="root", password="Milvus"
db_name="default",
)
Every keyword pymilvus.MilvusClient accepts passes straight through connect() — this layer
doesn't reinvent Milvus's own connection parameters.
Executing
cur = conn.cursor()
cur.execute("SELECT id FROM items WHERE category = :cat LIMIT 10", {"cat": "book"})
rows = cur.fetchall()
Cursor implements the usual PEP 249 surface: execute, executemany, fetchone, fetchmany,
fetchall, description, rowcount, lastrowid (populated from Milvus's auto_id-assigned
primary key after an INSERT — the standard sqlite3/MySQLdb convention), and iteration.
Loading a collection
search/query/hybrid_search need a loaded collection server-side. Rather than making every
caller issue LOAD TABLE by hand first, Cursor/AsyncCursor auto-LOAD a collection
transparently the first time a connection runs one of those three against it, then cache the
result — each collection loads at most once per connection. Explicit LOAD TABLE/RELEASE TABLE
are still available and useful for controlling replica count or warming a collection up ahead of
traffic; they're just no longer required before a plain SELECT will work. A collection still
needs an index before it's searchable at all — auto-LOAD doesn't create one.
What it does not do
- No real transactions.
commit()is a no-op (every mutation is already applied the momentpymilvusreturns);rollback()raisesNotSupportedError— Milvus has no multi-statement rollback, and a silent no-op here would read as "rolled back" to code that trusts it. - No
ALTER TABLEbeyondADD FIELD.DROP/ALTER COLUMN/MODIFYare rejected at parse time bysqlglot-milvus;RENAME TOparses fine but has no execution path and raisesNotSupportedErrorinmilvusql's own translate layer instead (see MilvusQL Concepts →ALTER TABLE).
Beyond a single collection
JOIN, GROUP BY, HAVING, subqueries, window functions, CTEs, set operations and correlated
EXISTS all execute too — translate.ast_to_pymilvus routes anything that needs more than one
Milvus read into a second dispatch table, translate.relational, which plans one read per
collection and evaluates the rest client-side with Polars. See
MilvusQL Concepts
for what's pushed to Milvus versus evaluated client-side, and what's still rejected by name
(WITH RECURSIVE, INTERSECT ALL/EXCEPT ALL, window frame clauses, ...).
Full-text search (TEXT columns, BM25_SCORE, MATCH ... AGAINST), ARRAY/JSON filter
functions, and introspection (SHOW TABLES/SHOW DATABASES, DESCRIBE, CREATE/DROP DATABASE,
USE, DROP INDEX) are ordinary statements through the same Cursor.execute() — nothing at the
DBAPI surface changes to support them. See MilvusQL Concepts for the
full language reference.
Next
- Sync and Async — the same dispatch table, two call-site layers
- Errors — the PEP 249 hierarchy and what maps to what
- Consistency Level — query-level vs. connection-level defaults