Release date: 2026-06-18
Summary
Section titled “Summary”qql-go 0.4.0 transforms the project from a CLI-only tool into a full platform. A Connect RPC gateway enables remote access over gRPC and HTTP. Official Go, Python, and TypeScript SDKs provide programmatic integration. The Formula/BOOST engine executes complex score shaping directly inside Qdrant using SQL-native expressions. The config grammar is unified to (key = value) syntax across all statements. Hot paths are optimized for ~6-8x throughput. Thirteen production issues from a full audit are resolved. Test coverage is expanded with edge cases across 6 packages.
100 files changed. ~6,800 insertions, ~770 deletions across 18 commits.
Highlights
Section titled “Highlights”Connect RPC Gateway
Section titled “Connect RPC Gateway”qql-go gateway --listen :50051 --qdrant-url http://localhost:6334A standalone server accepting QQL queries over Connect RPC (gRPC + gRPC-Web + HTTP/1.1). Auto-detects pure QUERY batches and routes them to Qdrant's native QueryBatch API for single round-trip execution. Mixed-statement batches fall back to sequential execution.
Exposes: Exec, ExecBatch, Explain, Health.
Go, Python, and TypeScript SDKs
Section titled “Go, Python, and TypeScript SDKs”Go (pkg/qql):
client, _ := qql.NewQdrantClient(qql.ClientConfig{URL: "localhost:6334"}) result, _ := qql.Exec(ctx, client, "QUERY 'search' FROM docs LIMIT 5")// With explicit config for model resolution and BM25 params result, _ = qql.ExecWithConfig(ctx, client, query, cfg)// Native batch — single round-trip results, _ = qql.BatchQueryWithConfig(ctx, client, queries, cfg)Python (sdks/python/):
from qql import QQLClient client = QQLClient("http://localhost:50051") result = client.exec("QUERY 'search' FROM docs LIMIT 5")TypeScript (sdks/typescript/):
const client = new QQLClient("http://localhost:50051") const result = await client.exec("QUERY 'search' FROM docs LIMIT 5")Formula / Score Boosting (BOOST)
Section titled “Formula / Score Boosting (BOOST)”A Pratt parser expression engine for Qdrant's Score Builder API. All 19 Qdrant Expression variants are covered.
QUERY 'emergency care' FROM docs LIMIT 10 BOOST ( $score * 2 + CASE WHEN priority = 'high' THEN 10 ELSE 0 END + ABS(GEO_DISTANCE({lat: 40.7, lon: -74.0}, location)) * -0.1 + GAUSS_DECAY(timestamp, target: datetime('2026-01-01'), scale: 30d) ) DEFAULTS (priority_weight = 1.5)Supports:
- Arithmetic:
+,-,*,/ - Math functions:
ABS,SQRT,LOG,LN,EXP,POW - Geo-distance:
GEO_DISTANCE({lat, lon}, field)with dict syntax - Decay functions:
EXP_DECAY,GAUSS_DECAY,LIN_DECAYwith positional and keyword arguments - Datetime:
datetime('2026-01-01T00:00:00Z'),datetime_key('published_at') - Conditionals:
CASE WHEN <filter> THEN <expr> ELSE <expr> END - Variables:
$scorefor current score, bare names for payload fields
The CASE WHEN conditional maps to Qdrant's boolean evaluation by synthesizing Sum(Mult(Condition(cond), then), Mult(Condition(NOT cond), else)).
Unified Config Syntax
Section titled “Unified Config Syntax”All configuration blocks now use SQL-native (key = value) syntax:
-- Before (0.3.0) CREATE COLLECTION docs WITH HNSW { m: 32, ef_construct: 100 } CREATE COLLECTION docs QUANTIZE SCALAR QUANTILE 0.95 ALWAYS RAM CREATE INDEX ON docs FOR tags WITH { is_tenant: true }-- After (0.4.0) CREATE COLLECTION docs WITH HNSW (m = 32, ef_construct = 100) CREATE COLLECTION docs WITH QUANTIZATION (type = 'scalar', quantile = 0.95, always_ram = true) CREATE INDEX ON docs FOR tags WITH (is_tenant = true)Per-vector quantization is also supported:
CREATE COLLECTION docs ( dense VECTOR(384, COSINE) WITH QUANTIZATION (type = 'scalar', always_ram = true), sparse VECTOR(768, DOT) )QUERY SAMPLE
Section titled “QUERY SAMPLE”Random point sampling via Qdrant's Sample_Random:
QUERY SAMPLE FROM docs LIMIT 10 QUERY SAMPLE FROM docs LIMIT 5 WHERE category = 'tech'Per-Prefetch Lookup Overrides
Section titled “Per-Prefetch Lookup Overrides”Override the target collection and vector for individual CTE prefetches:
WITH a AS (QUERY 'search' USING dense LIMIT 100) QUERY 'search' FROM docs LIMIT 10 PREFETCH (a LOOKUP FROM external_col VECTOR 'dense_vec') FUSION RRFPerformance Optimizations
Section titled “Performance Optimizations”All hot paths optimized for agent-scale bulk query throughput:
| Benchmark | 0.3.0 | 0.4.0 | Speedup |
|---|---|---|---|
Lex_Simple | 2,370 ns/op | 304 ns/op | 7.8x |
Lex_Full | 8,900 ns/op | 945 ns/op | 9.4x |
Parse_Simple | 2,715 ns/op | 477 ns/op | 5.7x |
Parse_Full | 9,455 ns/op | 1,470 ns/op | 6.4x |
Changes:
- Lexer: O(1) stack-buffer keyword lookup. Go's compiler optimization ensures
string(buf[:n])as a map key does not allocate. Fast-pathreadStringreturns input slice for strings without escape sequences. - Parser: Zero-allocation
asciiEqual/asciiEqualLowerbyte-level comparison replacing allstrings.ToUpper/strings.ToLower. - Filters:
reflect.ValueOfremoved fromnormalizeFilterExpr, replaced with explicit type-switch over all 14FilterExprpointer types. - Sparse: Byte-level ASCII tokenization fast path with
toLowerASCII(no allocation when already lowercase). BM25 params cached withatomic.Pointer, eliminatingsync.RWMutex.RLock/RUnlockper token. - Pipeline:
buildDocumentOptionscomputed once per query viaQueryState.GetDocOptions()and shared across embed nodes.
Qdrant-Native Per-Request Timeout
Section titled “Qdrant-Native Per-Request Timeout”Config.RequestTimeout (seconds) propagates to Qdrant request objects via their native Timeout field:
{ "request_timeout": 10}Applies to: QueryPoints, UpsertPoints, DeletePoints, SetPayloadPoints, GetPoints, ScrollPoints, CreateFieldIndexCollection. Also controls the Go context deadline (was hardcoded 30s).
Production Hardening
Section titled “Production Hardening”13 issues from a full codebase audit:
| Issue | Fix |
|---|---|
BatchQuery panicked on empty queries | Early return with error |
| Cross-collection batch silently broken | Groups by collection with index mapping |
SDK Exec/BatchQuery used empty config | Added ExecWithConfig/BatchQueryWithConfig |
| Server handler swallowed marshal errors | Explicit error handling, returns CodeInternal |
DataJSON() swallowed json.Marshal error | Returns ([]byte, error) |
Error types missing Unwrap() | Added Err field + Unwrap() + Wrap* constructors |
CREATE INDEX TYPE typo → silent keyword | Returns error with valid options |
INSERT without id → silent UUID | Requires explicit id field |
Embedding client http.DefaultClient | http.Client{Timeout: 30s} |
| Port 6333 → silent redirect | Returns explicit error |
| Script unmatched delimiters | Detects } before { and unclosed delimiters |
doQuery/BuildQueryPoints ~170 lines duped | Extracted buildQueryStateAndPipeline |
BuildQueryPoints used context.Background() | Accepts ctx context.Context |
Breaking Changes
Section titled “Breaking Changes”Known Limits
Section titled “Known Limits”Validation
Section titled “Validation”go run docs/dev_tasks.go release-validate --version 0.4.0 [1/4] Version sync... OK [2/4] Quality checks... OK (gofmt, go vet, go test, go build) [3/4] Building binary... OK [4/4] Binary version... qql-go version 0.4.0 — OKFull Changelog
Section titled “Full Changelog”See CHANGELOG.md.