Skip to content

0.4.0 Release Notes

Release date: 2026-06-18

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.


Start Gateway
qql-go gateway --listen :50051 --qdrant-url http://localhost:6334

A 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 (pkg/qql):

Go SDK
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/):

Python SDK
from qql import QQLClient client = QQLClient("http://localhost:50051") result = client.exec("QUERY 'search' FROM docs LIMIT 5")

TypeScript (sdks/typescript/):

TypeScript SDK
const client = new QQLClient("http://localhost:50051") const result = await client.exec("QUERY 'search' FROM docs LIMIT 5")

A Pratt parser expression engine for Qdrant's Score Builder API. All 19 Qdrant Expression variants are covered.

BOOST Formula Example
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_DECAY with 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: $score for 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)).

All configuration blocks now use SQL-native (key = value) syntax:

Unified 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:

Per-vector Quantization
CREATE COLLECTION docs ( dense VECTOR(384, COSINE) WITH QUANTIZATION (type = 'scalar', always_ram = true), sparse VECTOR(768, DOT) )

Random point sampling via Qdrant's Sample_Random:

QUERY SAMPLE
QUERY SAMPLE FROM docs LIMIT 10 QUERY SAMPLE FROM docs LIMIT 5 WHERE category = 'tech'

Override the target collection and vector for individual CTE prefetches:

Per-Prefetch Lookup
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 RRF

All hot paths optimized for agent-scale bulk query throughput:

Benchmark0.3.00.4.0Speedup
Lex_Simple2,370 ns/op304 ns/op7.8x
Lex_Full8,900 ns/op945 ns/op9.4x
Parse_Simple2,715 ns/op477 ns/op5.7x
Parse_Full9,455 ns/op1,470 ns/op6.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-path readString returns input slice for strings without escape sequences.
  • Parser: Zero-allocation asciiEqual/asciiEqualLower byte-level comparison replacing all strings.ToUpper/strings.ToLower.
  • Filters: reflect.ValueOf removed from normalizeFilterExpr, replaced with explicit type-switch over all 14 FilterExpr pointer types.
  • Sparse: Byte-level ASCII tokenization fast path with toLowerASCII (no allocation when already lowercase). BM25 params cached with atomic.Pointer, eliminating sync.RWMutex.RLock/RUnlock per token.
  • Pipeline: buildDocumentOptions computed once per query via QueryState.GetDocOptions() and shared across embed nodes.

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).

13 issues from a full codebase audit:

IssueFix
BatchQuery panicked on empty queriesEarly return with error
Cross-collection batch silently brokenGroups by collection with index mapping
SDK Exec/BatchQuery used empty configAdded ExecWithConfig/BatchQueryWithConfig
Server handler swallowed marshal errorsExplicit error handling, returns CodeInternal
DataJSON() swallowed json.Marshal errorReturns ([]byte, error)
Error types missing Unwrap()Added Err field + Unwrap() + Wrap* constructors
CREATE INDEX TYPE typo → silent keywordReturns error with valid options
INSERT without id → silent UUIDRequires explicit id field
Embedding client http.DefaultClienthttp.Client{Timeout: 30s}
Port 6333 → silent redirectReturns explicit error
Script unmatched delimitersDetects } before { and unclosed delimiters
doQuery/BuildQueryPoints ~170 lines dupedExtracted buildQueryStateAndPipeline
BuildQueryPoints used context.Background()Accepts ctx context.Context



Terminal
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 — OK

See CHANGELOG.md.