Skip to main content
Version: Next

Snowflake

The Snowflake frontend lets Snowflake clients (SnowSQL CLI, Python snowflake-connector-python, JDBC) connect to QueryFlux as if it were a Snowflake account. QueryFlux terminates the Snowflake wire protocol entirely — no Snowflake account or warehouse is required. SQL is dispatched to whatever backend engine (Trino, StarRocks, DuckDB, etc.) is configured for the incoming session.

Both wire sub-protocols share a single port:

Sub-protocolAlso known asUsed by
Snowflake HTTP wire (snowflakeHttp)Form 1 / "Snowflake wire"Python connector (default), SnowSQL CLI, most JDBC drivers
Snowflake SQL REST API v2 (snowflakeSqlApi)Form 2 / REST API v2Programmatic REST clients, curl, direct API users

Configuration​

queryflux:
frontends:
snowflakeHttp:
enabled: true
port: 8443
sessionAffinityAcknowledged: false # see below
snowflakeSessionMaxAgeSecs: 86400 # optional, default 86400 (24 h)
snowflakeSessionIdleTimeoutSecs: 14400 # optional, default 14400 (4 h)

Config key: snowflakeHttp. Protocol identifiers: FrontendProtocol::SnowflakeHttp (wire), FrontendProtocol::SnowflakeSqlApi (REST v2). Default dialect: SqlDialect::Snowflake.

The SQL API v2 routes are served on the same port as the wire protocol — there is no separate snowflakeSqlApi config block.

Session affinity​

The Snowflake HTTP wire protocol is stateful: a session token issued at login must be presented on every subsequent request. If you run multiple QueryFlux instances behind a load balancer, all requests from the same client must go to the same instance. Set sessionAffinityAcknowledged: true to confirm your load balancer provides this guarantee; otherwise QueryFlux will warn at startup. See Multi-replica operations for the full HA checklist.

Endpoints​

Snowflake HTTP wire (/session/…, /queries/…)​

MethodPathDescription
POST/session/v1/login-requestAuthenticate and create a session
DELETE/sessionLogout — invalidate session token
GET/session/heartbeatKeep-alive ping
POST/session/token-requestRenew session token (returns remaining TTL)
POST/queries/v1/query-requestExecute SQL
GET/queries/v1/query-monitoring-requestAsync query poll (stub — see limitations)
DELETE/queries/v1/{query_id}Cancel query (no-op — see limitations)

Snowflake SQL REST API v2 (/api/v2/…)​

MethodPathDescription
POST/api/v2/statementsSubmit SQL and execute synchronously
GET/api/v2/statements/{handle}Poll statement (stub — see limitations)
DELETE/api/v2/statements/{handle}Cancel statement (stub — see limitations)

Authentication​

HTTP wire​

Credentials are extracted from the /session/v1/login-request body:

  • LOGIN_NAME — username
  • PASSWORD — password
  • DATABASE_NAME / SCHEMA_NAME — optional database/schema hints

Optional role and warehouse hints come from the login URL's roleName and warehouse query parameters.

The credentials are authenticated against the configured auth_provider. A session token (UUID) is issued on success and must be passed in every subsequent request as Authorization: Snowflake Token="<token>".

SQL REST API v2​

Stateless: credentials are provided on every request as Authorization: Bearer <token>. The token is validated against the configured auth_provider.

ADBC backend pool lifecycle​

When the backend is Snowflake over ADBC, caller credentials and session role, warehouse, or schema overrides use isolated sub-pools. Set scopedPoolIdleTimeoutSecs (default 900) and scopedPoolMaxCount (default 500) in the ADBC cluster config to control idle expiry and the LRU cache bound. These are separate from frontend session timeouts. Background eviction releases unused backend connections even without new queries and lets in-flight queries finish. See ADBC scoped connection pools for metrics and lifecycle details.

Execution model​

Both sub-protocols execute queries synchronously via execute_to_sink. All results are accumulated into memory and returned in a single response.

