SQL for Qdrant.
QQL is to Qdrant what SQL is to Postgres. One query for hybrid search, filters, and schema.
QUERY TEXT 'chest pain'
FROM medical
USING dense
WHERE department = 'cardio'
SHARD 'hospital-east'
LIMIT 5;Same query. Less JSON.
Switch REST JSON or the Python client. The QQL stays a few lines.
QUERY TEXT 'distributed consensus' FROM docsUSING HYBRID DENSE dense_vec SPARSE bm25_vec FUSION RRFWHERE status = 'active'LIMIT 10;{ "prefetch": [ { "query": { "nearest": "dense_vec" }, "using": "dense_vec", "filter": { "must": [{ "key": "status", "match": { "value": "active" } }] }, "limit": 100 }, { "query": { "nearest": "bm25_vec" }, "using": "bm25_vec", "filter": { "must": [{ "key": "status", "match": { "value": "active" } }] }, "limit": 100 } ], "query": { "fusion": "rrf" }, "limit": 10}client.query_points( collection_name="docs", prefetch=[ models.Prefetch( query=models.NearestQuery(nearest="dense_vec"), using="dense_vec", filter=models.Filter(must=[ models.FieldCondition( key="status", match=models.MatchValue(value="active"), ) ]), limit=100, ), models.Prefetch( query=models.NearestQuery(nearest="bm25_vec"), using="bm25_vec", filter=models.Filter(must=[ models.FieldCondition( key="status", match=models.MatchValue(value="active"), ) ]), limit=100, ), ], query=models.FusionQuery(fusion=models.Fusion.RRF), limit=10,)QUERY TEXT 'annual financial risks' FROM filingsWHERE department = 'finance'SHARD 'tenant-corp-99'LIMIT 5;{ "query": "<dense vector>", "filter": { "must": [ { "key": "department", "match": { "value": "finance" } } ] }, "limit": 5, "with_payload": true}
POST .../points/query?shard_key=tenant-corp-99client.query_points( collection_name="filings", shard_key_selector="tenant-corp-99", query=dense_embedding_vector, query_filter=models.Filter( must=[ models.FieldCondition( key="department", match=models.MatchValue(value="finance"), ), ] ), limit=5, with_payload=True,)QUERY TEXT 'industrial robotics' FROM productsWHERE (category = 'hardware' OR category = 'tools') AND price <= 1200.0 AND in_stock = trueLIMIT 20;{ "query": "<dense vector>", "filter": { "must": [ { "should": [ { "key": "category", "match": { "value": "hardware" } }, { "key": "category", "match": { "value": "tools" } } ] }, { "key": "price", "range": { "lte": 1200.0 } }, { "key": "in_stock", "match": { "value": true } } ] }, "limit": 20}client.query_points( collection_name="products", query=product_query_vector, query_filter=models.Filter( must=[ models.Filter(should=[ models.FieldCondition( key="category", match=models.MatchValue(value="hardware"), ), models.FieldCondition( key="category", match=models.MatchValue(value="tools"), ), ]), models.FieldCondition( key="price", range=models.Range(lte=1200.0), ), models.FieldCondition( key="in_stock", match=models.MatchValue(value=True), ), ] ), limit=20,)What you stop writing.
Filter trees
A WHERE clause instead of nested must/should JSON in every call.
Tenant copies
inject_filter rewrites the AST before planning. Miss a path and it fails closed.
Three clients
Plan once. REST, gRPC, and in-process edge share the same operation.
Install
CLI, Python, Node, Rust, WASM, and VS Code.
Questions
What is QQL?
A typed query language for Qdrant. One surface for retrieval, filtering, mutations, schema, and policy-safe AST rewriting.
Which runtimes ship today?
Rust crates, native Python and Node.js bindings, a ~1.3 MB WASM package, the qql CLI, and a VS Code extension with live diagnostics.
How does multitenancy work?
Parse untrusted QQL, then inject a trusted tenant filter into the AST before planning. SHARD routing is a separate locality concern and can run alongside the filter.
Does QQL replace Qdrant?
No. QQL plans operations for Qdrant and dispatches them over REST or gRPC, or evaluates the supported subset through the in-process edge backend.
Is it production-ready?
It is young: v0.3.0. Fail-closed defaults, OpenAPI contract tests, a conformance corpus, and a public gaps document. The API surface is stabilizing, not frozen.
Can I try it without a cluster?
Yes. The playground runs the real WASM parser in the browser. qql-edge runs the pipeline with local HNSW storage and ONNX embeddings.