Skip to content

API surface

The host bindings follow the same conceptual surface while respecting host naming and object ownership.

CapabilityRustPythonNode.jsWASM
Parse scriptParser::parse_allparseparseparse
ValidateParser::parse(_).is_ok()is_valid (parse + plan)isValid (parse + plan)isValid
Tokenizelexer APItokenizetokenizetokenize
Inject policyinject_filterinject_filterinjectFilterinject_filter / Stmt.injectFilter
Explainplanner/runtimeexplainexplainexplain / analyze
Execution profile (client timings plus server time and usage)Executor::explain_analyzeClient.explain_analyzeClient.explainAnalyzeClient.explainAnalyze
DB-API cursor (Python)pyqql.connect + cursor (execute/executemany/fetch*/nextset, description, rowcount)
Lazy scroll cursormanual SCROLL ... AFTER :cursor loop (see Rust SDK page)Client.scroll_cursor (sync generator) plus Client.scroll_cursor_async (async generator)Client.scrollCursor / scrollCursor, Client.scrollStream / scrollStreamdx.js scrollCursor helper over Client.execute (see WASM SDK page)
Compileplanner/runtimecompile_querycompileQuerycompile
Bind parametersbind_named / bind_positional (+ *_readable)bind, Stmt.bind, execute(…, params=)bind, Stmt.bind, execute(…, { params })bind, Stmt.bind, execute(…, { params }) — typed arrays convert directly
ExecuteExecutor::executeClient.executeClient.executeClient.execute
Parse to JSON stringparse_jsonparseJsonparseJson
Bulk ingest (:rows template, chunked)Executor::upsert_manyClient.upsert_manyClient.upsertManyClient.upsertMany
Typed hits shortcutClient.execute_hits / execute_async_hitsClient.executeHits / module executeHitsdx.js executeHits (wrap ExecutionReport for the rest)
  1. One statement: the strict single-statement parser.
  2. One script: semicolon-delimited source parsed as one ordered unit.
  3. Host batch: an array of independent statements or scripts.

These are not interchangeable APIs. In particular, an array boundary is owned by the host, while semicolon ordering is part of QQL source.

Hosts expose the same statement set the planner accepts. Schema and cluster administration include:

FamilyStatements
RetrievalQUERY …, SCROLL, COUNT
DataUPSERT, UPDATE … SET VECTOR/PAYLOAD, DELETE, CLEAR PAYLOAD, DELETE PAYLOAD, DELETE VECTOR
SchemaCREATE/ALTER/DROP COLLECTION, CREATE/DROP INDEX, CREATE/DROP SHARD KEY
InspectSHOW COLLECTIONS, SHOW COLLECTION, SHOW SHARD KEYS, SHOW QUOTAS
Cluster (REST)SET QUOTA (…) [WAIT bool]

SHOW QUOTAS / SET QUOTA plan to REST /quotas only. gRPC and edge reject them with stable codes (QQL-GRPC-QUOTA, QQL-EDGE-UNSUPPORTED-QUOTA). Transport metadata such as route affinity is configured on the client — Rust RestQdrant::with_route_affinity / GrpcQdrant::with_route_affinity, pyqql.Client(route_affinity=…), nqql new Client({ routeAffinity }), and WASM client.setRouteAffinity(key) — not as QQL syntax. Edge has no route affinity (single node).

Python, Node.js, and WASM expose statement handles so a host can parse once, inject policy, set optional shard routing, inspect the AST, and execute the statement. In addition to toObject/toJSON, Stmt across Python, Node, and WASM exposes bind(params) to substitute :name / ? placeholders into the statement AST and compile_route / compileRoute (optionally accepting params) to inspect the compiled REST route of a single statement. WASM owners must call free() explicitly when they are finished.

Token objects are consistent across hosts: Python, Node, and WASM bindings emit { kind, text, pos, end, len }, matching the underlying Span { start, end } model.

Execution returns a stable report with aggregate ok, ordered per-operation results, and succeeded/failed counts. on_error / onError selects stop or continue behavior for multi-operation inputs. Python, Node, and WASM reports add typed accessors through ExecutionReport (pyqql, dx-common.js, dx.js): hits(stmt) / points(stmt) return ScoredPoint objects (id, score, payload, text, collection), facet(stmt) returns normalized [{ value, count }] hits, count(stmt) returns an integer, and groups(stmt) returns backend group objects (see below). Python execute_hits / execute_async_hits, Node executeHits, and WASM dx.js executeHits(client, query, options) skip the report entirely. Passing params as a list/array with one entry per statement binds parameters statement-scoped: the length must match the statement count exactly, and each entry is a dict/object (named) or a list/array of scalars (positional). The WASM Client is REST-only: it compiles to and executes Qdrant REST routes and does not open gRPC connections; wrap its plain { ok, results, succeeded, failed, telemetry } object with the dx.js ExecutionReport for typed hits() / facet() / count() / groups() accessors, telemetry reads, and the executeHits(client, query, options) one-shot.