HTTP wire response format​

Results are returned in Snowflake JSON format (columnar values, type metadata). The QUERY_RESULT_FORMAT session parameter is set to ARROW_FORCE during login, which causes the Python connector to request Arrow-encoded results internally; QueryFlux converts its Arrow record batches to the Snowflake Arrow wire format.

SQL REST API v2 response format​

Results are returned in jsonv2 format — a JSON array of rows where every value is stringified. Column metadata is embedded in resultSetMetaData.rowType.

Session context​

HTTP wire​

FieldSource
userUser resolved by the auth provider from the login credentials
catalogSnowflake database (DATABASE_NAME); remains unset when omitted
databaseSnowflake schema (SCHEMA_NAME); unset when omitted or empty
tagsNot populated
extra["snowflake.role"]Login URL's roleName query parameter
extra["snowflake.warehouse"]Login URL's warehouse query parameter
extra["snowflake.schema"]SCHEMA_NAME from the login request body

Snowflake's database.schema.table maps to QueryFlux's catalog.database.table at login and for subsequent queries. The three extra keys above are populated before login routing only when the corresponding option is present and non-empty. Values are preserved exactly, without trimming, case normalization, or default values, so routing rules can inspect role, warehouse, and schema when selecting the session's cluster group.

Routing (group selection) happens at login time, not at query time. The cluster group is stored in the session and reused for all queries in that session. Changing the database after login (e.g. via USE DATABASE) does not re-route.

USE DATABASE (or bare USE <database>) changes the catalog and clears the schema override. QueryFlux does not resolve backend default schemas such as PUBLIC; clients can explicitly select a schema with USE SCHEMA <schema> or USE SCHEMA <database>.<schema>. Subsequent queries use the updated namespace, while retaining the session's role and warehouse.

SQL REST API v2​

FieldSource
userResolved from Bearer token by auth provider
databaseNot extracted (always None)
tagsNot populated
extraEmpty

Client examples​

# SnowSQL CLI
snowsql -a <account-placeholder> -u dev -h localhost -p 8443 --protocol https --insecure
# Python snowflake-connector-python
import snowflake.connector

conn = snowflake.connector.connect(
account="queryflux",
user="dev",
password="secret",
host="localhost",
port=8443,
protocol="http", # or https if TLS is configured upstream
database="my_catalog",
schema="my_schema",
)
cur = conn.cursor()
cur.execute("SELECT 42 AS answer")
print(cur.fetchone())
# SQL API v2 (curl)
curl -X POST http://localhost:8443/api/v2/statements \
-H "Authorization: Bearer <your-token>" \
-H "Content-Type: application/json" \
-d '{"statement": "SELECT 42 AS answer", "timeout": 60}'

Not supported / Known limitations​

FeatureStatus
Async query execution (asyncExec: true)Not supported. query-monitoring-request returns an empty polling response so the connector stops polling. All queries run synchronously.
Query cancel (DELETE /queries/v1/{id}, DELETE /api/v2/statements/{handle})No-op — synchronous execution cannot be interrupted mid-flight via HTTP. Returns a success response.
SQL REST API v2 polling (GET /api/v2/statements/{handle})Stub — returns HTTP 404 with "already complete" message.
Query tagsNot extracted for either sub-protocol. The tags router type cannot be used with Snowflake frontends.
Multiple statements per requestNot supported. Only the first statement in the request body is executed.
Transactions (BEGIN / COMMIT / ROLLBACK)No transaction state is maintained. These statements are forwarded to the backend as-is.
ALTER SESSION SET …Acknowledged but not stored in the session. Use LOGIN_NAME session parameters at connect time instead.
Session migration across instancesSessions are in-memory per instance. Without sticky load balancing, requests routed to a different instance will fail with "session not found".
TLSNot terminated by QueryFlux. Use an external TLS terminator (e.g. nginx, Envoy) in front of the Snowflake frontend.