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
| Frontend | Config key | Default port | Protocol | Dialect | Status |
|---|---|---|---|---|---|
| Trino HTTP | trinoHttp | 8080 | HTTP REST (JSON) | Trino | Done |
| PostgreSQL wire | postgresWire | 5432 | PostgreSQL v3 wire | Postgres | Done |
| MySQL wire | mysqlWire | 3306 | MySQL wire | MySQL | Done |
| Arrow Flight SQL | flightSql | 50051 | gRPC (Arrow Flight) | Generic | Done |
| Snowflake | snowflakeHttp | 8443 | Snowflake HTTP wire + SQL API v2 | Snowflake | Done |
| ClickHouse HTTP | clickhouseHttp | 8123 | HTTP | ClickHouse | Planned |
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:
| Path | Used by | Behavior |
|---|---|---|
dispatch_query | Trino 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 — native | MySQL wire ↔ MysqlWire backend; Postgres wire ↔ PostgresWire backend | Wait for cluster capacity, call execute_native on the adapter, stream NativeResultChunks directly to the sink. Zero Arrow allocation. |
execute_to_sink — Arrow | All other frontend/backend combinations | 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:
| Field | Type | Description |
|---|---|---|
user | Option<String> | Resolved user identity, extracted at connection time. |
database | Option<String> | Target database/catalog hint for routing and adapters. |
tags | QueryTags | Query tags for routing and metrics. |
extra | HashMap<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.
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
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
Omitting a frontend block or setting enabled: false disables that listener entirely.