Skip to main content
Version: Next

Frontends

A frontend is the entry point for client traffic into QueryFlux. Each frontend speaks a specific wire or HTTP protocol, parses incoming SQL, builds a SessionContext, and hands the query to the shared dispatch layer. The client never knows which backend engine actually runs the query — the frontend translates results back into its native format before responding.

Available frontends​

FrontendConfig keyDefault portProtocolDialectStatus
Trino HTTPtrinoHttp8080HTTP REST (JSON)TrinoDone
PostgreSQL wirepostgresWire5432PostgreSQL v3 wirePostgresDone
MySQL wiremysqlWire3306MySQL wireMySQLDone
Arrow Flight SQLflightSql50051gRPC (Arrow Flight)GenericDone
SnowflakesnowflakeHttp8443*Snowflake HTTP wire + SQL API v2SnowflakeDone
MCPmcp—Streamable HTTP (MCP tool calls)GenericDone
ClickHouse HTTPclickhouseHttp8123HTTPClickHousePlanned

*Snowflake is optional: you must set snowflakeHttp.port in YAML (no implicit default). 8443 is typical; some repo examples use 8445. Wire and SQL API v2 share this listener; routing uses snowflakeHttp vs snowflakeSqlApi — see Snowflake.

Shared architecture​

All frontends converge on the same internal pipeline. The differences are only in how SQL enters and how results leave.

Client ──(native protocol)──► Frontend Listener
│
SessionContext + SQL
│
▼
Router Chain ──► ClusterGroupName
│
▼
ClusterGroupManager ──► ClusterName
│
▼
Translation Service ──► translated SQL
│
▼
Engine Adapter ──► results
│
▼
ResultSink ──► native protocol response

Dispatch paths​

The dispatch layer offers three execution paths:

PathUsed byBehavior
dispatch_queryTrino HTTP (async-capable groups)Submit to engine, persist handle, return polling URL. Client follows nextUri to stream pages. Falls back to execute_to_sink when a sync adapter is selected from a mixed-engine group.
execute_to_sink — nativeMySQL wire ↔ MysqlWire backend; Postgres wire ↔ PostgresWire backendWait for cluster capacity, call execute_native on the adapter, stream NativeResultChunks directly to the sink. Zero Arrow allocation.
execute_to_sink — ArrowAll other frontend/backend combinations (including Snowflake HTTP wire and SQL API v2)Wait for cluster capacity, call execute_as_arrow, stream RecordBatches through a ResultSink that re-encodes to the native protocol response.

Protocol matching (ConnectionFormat)​

Each adapter declares the wire format it natively produces via connection_format(). Dispatch compares this against the incoming frontend protocol — when they match, the native (zero-serialization) path is taken; otherwise the Arrow path is used as a universal fallback.

Adapter declares: Frontend needs: Dispatch:
Arrow (ADBC/DuckDB) ↔ FlightSql → match → Arrow passthrough (optimal)
MysqlWire (mysql_async) ↔ MySqlWire → match → native NativeResultChunk path
PostgresWire (tokio-pg) ↔ PostgresWire → match → native NativeResultChunk path
TrinoHttp ↔ TrinoHttp → match → Raw bytes passthrough

MysqlWire ↔ FlightSql → no match → Arrow conversion
Arrow ↔ MySqlWire → no match → Arrow conversion

The ConnectionFormat declaration is the only thing an adapter needs to change to opt into the native path — dispatch and frontends require no per-engine changes.

SessionContext​

Each frontend builds a SessionContext that travels with the query through routing and into dispatch. It is a flat struct — not protocol-specific:

FieldTypeDescription
userOption<String>Resolved user identity, extracted at connection time.
databaseOption<String>Target database/catalog hint for routing and adapters.
tagsQueryTagsQuery tags for routing and metrics.
extraHashMap<String, String>Protocol-specific key-value bag (headers, session params, etc.).

Each frontend is responsible for extracting user and database from its protocol's native fields and placing remaining data into extra. Key conventions for extra: Trino/ClickHouse HTTP store lowercased header names; Postgres wire stores startup parameters; MySQL wire stores session variables; Snowflake HTTP / SQL API v2 store session and account-related fields from login or request metadata.

Authentication​

All frontends extract credentials from their protocol's native mechanism (HTTP headers, wire auth packets, gRPC metadata) and pass them to the shared auth_provider. The auth result determines whether the query proceeds and provides context for authorization checks downstream.

Routing​

The FrontendProtocol enum identifies which frontend originated a query. The protocolBased router uses this to map traffic from different protocols to different cluster groups:

routers:
- type: protocolBased
trinoHttp: trino-default
postgresWire: trino-default
mysqlWire: starrocks-group
flightSql: flight-analytics
snowflakeHttp: trino-default
snowflakeSqlApi: trino-default
mcp: agent-queries

SQL dialect and translation​

Each frontend has a default source dialect (protocol.default_dialect()). When the target engine's dialect differs, sqlglot translates the SQL automatically. When dialects match, translation is skipped entirely.

Configuration​

Each frontend is enabled under queryflux.frontends in config.yaml:

queryflux:
frontends:
trinoHttp:
enabled: true
port: 8080
postgresWire:
enabled: true
port: 5432
mysqlWire:
enabled: true
port: 3306
flightSql:
enabled: true
port: 50051
snowflakeHttp:
enabled: true
port: 8443
sessionAffinityAcknowledged: false
mcp:
enabled: true
port: 8811

Omitting postgresWire, mysqlWire, flightSql, snowflakeHttp, or mcp disables that listener. For any frontend block, enabled: false turns the listener off while keeping other settings. When trinoHttp is omitted from YAML, serde defaults apply (enabled: true, port 8080).

See Snowflake for session affinity, TTL fields, and SQL API v2 behavior.