Skip to content

Cloud lanes

Four dialect ports target engines that run in no container — Snowflake, BigQuery, Redshift and Databricks — and each is established by the ladder Dialects describes: offline rungs on every pull request, a surrogate lane that is evidence and never the oracle, and the engine's own compiler as the oracle. The surrogate lanes need nothing but Docker (and a JVM, which CI installs, for the Spark one). The authoritative lanes need an account each, and every one of them skips with a stated reason until its account is wired, so a green run without one says only that the lane did not run. This page is what to create, where to put it, and what it costs.

Every credential lives in a GitHub environment, never in the repository, and a pull request from a fork reaches none of them (S-0012/D-5). Variables name things a log may print — an account, a project, a warehouse; secrets hold the one bearer per engine. The authoritative lanes run on a push to main and on a release tag (force_full), never on a pull request; the surrogate lanes need no credential and run on a pull request too, whenever its diff touches engine code.

Snowflake — EXPLAIN USING JSON, then the execution corpus

What Where Value
environment snowflake exists, empty
SNOWFLAKE_ACCOUNT variable the account identifier, e.g. xy12345.eu-central-1
SNOWFLAKE_DATABASE, SNOWFLAKE_SCHEMA, SNOWFLAKE_ROLE variables the database and schema the compile lane resolves names in, and the role the session takes
SNOWFLAKE_TOKEN secret a programmatic access token for a user holding that role
SNOWFLAKE_WAREHOUSE variable optional — names the warehouse the execution corpus runs on; unset, the lane is compile-only

The compile lane reads the named database and schema and writes nothing. The execution corpus does not use them: it creates a transient database of its own per run, builds in it, and drops it at the end (tests/support/snowflake.py), so the role needs CREATE DATABASE on the account and USAGE on the warehouse — not only rights inside the documented database.

The compile lane (tests/engines/test_snowflake_live.py, job snowflake-compile) submits EXPLAIN USING JSON for every statement the shared corpus renders: the engine's parser, binder and function resolution, no data scanned, no warehouse needed. The execution corpus in the same module runs only when SNOWFLAKE_WAREHOUSE is set; an X-Small warehouse with auto-suspend at one minute holds its cost to seconds of compute per run.

Snowflake has no perpetual free tier. A trial account (30 days, with credits) is enough to wire the lane and to run the corpus a few times; after that the compile lane is the one worth keeping, since it costs no compute.

BigQuery — a dry run, then the execution corpus

What Where Value
environment bigquery create it
BIGQUERY_PROJECT, BIGQUERY_DATASET variables a project and a tiny dataset that exists before the lane does (S-0014/D-5)
BIGQUERY_WORKLOAD_IDENTITY_PROVIDER variable projects/<number>/locations/global/workloadIdentityPools/<pool>/providers/<provider>
BIGQUERY_SERVICE_ACCOUNT variable the service account's email
BIGQUERY_EXECUTE variable optional — any value enables the execution corpus beside the dry run

No key file anywhere: CI authenticates with Workload Identity Federation from the job's OIDC token (S-0014/D-4), which is what id-token: write on the bigquery-dry-run job is for. The BigQuery sandbox needs no billing account and covers this lane whole: a dry run scans no bytes and bills nothing, and the execution corpus reads a few kilobytes.

Once, with gcloud on the project:

PROJECT=<project-id>; NUMBER=$(gcloud projects describe "$PROJECT" --format='value(projectNumber)')
gcloud iam workload-identity-pools create github --location=global --project="$PROJECT"
gcloud iam workload-identity-pools providers create-oidc github \
  --location=global --workload-identity-pool=github --project="$PROJECT" \
  --issuer-uri="https://token.actions.githubusercontent.com" \
  --attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository" \
  --attribute-condition="assertion.repository == 'morzecrew/bloomery'"
gcloud iam service-accounts create bloomery-ci --project="$PROJECT"
SA="bloomery-ci@$PROJECT.iam.gserviceaccount.com"
gcloud projects add-iam-policy-binding "$PROJECT" --member="serviceAccount:$SA" --role=roles/bigquery.jobUser
gcloud iam service-accounts add-iam-policy-binding "$SA" --project="$PROJECT" \
  --role=roles/iam.workloadIdentityUser \
  --member="principalSet://iam.googleapis.com/projects/$NUMBER/locations/global/workloadIdentityPools/github/attribute.repository/morzecrew/bloomery"
bq --project_id="$PROJECT" mk --dataset "$PROJECT:bloomery_ci"

Then grant the service account BigQuery Data Editor on that dataset, and set the variables: the provider is projects/$NUMBER/locations/global/workloadIdentityPools/github/providers/github.

Databricks — EXPLAIN EXTENDED and DESCRIBE QUERY, then the runtime corpus

What Where Value
environment databricks-free create it
DATABRICKS_HOST variable the workspace URL
DATABRICKS_WAREHOUSE_ID variable a serverless SQL warehouse's id (2X-Small is enough)
DATABRICKS_TOKEN secret a personal access token (S-0016/D-5: the documented path on Free Edition)

The workflow is .github/workflows/databricks-live.yaml: weekly and on demand, never on a pull request; compile first, runtime after it, in catalog workspace and schema bloomery_conformance. Databricks Free Edition is enough to bootstrap it (S-0016/D-6) and not enough to run the generated corpus at scale, which is why the lane stays small.

The Spark surrogate (tests/engines/test_databricks_surrogate.py, marker surrogate("databricks_spark")) needs no account: local PySpark from the spark dependency group and a JVM, both of which the engine-e2e job now provides.

Redshift — EXPLAIN, DDL and the targeted corpus

What Where Value
environment redshift create it
REDSHIFT_CONFIGURED repository variable true turns the job on
REDSHIFT_DSN secret a full postgresql:// DSN to a cluster or Serverless workgroup

Nothing here is free for long: a Redshift Serverless trial carries credits for a limited time, and a provisioned cluster bills by the hour. Until one exists the job redshift-live stays off, and the PostgreSQL surrogate (tests/engines/test_redshift_surrogate.py, marker surrogate("redshift_postgres")) is the only local evidence — which is exactly why its test names say PostgreSQL accepted the query, and nothing more (S-0015/D-2).

Reading a run

Every lane above skips rather than fails when its account is missing, and each skip names the variable that is unset (-rs in the job's pytest call prints them). A lane that runs and is green has spoken for the engine; a lane that skipped has not, and the job's log is where the difference shows.