Query Qdrant With SQL-Like QQL in Python, Node, and Rust
Write Qdrant hybrid search as SQL-like statements instead of nested JSON. Same language in Python, Node, Rust, WASM, and local edge.

QQL is a SQL-like language for Qdrant. You write QUERY TEXT 'refund policy' FROM docs USING dense LIMIT 5 instead of assembling nested query_points JSON in Python, Node, or Rust. Package 0.1.5 is what you install (pip, npm, cargo). Language 1.2 is the grammar. They are not the same number.
Python workers usually hand-build JSON. Node services chain SDK calls. Rust talks gRPC. Those three payloads drift. 0.1.5 is one parser, one AST, five runtimes: pyqql / nqql against a cluster, pyqql-edge / nqql-edge in-process, qql-wasm in the browser, qql-cli on the terminal.
Newer line: QQL 0.3.0 (language 1.5: bound queries, idf = WHERE, matching BM25). This page stays the 0.1.5 install and language 1.2 surface.
Install QQL 0.1.5 in Python, Node, and Rust
One parser, one plan IR, five surfaces:
1.1 The Cross-Platform Package Matrix
| Target Surface | Package Artifact | Runtime Capability |
|---|---|---|
| Parser / Plan / Network | qql-core, qql-plan, qql, pyqql, @veristamp/nqql |
Full REST and gRPC compilation targeting Qdrant clusters |
| Local In-Process Shard | qql-edge, pyqql-edge, @veristamp/nqql-edge |
Embedded fastembed-rs vector search without server infrastructure |
| Browser Sandbox | qql-wasm |
Offline query analysis, AST inspection, and optional client REST execution |
| Command Line Interface | qql-cli |
Terminal-based query execution, explain output, and schema validation |
| Embeddings Engine | qql-embed |
Local fastembed-rs model loading (via ort & tokenizers) and remote HTTP resolution |
Installing the required package for your target runtime requires pinning version 0.1.5 across all service manifests:
# Python service workers and edge scripts
pip install pyqql==0.1.5 pyqql-edge==0.1.5
# Node.js backends and browser tooling
npm install @veristamp/[email protected] @veristamp/[email protected] [email protected]
# Rust microservices
cargo add [email protected] [email protected]1.2 Architectural Decoupling in qql-core
Architecturally, qql-core remains isolated from network I/O. Statement planning lives in qql-plan, while vector embedding resolution is isolated within qql-embed. Downstream execution backends (RestQdrant, GrpcQdrant, and EdgeQdrant) lower the identical PlannedOperation intermediate representation into wire-level requests.1
A single query language across Rust, Python, Node, WASM, and Edge means your team maintains zero fragmented SQL or custom JSON schemas.
Having a single parser implementation across all language bindings ensures that query validation occurs before network packets hit database ports. Frontend browser sandboxes using qql-wasm evaluate identical syntax constraints as backend Rust microservices, preventing invalid syntax from traversing API gateways.
Map payload fields to named vectors on UPSERT
Early vector databases assumed single-vector records or relied on implicit client naming conventions to route vectors to specific index fields. QQL 1.1 introduced explicit ON FIELD and INTO clause specifications for multi-vector UPSERT statements. QQL 0.1.5 preserves these structures while adding strict validation against empty targets and duplicate field assignments.
2.1 Multi-Vector Ingestion via Python Bindings
Consider a multi-vector ingestion payload parsed via Python bindings:
import pyqql
import json
query_source = """
UPSERT INTO documents VALUES {
id: 42,
title: 'System Architecture Notes',
body: '''line1
line2'''
}
USING DENSE MODEL 'BAAI/bge-small-en-v1.5' ON FIELD body INTO dense_body,
DENSE MODEL 'BAAI/bge-small-en-v1.5' ON FIELD title INTO dense_title
"""
statement = pyqql.parse(query_source)[0]
embedding_ast = statement.to_dict()["Upsert"]["embedding"]
print(json.dumps(embedding_ast, indent=2))2.2 AST Structural Representation
The resulting Abstract Syntax Tree (AST) reflects explicit structural target mapping:
{
"Multi": [
{
"Dense": {
"model": "BAAI/bge-small-en-v1.5",
"vector": "dense_body",
"field": "body"
}
},
{
"Dense": {
"model": "BAAI/bge-small-en-v1.5",
"vector": "dense_title",
"field": "title"
}
}
]
}Notice that multiline triple-quoted string values preserve exact whitespace and newline characters ("line1\n\nline2"). This string handling model guarantees that raw payload formatting remains untruncated during ingestion.
When statement intent centers specifically on vector generation rather than point insertion, the explicit EMBED syntax provides direct target mapping:
EMBED body INTO dense_body MODEL 'BAAI/bge-small-en-v1.5' FOR POINT 42 IN documents;Retrieval operations remain decoupled from ingestion semantics, referencing named vector targets during search:
QUERY TEXT 'how do named vectors work?' FROM documents
USING dense_body
LIMIT 5;Structural target mapping prevents subtle data contamination bugs in production multi-vector systems. When title vectors and body vectors occupy different vector spaces, implicit routing can inadvertently push title embeddings into body indices. Enforcing explicit field bindings at the AST parser level prevents misrouted vectors before storage ingestion takes place.
Put paths and multiline text in QQL without backslash soup
Handling raw paths, multiline prompts, and JSON payloads inside SQL-like syntax frequently leads to complex backslash escaping. QQL 0.1.5 supports four distinct string delimiter formats that all parse to identical internal string representations:
3.1 Supported String Delimiter Formats
| String Form | Syntax Example | Primary Engineering Use Case |
|---|---|---|
| Single Quoted | 'refund policy' |
Standard literal strings and field values |
| Raw String | r'C:\Users\operator\docs' |
File paths and regex patterns where backslashes stay literal |
| Triple Quoted | '''line1\n\nline2''' |
Multiline context blocks, system prompts, and raw payloads |
| Backtick Quoted | `He said "keep quotes".` |
String values containing inline double or single quote marks |
3.2 Verification via Lexer Evaluation
The raw string parser evaluation can be verified programmatically:
import pyqql
raw_query = r"QUERY TEXT r'C:\Users\operator\docs' FROM d USING dense LIMIT 1"
statement = pyqql.parse(raw_query)[0]
extracted_text = statement.to_dict()["Query"]["expression"]["Nearest"]["input"]["Text"]["text"]
assert extracted_text == r"C:\Users\operator\docs"A raw string delimiter is a lexer convenience, not a security boundary. It prevents the lexer from consuming backslash characters during parsing, but host applications must still parse, authorize, and validate user input prior to execution.
Supporting multiline triple-quoted strings simplifies the ingestion of raw LLM generation outputs, system prompts, and structured JSON documents. Developers can embed raw text directly into QQL statements without building custom string sanitization libraries or complex escape routines.
Isolate tenants with WHERE. Route shards with SHARD.
An architectural refactoring in QQL 0.1.5 is the removal of inject_shard_key. Previous revisions mixed tenant security isolation with physical cluster routing. In 0.1.5, security isolation and cluster routing are decoupled into independent subsystems:
4.1 Security Isolation vs Physical Routing Matrix
| Engineering Concern | Operational Mechanism | Qdrant Wire Representation |
|---|---|---|
| Security Isolation | WHERE clause or inject_filter() |
Qdrant Filter payload comparison |
| Physical Routing | SHARD 'key' clause or stmt.shard_key |
REST shard_key query param / gRPC ShardKeySelector |
| Index Configuration | CREATE INDEX … is_tenant = true |
Tenant payload index optimization |
| Partition DDL | CREATE SHARD KEY 'key' |
Cluster administration API |
4.2 Production Shard Execution Pattern
This separation is reflected in production query execution:
-- Step 1: Provision physical partition (Admin operation)
CREATE SHARD KEY 'acme' ON COLLECTION tenants WITH (shards_number = 2);
-- Step 2: Route request to physical shard while enforcing mandatory tenant filter
QUERY TEXT 'security risk assessment' FROM tenants
USING dense
WHERE tenant_id = 'acme'
SHARD 'acme'
LIMIT 10;Clause evaluation order is strictly enforced: USING → WHERE → SHARD → LIMIT. Placing a WHERE filter before a USING specification triggers a parse error with QQL-PARSE-CLAUSE-ORDER.2
import pyqql
statement = pyqql.parse("QUERY TEXT 'audit' FROM docs USING dense SHARD 'acme' LIMIT 5")[0]
assert statement.to_dict()["Query"]["shard_key"] == "acme"
# Programmatic host-level routing (decoupled from isolation filters):
statement.shard_key = "acme"Decoupling security isolation from node routing reinforces zero-trust principles. Routing directs network packets; inject_filter protects tenant boundaries.
Local edge executors reject custom SHARD clauses at execution time, as physical sharding applies only to distributed Qdrant clusters. Further details on local execution and security filtering are available in our local edge search post and AST filter injection post.
Decoupling security isolation from node routing reinforces zero-trust principles. Routing directs network packets to specific cluster nodes for performance, while inject_filter guarantees that tenant boundaries remain enforced even if node routing configurations change. For isolation, use inject_filter (Prevent RAG data leaks). The Go gateway and Go CLI posts are archived with qql-go. Current CLI is qql from qql-rs.
Add a parse-and-plan test before you bump the pin
Relying solely on package release notes is insufficient for production deployments. Pinning package dependencies should be accompanied by an automated conformance fixture in your test suite:
5.1 Python Conformance Fixture
import pyqql
CONFORMANCE_TEST_CASES = [
"QUERY TEXT r'C:\\tmp\\data' FROM docs USING dense LIMIT 1",
"QUERY TEXT `quoted \"value\"` FROM docs USING dense LIMIT 1",
"""UPSERT INTO docs VALUES {id: 101, body: '''multiline
content'''} USING DENSE ON FIELD body INTO dense_body""",
"QUERY TEXT 'audit' FROM docs USING dense WHERE tenant_id = 'acme' SHARD 'acme' LIMIT 5",
"DELETE PAYLOAD legacy_tag FROM docs WHERE status = 'deprecated'",
]
def test_qql_grammar_conformance():
for query in CONFORMANCE_TEST_CASES:
parsed_statements = pyqql.parse(query)
assert len(parsed_statements) == 1, f"Failed to parse: {query}"
explanation = pyqql.explain(parsed_statements[0])
assert explanation["ok"], f"Planning failed for {query}: {explanation}"
if __name__ == "__main__":
test_qql_grammar_conformance()
print("All QQL 0.1.5 conformance tests passed successfully.")5.2 Multi-Language Test Execution
Running these fixtures across Python, Node.js (@veristamp/nqql), and browser environments (qql-wasm) ensures query compilation remains identical throughout your architecture. For browser testing details, refer to our WASM playground guide.
Automated conformance testing provides continuous verification during dependency updates. When upgrading QQL packages, running conformance suites validates that syntax parsing, plan generation, and error reporting maintain complete backwards compatibility across all deployment environments. Similar governance principles apply to schema-driven extraction pipelines as described in our Schema-First LLM Wiki post and Governed Code Mode guide.
Frequently asked questions
What is QQL?
A SQL-like language for Qdrant. Search, filters, hybrid fusion, upserts, and schema are statements instead of nested JSON. One parser lowers them to REST, gRPC, or an in-process edge shard.
Is 0.1.5 the language version or the package version?
Package. Install pyqql==0.1.5 / @veristamp/[email protected]. The grammar inside that pin is 1.2. QQL 0.3.0 is a later package line (language 1.5, wire-compatible BM25).
Can I query Qdrant without writing Python SDK code?
Yes. Write QQL and run it from Python, Node, Rust, the CLI, or the WASM playground. The SDK is still there if you want it. QQL is the statement layer on top.
How do I keep one tenant from seeing another tenant’s points?
WHERE tenant_id = 'acme' in the statement, or inject_filter at the host so untrusted QQL cannot drop it. SHARD 'acme' only routes the request. It is not a security control. Details: inject tenant filters and local edge search.
Footnotes
-
QQL. Source repository, architecture specs, and release documentation for the 0.1.5 release line. GitHub(opens in a new tab). ↩
-
QQL. Grammar specification and syntax reference guide. docs/syntax.md(opens in a new tab). ↩