Production multitenancy has distinct security, routing, layout, and lifecycle concerns.
| Concern | QQL or host API | Effect |
|---|---|---|
| Isolation | WHERE / inject_filter | Qdrant Filter |
| Routing | SHARD / stmt.shard_key | ShardKeySelector |
| Layout | index option is_tenant = true | tenant-aware index organization |
| Partition lifecycle | CREATE/DROP SHARD KEY | custom shard administration |
CREATE COLLECTION documents ( dense VECTOR(384, COSINE))WITH PARAMS (shard_number = 8, sharding_method = 'custom', shard_keys = ['acme', 'globex']);
CREATE INDEX ON COLLECTION documents FOR tenant_id TYPE keyword WITH (is_tenant = true);QUERY 'risk factors'FROM documentsUSING denseWHERE tenant_id = 'acme'SHARD 'acme'LIMIT 10;Host-side enforcement
Section titled “Host-side enforcement”For untrusted user or agent input:
- Parse the complete statement or script.
- Call
inject_filterwith the trusted tenant value. - Optionally assign the trusted shard key.
- Inspect or explain the rewritten AST.
- Execute the rewritten statement, never the original source.
inject_filter is statement-aware. It recurses into CTE and prefetch branches for queries, wraps the point selector for mutations so they can only touch the caller's tenant, stamps the tenant value onto payloads for UPSERT, and fails closed for DDL, SHOW, and vector updates. See the Filter injection guide for the full behavior.
from pyqql import Client, parse
client = Client("http://localhost:6333")stmt = parse(query)[0]stmt.inject_filter("tenant_id", "=", "acme")stmt.shard_key = "acme"
report = client.execute(stmt)const { parse } = require("@veristamp/nqql");
const [stmt] = parse(query);stmt.injectFilter("tenant_id", "=", "acme");stmt.shardKey = "acme";
const report = await client.execute(stmt);import init, { Stmt } from "qql-wasm";await init();
const stmt = new Stmt(query);stmt.injectFilter("tenant_id", "=", "acme");stmt.shardKey = "acme"; // routing only — isolation comes from injectFilterconst report = await client.executeStmt(stmt);Per-stack recipes
Section titled “Per-stack recipes”For copy-ready FastAPI and Express middleware wiring request tenants to inject_filter plus SHARD plus is_tenant DDL, with a cross-tenant check proving isolated reads return zero rows, see the Multitenancy recipes guide.