Skip to content

Multitenancy

Production multitenancy has distinct security, routing, layout, and lifecycle concerns.

ConcernQQL or host APIEffect
IsolationWHERE / inject_filterQdrant Filter
RoutingSHARD / stmt.shard_keyShardKeySelector
Layoutindex option is_tenant = truetenant-aware index organization
Partition lifecycleCREATE/DROP SHARD KEYcustom shard administration
QQLTenant-aware schemaTry in playground
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);
QQLIsolation and routing togetherTry in playground
QUERY 'risk factors'
FROM documents
USING dense
WHERE tenant_id = 'acme'
SHARD 'acme'
LIMIT 10;

For untrusted user or agent input:

  1. Parse the complete statement or script.
  2. Call inject_filter with the trusted tenant value.
  3. Optionally assign the trusted shard key.
  4. Inspect or explain the rewritten AST.
  5. 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 injectFilter
const report = await client.executeStmt(stmt);

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.