Every typed accessor takes an optional statement index with Python list semantics on all hosts: 0 is the first statement (the default), -1 is the last statement, and any out-of-range index returns an empty list (hits, points, ids, facet, groups) or 0 (count) instead of raising. This applies to pyqql.ExecutionReport, Node ExecutionReport, and WASM dx.js ExecutionReport alike. Rust callers use report.hits(0) style helpers with usize indexes on ExecutionReport.

groups(stmt) returns the raw backend group list for a GROUP BY query, normalized across the {"result": {"groups": [...]}} and bare {"groups": [...]} envelopes. Each entry is { "id": <group key>, "hits": [<point records>] } where id is the group key value and hits holds the ordered point records for that group. Out-of-range indexes return []. See the Python, Node, and WASM SDK pages for per-host examples.

Every ExecResponse may carry telemetry with server time and usage when the backend reported it: { "time_s": <seconds or null>, "usage": <hardware and inference usage or null> }. Missing telemetry reads as None (Python), null or undefined (Node, WASM), never as an error. The report also carries aggregated telemetry (server times summed, hardware counters and per-model tokens merged). explainAnalyze / explain_analyze returns the same telemetry plus server_time_s and usage top-level fields alongside plan, phases, and results. See the Python SDK, Node.js SDK, WebAssembly SDK, and Rust SDK pages.

Bulk ingest contract (upsertMany / upsert_many)

Section titled “Bulk ingest contract (upsertMany / upsert_many)”

One :rows template is prepared once, then each batchSize / batch_size chunk splices through the point-splice path with no re-parse and no per-batch schema fetch. The contract is identical on Python, Node, and WASM (see the per-SDK pages for call shapes):

  • rows must be an array or list of point objects ({id, vector, ...payload}). Any other top-level shape fails closed with QQL-BIND-TYPE-MISMATCH.
  • Each point has the same shape as an inline VALUES {...} row. A missing id fails with QQL-VALIDATION-UPSERT-ID; any other misshapen row fails with QQL-BIND-TYPE-MISMATCH instead of landing partial data.
  • Row vectors accept the same shapes everywhere: plain lists or arrays of numbers, packed 1-D float buffers or typed arrays (numpy, array.array, memoryview on Python; Float32Array / Float64Array on Node and WASM, one copy with no per-element walk), integer typed arrays or lists for sparse indices (Int32Array / Uint32Array on Node and WASM), and the flat {data, dim} (Python {"data": [...], "dim": N}) multivector form.
  • Raw binary without a float view fails closed with a wrap-first typed error: wrap the bytes in a Float32Array or Float64Array view first (Python bytes and bytearray take the same path: pass a float buffer instead). On Node and WASM the code is QQL-BIND-INVALID-PARAMS; on Python it is a ValueError with the same guidance.
  • batchSize / batch_size must be an integer greater than or equal to 1. Values below 1 fail closed with QQL-VALIDATION-UPSERT-BATCH before any I/O.
  • Empty rows returns an empty ok report without I/O.

HttpEmbedder configuration is not portable across hosts. Use this table before assuming a dense config covers multi-vector, image, or rerank work:

CapabilityNode HttpEmbedderPython HttpEmbedderWASM client
Dense text (endpoint, model, dimension)yesyes (dense settings; sparse resolves to local BM25)yes via setHttpEmbedder(endpoint, model, dimension)
Multi or ColBERT (multiEndpoint, multiModel, multiDimension)yesyesyes via setHttpMultiEmbedder(endpoint, model, dimension)
Image or CLIP (imageEndpoint, imageModel, imageDimension)yesyesyes via setHttpImageEmbedder(endpoint, model, dimension)
Cross-encoder rerank (rerankEndpoint, rerankModel)yesyesyes via setHttpReranker(endpoint, model)
JS callback (async (texts) => number[][], batched)no (native transport)noyes via setEmbedder(fn)

WASM accepts HTTP dense, multi-vector, image, and reranker endpoints plus one JS callback; use one setter per group. setRemoteEmbedder on WASM is an alias for setHttpEmbedder.

Every host surfaces the same typed error fields: code (stable QQL-* string), kind (Lex, Parse, Validation, Execution, Transport, Backend), span (byte offsets or null), plus structured fields and request_id when the transport provides them. Python raises QqlError subclasses (QqlSyntaxError, QqlValidationError, QqlExecutionError, QqlTransportError, QqlBackendError); Node and WASM reject or throw Error objects with .code / .kind / .span / .fields / .request_id via buildError; Rust returns QqlError values.