query-gateway

module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 10, 2026 License: MIT

README

Query Gateway

A standalone Go library that resolves GLQL — GitLab Query Language, the language behind glql Markdown blocks — over HTTP.

Owned by group::global search. GitLab Workhorse is a consumer: it mounts this library's handler on the gateway route. The library was extracted out of Workhorse so that group::source code does not own or maintain search-team logic (discussion).

go get gitlab.com/gitlab-org/search-team/query-gateway

What it does

One HTTP request in, one GLQL result out. For each request the gateway:

  1. Compiles the GLQL query by running the GLQL transpiler WebAssembly module in-process (wazero, no cgo, no JavaScript engine).
  2. Routes the compiled query to a backend chosen by the compile mode the transpiler reports — standard compiles to a GraphQL document, orbit compiles to a graph-query traversal DSL body.
  3. Resolves it by forwarding to that backend with the caller's original credentials untouched.
  4. Transforms the backend result back into the GLQL result shape.

Two design properties are load-bearing and covered by tests:

  • No authorization here. The gateway holds no identity and caches nothing per user; every query is authorized per request by the backend that owns the data. That is what stops a cached result outliving a revoked permission.
  • Backend identity never leaks. Which backend answered is an implementation detail of a Resolver. Provider names are screened out of responses and out of log lines (see queryapi/backend_identity.go).

Public API

queryapi
Symbol Purpose
NewHandler(transpiler Transpiler, timeout time.Duration, resolvers ...Resolver) *Handler Builds the gateway handler. A query whose compile mode has no registered resolver is refused with a contract-defined error rather than routed elsewhere.
Handler.ServeHTTP(w, r) Standard http.Handler.
Transpiler interface Compile(ctx, query, compileContext) and Transform(ctx, data, transformContext). Must be safe for concurrent use. Satisfied by glqlwasm.Transpiler.
Resolver interface Mode() string plus Resolve(...) (Resolution, error). Adding a backend is a resolver registration, not a handler change.
NewGraphQLResolver(backend *url.URL, rt http.RoundTripper) Resolver The standard-mode resolver: posts the compiled GraphQL document upstream.
Resolution, UpstreamResponse, QueryErrors Result and error carriers. An *UpstreamResponse error carries a verbatim upstream reply that must be relayed unaltered.
ModeStandard Compile-mode constant ("standard"). orbit mode is recognised by the router but has no exported constant or bundled resolver yet; register your own Resolver for it.
MaxRequestBytes Inbound request-envelope cap, aligned with Analytics::Glql::ParserService::MAX_INPUT_SIZE.
queryapi/glqlwasm
Symbol Purpose
New(ctx, Options) (*Transpiler, error) Loads the .wasm module and pre-instantiates the instance pool. The AOT compile costs ~1.5s, so call this at process start-up, never in a request path.
Options{WasmPath string, PoolSize int} PoolSize bounds concurrent transpilations; defaults to GOMAXPROCS.
Transpiler.Compile / .Transform / .Close Implements queryapi.Transpiler.
ErrDisabled, ErrPoolExhausted, ErrQueryTooLarge, ErrResultTooLarge Sentinel errors callers can branch on.
MaxLegalTransformBytes Input ceiling for the transform stage.

Each instance's wasm linear memory is capped at 256 MiB. Exceeding it produces a size error and the instance is retired and replaced, leaving the pool intact — memory exhaustion fails well rather than poisoning the process.

Wiring example
tr, err := glqlwasm.New(ctx, glqlwasm.Options{WasmPath: "/opt/glql/glql.wasm"})
if err != nil {
        return err
}
defer tr.Close(ctx)

h := queryapi.NewHandler(tr, 30*time.Second,
        queryapi.NewGraphQLResolver(railsGraphQLURL, http.DefaultTransport))
mux.Handle("/api/glql", h)

Obtaining the wasm module

The module is not committed to this repository. It is the same artifact the GitLab frontend uses, and it ships as a base64 blob inlined into a rollup bundle — npm/dist/main.js inside the @gitlab/query-language-rust npm package — rather than as a .wasm file. That is why obtaining it is an extraction rather than a copy.

_support/extract_glql_wasm.rb performs the extraction: it locates the npm package, pulls the base64 argument out of the bundle's _loadWasmModule(...) call, decodes it, verifies the \x00asm magic number, and writes the module out.

# In a GitLab checkout that has had `yarn install` run:
GLQL_NODE_MODULES=/path/to/gitlab _support/extract_glql_wasm.rb /tmp/glql.wasm

GLQL_NODE_MODULES may name either a checkout root or a node_modules directory. With it unset, the script searches this repository, its parent, and a sibling gitlab/ checkout, in that order.

Reference artifact, so a future reader can confirm they have the same module:

size    2028464 bytes
sha256  a2f4ac75cd698bdaef87e7e5bfb7426645ad961fdf5a6890b0378bbca67d848d

Keeping the module out of this repo is deliberate: extracting from the already-pinned npm package is what keeps the module the gateway runs identical to the one the frontend and Rails run. Vendoring a copy would add a fourth place a GLQL version can drift. When the frontend bundle stops shipping wasm, replace the script with a build-time fetch of the published artifact, or vendor a copy behind a checksum gate.

Running the tests

go build ./...
go vet ./...

# Full suite. GLQL_WASM_PATH points at an extracted module (see above).
GLQL_WASM_PATH=/tmp/glql.wasm go test ./...

The go directive is 1.25.8 rather than a bare 1.25 because gitlab.com/gitlab-org/labkit declares that patch level and the module graph will not resolve below it.

GLQL_WASM_PATH is the only setup the tests need. Without it, the transpiler tests fall back to invoking _support/extract_glql_wasm.rb, and if that cannot find the npm package they skip rather than fail — a checkout with no GitLab tree next to it is not a broken build.

That fallback is a real coverage gap, not a pass. Verified counts:

Run Executed Skipped
GLQL_WASM_PATH set 78 0
no module available 50 28

So treat a green run with 28 skips as "the transpiler is untested here". CI prints the skip count for exactly this reason (see .gitlab-ci.yml). The glqlwasm package takes ~2 minutes with the real module, as several tests drive the 256 MiB memory ceiling on purpose.

License

MIT — see LICENSE.

Directories

Path Synopsis
Package queryapi implements the GLQL query gateway.
Package queryapi implements the GLQL query gateway.
glqlwasm
Package glqlwasm runs the GLQL transpiler WebAssembly module in-process.
Package glqlwasm runs the GLQL transpiler WebAssembly module in-process.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL