Skip to main content
Version: 0.1.0

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
SnowflakesnowflakeHttp8443Snowflake HTTP wire + SQL API v2SnowflakeDone
ClickHouse HTTPclickhouseHttp8123HTTPClickHousePlanned

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

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.