This is the full developer documentation for SQLAnvil
# SQLAnvil
> Define your data transformations in SQL. SQLAnvil compiles, versions, and runs them against BigQuery, PostgreSQL, Supabase, or MySQL/MariaDB.
## Use cases
[Section titled “Use cases”](#use-cases)
Integrate a second Postgres database
Operational data lives in Supabase, but a separate PostgreSQL database holds data that needs to be merged in. SQLAnvil orchestrates cross-database joins and writes the unified result back into Supabase schemas.
Enrich Supabase with BigQuery public data
Join Supabase operational tables against public datasets in BigQuery — ZIP code demographics, postal boundaries, currency rates — and sync the enriched rows back into Supabase for use in your app.
[How & why →](/docs/use-cases/enrich-supabase-with-bigquery/)
ML training data pipeline
Curate, clean, and version training datasets directly in Supabase using SQLX views and incremental tables. Downstream ML pipelines read from stable, tested data models instead of raw tables.
BigQuery reporting warehouse
Pull data from multiple sources — Supabase, Postgres, external APIs — into a curated BigQuery warehouse. SQLAnvil models, tests, and refreshes the reporting layer on a schedule.
eCommerce product feed for Google Ads
Transform your eCommerce catalog in Supabase or Postgres into a clean, structured dataset and export it as JSON for Google Ads product feeds — with assertions to catch missing prices or broken image URLs before they go live.
***
## What Dataform doesn’t do
[Section titled “What Dataform doesn’t do”](#what-dataform-doesnt-do)
**No Supabase or Postgres support.** Dataform OSS dropped non-BigQuery adapters. If your data isn’t in BigQuery, it can’t help you.
**Google-centric by design.** Dataform’s open-source version exists to serve Google Cloud workflows. Features, roadmap, and documentation all assume BigQuery — everything else is out of scope.
**SQLAnvil cares about your data, wherever it lives.**
***
## What SQLAnvil does
[Section titled “What SQLAnvil does”](#what-sqlanvil-does)
BigQuery
Full support for BigQuery — partitioning, clustering, labels, materialized views, incremental upserts via `MERGE`.
PostgreSQL
Idiomatic Postgres DDL — native partitioning, `INSERT … ON CONFLICT` upserts, btree/gin/gist indexes, tablespaces.
Supabase
Extends Postgres with RLS policies, Realtime publications, pgvector indexes, and Supabase Wrappers (FDW).
MySQL / MariaDB
One adapter for MySQL 8 and MariaDB 11 — portable DDL, `ON DUPLICATE KEY UPDATE` upserts, a `mysql:{}` block for indexes/engine/charset, COMMENT metadata, and materialized-view emulation.
SQLX + YAML + JS
Write SQL with config blocks, use `actions.yaml` for bulk definitions, or drive everything via the JavaScript API.
***
SQLAnvil is an open-source fork of [Dataform OSS](https://github.com/dataform-co/dataform) (Apache 2.0), extended with first-class PostgreSQL and Supabase support. It is not affiliated with or endorsed by Google.
# SQLAnvil Cloud
> The hosted control plane for SQLAnvil — connect a repo, configure warehouses, run branch CI, and schedule workflows from a web dashboard.
SQLAnvil Cloud is the hosted **control plane** for your SQLAnvil projects. The open-source CLI compiles and runs your SQL workflows; Cloud adds the layer around it — connecting your repo, brokering warehouse connections, running branch CI on pull requests, scheduling workflows, and giving you a web dashboard to review and operate it all.
It runs at **[app.sqlanvil.com](https://app.sqlanvil.com)** and is **free during early access** — see [pricing](https://sqlanvil.com/pricing/) for the planned plans (billing will only ever start with advance notice and your explicit agreement).
## What Cloud adds over the CLI
[Section titled “What Cloud adds over the CLI”](#what-cloud-adds-over-the-cli)
| | Open-source CLI | SQLAnvil Cloud |
| ----------------------- | --------------- | -------------- |
| Compile + run SQL | ✅ | ✅ (hosted) |
| Selection (tags, deps) | ✅ | ✅ |
| Branch CI on PRs | DIY | ✅ turnkey |
| Scheduled workflows | DIY (cron) | ✅ |
| Run history + dashboard | — | ✅ |
| Connect a GitHub repo | — | ✅ |
Cloud is **agent-native and self-service** — there’s no IDE to learn and no support desk. You author with your own tools (CLI, agents), and Cloud handles review and operation.
## The shape of a project
[Section titled “The shape of a project”](#the-shape-of-a-project)
1. **[Sign in with GitHub](#sign-in)** and create a project.
2. **[Connect a repo](/docs/cloud/connect-a-repo/)** — install the SQLAnvil GitHub App and pick the repository that holds your SQL project.
3. **[Configure a warehouse connection](/docs/cloud/connections/)** — the target your models run against.
4. Open pull requests → SQLAnvil runs **branch CI** and reports back on the PR.
5. **[Set up workflows](/docs/cloud/workflows/)** to run on a cadence.
## Sign in
[Section titled “Sign in”](#sign-in)
Cloud uses **Sign in with GitHub** — the same account that hosts your code. There’s no separate password to manage; GitHub verifies your identity. The first time you connect a repository you’ll also install the SQLAnvil GitHub App (a separate step — see [Connect a repo](/docs/cloud/connect-a-repo/)).
## Next steps
[Section titled “Next steps”](#next-steps)
* [Getting started with Cloud](/docs/cloud/getting-started/) — the full zero-to-green-PR path
* [Connect a repo](/docs/cloud/connect-a-repo/)
* [Warehouse connections](/docs/cloud/connections/) — including [Connect Supabase](/docs/cloud/connections/supabase/)
* [Workflows](/docs/cloud/workflows/)
* [The Code workspace](/docs/cloud/workspace/) — browse branches, preview results, see the DAG
* [The MCP server](/docs/cloud/mcp/) — operate and debug from Claude Code or any MCP client
* [Compilation overrides](/docs/cloud/compilation-overrides/)
* [The ingest token](/docs/cloud/ci/ingest-token/)
* [Data handling](/docs/cloud/data-handling/) — what Cloud stores, and what it never sees
# Billing & your account
> What SQLAnvil Cloud costs today, how billing will work, and how to cancel, delete your account, and understand what happens to your data when you leave.
## What it costs today
[Section titled “What it costs today”](#what-it-costs-today)
**SQLAnvil Cloud is free during early access.** The [pricing page](https://sqlanvil.com/pricing/) shows the planned plans; nothing is metered or charged right now.
When billing starts, it will start **with advance notice**, and you will never be charged without explicitly agreeing to a paid plan first — that’s a commitment in the [Terms of Service](https://sqlanvil.com/terms/), not just a promise on this page. There is no card on file to forget about, because we don’t collect one.
## Canceling
[Section titled “Canceling”](#canceling)
There’s no subscription to cancel during early access — stopping is entirely self-service:
1. **Stop running** — pause or delete [Workflows](/docs/cloud/workflows/), or close PRs; nothing runs unless a PR or schedule triggers it.
2. **Disconnect integrations** — uninstall the SQLAnvil GitHub App from your repository, and revoke the Supabase authorization (Settings → Supabase warehouse, or from your Supabase account’s authorized apps). Revoking ends our ability to run against your warehouse immediately.
3. **Delete projects** — deleting a project removes its runs and its stored configuration.
## Deleting your account
[Section titled “Deleting your account”](#deleting-your-account)
To delete your account and all associated data, email **from the email address on your account**. We confirm and complete deletion within 30 days, per the [Privacy Policy](https://sqlanvil.com/privacy/).
## What happens to your data when you leave
[Section titled “What happens to your data when you leave”](#what-happens-to-your-data-when-you-leave)
* **Your warehouse is untouched** — tables SQLAnvil built for you live in *your* database and stay there. Nothing about leaving Cloud affects your data or your warehouse.
* **Run detail** (compiled SQL + error text) is scrubbed 30 days after each run finishes regardless — see [Data handling](/docs/cloud/data-handling/).
* **Deleting a project** removes its runs and configuration immediately.
* **Authorization tokens** are deleted when you disconnect, revoke, or delete the project. We never held your database password to begin with.
* **Your code** was always in your GitHub repo — there’s nothing to export from us.
# The ingest token
> A per-project secret that lets your CI report SQLAnvil run results back to the Cloud dashboard.
The **ingest token** lets a CI job report run results back to your SQLAnvil Cloud dashboard. It powers the **self-hosted branch-CI** path: execution runs in your own GitHub Actions (your compute), and the result — status + log — is POSTed to Cloud so it shows up alongside your other runs.
This is distinct from the GitHub App’s webhook path (which creates runs directly). The ingest token is specifically for *your* CI calling *us*.
## Generate it
[Section titled “Generate it”](#generate-it)
The token is issued on demand, and only once a repo is connected (a token has no purpose without a repo for CI to live in).
1. In the project’s setup checklist, under **Generate an ingest token**, click **Generate token**.
2. Copy it immediately — only a hash is stored, so it’s shown **once**. (You can regenerate anytime, which invalidates the old one.)
3. Add it to your repository as a secret named **`SQLANVIL_CLOUD_TOKEN`**, and set a repository variable **`SQLANVIL_CLOUD_URL`** to `https://app.sqlanvil.com`.
## How CI uses it
[Section titled “How CI uses it”](#how-ci-uses-it)
Your branch-CI workflow POSTs the run result to the ingest endpoint:
```bash
curl -X POST "$SQLANVIL_CLOUD_URL/api/ingest/runs" \
-H "Authorization: Bearer $SQLANVIL_CLOUD_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "trigger": "branch_ci", "status": "success",
"gitBranch": "my-feature", "prNumber": 42, "log": "..." }'
```
The token authenticates the request to *your* project; the run then appears in the dashboard. Keep it secret — treat it like any other CI credential.
# Compilation overrides
> Per-project overrides that shape how your SQL compiles — default database, schema suffix, and table prefix.
**Compilation overrides** change how your project’s SQL compiles without editing `workflow_settings.yaml` — useful for separating environments (dev vs. prod) on the same codebase. Set them from **Settings → Compilation overrides**.
## Fields
[Section titled “Fields”](#fields)
* **Default database** — the database the compiled SQL targets. For BigQuery this is the project id; for Postgres/Supabase it’s the database name.
* **Schema suffix** — appended to every schema name, so environments don’t collide. For example a suffix of `dev` turns schema `analytics` into `analytics_dev`. (This mirrors Dataform’s workspace suffix.)
* **Table prefix** — prepended to every table/view name. Leave blank for none.
## Why overrides instead of branches in the file
[Section titled “Why overrides instead of branches in the file”](#why-overrides-instead-of-branches-in-the-file)
Your `workflow_settings.yaml` declares the project’s defaults. Overrides let the *same* compiled project target a different schema or database per environment — the basis for running `dev` and `prod` from one repo without divergent config files. They’re applied at compile time, so the generated SQL reflects them before anything runs.
## Relationship to environments
[Section titled “Relationship to environments”](#relationship-to-environments)
Overrides pair with a project’s [warehouse connection](/docs/cloud/connections/) and, where used, named environments — together they determine *where* and *under what names* your models land.
# Connect a repo
> Install the SQLAnvil GitHub App and connect a repository so Cloud can read your project and run branch CI on pull requests.
A SQLAnvil Cloud project is backed by a **GitHub repository** — the one holding your `.sqlx` files and `workflow_settings.yaml`. Connecting it is how Cloud reads your project to compile and run it, and how it receives pull-request events for branch CI.
Connecting goes through the **SQLAnvil GitHub App**, so the connection is *verified* — Cloud only ever sees repositories you’ve explicitly granted it, and it uses short-lived installation tokens rather than a stored personal access token.
## Sign-in vs. the GitHub App
[Section titled “Sign-in vs. the GitHub App”](#sign-in-vs-the-github-app)
These are two separate things, and you’ll touch both:
* **Sign in with GitHub** authenticates *you* (identity only).
* **The GitHub App** grants Cloud scoped access to *specific repositories* (read contents, write PR checks) and delivers pull-request webhooks. You install it once per account/org and choose which repos it can see.
## Connect a repository
[Section titled “Connect a repository”](#connect-a-repository)
1. In a project, open the setup checklist and choose **Connect a repo**.
2. If you haven’t installed the app yet, click **Install SQLAnvil on GitHub**. GitHub asks which account/org and which repositories to grant — pick “only select repositories” and choose the one holding your SQLAnvil project (you can add more later).
3. You’re returned to the project. **Connect a repo** now shows a dropdown of the repositories the app can access. Pick one and click **Connect**.
The project now shows a verified repo connection. You can change it anytime from **Settings → Repo**.
## What Cloud can and can’t do
[Section titled “What Cloud can and can’t do”](#what-cloud-can-and-cant-do)
The app requests the minimum it needs:
* **Contents: read** — to clone and compile your project.
* **Pull requests / Checks: write** — to post the “SQLAnvil CI” status on a PR.
* **Metadata: read** — required by all GitHub Apps.
It has **no write access to your code** and can only see the repositories you selected at install time. Revoke or adjust access anytime from your GitHub account’s **Settings → Applications → Installed GitHub Apps**.
## Branch CI
[Section titled “Branch CI”](#branch-ci)
Once the repo **and** your [Supabase warehouse](/docs/cloud/connections/supabase/) are connected, opening a pull request creates a SQLAnvil **run**: SQLAnvil branches your Supabase project, runs the workflow against that ephemeral branch, and posts a “SQLAnvil CI” check back to the PR (visible in the dashboard with full run detail). For **Postgres/BigQuery/MySQL**, run execution in your own CI and report with [the ingest token](/docs/cloud/ci/ingest-token/) instead.
# Warehouse connections
> Tell SQLAnvil Cloud which warehouse your project targets — adapter type and region. Credentials are brokered, never stored.
A project’s **warehouse connection** tells Cloud where your models run. You set it from **Settings → Warehouse**.
## Fields
[Section titled “Fields”](#fields)
* **Type** — the adapter SQLAnvil compiles for: `supabase`, `postgres`, `bigquery`, or `mysql`. This matches the `warehouse:` in your project’s `workflow_settings.yaml`.
* **Region** — where your warehouse runs (e.g. `us-east-1`). Used to reach the right connection endpoint.
These describe the *shape* of the connection. They’re safe to store and appear in the dashboard.
## Credentials are brokered, not stored
[Section titled “Credentials are brokered, not stored”](#credentials-are-brokered-not-stored)
SQLAnvil Cloud does **not** keep your database password or service-account key. Connections are **brokered at run time** — the runner obtains a short-lived credential for the specific run and discards it. This is the “custodian → broker” principle: Cloud orchestrates access without holding long-lived secrets.
For branch CI, this goes a step further: rather than touching your production database, SQLAnvil provisions a disposable **ephemeral branch** of your warehouse, runs against that, and destroys it — so a PR never writes to production. See [Supabase connections](/docs/cloud/connections/supabase/).
## Compilation overrides
[Section titled “Compilation overrides”](#compilation-overrides)
Alongside the connection, a project carries **[compilation overrides](/docs/cloud/compilation-overrides/)** — default database, schema suffix, and table prefix — that shape how your SQL compiles per environment.
# Supabase connections
> How SQLAnvil Cloud connects to Supabase — the session pooler, and ephemeral branches for branch CI.
Supabase is a first-class SQLAnvil warehouse (see the OSS [Supabase guide](/docs/guides/supabase/)). Cloud connects to it through a **revocable OAuth authorization** — you never give it your database password or service-role key.
## Connect your Supabase account
[Section titled “Connect your Supabase account”](#connect-your-supabase-account)
1. On your project, go to **Settings → Supabase warehouse** and click **Connect Supabase**.
2. **Authorize** on Supabase’s consent screen. You don’t register anything or paste a key — SQLAnvil runs the OAuth app; you’re just granting it access. Choose the organization/project to grant.
3. **Pick the warehouse project.** If your account has more than one project, a dropdown appears — choose the one CI should branch off. (If you granted a single project, it’s selected automatically.) The project must be on the **Pro** plan — branch CI needs Supabase branching.
That’s it. From then on, every PR runs against a fresh branch of that project.
**What we hold.** Only a Supabase OAuth **refresh token**, encrypted at rest, from which each run mints a short-lived (1-hour) access token. Never your DB password or service-role key. You can **revoke** it any time in your Supabase account, or **Reconnect** to re-scope or re-authorize. See [Data handling](/docs/cloud/data-handling/).
## Use the session pooler, not the direct host
[Section titled “Use the session pooler, not the direct host”](#use-the-session-pooler-not-the-direct-host)
Supabase’s **direct** database host (`db.[.supabase.co`) is IPv6-only and won’t resolve from many runtimes. Always connect via the **session pooler**:
* **Host:** `aws-]-.pooler.supabase.com` (the cluster number `` is shown in your project’s *Connect → Session pooler* dialog — it varies per project)
* **Port:** `5432` (session mode — not the `6543` transaction pooler)
* **User:** `postgres.`
* **SSL:** `require`
This is the connection shape Cloud builds automatically; it’s also what you’d put in a local `.df-credentials.json`.
## Branch CI uses ephemeral branches
[Section titled “Branch CI uses ephemeral branches”](#branch-ci-uses-ephemeral-branches)
For a pull request, SQLAnvil doesn’t touch your production database. It:
1. Creates a disposable **Supabase branch** of your project (via the Management API),
2. Runs the project against that fresh branch — models materialize and **assertions execute**,
3. Reports the result, then **destroys the branch** so nothing is left billing.
Branching is available on Supabase **Pro** projects and works through the Management API directly — no GitHub-integration setup or `supabase/` migrations directory required. SQLAnvil owns the full ephemeral-environment lifecycle.
## Credentials
[Section titled “Credentials”](#credentials)
Cloud brokers the branch credentials at run time and never stores your database password — see [Warehouse connections](/docs/cloud/connections/).
# Data handling
> What SQLAnvil Cloud stores when it runs your project, where it lives, how long it's kept, and what you should never put in a model.
SQLAnvil Cloud is a control plane: it orchestrates runs and shows you results. This page is the plain statement of what that means for your data — what we store, where, for how long, and the one thing you should keep out of your SQL.
## What we store
[Section titled “What we store”](#what-we-store)
When a run executes, we record, per action (table/view/assertion/operation/export/import):
* **Metadata** — the target (`schema.name`), type, status, start/duration, the source file, and the identity the run connected as. Low-sensitivity; kept as run history.
* **Run detail** — the **compiled SQL** for the action and, on failure, the **verbatim warehouse error**. This is what powers the run-detail view and “Copy failure context”.
We also keep the run’s overall status, branch/PR/commit, and a tail of the run log.
## Where it lives, and who can read it
[Section titled “Where it lives, and who can read it”](#where-it-lives-and-who-can-read-it)
* Stored in our control-plane database (Supabase Postgres), **encrypted at rest**.
* Protected by **row-level security**: a run and its detail are readable only by the project’s owner. Other customers cannot see your runs.
* Run detail is stored in **plaintext** (not end-to-end encrypted). Operationally this means SQLAnvil *could* read it; we use it only to run and display your runs, never for anything else, and there is no support team browsing your data ([SQLAnvil Cloud is fully self-service](/docs/cloud/)).
## Retention
[Section titled “Retention”](#retention)
The **sensitive fields — the compiled SQL and the error text — are scrubbed 30 days after a run finishes.** The lightweight metadata row (target, status, timing) is kept so your run history stays intact. Deleting a project removes its runs.
## What we never store
[Section titled “What we never store”](#what-we-never-store)
* **Warehouse credentials.** They are brokered at run time, never custodied — see [Warehouse connections](/docs/cloud/connections/).
* **Your table data.** We don’t copy rows out of your warehouse. We store the *SQL text* and *error text* of a run — not query results. (But note the next section: SQL and errors can *contain* data.)
## Important: don’t put secrets or sensitive data in your SQL
[Section titled “Important: don’t put secrets or sensitive data in your SQL”](#important-dont-put-secrets-or-sensitive-data-in-your-sql)
Because run detail is stored in plaintext, **treat your compiled SQL and warehouse errors as visible to the platform.** Two ways data can leak into them:
* **Inlined literals** — `select 'my-secret-token' as t` or a hard-coded PII value lands verbatim in the stored compiled SQL.
* **Error echoes** — a warehouse error can quote a row value (e.g. a unique-constraint violation printing an email address) into the stored error text.
This is standard hygiene for *any* CI system (build logs are not a vault). The rule:
* **Never inline secrets or sensitive values in models.** Reference secrets through your warehouse’s own mechanism, not SQL literals.
* Keep PII out of hard-coded values, seed `VALUES (...)`, and assertion messages.
If you have a hard requirement that the platform be unable to read run detail at all, that’s a client-side-encryption model — plaintext + RLS + 30-day scrub is the default today. If your team needs the stronger model, open a [GitHub Discussion](https://github.com/SQLAnvil/sqlanvil/discussions) or email .
# Getting Started with Cloud
> The full path from zero — author a project, connect your repo and Supabase, and get hosted branch CI on every PR.
SQLAnvil Cloud runs your project’s CI **for you**: open a pull request, and it builds your workflow against a throwaway branch of your Supabase project, then reports pass/fail on the PR. You never hand it your database password — it runs on a short-lived, revocable authorization you can pull at any time.
There are two parts: **author** the project (in your own tools), then **set it up in Cloud**.
## Part A — Author the project
[Section titled “Part A — Author the project”](#part-a--author-the-project)
Cloud runs a normal SQLAnvil project from a GitHub repo. If you don’t have one yet:
1. **Scaffold a project** — a `workflow_settings.yaml` + a `definitions/` folder. Start from the [`supabase-sqlanvil-starter`](https://github.com/SQLAnvil/supabase-sqlanvil-starter) template, or see the OSS [Getting Started](/docs/getting-started/).
2. **Pin the core version.** In `workflow_settings.yaml`, set `warehouse: supabase` and pin **`sqlanvilCoreVersion:`** to the current release — `sqlanvil init` writes this for you, and [What’s New](/docs/whats-new/) shows the latest version. Pinning a current version keeps the hosted runner fast (no per-run install).
3. **Author + push.** Build your models with the CLI, the Claude skill, or the VS Code extension, and push the repo to GitHub.
## Part B — Set it up in Cloud
[Section titled “Part B — Set it up in Cloud”](#part-b--set-it-up-in-cloud)
1. **Sign in.** Go to [app.sqlanvil.com](https://app.sqlanvil.com) → **Continue with GitHub**. Your GitHub account *is* your identity — no separate password.
2. **Create a project.** Give it a name. The setup checklist guides the rest.
3. **Connect the repo.** **Install SQLAnvil on GitHub**, grant access to your repository, then pick it from the dropdown and **Connect**. See [Connect a repo](/docs/cloud/connect-a-repo/).
4. **Connect Supabase.** In **Settings → Supabase warehouse → Connect Supabase**, authorize once. If your account has more than one project, pick the **warehouse project** to branch off — it must be on the **Pro** plan (branch CI needs Supabase branching). You never register anything or paste a key: SQLAnvil runs the OAuth app; you just click Connect, and we store only a **revocable authorization**, never your DB credentials. See [Connect Supabase](/docs/cloud/connections/supabase/).
5. **Open a pull request.** SQLAnvil mints a scoped, short-lived token, creates an **ephemeral branch** of your Supabase project, runs your workflow against it, posts a **SQLAnvil CI** check on the PR, and destroys the branch. Green or red, right on the PR.
6. **Debug from the run page.** Every run shows each action’s status, timing, the executed SQL, and — on failure — the verbatim error plus **Copy failure context** (a one-click bundle to paste into an AI agent).
7. **Schedule a workflow** *(optional).* On the project’s **Schedules** page, add a cron schedule with an optional tag selection. See [Workflows](/docs/cloud/workflows/).
## Not on Supabase?
[Section titled “Not on Supabase?”](#not-on-supabase)
Hosted branch CI targets **Supabase** today: it relies on [Supabase branching](https://supabase.com/docs/guides/deployment/branching), which requires a Supabase project on their **Pro plan** (a free-tier project can’t run branch CI). For **Postgres, BigQuery, or MySQL** — or Supabase without branching — run execution in your own CI (e.g. GitHub Actions) and report results to the dashboard with an [ingest token](/docs/cloud/ci/ingest-token/) — same run history and PR checks, your compute.
## What Cloud does and doesn’t do
[Section titled “What Cloud does and doesn’t do”](#what-cloud-does-and-doesnt-do)
* **Authoring** stays in your tools — the CLI and your AI agents. Cloud doesn’t replace your editor.
* **Reviewing and operating** — run history, run detail, schedules, PR checks — is the dashboard’s job.
* **Credentials** — we broker, we don’t custody. See [Data handling](/docs/cloud/data-handling/).
* **Two things hosted runs reject at compile time**, with a clear error before anything is provisioned: *local* file paths on [exports](/docs/guides/exports/)/[imports](/docs/guides/imports/) (the runner’s disk is ephemeral — stage on `s3://`/`gs://` instead), and [python script actions](/docs/guides/python-actions/) (user code doesn’t execute on the shared runner — run those projects with the local CLI or your own CI).
# MCP server
> Connect Claude Code or any MCP client to SQLAnvil Cloud — list projects and runs, debug failures with executed SQL, trigger workflows, and cut releases.
SQLAnvil Cloud exposes an **MCP server** (Model Context Protocol, Streamable HTTP) so AI agents can operate and debug your hosted projects directly: inspect run history, pull a failure’s executed SQL and verbatim warehouse errors, trigger workflow runs, cut releases, and refresh catalogs.
It is deliberately **not an authoring surface** — you edit and compile your project in your own repo with your own tools. The MCP covers what’s hosted: runs, workflows, releases, and their state.
## Connect
[Section titled “Connect”](#connect)
1. Create a personal API token at **app.sqlanvil.com → API tokens** (shown once).
2. Add the server to Claude Code:
```bash
claude mcp add --transport http sqlanvil https://app.sqlanvil.com/api/mcp \
--header "Authorization: Bearer sa_…"
```
Any MCP client that speaks Streamable HTTP works the same way. Revoking the token in the dashboard cuts access immediately.
## Tools
[Section titled “Tools”](#tools)
| Tool | What it does |
| ------------------------------- | ---------------------------------------------------------------------------- |
| `list_projects` / `get_project` | Your projects; config, warehouse connection, per-branch catalogs |
| `list_workflows` | Each workflow’s selection, schedule, release mode, enabled state |
| `list_runs` / `get_run` | Run history with filters; full per-action detail + log |
| `get_run_failure` | **The debugging bundle**: failed actions with executed SQL + verbatim errors |
| `list_releases` | Pinned compilation snapshots (branch, environment, commit, core version) |
| `run_workflow_now` | Enqueue an immediate run; returns a run id to poll |
| `cut_release` | Compile the branch head into a new pinned snapshot |
| `refresh_catalog` | Recompile a branch’s tag/action/environment catalog |
| `set_workflow_enabled` | Pause or resume a workflow’s schedule |
Runs are asynchronous: operate tools return a `run_id` immediately; poll `get_run` for the outcome (runs typically take one to a few minutes).
## Data shapes
[Section titled “Data shapes”](#data-shapes)
Graph-adjacent payloads reuse the engine’s own JSON forms — action targets as `schema.name`, compiled SQL as `sqlanvil compile --json` emits it — so an agent that already knows the CLI’s output needs no second vocabulary. Everything an MCP tool returns is data the dashboard already shows you, scoped to your account by your token.
# Workflows
> Branch-bound, reusable run configurations — a graph selection plus an optional cron, run on demand or on a schedule against your real warehouse.
A **workflow** is a named, reusable run configuration: *run **this selection** of the graph, against **this branch**, with **these dependency options** — on a schedule, or on demand.* It’s the hosted home for both your scheduled production runs and one-click “Run now” runs.
Manage them from a project’s **Workflows** page.
## Fields
[Section titled “Fields”](#fields)
* **Name** — a label for the workflow.
* **Branch** — the git branch the run compiles and executes (e.g. `main`).
* **Tags / Actions** — the selection. Empty runs the full graph; otherwise only actions matching the chosen tags (`--tags`) and/or the explicitly chosen actions (`--actions`, by `schema.name`). Tags and actions compose as a union. The dropdowns are populated from the branch’s compiled **catalog** — use **Refresh** to recompile it after the branch changes (the editor shows which commit the catalog is from).
* **Include dependencies** — also run the **upstream** actions the selection depends on (`--include-deps`). Needs a non-empty selection.
* **Include dependents** — also run the **downstream** actions that depend on the selection (`--include-dependents`). Needs a non-empty selection.
* **Environment** — an optional named environment (compilation overrides).
* **Schedule** — optional. A standard 5-field cron expression, e.g. `0 6 * * *` (daily at 06:00), evaluated in the workflow’s timezone. Leave it off for an on-demand-only workflow.
## How it runs
[Section titled “How it runs”](#how-it-runs)
**Workflow runs execute against the real warehouse** — their writes are the point, and they persist. (Pull-request CI is different: it runs on a disposable database branch that’s destroyed afterwards. A workflow run is the production counterpart.)
For Supabase warehouses connected via OAuth, no database password is ever stored: each run mints a short-lived, single-purpose database login through your Supabase connection, uses it for the run, and disables it immediately after. Objects it creates are owned by `postgres`, exactly as if you’d run the CLI yourself.
Scheduled workflows fire from a periodic tick; **Run now** enqueues the same run immediately. If a workflow’s previous run is still going, the tick skips that firing rather than overlapping it. Every firing appears in the project’s run history tagged with its workflow, with full per-action detail.
## Releases — pinned compilation snapshots
[Section titled “Releases — pinned compilation snapshots”](#releases--pinned-compilation-snapshots)
By default a workflow compiles the **branch head** at run time. Setting **Run from → Latest release** pins it to a **release**: a frozen compilation of the branch (and environment) at an exact commit. Pinned workflows keep running that exact code even as the branch moves — until a new release is cut.
* **Cut release** on the Workflows page compiles the branch head into a new snapshot (per environment, since environment overrides change the compiled SQL).
* **Pushes to a pinned branch cut a release automatically** — merging to `main` refreshes what your scheduled workflows run, Dataform-release-config style.
* Each release records the commit, action count, and the full compiled graph for audit; every run links back to the release it executed.
## Selection mirrors the CLI
[Section titled “Selection mirrors the CLI”](#selection-mirrors-the-cli)
A workflow is just a saved version of a run you could do by hand:
```bash
sqlanvil run . --tags daily --include-dependents --environment prod
```
Tag/action selection and both dependency-expansion flags behave exactly like the CLI’s.
# Code (read-only workspace)
> Browse a branch's files with syntax highlighting, flip any model to its compiled SQL, and jump to VS Code — without a cloud editor.
The **Code** page is a read-only workspace: pick a branch, browse the repo tree, and view any file with syntax highlighting. It is deliberately **not an editor** — authoring happens locally with your agent and git — but everything you’d want for *review* is here:
* **Branch-scoped tree** — files come straight from GitHub via the SQLAnvil app installation; nothing is cloned or stored.
* **Compiled SQL toggle** — for a `.sqlx` model, flip between the source and the SQL it compiled to. The compiled view comes from the branch’s **latest release** (pinned compilation snapshot) and is labeled with the commit it was compiled at — cut a release on the Workflows page if the branch doesn’t have one yet.
* **Model metadata** — target, action type, tags, dependencies, and dependents, from the same compiled graph.
* **Results preview** — for a model with a compiled release, the **Results** tab queries the warehouse directly (first 100 rows), two ways:
* **Current data** reads the materialized object — what the last completed run produced. Run the workflow (*Run now*), wait for it to finish, and preview again to see the fresh data.
* **Compiled query** executes the release’s stored SELECT without materializing anything — what the model *would* produce, even before it has ever run. For an assertion, rows returned are failures.
Previews are read-only by construction: the SQL comes from the stored release (never from the browser), runs through the same revocable Supabase authorization as workflow runs, and is wrapped so anything but a single SELECT is rejected. Nothing is stored — rows go straight to your screen.
* **Graph view** — the branch’s latest release drawn as a dependency DAG, data flowing left to right: tables, views, incrementals, assertions, sources and operations color-coded, each node clickable through to its file. Because it renders from the stored release, the graph always matches a specific commit (labeled), not a guess about the working tree.
* **Open in VS Code** — one click opens the file in [github.dev](https://github.dev), the full VS Code web IDE against your own repo and GitHub login, for when you want real editor ergonomics.
Together with run history and per-action detail, this completes the review loop: *read the model, read what it compiled to, read how it ran* — all without granting anything write access.
# Getting Started
> Install SQLAnvil, create a project, and run your first SQL workflow.
SQLAnvil compiles SQLX files and action configs into SQL, then executes them against your data warehouse. Projects are driven by a `workflow_settings.yaml` file that declares your warehouse connection and default schema.
## Prerequisites
[Section titled “Prerequisites”](#prerequisites)
* Node.js 20 LTS or later
* One of: a BigQuery project with a service account, a PostgreSQL database, a Supabase project, or a MySQL/MariaDB database
## Install
[Section titled “Install”](#install)
```bash
npm install -g @sqlanvil/cli
```
Or run without installing:
```bash
npx @sqlanvil/cli --help
```
## Create a project
[Section titled “Create a project”](#create-a-project)
1. **Initialize** a new SQLAnvil project (pick your warehouse):
```bash
sqlanvil init my-project --warehouse postgres # or: supabase | bigquery | mysql
cd my-project
```
Prefer to be walked through it? **`sqlanvil init --interactive`** (1.23+) asks the same questions one at a time — including whether to start fresh or [convert an existing Dataform project](/docs/guides/migrate-dataform/) — and writes your `.df-credentials.json` from a credentials Q\&A, so steps 1–2 collapse into one. Want the opposite? `sqlanvil init --bare` scaffolds only the directories and config, no sample files.
Every generated project also includes an **`AGENTS.md`** (1.24+) — a warehouse-tailored guide in the cross-agent standard that Codex, Cursor, Gemini CLI, and 30+ tools read natively, plus a `CLAUDE.md` bridge for Claude Code — so any [coding agent](/docs/guides/ai-agents/) pointed at your repo knows the sqlanvil dialect from the first prompt.
The default creates a sample project in the shape real ones take — source declarations, staging views over them, output tables built from the staging layer, and a business-rule assertion:
```plaintext
my-project/
├── definitions/
│ ├── sources/
│ │ ├── app_orders.sqlx ← declaration: a table your app already has
│ │ └── bigquery_zip_codes.sqlx ← declaration: BigQuery public data via a connection
│ ├── intermediate/
│ │ ├── stg_app_orders.sqlx ← staging views over the sources
│ │ └── stg_zip_codes.sqlx
│ ├── outputs/
│ │ ├── sales/daily_sales.sqlx ← tables built from the staging views
│ │ └── reporting/product_revenue.sqlx
│ │ reporting/orders_by_region.sqlx ← joins both staged sources
│ └── test/assert_sales_amounts_positive.sqlx
├── includes/ ← shared JavaScript helpers
└── workflow_settings.yaml ← includes the bigquery_public connection
```
`compile` works immediately. To `run`, point the `app_orders` declaration at a real table (and add BigQuery credentials for the `zip_codes` source, or delete that pair) — `sqlanvil validate` tells you exactly what’s still missing. MySQL projects skip the BigQuery source (cross-warehouse connections need a Postgres/Supabase warehouse).
2. **Add your connection.** `init` already wrote `workflow_settings.yaml` (with your `warehouse:` and default schema) plus a `.df-credentials.json` template. Fill in the credentials file — it’s gitignored, so secrets never get committed:
* PostgreSQL
.df-credentials.json
```json
{
"host": "db.example.com", "port": 5432, "database": "analytics",
"user": "sqlanvil_writer", "password": "...", "sslMode": "require", "defaultSchema": "public"
}
```
* Supabase
```json
// .df-credentials.json — Session pooler (copy the host verbatim from the dashboard Connect dialog)
{
"host": "aws-1-.pooler.supabase.com", "port": 5432, "database": "postgres",
"user": "postgres.", "password": "...", "sslMode": "require", "defaultSchema": "public"
}
```
* BigQuery
```json
// .df-credentials.json — projectId + location. Auth via `gcloud auth application-default login`,
// or add a "credentials" field containing a service-account key JSON.
{ "projectId": "my-gcp-project", "location": "US" }
```
* MySQL / MariaDB
```json
// .df-credentials.json — no defaultSchema field: the defaultDataset in
// workflow_settings.yaml IS the MySQL database. sslMode: "disable" for local Docker.
{
"host": "db.example.com", "port": 3306, "database": "analytics",
"user": "sqlanvil_writer", "password": "...", "sslMode": "require"
}
```
3. **Write your first action** (or start from the generated demo models) in `definitions/my_view.sqlx`:
```sql
config {
type: "view",
description: "My first SQLAnvil view."
}
SELECT 1 AS id, 'hello' AS greeting
```
4. **Compile** to inspect the SQL that will run (no database needed):
```bash
sqlanvil compile .
```
5. **Run** against your warehouse:
```bash
sqlanvil run .
```
With the sample project this stages your orders, builds `daily_sales` and the reporting tables, and checks the assertion. `run` reads `.df-credentials.json` from the project directory by default; pass `--credentials ` only to use a differently-named or relocated file.
## Project structure
[Section titled “Project structure”](#project-structure)
| Path | Purpose |
| ------------------------ | -------------------------------------------------- |
| `workflow_settings.yaml` | Warehouse connection, default schema/dataset, vars |
| `definitions/` | SQLX files, SQL files, `actions.yaml`, JS files |
| `includes/` | Shared JS macros and constants |
| `package.json` | Declares `@sqlanvil/core` version and any packages |
## Action types
[Section titled “Action types”](#action-types)
| Type | Creates |
| ------------- | -------------------------------- |
| `table` | A full-replace table |
| `view` | A SQL view |
| `incremental` | An incrementally-updated table |
| `assertion` | A data quality test |
| `operation` | Arbitrary SQL statements |
| `declaration` | A reference to an external table |
See the **Reference** section in the sidebar for full API documentation on each action type.
# Use with AI agents
> Give coding agents (Claude Code, Codex, Cursor, Gemini CLI, and others) working knowledge of sqlanvil — the official Agent Skill, and the Cloud MCP server for operating runs.
sqlanvil is a Dataform fork, and coding agents know Dataform — which means their instincts are *mostly* right and occasionally, specifically wrong (BigQuery config blocks, credential shapes, `;` separators, a `dataform` CLI that isn’t there). Two official integrations close that gap.
## The Agent Skill (authoring)
[Section titled “The Agent Skill (authoring)”](#the-agent-skill-authoring)
The **`sqlanvil-engineering-fundamentals`** skill teaches an agent the exact deltas between Dataform/BigQuery habits and correct sqlanvil code — config blocks, credentials, DDL, statement separators, cross-warehouse connections, the MySQL inversions, and the CLI. It follows the open [Agent Skills](https://agentskills.io) format supported by Claude Code, Codex CLI, Cursor, Gemini CLI, GitHub Copilot, and dozens of other tools.
Install it with the [skills CLI](https://skills.sh), which detects your installed agents:
```bash
npx skills add SQLAnvil/agent-skills
```
Or install manually: copy `skills/sqlanvil-engineering-fundamentals/` from [SQLAnvil/agent-skills](https://github.com/SQLAnvil/agent-skills) into your agent’s skills directory (for Claude Code, `~/.claude/skills/`).
The skill is versioned with the engine — it pins the current release and marks version-gated features (e.g. `≥1.22`), and is updated as part of every sqlanvil release.
## AGENTS.md in every project (1.24+)
[Section titled “AGENTS.md in every project (1.24+)”](#agentsmd-in-every-project-124)
Projects generated by `sqlanvil init` (all modes), `sqlanvil migrate-dataform`, and the Cloud wizard include a warehouse-tailored **`AGENTS.md`** — the repo-scoped guide in the cross-agent [AGENTS.md standard](https://agents.md) — plus a one-line `CLAUDE.md` bridge for Claude Code. That covers agents working *inside one of your project repos* with zero installation; the skill below adds the fuller guide to the agent itself, across all repos. Pre-existing `AGENTS.md`/`CLAUDE.md` files are never overwritten.
## llms.txt (docs for agents)
[Section titled “llms.txt (docs for agents)”](#llmstxt-docs-for-agents)
The full documentation is also published in the [llms.txt](https://llmstxt.org) format for web-capable agents: [`/llms.txt`](/llms.txt) (index), [`/llms-small.txt`](/llms-small.txt) (abridged), and [`/llms-full.txt`](/llms-full.txt) (complete docs in one file).
## The MCP server (operating, Cloud)
[Section titled “The MCP server (operating, Cloud)”](#the-mcp-server-operating-cloud)
The skill covers *writing* projects; for *operating* them on [SQLAnvil Cloud](https://app.sqlanvil.com) — inspecting runs, debugging failures, checking workflow status — connect your agent to the remote MCP server at `https://app.sqlanvil.com/api/mcp`. See the [MCP server guide](/docs/cloud/mcp/) for token setup and the available tools.
## Which do I need?
[Section titled “Which do I need?”](#which-do-i-need)
| You want an agent to… | Use |
| -------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| Write or edit `.sqlx` models, configs, credentials | the Agent Skill |
| Convert a Dataform project | the Agent Skill + [`sqlanvil migrate-dataform`](/docs/guides/migrate-dataform/) |
| Investigate a failed Cloud run, check schedules | the MCP server |
| Answer business questions about your data (BI copilots, text-to-SQL) | [warehouse-persisted metadata](/docs/guides/metadata/) |
| All of the above | both — they’re complementary |
# Artifacts & Catalog
> SQLAnvil writes your compiled graph and run history to Parquet under target/ — query it with SQL (sqlanvil query), summarize it (sqlanvil inspect), or generate an HTML catalog (sqlanvil docs).
Every `compile` and `run` writes **queryable artifacts** under `target/` — a catalog of your models and a history of your runs, as **Parquet**. Query them with SQL, summarize them, or generate a browsable HTML catalog. No warehouse connection needed to read them.
Note
Requires `@sqlanvil/cli` **1.10.0** or newer. Add `target/` to your `.gitignore`.
## What gets written
[Section titled “What gets written”](#what-gets-written)
* `compile` → `target/catalog/{actions,dependencies,columns}.parquet` (models, object-level dependency edges, documented columns).
* `run` → `target/runs/run_.parquet` (one row per action: status, timing, error).
Writing is **best-effort** — it never fails your compile/run. Disable with `--no-artifacts`.
## Query it
[Section titled “Query it”](#query-it)
```bash
sqlanvil query "select type, count(*) as n from actions group by 1 order by 1"
sqlanvil query "select readable_name, error_message from runs
where run_id = (select max(run_id) from runs) and status = 'FAILED'"
sqlanvil query "select * from dependencies" --json
```
Views available: **`actions`**, **`dependencies`**, **`columns`**, **`runs`**. It’s just DuckDB SQL — join, filter, aggregate however you like. The Parquet files are also readable directly by pandas/polars, DuckDB, or as external tables in BigQuery/Snowflake.
## Summarize it
[Section titled “Summarize it”](#summarize-it)
```bash
sqlanvil inspect
```
Prints action counts by type, the latest run’s status/timing, and any recent failures. `--json` for a structured summary.
## Browse it
[Section titled “Browse it”](#browse-it)
```bash
sqlanvil docs # writes target/docs/index.html
```
A self-contained HTML catalog — models with their type, tags, description, columns, what they depend on, and last-run status, with a filter box. One file, no server; open it in a browser.
# BigQuery
> Configure SQLAnvil for Google BigQuery — connection, partitioning, clustering, labels, and incremental tables.
## Connection
[Section titled “Connection”](#connection)
Non-secret settings in `workflow_settings.yaml` (committed):
workflow\_settings.yaml
```yaml
warehouse: bigquery
defaultProject: my-gcp-project # GCP project ID
defaultLocation: US # BigQuery region
defaultDataset: analytics # default dataset for actions
sqlanvilCoreVersion: 1.24.0 # pin the release you installed — `sqlanvil init` writes this
```
Auth lives in `.df-credentials.json` (gitignored). For BigQuery you typically only need the project and location — credentials resolve via [Application Default Credentials](https://cloud.google.com/docs/authentication/application-default-credentials) (`gcloud auth application-default login` locally, or an attached service account in CI):
.df-credentials.json
```json
{ "projectId": "my-gcp-project", "location": "US" }
```
To use an explicit service account instead of ADC, add its key JSON as a `credentials` field.
For **keyless** setups, provide a short-lived OAuth2 `accessToken` instead of a key — SQLAnvil authenticates BigQuery with it directly (precedence: `accessToken` → `credentials` key → ADC). This suits callers that already run inside GCP and can mint a token by impersonating a service account (e.g. SQLAnvil Cloud’s runner), so no key is ever written. Tokens are short-lived (≈1h):
.df-credentials.json
```json
{ "projectId": "my-gcp-project", "location": "US", "accessToken": "ya29...." }
```
## Partitioning and clustering
[Section titled “Partitioning and clustering”](#partitioning-and-clustering)
Use `partitionBy` and `clusterBy` in the action config block:
```sql
-- definitions/orders.sqlx
config {
type: "table",
partitionBy: "DATE(created_at)",
clusterBy: ["customer_id", "region"],
partitionExpirationDays: 90
}
SELECT
order_id,
customer_id,
region,
created_at
FROM ${ref("raw_orders")}
```
Note
`partitionBy` and `clusterBy` are **BigQuery-only** fields. They have no effect when targeting Postgres or Supabase. See [PostgreSQL guide](/guides/postgres/) for Postgres-native partitioning syntax.
## Incremental tables
[Section titled “Incremental tables”](#incremental-tables)
SQLAnvil generates a BigQuery `MERGE` statement for incremental tables:
```sql
-- definitions/events_incremental.sqlx
config {
type: "incremental",
uniqueKey: ["event_id"],
partitionBy: "DATE(event_timestamp)"
}
SELECT
event_id,
event_timestamp,
user_id,
event_name
FROM ${ref("raw_events")}
${when(incremental(), `WHERE event_timestamp > (SELECT MAX(event_timestamp) FROM ${self()})`)}
```
## Labels
[Section titled “Labels”](#labels)
Attach BigQuery labels to any action for cost tracking:
```sql
config {
type: "table",
labels: {
team: "analytics",
env: "production"
}
}
SELECT ...
```
## Materialized views
[Section titled “Materialized views”](#materialized-views)
```sql
config {
type: "view",
materialized: true,
partitionBy: "DATE(created_at)"
}
SELECT ...
```
Caution
Materialized views in BigQuery are auto-refreshed. In Postgres, materialized views require an explicit `REFRESH MATERIALIZED VIEW` — SQLAnvil handles this via the Postgres adapter.
## BigQuery reservations
[Section titled “BigQuery reservations”](#bigquery-reservations)
To run actions on a specific BigQuery reservation (slot commitment):
workflow\_settings.yaml
```yaml
defaultReservation: projects/my-project/locations/US/reservations/my-reservation
```
Or per-action in YAML:
definitions/actions.yaml
```yaml
actions:
- table:
filename: heavy_model.sql
reservation: projects/my-project/locations/US/reservations/heavy
```
## Additional BigQuery options
[Section titled “Additional BigQuery options”](#additional-bigquery-options)
Pass arbitrary BigQuery table options via `additionalOptions`:
```sql
config {
type: "table",
additionalOptions: {
kms_key_name: "projects/my-proj/locations/us/keyRings/my-ring/cryptoKeys/my-key"
}
}
SELECT ...
```
## Assertions
[Section titled “Assertions”](#assertions)
SQLAnvil generates assertion views in BigQuery under your `defaultDataset`. A failing assertion (one that returns rows) is reported as an error:
```sql
config {
type: "assertion"
}
SELECT * FROM ${ref("orders")} WHERE order_total < 0
```
# Named Environments
> Run a SQLAnvil project against dev, staging, and prod with --environment — per-environment overrides and credentials, secrets kept out of committed config.
Run the same project against dev, staging, and prod without juggling flags. Define each **environment** once in `workflow_settings.yaml` — its schema suffix, vars, and **its own credentials file** — and select it with `--environment ` on `compile`, `run`, or `test`.
Note
Requires `@sqlanvil/core` **1.7.0** or newer. Pin `sqlanvilCoreVersion: 1.7.0`.
Why not just `--schema-suffix`? A schema suffix only renames output schemas — it can’t point at a different **database or credentials**. Prod and staging usually differ by more than a name. A named environment bundles the suffix *and* the connection; `--schema-suffix` stays the low-level primitive.
## Define environments
[Section titled “Define environments”](#define-environments)
Add an `environments:` map to `workflow_settings.yaml`. It holds only **non-secret** overrides plus a **pointer** to each environment’s credentials file — never the secrets themselves:
```yaml
warehouse: postgres
defaultDataset: analytics
environments:
dev:
schemaSuffix: dev # output schemas get a _dev suffix
credentials: .df-credentials.dev.json
prod:
defaultDatabase: prod_db
vars:
region: us-prod
credentials: .df-credentials.prod.json
```
| Field | Effect |
| ----------------- | ------------------------------------------------------------------------- |
| `schemaSuffix` | Appends `_` to every output schema (same as `--schema-suffix`) |
| `vars` | Variables for `${sqlanvil.projectConfig.vars.…}` (merged per-key) |
| `defaultDatabase` | Overrides the default database / BigQuery project |
| `defaultLocation` | Overrides the default location (BigQuery) |
| `credentials` | Path (relative to the project dir) to this environment’s credentials file |
Caution
Each `credentials:` is just a **path** — the host/password lives in that gitignored file. `sqlanvil init` ignores `.df-credentials*.json`, so per-env files (`.df-credentials.dev.json`, etc.) stay out of version control. Nothing secret goes in the committed `workflow_settings.yaml`.
## Use an environment
[Section titled “Use an environment”](#use-an-environment)
```bash
sqlanvil run . --environment prod
# loads prod's overrides (defaultDatabase, vars) AND its credentials file
```
`--environment` works on `compile`, `run`, and `test`. `compile` applies the config overrides; `run`/`test` additionally connect with the environment’s credentials.
## Precedence
[Section titled “Precedence”](#precedence)
**Explicit CLI flag > environment > `workflow_settings` defaults.** An explicit flag always wins, so you can override one piece ad hoc:
```bash
sqlanvil run . --environment dev --schema-suffix qa
# dev's credentials + vars, but the schema suffix is qa
```
* `vars` merge **per-key** — a `--vars` key overrides that key from the environment, which overrides `workflow_settings`; other keys are preserved.
* Credentials: explicit `--credentials ` > the environment’s `credentials` > the default `.df-credentials.json`.
## Errors
[Section titled “Errors”](#errors)
* `--environment ` with no matching entry fails fast: `Environment "" not found. Available environments: …`.
* `--environment` with no `environments:` block: `No environments defined in workflow_settings.yaml`.
Note
Per-environment **warehouse type** isn’t supported — `warehouse:` stays global (prod/staging differ by database/credentials, not engine). Declared sources are exempt from `schemaSuffix`, so a `dev` run reads your real sources while writing to `_dev` schemas.
# File Exports
> Write query results to Parquet/CSV/JSON files with type "export" — BigQuery EXPORT DATA, or Postgres/Supabase via DuckDB to s3/gs/local.
A `type: "export"` action writes the result of a query to a **Parquet / CSV / JSON** file at a cloud or local location. It’s a sink: it runs after the relations its query `${ref()}`s and produces a file instead of a warehouse object.
Note
Requires `@sqlanvil/core` **1.8.0** or newer. Pin `sqlanvilCoreVersion: 1.8.0`.
```sql
-- definitions/orders_export.sqlx
config {
type: "export",
export: {
location: "s3://my-bucket/orders/", // folder/prefix URI
format: "parquet", // parquet | csv | json (json = JSONL)
overwrite: true // default true
}
}
SELECT order_id, total::text AS total FROM ${ref("orders")} ORDER BY order_id
```
## How it runs per warehouse
[Section titled “How it runs per warehouse”](#how-it-runs-per-warehouse)
* **BigQuery** — compiles to native `EXPORT DATA OPTIONS(uri='gs://…/_*.parquet', …) AS `. In-engine, using the warehouse’s own GCS access. **`gs://` only.**
* **Postgres / Supabase** — the CLI runs the export via **DuckDB**: it attaches the database read-only and `COPY (SELECT … FROM postgres_query('pg', )) TO ''`. The SELECT runs on Postgres; DuckDB only encodes + uploads. Targets `s3://`, `gs://`, **Supabase Storage** (its S3-compatible endpoint), or a local path.
MySQL/MariaDB export isn’t supported yet.
## `export` options
[Section titled “export options”](#export-options)
| Field | Description |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `location` | **Required.** Destination folder/prefix URI; SQLAnvil derives the filename. `${}` interpolation works (dates, `schemaSuffix`, vars). |
| `format` | **Required.** `parquet`, `csv`, or `json` (JSONL). |
| `overwrite` | Replace an existing file/object. Default `true`. |
| `filename` | Output base filename. Defaults to the action name. |
| `options` | Format-specific passthrough (e.g. `compression`). |
Caution
BigQuery **requires** a `*` in the export URI — SQLAnvil injects it automatically (`/_*.`). DuckDB writes a single file. A BigQuery project with a non-`gs://` location is a **compile error**.
## Schemes & per-warehouse validity
[Section titled “Schemes & per-warehouse validity”](#schemes--per-warehouse-validity)
| Scheme | BigQuery | Postgres / Supabase |
| -------------------------------- | ---------- | ----------------------- |
| `gs://` | ✓ (native) | ✓ (DuckDB) |
| `s3://` (incl. Supabase Storage) | ✗ | ✓ |
| `local://…` or a path | ✗ | ✓ (handy for debugging) |
## Storage credentials
[Section titled “Storage credentials”](#storage-credentials)
Cloud destinations on Postgres/Supabase read credentials from a `storage` section of the gitignored `.df-credentials.json` (BigQuery uses its own GCS access; `local://` needs none):
```jsonc
{
"host": "…", "port": 5432, "database": "…", "user": "…", "password": "…",
"storage": {
"s3": {
"endpoint": ".supabase.co/storage/v1/s3",
"region": "us-east-1",
"accessKeyId": "…",
"secretAccessKey": "…"
},
"gcs": { "keyId": "…", "secret": "…" }
}
}
```
Per-environment via [`--environment`](/docs/guides/environments/)’s credentials file.
Note
The DuckDB dependency ships with `@sqlanvil/cli` and loads only when a Postgres/Supabase export runs. Exports depend on the relations they `${ref()}`, so `--include-dependencies` pulls in upstream models.
# Foreign Data Wrappers
> Query another warehouse in place from Postgres/Supabase — join BigQuery public data with your operational tables using a single wrapper() action.
Foreign Data Wrappers (FDW) let a Postgres or Supabase database **query another system as if it were a local table**. SQLAnvil’s `wrapper()` action declares the whole bridge — extension, wrapper, server, and `ref()`-able foreign tables — in a single call, so you can join data that lives in BigQuery (or any FDW-backed source) with your operational tables and never leave SQL.
Note
The FDW *mechanism* is core PostgreSQL, so `wrapper()` works on both the `postgres` and `supabase` warehouses. The **BigQuery** wrapper specifically relies on Supabase’s [`wrappers`](https://fdw.dev) extension — available on Supabase, or on self-managed Postgres where you’ve installed it. Credentials use Supabase Vault on `supabase`.
## Named connections (recommended)
[Section titled “Named connections (recommended)”](#named-connections-recommended)
The cleanest way to read a foreign source is to define a **named connection** in `workflow_settings.yaml` and tag a `declaration` with it. SQLAnvil generates the FDW bridge for you, and the declared table becomes `ref()`-able like any other source.
```yaml
# workflow_settings.yaml (committed)
warehouse: my_supabase
connections:
my_supabase:
platform: supabase
defaultSchema: public
bigquery_public:
platform: bigquery
project: bigquery-public-data # where the source dataset lives
dataset: geo_us_boundaries
billingProject: your-gcp-project # bills FDW jobs to your project (see below)
saKeyId: # non-secret Vault pointer
```
```sql
-- definitions/sources/zip_codes.sqlx
config {
type: "declaration",
connection: "bigquery_public",
name: "zip_codes",
columnTypes: { zip_code: "text", internal_point_lat: "float8", internal_point_lon: "float8" }
}
```
Now `${ref("zip_codes")}` resolves to a foreign table SQLAnvil created behind the scenes (in a `bigquery_public_ext` schema), and you join it with your Supabase tables in plain SQL. Connection **definitions** (non-secret: platform, project, dataset, `saKeyId`, host/port/db) live in `workflow_settings.yaml`; the actual **secrets** stay in the gitignored `.df-credentials.json`, keyed by connection name. `connection:` is valid only on declarations — tables and views always build into your one read/write warehouse.
### `runner-extract` mode — materialize instead of a live foreign table (1.15.0)
[Section titled “runner-extract mode — materialize instead of a live foreign table (1.15.0)”](#runner-extract-mode--materialize-instead-of-a-live-foreign-table-1150)
By default a connection builds a **live FDW foreign table** (`mode: fdw`), which needs the `wrappers` (and often `postgis`) extensions plus a Vault secret on the warehouse. Set **`mode: runner-extract`** on a BigQuery connection to instead have SQLAnvil **read the source at run time and materialize the rows into a plain table** — `ref()`-able exactly like the FDW bridge, but with **no Vault secret and no `wrappers`/`postgis`**, so it works on bare or ephemeral databases where an FDW can’t be provisioned:
```yaml
bigquery_public:
platform: bigquery
project: bigquery-public-data
dataset: geo_us_boundaries
billingProject: your-gcp-project
mode: runner-extract # read + materialize, no FDW/Vault
```
Source auth goes in `.df-credentials.json` under `connections.bigquery_public` — a short-lived `accessToken` (keyless), a service-account `credentials` key, or Application Default Credentials. Keep the default `fdw` mode when you want a **live** foreign table on a persistent warehouse; `runner-extract` is *materialize-then-use* (a snapshot per run), which is the right default for ephemeral/branch runs.
**Datasets and naming (1.22.0):** a declaration’s **`schema:` overrides the connection’s `dataset`**, so one connection per source GCP project serves declarations across many datasets — and the extracted table **keeps that name as its Postgres schema** (`schema: "ods", name: "zip_code"` materializes `ods.zip_code`), which keeps schema-qualified `ref("ods", "zip_code")` calls working (e.g. after a [Dataform migration](/docs/guides/migrate-dataform/)). Declarations without a `schema:` use the connection’s `dataset` and land in `_ext` as before. Also since 1.22.0, **empty `columnTypes` compiles** — the extract fails at *run* time (before touching or billing the source) with the exact `sqlanvil introspect` command to scaffold it; only FDW-mode declarations still require `columnTypes` at compile.
### MySQL/MariaDB sources (1.18.0)
[Section titled “MySQL/MariaDB sources (1.18.0)”](#mysqlmariadb-sources-1180)
A **`platform: mysql`** connection makes a MySQL or MariaDB database a read-only source for your Postgres/Supabase warehouse. There is no Postgres FDW for MySQL, so MySQL sources are **runner-extract only** — it’s the default (you can omit `mode:`), and an explicit `mode: fdw` is a compile-time error:
```yaml
shop_mysql:
platform: mysql
host: mysql.internal.example.com
port: 3306 # optional, default 3306
database: shop # source database (a declaration's `schema:` overrides it)
```
Source credentials go in `.df-credentials.json` under `connections.shop_mysql`: `{ "host": "…", "port": 3306, "user": "…", "password": "…", "sslMode": "require" }`. Each run reads `database.table` over the wire (capped at 1M rows / 512 MB, truncation logged) and materializes it as a plain `shop_mysql_ext.` table. Validated against MySQL 8 and MariaDB 11.
Which non-secret fields a connection needs depends on its platform:
| Platform | Fields |
| :---------------------- | :-------------------------------------------------------------------------------------------------------------------------------- |
| `bigquery` | `platform`, `project`, `dataset`, `saKeyId` (fdw mode), `billingProject` (optional), `mode` (optional: `fdw` \| `runner-extract`) |
| `postgres` / `supabase` | `platform`, `host`, `port`, `database`, `defaultSchema` |
| `mysql` | `platform`, `host`, `port`, `database` (runner-extract only) |
**`billingProject`** (BigQuery, optional; added in 1.13.0). BigQuery bills query jobs to the FDW server’s project. By default that’s `project` — fine when you own it. But to read a dataset you can **read but not bill** — e.g. `bigquery-public-data` — set `billingProject` to your own GCP project: SQLAnvil then bills that project and reads the source via a full-FQN subquery. The service account needs `bigquery.jobUser` on the billing project.
`columnTypes` is required on a connection-tagged declaration (the FDW foreign table needs column types) — but you rarely hand-write it; see below.
## How the bridge maps
[Section titled “How the bridge maps”](#how-the-bridge-maps)
When you compile, SQLAnvil turns each named connection into the FDW objects below (it adds them as extra actions alongside your own models). Multiple declarations on the same connection share one server:
| You write | SQLAnvil generates |
| :--------------------------- | :---------------------------------------------------------- |
| connection `bigquery_public` | server `bigquery_public_srv` + schema `bigquery_public_ext` |
| declaration `zip_codes` | foreign table `bigquery_public_ext.zip_codes` |
| `${ref("zip_codes")}` | a query against the live foreign table |
## Introspecting source schemas
[Section titled “Introspecting source schemas”](#introspecting-source-schemas)
`sqlanvil introspect` reads a source table’s columns and writes the declaration for you, with `columnTypes` (and any column descriptions) filled in:
```bash
sqlanvil introspect bigquery_public geo_us_boundaries.zip_codes \
--output definitions/sources/zip_codes.sqlx
```
It connects directly to the source with that connection’s credentials, maps the source types to your warehouse dialect (BigQuery → Postgres; Postgres → Postgres; MySQL → Postgres since 1.18.0 — e.g. `datetime` → `timestamp`, `json` → `jsonb`), and prints the declaration (or writes it with `--output`). It’s a **dev-time** command — `compile` and `run` never touch the network for schema.
## Source credentials: build-time vs run-time
[Section titled “Source credentials: build-time vs run-time”](#source-credentials-build-time-vs-run-time)
A named connection touches credentials at two distinct moments, and they live in different places:
* **Run-time** — what the FDW server uses to read the source during `run`.
* *BigQuery sources* read a service-account key from **Supabase Vault** by **id** (`saKeyId`) — see [Credentials](#credentials) below.
* *Postgres/Supabase sources* read `user`/`password` from `.df-credentials.json` under `connections.`; SQLAnvil injects them into the generated `CREATE USER MAPPING` at run time. The non-secret `host`/`port`/`database` from `workflow_settings.yaml` feed the foreign server at compile time.
* **Build-time** — what `sqlanvil introspect` uses to read the source schema from your machine. These also live in `.df-credentials.json`, under a `connections` map keyed by connection name, alongside (not mixed into) your flat write-warehouse credentials:
```jsonc
{
"host": "aws-1-us-east-1.pooler.supabase.com",
"port": 5432,
"database": "postgres",
"user": "postgres.",
"password": "",
"sslMode": "require",
"defaultSchema": "public",
"connections": {
// BigQuery source: the service-account key JSON
"bigquery_public": { "credentials": { "type": "service_account", "...": "service-account JSON" } }
// Postgres/Supabase source: { "host", "port", "database", "user", "password", "sslMode" }
}
}
```
The `connections` map is read only by `introspect`; `run` reads the flat warehouse credentials and ignores it. This build-time credential (for reading the schema) is distinct from the run-time Vault `saKeyId` the FDW server uses inside Supabase.
Note
Postgres/Supabase sources require `@sqlanvil/core` **1.2.0** or newer (the `postgres_fdw` user mapping + run-time credential injection landed in 1.2.0). BigQuery sources work from **1.1.1**. Pin it with `sqlanvilCoreVersion` in `workflow_settings.yaml`.
## The `wrapper()` action
[Section titled “The wrapper() action”](#the-wrapper-action)
For full control (or sources without a connection preset), `wrapper()` is the lower-level JavaScript-API action that emits the FDW setup directly — put it in a `.js` file under `definitions/`:
definitions/sources/bigquery\_zip\_codes.js
```javascript
wrapper({
name: "bq_setup",
provider: "bigquery", // preset: wrappers ext + bigquery handler/validator
server: "bq_geo_server",
serverOptions: {
project_id: "bigquery-public-data",
dataset_id: "geo_us_boundaries"
},
credential: {
// A Vault secret id — a non-secret pointer. The service-account key JSON
// is stored in Vault, never in your repo. (See "Credentials" below.)
saKeyId: sqlanvil.projectConfig.vars.bq_sa_key_id
},
foreignTables: [
{
name: "zip_codes", // → ref("zip_codes") works downstream
schema: "bq_ext",
options: { table: "zip_codes", location: "US" },
columns: {
zip_code: "text",
internal_point_lat: "float8",
internal_point_lon: "float8"
}
}
]
});
```
This compiles to the full setup, in dependency order:
```sql
create extension if not exists "wrappers" cascade;
-- idempotent foreign data wrapper creation
do $$ begin
if not exists (select 1 from pg_foreign_data_wrapper where fdwname = 'bigquery_wrapper') then
create foreign data wrapper bigquery_wrapper
handler big_query_fdw_handler validator big_query_fdw_validator;
end if;
end $$;
drop server if exists "bq_geo_server" cascade;
create server "bq_geo_server" foreign data wrapper "bigquery_wrapper"
options (project_id 'bigquery-public-data', dataset_id 'geo_us_boundaries', sa_key_id '');
-- one per foreignTables[] entry:
drop foreign table if exists "bq_ext"."zip_codes";
create foreign table "bq_ext"."zip_codes" ("zip_code" text, "internal_point_lat" float8, "internal_point_lon" float8)
server "bq_geo_server" options (table 'zip_codes', location 'US');
```
## Referencing foreign tables
[Section titled “Referencing foreign tables”](#referencing-foreign-tables)
Each entry in `foreignTables[]` becomes a `ref()`-able target that depends on the server setup, so downstream models consume it like any other source:
```sql
-- definitions/staging/stg_zip_codes.sqlx
config { type: "view", schema: "bq_ext" }
SELECT zip_code, internal_point_lat AS lat, internal_point_lon AS lon
FROM ${ref("zip_codes")}
```
A common pattern is to **materialize** the foreign data into a real table once, so downstream joins don’t re-hit the remote warehouse on every query:
```sql
-- definitions/staging/zip_codes_cache.sqlx
config { type: "table", schema: "public" }
SELECT * FROM ${ref("stg_zip_codes")}
```
## Credentials
[Section titled “Credentials”](#credentials)
`wrapper()` never handles your service-account key. Store it once in Supabase Vault and reference its **id**:
```sql
-- Run once in the Supabase SQL editor:
select vault.create_secret('', 'bigquery_sa');
select id from vault.secrets where name = 'bigquery_sa';
```
Pass the returned id as `credential.saKeyId` (e.g. via a `var` so it stays out of committed config).
## Config reference
[Section titled “Config reference”](#config-reference)
`wrapper()` config:
| Field | Required | Description |
| ----------------------------------- | -------- | ---------------------------------------------------------------------------------------- |
| `name` | ✓ | Name of the server-setup action |
| `provider` | | Preset that infers the extension + handler/validator (e.g. `"bigquery"`) |
| `wrapper` / `handler` / `validator` | | Explicit FDW for generic Postgres FDWs (required when `provider` is omitted) |
| `server` | ✓ | Foreign server name |
| `serverOptions` | | Map of server options (e.g. `project_id`, `dataset_id`) |
| `credential.saKeyId` | | Vault secret id passed to the server as `sa_key_id` (Supabase) |
| `foreignTables[]` | | Foreign tables to expose (`name`, `schema`, `options`, `columns`) — each is `ref()`-able |
Caution
The BigQuery wrapper queries are billed to the GCP project behind your service-account key. Reading `bigquery-public-data` is free up to the monthly tier, but you still need a billing-enabled project.
## Troubleshooting
[Section titled “Troubleshooting”](#troubleshooting)
| Symptom | Likely cause | Fix |
| :---------------------------------------------------------------------- | :----------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------- |
| `Unknown connection "X" on declaration "Y"` at compile | the declaration’s `connection:` doesn’t match a key under `connections:` | Check the name. If it *does* match, ensure `@sqlanvil/core` is **≥ 1.1.1** — 1.1.0 dropped connections in the published package. |
| `Declaration "X" on connection "Y" requires columnTypes` | a connection-tagged declaration with no `columnTypes` | Add them, or run `sqlanvil introspect --output `. |
| `Reading connection "X" from a bigquery warehouse is not yet supported` | your `warehouse:` is `bigquery` | The read side must be `postgres`/`supabase` — the FDW bridge is a Postgres feature. |
| Wrapper/extension errors on `run` | the `wrappers` extension isn’t enabled on the database | Enable it (Supabase Dashboard → Database → Extensions). |
## Full example
[Section titled “Full example”](#full-example)
The [`supabase_bigquery_mailing_list`](https://github.com/SQLAnvil/sqlanvil/tree/main/examples/supabase_bigquery_mailing_list) example builds a proximity mailing list — customers who purchased recently and live within a radius of a target ZIP — by joining Supabase operational data with Google’s public ZIP geo data over a live BigQuery FDW, with PostGIS distance math.
# File Imports
> Load Parquet/CSV/JSON files into a warehouse table with type "import" — BigQuery LOAD DATA, or Postgres/Supabase via DuckDB from s3/gs/local.
A `type: "import"` action loads a **Parquet / CSV / JSON** file into a table in the warehouse — the symmetric inverse of [`type: "export"`](/docs/guides/exports/). Unlike an export (a sink), an import is a **producer**: the table it creates is `ref()`-able by downstream models, just like a `table` or a `declaration` that actually carries data.
Note
Requires `@sqlanvil/core` **1.12.0** or newer. Pin `sqlanvilCoreVersion: 1.12.0`.
It’s a **config-only** action — there’s no SQL body (like a `declaration`). The source is the file at `location`; the destination is the action’s own target.
Need to *produce* the file first — download it, unzip it, reshape it? Pair the import with a [**python script action**](/docs/guides/python-actions/) (1.20+) it depends on: the script stages the file in-DAG, the import loads it.
```sql
-- definitions/orders_in.sqlx
config {
type: "import",
import: {
location: "s3://my-bucket/orders/*.parquet", // exact source file or glob
format: "parquet", // parquet | csv | json (json = JSONL)
overwrite: true // default true
}
}
```
```sql
-- definitions/clean_orders.sqlx — ref() the imported table like any other model
config { type: "table" }
SELECT order_id, total FROM ${ref("orders_in")}
```
## How it runs per warehouse
[Section titled “How it runs per warehouse”](#how-it-runs-per-warehouse)
* **BigQuery** — compiles to native `LOAD DATA OVERWRITE|INTO FROM FILES(format=…, uris=['gs://…'])`. In-engine, using the warehouse’s own GCS access. **`gs://` only.**
* **Postgres / Supabase** — the CLI runs the import via **DuckDB**: it attaches the database read-write, then `CREATE TABLE … AS SELECT * FROM read_parquet('')` (replace) or `INSERT INTO … SELECT …` (append). DuckDB decodes the file; the table lands in your warehouse. Reads from `s3://`, `gs://`, **Supabase Storage** (its S3-compatible endpoint), or a local path — filling the gap where managed Postgres/Supabase can’t read Parquet itself.
MySQL/MariaDB import isn’t supported yet.
Caution
Import is **file movement into your warehouse**, not “your warehouse reads the file live.” The data is materialized as a real table. It loads **as-is** (no transform — shape it in a downstream model), preserving SQLAnvil’s single-write-warehouse model.
## `import` options
[Section titled “import options”](#import-options)
| Field | Description |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `location` | **Required.** The exact source file/glob/URI to read (e.g. `gs://b/orders/*.parquet`). Used verbatim — unlike export, no filename is derived. `${}` interpolation works. |
| `format` | **Required.** `parquet`, `csv`, or `json` (JSONL). |
| `overwrite` | `true` (default) replaces the table (drop + create). `false` appends (`INSERT … SELECT`) into an existing table. |
| `options` | Format-specific passthrough (reserved). |
## Schemes & per-warehouse validity
[Section titled “Schemes & per-warehouse validity”](#schemes--per-warehouse-validity)
| Scheme | BigQuery | Postgres / Supabase |
| -------------------------------- | ---------- | ----------------------- |
| `gs://` | ✓ (native) | ✓ (DuckDB) |
| `s3://` (incl. Supabase Storage) | ✗ | ✓ |
| `local://…` or a path | ✗ | ✓ (handy for debugging) |
## Storage credentials
[Section titled “Storage credentials”](#storage-credentials)
Cloud sources on Postgres/Supabase read credentials from the `storage` section of the gitignored `.df-credentials.json` — the **same** section [exports](/docs/guides/exports/#storage-credentials) use (BigQuery uses its own GCS access; `local://` needs none).
Note
The DuckDB dependency ships with `@sqlanvil/cli` and loads only when a Postgres/Supabase import runs. Because an imported table’s columns aren’t known until the file is read, `sqlanvil validate` reports imports as **SKIPPED** — and a model that `ref()`s an imported table is reported **BLOCKED** rather than validated against guessed columns.
# Metadata for AI & BI
> Declare table and column descriptions once in config — SQLAnvil persists them into your warehouse's own catalog on every run, where AI business engines, BI copilots, and text-to-SQL tools read them.
AI business engines — text-to-SQL agents, BI copilots, semantic-search layers — are only as good as the metadata they can find. When they ask *“what does this table mean, what does this column hold, how do these objects relate?”*, the answer has to live somewhere machine-readable.
SQLAnvil’s answer: **declare it once in the model, and it persists into the warehouse’s own catalog on every run.** Descriptions aren’t a sidecar file or a proprietary metadata store — they land as native `COMMENT`s and object descriptions, exactly where every catalog-aware tool already looks.
## Declaring metadata
[Section titled “Declaring metadata”](#declaring-metadata)
Every action config accepts a `description` (the object) and a `columns: {}` block (its fields):
```sql
config {
type: "table",
description: "One row per customer order. Source of truth for revenue reporting.",
columns: {
order_id: "Unique order identifier, assigned at checkout.",
customer_id: "The ordering customer — joins to public.customers.order_id.",
order_date: "Date the order was placed (warehouse local time).",
amount: "Order total in USD, after discounts, before tax."
}
}
SELECT ...
```
The same fields work in `actions.yaml` entries and via `.columns()` in JS — see the [reference](/docs/reference/table/). Declarations accept `columns: {}` too, so external sources can be documented alongside your models (see [scope](#what-persists-where) below).
## What persists, where
[Section titled “What persists, where”](#what-persists-where)
Metadata is applied by the adapter **after every run**, so it survives full-replace rebuilds — a `table` that is dropped and recreated gets its comments re-applied in the same run:
| Warehouse | Object description | Column descriptions | Ends up in |
| --------------- | ------------------------------------------------- | ------------------------------------------------------------------ | ---------------------------------------------------------------- |
| PostgreSQL | `COMMENT ON TABLE` / `VIEW` / `MATERIALIZED VIEW` | `COMMENT ON COLUMN` | `pg_description`, `information_schema` |
| Supabase | same as Postgres | same as Postgres | Postgres catalogs (visible in Studio and any catalog-aware tool) |
| BigQuery | table description | column descriptions, **including nested fields**, plus policy tags | Table metadata, `INFORMATION_SCHEMA` |
| MySQL / MariaDB | `ALTER TABLE … COMMENT` | column comments | `information_schema` |
Notes:
* **MySQL views** cannot carry comments (a MySQL limitation, not a SQLAnvil one) — the adapter skips them rather than failing.
* **Declarations** aren’t executed, so nothing is written to the source system — but their descriptions and columns are part of the compiled graph and the [catalog artifacts](/docs/guides/artifacts/), so agents still see them.
## Where an AI engine reads it
[Section titled “Where an AI engine reads it”](#where-an-ai-engine-reads-it)
**In the warehouse** — the point of persisting natively is that nothing SQLAnvil-specific is needed to read it back:
```sql
-- Postgres / Supabase
SELECT c.column_name, pgd.description
FROM information_schema.columns c
JOIN pg_catalog.pg_statio_all_tables st ON st.relname = c.table_name
LEFT JOIN pg_catalog.pg_description pgd
ON pgd.objoid = st.relid AND pgd.objsubid = c.ordinal_position
WHERE c.table_name = 'daily_sales';
```
```sql
-- BigQuery (nested fields included)
SELECT field_path, description
FROM `my-project.analytics.INFORMATION_SCHEMA.COLUMN_FIELD_PATHS`
WHERE table_name = 'daily_sales';
```
```sql
-- MySQL / MariaDB
SELECT COLUMN_NAME, COLUMN_COMMENT
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = 'analytics' AND TABLE_NAME = 'daily_sales';
```
**Outside the warehouse** — the same metadata is available without a connection:
* [`target/catalog/*.parquet`](/docs/guides/artifacts/) — `columns.parquet` holds every column descriptor in the project; `dependencies.parquet` holds the object graph. Queryable with `sqlanvil query`, no warehouse needed.
* `sqlanvil docs` — a self-contained HTML catalog of models, descriptions, and columns.
* On [SQLAnvil Cloud](/docs/cloud/), agents can reach project and run state over the [MCP server](/docs/cloud/mcp/).
## Keys and relationships
[Section titled “Keys and relationships”](#keys-and-relationships)
Key and relationship metadata comes from two complementary mechanisms: **declare** constraints into the warehouse catalog with `post_operations`, and **enforce** them with `uniqueKey`/assertions.
### Declaring constraints: `post_operations`
[Section titled “Declaring constraints: post\_operations”](#declaring-constraints-post_operations)
A `post_operations { }` block runs right after the object is built — on every run — so constraints land in the warehouse catalog and persist across rebuilds:
```sql
-- Postgres / Supabase / MySQL: real, enforced constraints
post_operations {
ALTER TABLE ${self()} ADD PRIMARY KEY (order_id)
---
ALTER TABLE ${self()} ADD CONSTRAINT fk_orders_customer
FOREIGN KEY (customer_id) REFERENCES ${ref("customers")} (customer_id)
}
```
```sql
-- BigQuery: the same, with NOT ENFORCED (BigQuery stores these as catalog
-- metadata for exactly this purpose — query engines and AI tools read them)
post_operations {
ALTER TABLE ${self()} ADD PRIMARY KEY (order_id) NOT ENFORCED
---
ALTER TABLE ${self()} ADD CONSTRAINT fk_orders_customer
FOREIGN KEY (customer_id) REFERENCES ${ref("customers")} (customer_id) NOT ENFORCED
}
```
One caveat by table type: a full-replace `table` is recreated each run, so plain `ADD` works every time. An **incremental** table keeps its constraints between runs — guard the `ALTER` so it only fires on the initial full build:
```sql
post_operations {
${when(!incremental(),
`ALTER TABLE ${self()} ADD PRIMARY KEY (order_id)`)}
}
```
### Enforcing keys: `uniqueKey` and assertions
[Section titled “Enforcing keys: uniqueKey and assertions”](#enforcing-keys-uniquekey-and-assertions)
* **`uniqueKey`** on [incremental tables](/docs/reference/incrementaltable/) declares the merge key — rows are upserted on it, so uniqueness is maintained by construction. On MySQL, SQLAnvil creates the required unique index automatically.
* **`assertions: { uniqueKey: [...], nonNull: [...] }`** turn key claims into [tested guarantees](/docs/reference/assertion/), verified on every run. Multiple key sets? Use the plural form: `assertions: { uniqueKeys: [["order_id"], ["customer_id", "order_date"]] }`.
* **`${ref()}`** builds the object graph: which models feed which — exported in `dependencies.parquet` and drawn as the [DAG in Cloud](/docs/cloud/workspace/).
Together: `post_operations` puts PK/FK facts where catalog-reading engines look, `uniqueKey`/assertions make those facts *verified* rather than aspirational, and the `ref()` graph supplies lineage.
## A practical pattern
[Section titled “A practical pattern”](#a-practical-pattern)
For models an AI engine will query, treat metadata as part of the model’s contract:
```sql
config {
type: "incremental",
uniqueKey: ["order_id"],
description: "One row per order; incrementally merged on order_id.",
columns: {
order_id: "Primary key. Unique order identifier.",
customer_id: "Foreign key to public.customers.customer_id.",
amount: "Order total in USD, after discounts, before tax."
},
assertions: {
uniqueKey: ["order_id"],
nonNull: ["order_id", "customer_id", "amount"]
}
}
SELECT ...
post_operations {
${when(!incremental(),
`ALTER TABLE ${self()} ADD PRIMARY KEY (order_id)`)}
}
```
Descriptions carry the semantics, `post_operations` writes the key facts into the catalog, `uniqueKey` + assertions make them *tested* claims, and every run keeps the warehouse in sync with the code.
# Migrate from Dataform
> Convert a Dataform/BigQuery project into a SQLAnvil-on-Postgres project with sqlanvil migrate-dataform — read-only source, honest dialect flags, and a machine-readable migration report.
**`sqlanvil migrate-dataform`** converts a Dataform/BigQuery project into a SQLAnvil-on-Postgres project — one command, directory in → directory out:
```bash
sqlanvil migrate-dataform ~/work/my-dataform-project ./my-sqlanvil-project
sqlanvil compile ./my-sqlanvil-project # should already be green
```
Note
Requires `@sqlanvil/cli` **1.22.0** or newer. The **source directory is read-only** — the converter never modifies it, and refuses to write anywhere outside the output directory.
Prefer a guided flow? `sqlanvil init --interactive` (1.23+) asks “start fresh, or convert an existing Dataform project?” and runs this same converter, then offers a credentials Q\&A for the converted project.
## What it does
[Section titled “What it does”](#what-it-does)
* **Sources stay in BigQuery.** Classification is by action type: every `type: "declaration"` is a *source* — its data does not move. The converter groups them into **one named [connection](/docs/guides/foreign-wrappers/) per source GCP project** (`mode: runner-extract` — no Vault secret, no extensions) and rewrites each declaration’s config to ride it. The declaration’s `schema:` keeps naming its dataset, and the extracted table keeps that name in Postgres, so schema-qualified `ref()`s resolve unchanged.
* **Targets move to Postgres.** Materializing actions (tables, views, incrementals, assertions, operations) get an honest dialect pass: **safe lexical rewrites only** (`SAFE_CAST` → `CAST`, `CURRENT_DATE()` → `CURRENT_DATE`, the `dataform.projectConfig` → `sqlanvil.projectConfig` compile-global rename), and an inline `-- SQLANVIL-MIGRATE:` marker on every construct that needs human review (`SAFE_DIVIDE`, `QUALIFY`, `UNNEST`/`STRUCT`, BigQuery date functions, `EXPORT DATA`, backticked FQNs, …). BigQuery-only config (`bigquery: {}`, `partitionBy`/`clusterBy`, project qualifiers) is commented out and flagged for translation to [`postgres: {}`](/docs/guides/postgres/).
* **Config converts mechanically.** `dataform.json`/`workflow_settings.yaml` becomes a sqlanvil `workflow_settings.yaml` (`warehouse: supabase`, connections block, vars carried).
* **Credentials never travel.** `.df-credentials*`, service-account keys, `.env` files, and stale build artifacts in the source are detected and excluded from the output (and listed in the report).
## Staying on BigQuery (`--target-warehouse bigquery`, 1.24+)
[Section titled “Staying on BigQuery (--target-warehouse bigquery, 1.24+)”](#staying-on-bigquery---target-warehouse-bigquery-124)
Don’t want to move warehouses at all — just stop using Dataform? Convert with:
```bash
sqlanvil migrate-dataform ./my-dataform-project ./my-sqlanvil-project --target-warehouse bigquery
```
This is a **tooling swap**, and it’s deliberately boring: BigQuery is a first-class sqlanvil warehouse, so your SQL bodies, `bigquery: {}` blocks (`partitionBy`/`clusterBy`), and declarations pass through **byte-identical**. `defaultProject` and `defaultLocation` carry into the new `workflow_settings.yaml`, dataset casing is preserved, and there are no connections — the sources are already native. The only changes: the settings file converts (`sqlanvilCoreVersion:` instead of `dataformCoreVersion:`) and the `dataform.projectConfig` compile global renames to `sqlanvil.projectConfig`. No Supabase or Postgres anywhere — not even an account.
Credentials come from `gcloud auth application-default login` (ADC) or a service-account key in a gitignored `.df-credentials.json`. [`sqlanvil validate`](/docs/guides/validate/) dry-runs every model against BigQuery without executing — all-PASS means the swap is done. The `init --interactive` convert path asks which target you want, with keep-BigQuery as the default.
## The migration report
[Section titled “The migration report”](#the-migration-report)
`migration-report.md` + `migration-report.json` land in the output root: the connections and their per-declaration `sqlanvil introspect` to-dos, every flagged file with line-level notes, and a warning when a schema is **both** source and target (its declared tables are probably produced by the pipeline itself — convert those to plain declarations once their producers move).
## Finishing the migration
[Section titled “Finishing the migration”](#finishing-the-migration)
1. `sqlanvil compile` — green out of the conversion.
2. Scaffold `columnTypes` for the declarations you actually read: `sqlanvil introspect . --output `. (Un-introspected declarations compile; their extracts fail at run time with this exact command.)
3. [`sqlanvil validate`](/docs/guides/validate/) against your warehouse — PASS/FAILURE/BLOCKED per model is the migration to-do list. Work the `SQLANVIL-MIGRATE:` markers until it’s green.
4. The report is machine-readable — pointing an AI agent at `migration-report.json` plus the inline markers is the fastest path through the dialect work.
## What it deliberately does not do
[Section titled “What it deliberately does not do”](#what-it-deliberately-does-not-do)
Full SQL transpilation (BigQuery→Postgres can’t be auto-translated honestly — flags beat guesses), moving the BigQuery *data* (connections cover read paths), automatic `columnTypes` introspection (needs credentials the converter never touches), and non-Dataform sources (dbt is its own beast).
# MySQL / MariaDB
> Configure SQLAnvil for MySQL or MariaDB — connection, indexes, table options, upserts, comments, and materialized-view emulation.
One `mysql` adapter targets **both MySQL 8 and MariaDB 11** — the same project runs against either. SQLAnvil generates portable MySQL-dialect SQL; engine-specific features ride through `operations`.
Note
MySQL support landed in `@sqlanvil/core` **1.5.0** (the `mysql: {}` block, COMMENT metadata, and materialized-view emulation arrived in 1.6.0). Pin `sqlanvilCoreVersion: 1.6.0` or newer.
## Connection
[Section titled “Connection”](#connection)
Non-secret settings go in `workflow_settings.yaml` (committed); the connection — including the password — goes in `.df-credentials.json` (gitignored):
workflow\_settings.yaml
```yaml
warehouse: mysql
defaultDataset: analytics # the MySQL DATABASE your models build into
defaultAssertionDataset: sqlanvil_assertions
sqlanvilCoreVersion: 1.6.0
```
```json
// .df-credentials.json (gitignored) — MysqlConnection
{
"host": "localhost",
"port": 3306,
"database": "analytics",
"user": "root",
"password": "your-password",
"sslMode": "disable"
}
```
Caution
MySQL has no schema-vs-database split — `defaultDataset` is the **database**, and the credentials have **no `defaultSchema`** field. `sslMode` is `disable` (local) or `require` (managed/TLS).
Try it locally:
```bash
docker run --rm -e MYSQL_ROOT_PASSWORD=password -e MYSQL_DATABASE=analytics -p 3306:3306 -d mysql:8
# or MariaDB: ... -p 3307:3306 -d mariadb:11
```
## Tables
[Section titled “Tables”](#tables)
```sql
-- definitions/orders.sqlx
config { type: "table" }
SELECT order_id, customer_id, total FROM ${ref("raw_orders")}
```
Generates a drop + `CREATE TABLE … AS SELECT`. Identifiers compile to two-part backticks — `` `analytics`.`orders` ``.
## Incremental tables (upserts)
[Section titled “Incremental tables (upserts)”](#incremental-tables-upserts)
```sql
config { type: "incremental", uniqueKey: ["order_id"] }
SELECT order_id, customer_id, total FROM ${ref("raw_orders")}
${ when(incremental(), `WHERE updated_at > (SELECT MAX(updated_at) FROM ${self()})`) }
```
`uniqueKey` compiles to `INSERT … ON DUPLICATE KEY UPDATE`, and the adapter **auto-creates the matching unique index** on the first/`--full-refresh` build — you don’t add your own PK.
## MySQL-specific config (`mysql:` block)
[Section titled “MySQL-specific config (mysql: block)”](#mysql-specific-config-mysql-block)
Declare secondary indexes and table options:
```sql
config {
type: "table",
mysql: {
engine: "InnoDB",
charset: "utf8mb4",
collation: "utf8mb4_unicode_ci",
indexes: [
{ name: "ix_customer", columns: ["customer_id"], unique: false }
]
}
}
SELECT ...
```
| Field | Type | Description |
| ------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `engine` | string | Storage engine → `ENGINE=` (e.g. `InnoDB`) |
| `charset` | string | Default character set → `DEFAULT CHARSET=` |
| `collation` | string | Default collation → `COLLATE=` |
| `rowFormat` | string | Row format → `ROW_FORMAT=` (e.g. `DYNAMIC`, `COMPRESSED`) (≥ `1.19.0`) |
| `indexes[].name` | string | Index name (derived if omitted) |
| `indexes[].columns` | string\[] | Indexed columns; a column may carry a MySQL prefix length — `"body(50)"` → `` `body`(50) `` (required to index `TEXT`/`BLOB`) (≥ `1.19.0`) |
| `indexes[].unique` | boolean | Create a `UNIQUE` index |
| `indexes[].type` | string | `"fulltext"` or `"spatial"` (≥ `1.19.0`); mutually exclusive with `unique` |
No `where`/`include`/`opclass` (those are Postgres-only). A `SPATIAL` index requires a `NOT NULL` SRID-attributed geometry column — CTAS-created columns are nullable, so spatial normally needs an `ALTER TABLE … MODIFY` in `post_operations` first.
### Partitioning
[Section titled “Partitioning”](#partitioning)
Native MySQL/MariaDB partitioning via `mysql: { partition: {...} }` (≥ `1.11.0`):
```js
// RANGE (explicit child partitions)
config { type: "table", mysql: { partition: {
kind: "RANGE", expression: "id",
partitions: [
{ name: "p0", values: "values less than (1000)" },
{ name: "pmax", values: "values less than maxvalue" }
]
}}}
// HASH / KEY (partition count)
config { type: "table", mysql: { partition: { kind: "HASH", expression: "id", count: 4 } } }
```
`kind` is `RANGE`/`LIST`/`HASH`/`KEY`. RANGE/LIST take explicit `partitions[]` (the `values` body is emitted verbatim — `values less than (...)` / `values in (...)`, use `MAXVALUE` for a catch-all); HASH/KEY take `count`. **MySQL requires every column in the partition expression to be in every UNIQUE/PRIMARY key**, so a partitioned incremental’s `uniqueKey` must include the partition column(s). `RANGE COLUMNS` / `LIST COLUMNS` aren’t supported yet — use raw DDL in `operations`.
## Comments
[Section titled “Comments”](#comments)
`description:` and `columns:` apply as real table/column comments, read back from `information_schema`:
```sql
config {
type: "table",
description: "Cleaned orders.",
columns: { order_id: "primary key", total: "order total in cents" }
}
SELECT ...
```
Note
Comments apply to tables and incremental tables only — MySQL views can’t carry comments.
## Materialized views
[Section titled “Materialized views”](#materialized-views)
MySQL/MariaDB have no native materialized views, so `materialized: true` is **emulated as a refreshed table snapshot** — rebuilt (drop + `CREATE TABLE … AS SELECT`) on every run, honoring the `mysql: {}` block:
```sql
config { type: "view", materialized: true, mysql: { indexes: [{ columns: ["id"], unique: true }] } }
SELECT id, count(*) AS n FROM ${ref("events")} GROUP BY id
```
It reads back as a regular table; refresh = re-run. (The `mysql: {}` block on a `type: "view"` config compiles through since `1.19.0` — earlier versions silently ignored it there.)
## Procedures, functions, triggers
[Section titled “Procedures, functions, triggers”](#procedures-functions-triggers)
Use `type: "operations"`. Statements are separated by `---`, never `;`, so a `CREATE PROCEDURE … BEGIN … END` body is one statement (its internal `;` survive) — **don’t use `DELIMITER`** (it’s a client-only directive and will fail).
Note
`sqlanvil introspect` works for MySQL/MariaDB (≥ `1.11.0`): point it at a `platform: mysql` connection to scaffold a `declaration` from an existing table’s columns/types/comments. Since **1.18.0**, a Postgres/Supabase project can also **read MySQL data at run time**: MySQL sources are runner-extract only (no FDW — the rows are materialized into a plain table each run). See [Foreign sources](/docs/guides/foreign-wrappers/).
## Configuration reference
[Section titled “Configuration reference”](#configuration-reference)
**`workflow_settings.yaml`** (committed):
| Field | Required | Description |
| ------------------------- | -------- | ------------------------------------------------------- |
| `warehouse` | ✓ | `mysql` |
| `defaultDataset` | ✓ | Default **database** for actions |
| `defaultAssertionDataset` | | Database for generated assertions |
| `sqlanvilCoreVersion` | ✓ | Pin `@sqlanvil/core` (≥ `1.6.0` for the `mysql:` block) |
**`.df-credentials.json`** (gitignored — the `MysqlConnection`):
| Field | Required | Description |
| ---------- | -------- | ------------------------------- |
| `host` | ✓ | Database host |
| `port` | ✓ | Database port (default: `3306`) |
| `database` | ✓ | Database name |
| `user` | ✓ | Database user |
| `password` | ✓ | Password |
| `sslMode` | | `disable` or `require` |
## Troubleshooting
[Section titled “Troubleshooting”](#troubleshooting)
| Symptom | Likely cause | Fix |
| -------------------------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `Could not connect to MySQL … Access denied` | Wrong user/password | Check `.df-credentials.json`; for the local container the user is `root`, password `password`. |
| `Could not connect … ECONNREFUSED` | Wrong host/port, or DB not up yet | Confirm the container is running and the port (3306 MySQL, 3307 MariaDB); wait for it to accept connections. |
| `Unexpected property "defaultSchema"` | Postgres-shaped creds on a mysql project | MySQL `.df-credentials.json` has no `defaultSchema` — use `host/port/database/user/password/sslMode`. |
| `Unsupported warehouse "mysql"` | `sqlanvilCoreVersion` pinned too low | MySQL warehouse support landed in core **1.5.0**; remove the pin or set it ≥ 1.5.0. |
| TLS / `HANDSHAKE_NO_SSL_SUPPORT` | `sslMode: require` against a server without TLS | Use `"sslMode": "disable"` for local/non-TLS servers. |
## Notes & limits
[Section titled “Notes & limits”](#notes--limits)
Note
* Backtick-quote identifiers in raw DDL (`` `col` ``), never double-quote.
* The `mysql: {}` block covers engine/charset/collation/`rowFormat`, plain/unique/`FULLTEXT`/`SPATIAL`/prefix indexes, and partitioning; anything beyond that (e.g. `AUTO_INCREMENT` seeds, tablespace clauses) is raw DDL in `operations`.
* A **mysql warehouse** can’t read cross-warehouse named connections (the bridge targets Postgres/Supabase). MySQL **as a source** for a Postgres/Supabase warehouse works since 1.18.0 (runner-extract) — see [Foreign sources](/docs/guides/foreign-wrappers/).
# Packages
> Extend SQLAnvil with npm-style packages — reusable macros, utility tables, and shared includes declared as dependencies of @sqlanvil/core.
SQLAnvil supports npm-style packages that extend the framework with reusable macros, utility tables, and shared includes. Packages are declared in `package.json` as dependencies of `@sqlanvil/core`.
## Using a package
[Section titled “Using a package”](#using-a-package)
Add a package to your project’s `package.json`:
```json
{
"dependencies": {
"@sqlanvil/core": "latest",
"sqlanvil-scd": "^1.0.0"
}
}
```
Then import it in an `includes/` file:
includes/scd.js
```js
const scd = require("sqlanvil-scd");
module.exports = { scd };
```
## Creating a package
[Section titled “Creating a package”](#creating-a-package)
Creating a package requires familiarity with the SQLAnvil JavaScript API. A package is an npm module that exports functions using the SQLAnvil `session` object.
### Basic structure
[Section titled “Basic structure”](#basic-structure)
A minimal package looks like:
```plaintext
my-package/
├── index.js ← exports your macros/helpers
├── example.js ← demonstrates usage against a real project
└── README.md
```
### `index.js` example
[Section titled “index.js example”](#indexjs-example)
```js
// A macro that creates a standard SCD Type 2 dimension table
function scdType2(tableName, naturalKey, columns) {
return session.publish(tableName, {
type: "incremental",
uniqueKey: [naturalKey],
description: `SCD Type 2 dimension: ${tableName}`
}).query(ctx => `
SELECT
${naturalKey},
${columns.join(",\n ")},
CURRENT_TIMESTAMP AS valid_from,
NULL AS valid_to
FROM ${ctx.ref("staging_" + tableName)}
`);
}
module.exports = { scdType2 };
```
### Test your package
[Section titled “Test your package”](#test-your-package)
Connect to a data warehouse (BigQuery, Postgres, or Supabase) and run:
```bash
sqlanvil compile
sqlanvil run --actions my_dimension_table
```
### Publish to npm
[Section titled “Publish to npm”](#publish-to-npm)
Once ready, publish under your own npm scope:
```bash
npm publish --access public
```
## Community packages
[Section titled “Community packages”](#community-packages)
Note
The packages below were written for upstream Dataform (BigQuery-only). They may work for BigQuery-targeted SQLAnvil projects. Postgres/Supabase-specific packages are in development.
* [dataform-co/dataform-scd](https://github.com/dataform-co/dataform-scd) — Slowly Changing Dimensions
* [dataform-co/dataform-fivetran-log](https://github.com/dataform-co/dataform-fivetran-log) — Fivetran sync log analysis
* [dataform-co/dataform-segment](https://github.com/dataform-co/dataform-segment) — Segment event modeling
To discuss packages or share your own, open a [GitHub Discussion](https://github.com/SQLAnvil/sqlanvil/discussions).
# PostgreSQL
> Configure SQLAnvil for PostgreSQL — connection, native partitioning, indexes, upserts, and Postgres-specific action config.
SQLAnvil generates idiomatic PostgreSQL DDL/DML. A user who has never touched BigQuery should never encounter BigQuery-specific concepts (NOT ENFORCED primary keys, `OPTIONS(...)` table options, `MERGE` dialect).
## Connection
[Section titled “Connection”](#connection)
Non-secret settings go in `workflow_settings.yaml` (committed); the connection — including the password — goes in `.df-credentials.json` (gitignored, never committed):
workflow\_settings.yaml
```yaml
warehouse: postgres
defaultDataset: analytics # the schema your models build into
defaultAssertionDataset: sqlanvil_assertions
sqlanvilCoreVersion: 1.24.0 # pin the release you installed — `sqlanvil init` writes this
```
```json
// .df-credentials.json (gitignored)
{
"host": "db.example.com",
"port": 5432,
"database": "analytics",
"user": "sqlanvil_writer",
"password": "your-password",
"sslMode": "require",
"defaultSchema": "public"
}
```
`sslMode` accepts `disable | allow | prefer | require | verify-ca | verify-full`. Pass a different credentials path at run time with `--credentials `.
## Tables
[Section titled “Tables”](#tables)
SQLAnvil creates Postgres tables using a transactional drop-and-recreate pattern — no BigQuery `CREATE OR REPLACE TABLE`:
```sql
-- definitions/orders.sqlx
config {
type: "table"
}
SELECT
order_id,
customer_id,
created_at
FROM ${ref("raw_orders")}
```
Generated SQL (simplified):
```sql
BEGIN;
DROP TABLE IF EXISTS public.orders;
CREATE TABLE public.orders AS SELECT ...;
COMMIT;
```
## Incremental tables (upserts)
[Section titled “Incremental tables (upserts)”](#incremental-tables-upserts)
Postgres uses `INSERT ... ON CONFLICT` — not BigQuery’s `MERGE`:
```sql
-- definitions/events.sqlx
config {
type: "incremental",
uniqueKey: ["event_id"]
}
SELECT event_id, user_id, event_timestamp, event_name
FROM ${ref("raw_events")}
${when(incremental(), `WHERE event_timestamp > (SELECT MAX(event_timestamp) FROM ${self()})`)}
```
Generated SQL for incremental runs:
```sql
INSERT INTO public.events (event_id, user_id, event_timestamp, event_name)
SELECT ...
ON CONFLICT (event_id) DO UPDATE SET
user_id = EXCLUDED.user_id,
event_timestamp = EXCLUDED.event_timestamp,
event_name = EXCLUDED.event_name;
```
## Postgres-specific config (`postgres:` block)
[Section titled “Postgres-specific config (postgres: block)”](#postgres-specific-config-postgres-block)
```sql
config {
type: "table",
postgres: {
tablespace: "fast_ssd",
fillfactor: 80,
partition: {
kind: 0, // numeric enum: RANGE=0, LIST=1, HASH=2
columns: ["order_date"]
},
indexes: [
{
name: "ix_orders_customer",
columns: ["customer_id"]
// method omitted → btree (BTREE=0, HASH=1, GIN=2, GIST=3, BRIN=4)
},
{
name: "ix_orders_search",
columns: ["description"],
method: 2 // GIN
}
]
}
}
SELECT ...
```
### `postgres:` field reference
[Section titled “postgres: field reference”](#postgres-field-reference)
| Field | Type | Description |
| ------------------- | ------------------------------------------- | ---------------------------------------------------------- |
| `tablespace` | string | Postgres tablespace for the table |
| `fillfactor` | number | Storage fillfactor (1–100) |
| `unlogged` | boolean | Create as `UNLOGGED TABLE` (faster writes, not crash-safe) |
| `partition.kind` | numeric enum: `0` RANGE, `1` LIST, `2` HASH | Declarative partitioning strategy |
| `partition.columns` | string\[] | Partition key columns |
| `indexes` | Index\[] | Indexes to create after the table |
| `with_data` | boolean | `WITH DATA` vs `WITH NO DATA` for materialized views |
### Index field reference
[Section titled “Index field reference”](#index-field-reference)
| Field | Type | Description |
| --------- | ------------------------------------------------------------------------------- | ---------------------------------------------------- |
| `name` | string | Index name |
| `columns` | string\[] | Indexed columns |
| `method` | numeric enum: `0` BTREE, `1` HASH, `2` GIN, `3` GIST, `4` BRIN (omit for btree) | Index access method |
| `where` | string | Partial index predicate (e.g. `"status = 'active'"`) |
| `unique` | boolean | Create a `UNIQUE` index |
| `include` | string\[] | Non-key columns to include (covering index) |
## Native partitioning
[Section titled “Native partitioning”](#native-partitioning)
```sql
config {
type: "table",
postgres: {
partition: { kind: 0, columns: ["order_date"] } // RANGE=0, LIST=1, HASH=2
}
}
SELECT order_id, customer_id, order_date FROM ${ref("raw_orders")}
```
Generates:
```sql
CREATE TABLE public.orders (
order_id BIGINT,
customer_id BIGINT,
order_date DATE
) PARTITION BY RANGE (order_date);
```
Note
BigQuery’s `partitionBy` / `clusterBy` fields are **not** valid for Postgres targets. Use `postgres.partition` / `postgres.indexes` instead.
## Configuration reference
[Section titled “Configuration reference”](#configuration-reference)
**`workflow_settings.yaml`** (committed):
| Field | Required | Description |
| ------------------------- | -------- | ----------------------------------------------- |
| `warehouse` | ✓ | `postgres` |
| `defaultDataset` | ✓ | Default schema for actions (e.g. `public`) |
| `defaultAssertionDataset` | | Schema for generated assertions |
| `sqlanvilCoreVersion` | ✓ | Pin the `@sqlanvil/core` version (e.g. `1.0.1`) |
**`.df-credentials.json`** (gitignored — the `PostgresConnection`):
| Field | Required | Description |
| --------------- | -------- | ------------------------------------------------------------------- |
| `host` | ✓ | Database host |
| `port` | ✓ | Database port (default: `5432`) |
| `database` | ✓ | Database name |
| `user` | ✓ | Database user |
| `password` | ✓ | Password |
| `sslMode` | | `disable`, `allow`, `prefer`, `require`, `verify-ca`, `verify-full` |
| `defaultSchema` | | Connection search-path schema (e.g. `public`) |
# Python Script Actions
> Run Python file-staging and glue steps as first-class DAG nodes with the python action — validated environments, no warehouse credentials, import as the loading boundary.
A **`python:` script action** runs a Python script as a first-class node in your DAG — the ingestion slot most SQL workflow tools leave outside the pipeline. Download a file, unzip an archive, call an API, reshape a CSV: steps that used to live in a crontab beside the pipeline become actions with dependencies, run history, and validation.
```plaintext
python: fetch_openaddresses.py → import (CSV) → staging view → mart → assertions
```
One `sqlanvil run` executes the whole chain, in order.
Note
Requires `@sqlanvil/core` **1.20.0** or newer. Pin `sqlanvilCoreVersion: 1.20.0`.
## Declaring one
[Section titled “Declaring one”](#declaring-one)
Script actions are declared in **`definitions/actions.yaml`** (not a new file extension), pointing at a plain `.py` file anywhere in your project:
```yaml
actions:
- python:
name: load_openaddresses
file: loader/load_openaddresses.py
args: ["northeast"] # passed verbatim as sys.argv[1:]
requirements: loader/requirements.txt # optional — validated, never installed
pythonVersion: ">=3.11" # optional PEP 440 specifier
venv: .venv # optional — use this venv's interpreter
dependencies: ["some_upstream_action"] # standard dependencies
tags: ["ingest"]
```
Downstream actions depend on it by name like any action — typically a [`type: "import"`](/docs/guides/imports/) loads the file the script staged:
```sql
-- definitions/sources/orders_in.sqlx
config {
type: "import",
dependencyTargets: [{name: "load_openaddresses"}],
import: { location: "staged/orders.csv", format: "csv", overwrite: true }
}
```
## The execution contract
[Section titled “The execution contract”](#the-execution-contract)
* The runner spawns ` ` with **cwd = your project directory**.
* Interpreter: the declared `venv`’s own `bin/python` if set, otherwise `python3` from PATH. A declared venv with no interpreter fails loudly (rather than silently running a different environment than the one you declared).
* Environment variables: `SA_VARS` (your `vars` as a JSON object) and `SA_ACTION_NAME`.
* **No warehouse credentials are ever injected.** Scripts stage *files*; [`import`](/docs/guides/imports/) is the loading boundary. This keeps SQLAnvil’s zero-credential-custody and single-write-warehouse models intact — and keeps the feature from becoming “Airflow but worse.”
* Exit code 0 is success. Output streams into the run log (and the tail is attached to the error on failure).
* Timeout: 30 minutes by default; set `timeoutMillis` per action.
```python
# loader/load_openaddresses.py — the script side of the contract
import json, os, sys
vars = json.loads(os.environ["SA_VARS"]) # workflow_settings.yaml vars / --vars
region = sys.argv[1] if len(sys.argv) > 1 else "all"
print(f"staging {region} for action {os.environ['SA_ACTION_NAME']}")
# ... write files under the project dir; a downstream import loads them ...
```
## You own the environment; SQLAnvil validates it
[Section titled “You own the environment; SQLAnvil validates it”](#you-own-the-environment-sqlanvil-validates-it)
SQLAnvil **never installs anything** — no pip, no resolver, no lockfiles. `requirements:` declares the contract; your venv, container, or CI step satisfies it (`pip install -r …`).
What you get in exchange is real validation. [`sqlanvil validate`](/docs/guides/validate/) (and `run --dry-run`) checks each script action **without executing it** — the Python analog of validating a model with `EXPLAIN`:
1. the resolved interpreter satisfies `pythonVersion`;
2. every `requirements:` spec is satisfied by the **installed** packages (checked offline via `importlib.metadata`);
3. the script parses (a compile check, catching syntax errors).
Failures read like a to-do list, and anything downstream of a failed script is **BLOCKED**:
```plaintext
FAIL public.load_openaddresses
python interpreter: installed 3.9.6 does not satisfy ">=3.11"
requirements: requests is not installed (need >=2.31)
SKIP oa_ext.orders_in (import — not validated)
BLOCK public.stg_orders — blocked by an upstream failure
```
Compile stays hermetic: `sqlanvil compile` never runs Python. It checks config shape, that the script and requirements files exist in the project, and that `pythonVersion` is a well-formed specifier — all deterministic.
## Where it runs
[Section titled “Where it runs”](#where-it-runs)
* **Local CLI** — works out of the box (`python3` on PATH, or your `venv:`).
* **Your own CI** — run `actions/setup-python` + `pip install -r requirements.txt` before `sqlanvil run`; `validate` passes there because the env satisfies the contract.
* **SQLAnvil Cloud** — hosted runs **reject script actions at compile time** (like local file paths): arbitrary user code on the shared runner is not supported. Use the local CLI or your own CI for projects with script actions.
## Worked example
[Section titled “Worked example”](#worked-example)
The in-repo [`examples/supabase_bigquery_mailing_list`](https://github.com/SQLAnvil/sqlanvil/tree/main/examples/supabase_bigquery_mailing_list) stages [OpenAddresses.io](https://openaddresses.io) address points with a `python:` action (bundled sample by default; set an `oa_source_url` var to download a real region), imports the staged CSV, and joins it to customers for address-level distance filtering — with an assertion guarding coordinate quality.
## FAQ
[Section titled “FAQ”](#faq)
**Is this compile-time Python, like `.js` files?** No. `.js` files are compile-time preprocessors that shape SQL; `python:` actions are **execution-time** steps that run when the DAG runs. Compile never executes Python.
**Can a script return rows / a dataframe?** No — scripts produce **files**, and `import` loads them. That boundary is deliberate (dbt-style Python models require warehouse-native Python compute, which Postgres/Supabase don’t have).
**Other languages?** The underlying action is language-neutral (`script:` with a `language` field — `python:` is sugar), so additional runtimes can arrive without breaking changes. Python is the only supported language today.
# Supabase
> Build derived tables, incremental rollups, materialized views, and assertions on Supabase — plus RLS policies, Realtime publications, and pgvector indexes.
SQLAnvil treats Supabase as **first-class Postgres**: your models compile to idiomatic Postgres and run directly against your Supabase database — no data warehouse required. On top of that, Supabase gets native action types for **RLS policies, Realtime publications, and pgvector indexes**.
## Quickstart
[Section titled “Quickstart”](#quickstart)
```bash
npm i -g @sqlanvil/cli
sqlanvil init my_project --warehouse supabase
cp my_project/.df-credentials.example.json my_project/.df-credentials.json # then edit it (below)
sqlanvil compile my_project
sqlanvil run my_project --credentials my_project/.df-credentials.json
```
Prefer a working example? Start from the [supabase-sqlanvil-starter](https://github.com/SQLAnvil/supabase-sqlanvil-starter) template.
## Connection
[Section titled “Connection”](#connection)
Connection settings are split in two — **non-secret settings** in `workflow_settings.yaml` (committed) and the **connection itself** in `.df-credentials.json` (gitignored, never committed):
workflow\_settings.yaml
```yaml
warehouse: supabase
defaultDataset: public # the schema your models build into
defaultAssertionDataset: sqlanvil_assertions
sqlanvilCoreVersion: 1.24.0 # pin the release you installed — `sqlanvil init` writes this
```
```json
// .df-credentials.json (gitignored)
{
"host": "aws-1-.pooler.supabase.com",
"port": 5432,
"database": "postgres",
"user": "postgres.",
"password": "",
"sslMode": "require",
"defaultSchema": "public"
}
```
Get these from your project’s **Connect** button in the Supabase dashboard. Two things to know:
* **Use the Session pooler on IPv4 networks** (most laptops, CI, containers). The **direct** connection (`db.[.supabase.co`) is IPv6-only. **Copy the host verbatim** — the `aws-0-`/`aws-1-` prefix and region slug aren’t guessable; a hand-built host fails with `tenant ... not found`.
* The pooler **user is `postgres.]`** (not just `postgres`), and **`sslMode` must be `require`**.
### Troubleshooting
[Section titled “Troubleshooting”](#troubleshooting)
| Symptom | Fix |
| :----------------------------------------- | :------------------------------------------------------------------------------------------------- |
| `tenant ... not found` | Copy the pooler **host verbatim** from the Connect dialog; set `user` to `postgres.`. |
| Connection times out / `ENETUNREACH` | You’re using the direct (IPv6-only) host on IPv4 — switch to the Session pooler. |
| `password authentication failed` | Wrong DB password — reset it in Settings → Database. |
| `ECIRCUITBREAKER` / too many auth failures | A wrong password tripped Supabase’s pooler lockout; fix the password and wait \~1–2 min. |
## Supabase-native actions
[Section titled “Supabase-native actions”](#supabase-native-actions)
### RLS policies (`rlsPolicy`)
[Section titled “RLS policies (rlsPolicy)”](#rls-policies-rlspolicy)
```sql
config {
type: "rlsPolicy",
table: "orders",
name: "users_see_own_orders",
command: "select",
roles: ["authenticated"],
using: "user_id = auth.uid()",
withCheck: "user_id = auth.uid()"
}
```
Compiles to:
```sql
CREATE POLICY users_see_own_orders ON orders
FOR SELECT TO authenticated
USING (user_id = auth.uid())
WITH CHECK (user_id = auth.uid());
```
### Realtime publications (`realtimePublication`)
[Section titled “Realtime publications (realtimePublication)”](#realtime-publications-realtimepublication)
```sql
config {
type: "realtimePublication",
table: "orders",
events: ["insert", "update", "delete"]
}
```
Compiles to `ALTER PUBLICATION supabase_realtime ADD TABLE orders;`
### pgvector indexes
[Section titled “pgvector indexes”](#pgvector-indexes)
```sql
config {
type: "table",
supabase: {
vectors: [
{
column: "embedding",
dimensions: 1536,
indexType: "hnsw",
params: { m: "16", ef_construction: "64" }
}
]
}
}
SELECT ...
```
### Foreign Data Wrappers (`wrapper`)
[Section titled “Foreign Data Wrappers (wrapper)”](#foreign-data-wrappers-wrapper)
Query BigQuery (or any FDW-backed source) in place and join it with your Supabase tables. A single `wrapper()` call declares the extension, server, and `ref()`-able foreign tables:
definitions/sources/bigquery\_zip\_codes.js
```javascript
wrapper({
name: "bq_setup",
provider: "bigquery",
server: "bq_geo_server",
serverOptions: { project_id: "bigquery-public-data", dataset_id: "geo_us_boundaries" },
credential: { saKeyId: sqlanvil.projectConfig.vars.bq_sa_key_id },
foreignTables: [
{ name: "zip_codes", schema: "bq_ext", options: { table: "zip_codes", location: "US" },
columns: { zip_code: "text", internal_point_lat: "float8", internal_point_lon: "float8" } }
]
});
```
See the [Foreign Data Wrappers guide](/docs/guides/foreign-wrappers/) for the full walkthrough, credentials, and a complete BigQuery + Supabase example.
## Postgres features
[Section titled “Postgres features”](#postgres-features)
Everything in the [Postgres guide](/docs/guides/postgres/) applies — incremental tables, materialized views, native partitioning, indexes (btree/gin/gist/brin), assertions, and `operations` for functions/procedures.
# Validate
> Catch broken SQL before you run it — sqlanvil validate checks every model against the warehouse planner (EXPLAIN / dry-run) without executing.
`sqlanvil validate` checks that **every model’s SQL is valid** — syntax, missing tables, missing columns, type errors — **before** you run the project, without executing or materializing anything. It’s the fast feedback loop: catch the typo in seconds instead of partway through a run.
Note
Requires `@sqlanvil/cli` **1.9.0** or newer. Available on **PostgreSQL, Supabase, MySQL/MariaDB, and BigQuery**.
## Usage
[Section titled “Usage”](#usage)
```bash
sqlanvil validate # validate the whole project
sqlanvil validate --actions my_model --include-deps
sqlanvil validate --environment dev # use a named environment's config + credentials
sqlanvil validate --json # machine-readable results
```
`run --dry-run` is an alias on PostgreSQL/Supabase/MySQL — it validates instead of executing. (On BigQuery, `run --dry-run` keeps BigQuery’s own server-side dry-run.)
## How it works
[Section titled “How it works”](#how-it-works)
SQLAnvil walks your DAG in dependency order and validates each model against the **warehouse’s own planner** — `EXPLAIN` on PostgreSQL/Supabase/MySQL, a dry-run on BigQuery — so there’s no separate SQL engine to drift from your warehouse’s behavior.
The trick to validating a *whole project* (where model B selects from model A, which doesn’t exist yet) is an **isolated shadow namespace**: SQLAnvil compiles into a temporary, timestamped schema (a dataset on BigQuery, a database on MySQL), and after each model validates it materializes an **empty** stub (`CREATE TABLE … WITH NO DATA`, or `LIMIT 0`) so the next model’s references resolve. The shadow namespace is dropped when validation finishes — nothing touches your real tables.
## Results
[Section titled “Results”](#results)
Each action is reported as one of:
| Status | Meaning |
| --------- | ------------------------------------------------------------------------------------- |
| **PASS** | The model’s SQL planned cleanly. |
| **FAIL** | The model’s own SQL is invalid (with the error message + line/column). |
| **BLOCK** | Skipped because an upstream model failed — so this one can’t be meaningfully checked. |
| **SKIP** | Not validated (e.g. an `operations` block — arbitrary, possibly side-effecting SQL). |
`validate` exits non-zero if anything **FAIL**ed or was **BLOCK**ed, so it drops straight into CI.
## Script actions (1.20+)
[Section titled “Script actions (1.20+)”](#script-actions-120)
[Python script actions](/docs/guides/python-actions/) get their own validation — the Python analog of `EXPLAIN`, run **without executing the script**: the resolved interpreter must satisfy `pythonVersion`, every `requirements:` spec must be satisfied by the installed packages (checked offline — nothing is installed), and the script must parse. A failing script action reports **FAIL** with each problem on its own line (“`requests` is not installed (need >=2.31)”), and its dependents are **BLOCK**ed, exactly like models.
```text
$ sqlanvil validate
PASS analytics.stg_orders
FAIL analytics.orders
column "totl" does not exist (line 4, col 8)
BLOCK analytics.daily_revenue — blocked by an upstream failure
1 passed, 1 failed, 1 blocked
```
The **FAIL** vs **BLOCK** split keeps the signal clean: one real error doesn’t bury you in a cascade of confusing failures — you see the one model to fix.
# Assertion
> API reference for the Assertion action type in SQLAnvil.
# Class: Assertion
[Section titled “Class: Assertion”](#class-assertion)
An assertion is a data quality test query that finds rows that violate one or more conditions specified in the query. If the query returns any rows, the assertion fails.
You can create assertions in the following ways. Available config options are defined in [AssertionConfig](configs#sqlanvil-ActionConfig-AssertionConfig), and are shared across all the following ways of creating assertions.
**Using a SQLX file:**
```sql
-- definitions/name.sqlx
config {
type: "assertion"
}
SELECT * FROM table WHERE a IS NULL
```
**Using built-in assertions in the config block of a table:**
See [TableConfig.assertions](configs#sqlanvil-ActionConfig-TableConfig)
**Using action configs files:**
definitions/actions.yaml
```yaml
actions:
- assertion:
filename: name.sql
```
```sql
-- definitions/name.sql
SELECT * FROM table WHERE a IS NULL
```
**Using the Javascript API:**
definitions/file.js
```js
assert("name").query("SELECT * FROM table WHERE a IS NULL")
```
Note: When using the Javascript API, methods in this class can be accessed by the returned value. This is where `query` comes from.
## Hierarchy
[Section titled “Hierarchy”](#hierarchy)
* ActionBuilder‹Assertion›
↳ **Assertion**
## Index
[Section titled “Index”](#index)
### Methods
[Section titled “Methods”](#methods)
* [database](#database)
* [dependencies](#dependencies)
* [description](#description)
* [disabled](#disabled)
* [hermetic](#hermetic)
* [query](#query)
* [schema](#schema)
* [tags](#tags)
## Methods
[Section titled “Methods”](#methods-1)
### database
[Section titled “database”](#database)
▸ **database**(`database`: string): *this*
**`deprecated`** Deprecated in favor of [AssertionConfig.project](configs#sqlanvil-ActionConfig-AssertionConfig).
Sets the database in which to create the corresponding view for this assertion. For BigQuery targets this is the Google Cloud project ID; for Postgres/Supabase targets this is the database name configured in `workflow_settings.yaml`.
**Parameters:**
| Name | Type |
| ---------- | ------ |
| `database` | string |
**Returns:** *this*
***
### dependencies
[Section titled “dependencies”](#dependencies)
▸ **dependencies**(`value`: Resolvable | Resolvable\[]): *this*
**`deprecated`** Deprecated in favor of [AssertionConfig.dependencies](configs#sqlanvil-ActionConfig-AssertionConfig).
Sets dependencies of the assertion.
**Parameters:**
| Name | Type |
| ------- | --------------------------- |
| `value` | Resolvable \| Resolvable\[] |
**Returns:** *this*
***
### description
[Section titled “description”](#description)
▸ **description**(`description`: string): *this*
**`deprecated`** Deprecated in favor of [AssertionConfig.description](configs#sqlanvil-ActionConfig-AssertionConfig).
Sets the description of this assertion.
**Parameters:**
| Name | Type |
| ------------- | ------ |
| `description` | string |
**Returns:** *this*
***
### disabled
[Section titled “disabled”](#disabled)
▸ **disabled**(`disabled`: boolean): *this*
**`deprecated`** Deprecated in favor of [AssertionConfig.disabled](configs#sqlanvil-ActionConfig-AssertionConfig).
If called with `true`, this action is not executed. The action can still be depended upon. Useful for temporarily turning off broken actions.
**Parameters:**
| Name | Type | Default |
| ---------- | ------- | ------- |
| `disabled` | boolean | true |
**Returns:** *this*
***
### hermetic
[Section titled “hermetic”](#hermetic)
▸ **hermetic**(`hermetic`: boolean): *void*
**`deprecated`** Deprecated in favor of [AssertionConfig.hermetic](configs#sqlanvil-ActionConfig-AssertionConfig).
If true, this indicates that the action only depends on data from explicitly-declared dependencies. Otherwise if false, it indicates that the action depends on data from a source which has not been declared as a dependency.
**Parameters:**
| Name | Type |
| ---------- | ------- |
| `hermetic` | boolean |
**Returns:** *void*
***
### query
[Section titled “query”](#query)
▸ **query**(`query`: AContextable‹string›): *this*
Sets the query to be run by the assertion.
**Parameters:**
| Name | Type |
| ------- | -------------------- |
| `query` | AContextable‹string› |
**Returns:** *this*
***
### schema
[Section titled “schema”](#schema)
▸ **schema**(`schema`: string): *this*
**`deprecated`** Deprecated in favor of [AssertionConfig.dataset](configs#sqlanvil-ActionConfig-AssertionConfig).
Sets the schema in which to create the corresponding view for this assertion. For BigQuery targets this is the dataset name; for Postgres/Supabase targets this is the schema name (e.g. `public`).
**Parameters:**
| Name | Type |
| -------- | ------ |
| `schema` | string |
**Returns:** *this*
***
### tags
[Section titled “tags”](#tags)
▸ **tags**(`value`: string | string\[]): *this*
**`deprecated`** Deprecated in favor of [AssertionConfig.tags](configs#sqlanvil-ActionConfig-AssertionConfig).
Sets a list of user-defined tags applied to this action.
**Parameters:**
| Name | Type |
| ------- | ------------------- |
| `value` | string \| string\[] |
**Returns:** *this*
# Configs Reference
> Protocol buffer config reference for all SQLAnvil action types and WorkflowSettings.
# Protocol Documentation
[Section titled “Protocol Documentation”](#protocol-documentation)
[]()
## Table of Contents
[Section titled “Table of Contents”](#table-of-contents)
* [configs.proto](#configs-proto)
* [ActionConfig](#sqlanvil-ActionConfig)
* [ActionConfig.AssertionConfig](#sqlanvil-ActionConfig-AssertionConfig)
* [ActionConfig.ColumnDescriptor](#sqlanvil-ActionConfig-ColumnDescriptor)
* [ActionConfig.DataPreparationConfig](#sqlanvil-ActionConfig-DataPreparationConfig)
* [ActionConfig.DataPreparationConfig.ErrorTableConfig](#sqlanvil-ActionConfig-DataPreparationConfig-ErrorTableConfig)
* [ActionConfig.DeclarationConfig](#sqlanvil-ActionConfig-DeclarationConfig)
* [ActionConfig.DeclarationConfig.ColumnTypesEntry](#sqlanvil-ActionConfig-DeclarationConfig-ColumnTypesEntry)
* [ActionConfig.ExportConfig](#sqlanvil-ActionConfig-ExportConfig)
* [ActionConfig.ExportOptions](#sqlanvil-ActionConfig-ExportOptions)
* [ActionConfig.ExportOptions.OptionsEntry](#sqlanvil-ActionConfig-ExportOptions-OptionsEntry)
* [ActionConfig.ForeignWrapperConfig](#sqlanvil-ActionConfig-ForeignWrapperConfig)
* [ActionConfig.ForeignWrapperConfig.OptionsEntry](#sqlanvil-ActionConfig-ForeignWrapperConfig-OptionsEntry)
* [ActionConfig.IcebergTableConfig](#sqlanvil-ActionConfig-IcebergTableConfig)
* [ActionConfig.ImportConfig](#sqlanvil-ActionConfig-ImportConfig)
* [ActionConfig.ImportOptions](#sqlanvil-ActionConfig-ImportOptions)
* [ActionConfig.ImportOptions.OptionsEntry](#sqlanvil-ActionConfig-ImportOptions-OptionsEntry)
* [ActionConfig.IncrementalTableConfig](#sqlanvil-ActionConfig-IncrementalTableConfig)
* [ActionConfig.IncrementalTableConfig.AdditionalOptionsEntry](#sqlanvil-ActionConfig-IncrementalTableConfig-AdditionalOptionsEntry)
* [ActionConfig.IncrementalTableConfig.LabelsEntry](#sqlanvil-ActionConfig-IncrementalTableConfig-LabelsEntry)
* [ActionConfig.LoadModeConfig](#sqlanvil-ActionConfig-LoadModeConfig)
* [ActionConfig.Metadata](#sqlanvil-ActionConfig-Metadata)
* [ActionConfig.NotebookConfig](#sqlanvil-ActionConfig-NotebookConfig)
* [ActionConfig.OperationConfig](#sqlanvil-ActionConfig-OperationConfig)
* [ActionConfig.RealtimePublicationConfig](#sqlanvil-ActionConfig-RealtimePublicationConfig)
* [ActionConfig.RlsPolicyConfig](#sqlanvil-ActionConfig-RlsPolicyConfig)
* [ActionConfig.ScriptConfig](#sqlanvil-ActionConfig-ScriptConfig)
* [ActionConfig.TableAssertionsConfig](#sqlanvil-ActionConfig-TableAssertionsConfig)
* [ActionConfig.TableAssertionsConfig.UniqueKey](#sqlanvil-ActionConfig-TableAssertionsConfig-UniqueKey)
* [ActionConfig.TableConfig](#sqlanvil-ActionConfig-TableConfig)
* [ActionConfig.TableConfig.AdditionalOptionsEntry](#sqlanvil-ActionConfig-TableConfig-AdditionalOptionsEntry)
* [ActionConfig.TableConfig.LabelsEntry](#sqlanvil-ActionConfig-TableConfig-LabelsEntry)
* [ActionConfig.Target](#sqlanvil-ActionConfig-Target)
* [ActionConfig.VectorIndexConfig](#sqlanvil-ActionConfig-VectorIndexConfig)
* [ActionConfig.VectorIndexConfig.ParamsEntry](#sqlanvil-ActionConfig-VectorIndexConfig-ParamsEntry)
* [ActionConfig.ViewConfig](#sqlanvil-ActionConfig-ViewConfig)
* [ActionConfig.ViewConfig.AdditionalOptionsEntry](#sqlanvil-ActionConfig-ViewConfig-AdditionalOptionsEntry)
* [ActionConfig.ViewConfig.LabelsEntry](#sqlanvil-ActionConfig-ViewConfig-LabelsEntry)
* [ActionConfigs](#sqlanvil-ActionConfigs)
* [BigQueryConnection](#sqlanvil-BigQueryConnection)
* [ConnectionConfig](#sqlanvil-ConnectionConfig)
* [DefaultIcebergConfig](#sqlanvil-DefaultIcebergConfig)
* [Environment](#sqlanvil-Environment)
* [Environment.VarsEntry](#sqlanvil-Environment-VarsEntry)
* [MysqlConnection](#sqlanvil-MysqlConnection)
* [MysqlOptions](#sqlanvil-MysqlOptions)
* [MysqlOptions.Index](#sqlanvil-MysqlOptions-Index)
* [MysqlOptions.Partition](#sqlanvil-MysqlOptions-Partition)
* [MysqlOptions.Partition.Bound](#sqlanvil-MysqlOptions-Partition-Bound)
* [NotebookRuntimeOptionsConfig](#sqlanvil-NotebookRuntimeOptionsConfig)
* [PostgresConnection](#sqlanvil-PostgresConnection)
* [PostgresOptions](#sqlanvil-PostgresOptions)
* [PostgresOptions.Index](#sqlanvil-PostgresOptions-Index)
* [PostgresOptions.Partition](#sqlanvil-PostgresOptions-Partition)
* [PostgresOptions.Partition.Bound](#sqlanvil-PostgresOptions-Partition-Bound)
* [RepositorySnapshotDestinationConfig](#sqlanvil-RepositorySnapshotDestinationConfig)
* [SupabaseConnection](#sqlanvil-SupabaseConnection)
* [SupabaseOptions](#sqlanvil-SupabaseOptions)
* [SupabaseOptions.VectorConfig](#sqlanvil-SupabaseOptions-VectorConfig)
* [SupabaseOptions.VectorConfig.ParamsEntry](#sqlanvil-SupabaseOptions-VectorConfig-ParamsEntry)
* [WarehouseConfig](#sqlanvil-WarehouseConfig)
* [WorkflowSettings](#sqlanvil-WorkflowSettings)
* [WorkflowSettings.ConnectionsEntry](#sqlanvil-WorkflowSettings-ConnectionsEntry)
* [WorkflowSettings.EnvironmentsEntry](#sqlanvil-WorkflowSettings-EnvironmentsEntry)
* [WorkflowSettings.VarsEntry](#sqlanvil-WorkflowSettings-VarsEntry)
* [ActionConfig.IcebergTableConfig.FileFormat](#sqlanvil-ActionConfig-IcebergTableConfig-FileFormat)
* [ActionConfig.LoadMode](#sqlanvil-ActionConfig-LoadMode)
* [ActionConfig.OnSchemaChange](#sqlanvil-ActionConfig-OnSchemaChange)
* [MysqlOptions.Partition.Kind](#sqlanvil-MysqlOptions-Partition-Kind)
* [PostgresOptions.Index.Method](#sqlanvil-PostgresOptions-Index-Method)
* [PostgresOptions.Partition.Kind](#sqlanvil-PostgresOptions-Partition-Kind)
* [SupabaseOptions.VectorConfig.IndexType](#sqlanvil-SupabaseOptions-VectorConfig-IndexType)
* [Scalar Value Types](#scalar-value-types)
[]()
[Top](#top)
## configs.proto
[Section titled “configs.proto”](#configsproto)
[]()
### ActionConfig
[Section titled “ActionConfig”](#actionconfig)
Action config defines the contents of `actions.yaml` configuration files.
| Field | Type | Label | Description |
| ------------------- | ------------------------------------------------------------------------------------------ | ----- | ----------- |
| table | [ActionConfig.TableConfig](#sqlanvil-ActionConfig-TableConfig) | | |
| view | [ActionConfig.ViewConfig](#sqlanvil-ActionConfig-ViewConfig) | | |
| incrementalTable | [ActionConfig.IncrementalTableConfig](#sqlanvil-ActionConfig-IncrementalTableConfig) | | |
| assertion | [ActionConfig.AssertionConfig](#sqlanvil-ActionConfig-AssertionConfig) | | |
| operation | [ActionConfig.OperationConfig](#sqlanvil-ActionConfig-OperationConfig) | | |
| declaration | [ActionConfig.DeclarationConfig](#sqlanvil-ActionConfig-DeclarationConfig) | | |
| notebook | [ActionConfig.NotebookConfig](#sqlanvil-ActionConfig-NotebookConfig) | | |
| dataPreparation | [ActionConfig.DataPreparationConfig](#sqlanvil-ActionConfig-DataPreparationConfig) | | |
| rlsPolicy | [ActionConfig.RlsPolicyConfig](#sqlanvil-ActionConfig-RlsPolicyConfig) | | |
| realtimePublication | [ActionConfig.RealtimePublicationConfig](#sqlanvil-ActionConfig-RealtimePublicationConfig) | | |
| foreignWrapper | [ActionConfig.ForeignWrapperConfig](#sqlanvil-ActionConfig-ForeignWrapperConfig) | | |
| vectorIndex | [ActionConfig.VectorIndexConfig](#sqlanvil-ActionConfig-VectorIndexConfig) | | |
| export | [ActionConfig.ExportConfig](#sqlanvil-ActionConfig-ExportConfig) | | |
| import | [ActionConfig.ImportConfig](#sqlanvil-ActionConfig-ImportConfig) | | |
| script | [ActionConfig.ScriptConfig](#sqlanvil-ActionConfig-ScriptConfig) | | |
[]()
### ActionConfig.AssertionConfig
[Section titled “ActionConfig.AssertionConfig”](#actionconfigassertionconfig)
| Field | Type | Label | Description |
| ---------------------------- | -------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| name | [string](#string) | | The name of the assertion. |
| dataset | [string](#string) | | The dataset (schema) of the assertion. |
| project | [string](#string) | | The Google Cloud project (database) of the assertion. |
| dependencyTargets | [ActionConfig.Target](#sqlanvil-ActionConfig-Target) | repeated | Targets of actions that this action is dependent on. |
| filename | [string](#string) | | Path to the source file that the contents of the action is loaded from. |
| tags | [string](#string) | repeated | A list of user-defined tags with which the action should be labeled. |
| disabled | [bool](#bool) | | If set to true, this action will not be executed. However, the action can still be depended upon. Useful for temporarily turning off broken actions. |
| description | [string](#string) | | Description of the assertion. |
| hermetic | [bool](#bool) | | If true, this indicates that the action only depends on data from explicitly-declared dependencies. Otherwise if false, it indicates that the action depends on data from a source which has not been declared as a dependency. |
| dependOnDependencyAssertions | [bool](#bool) | | If true, assertions dependent upon any of the dependencies are added as dependencies as well. |
| reservation | [string](#string) | | Optional. The BigQuery reservation to use for execution. If unset, the value from workflow\_settings.yaml is used. If neither is set, default BigQuery behavior applies. sqlanvil CLI only (GCP sqlanvil support pending). |
| metadata | [ActionConfig.Metadata](#sqlanvil-ActionConfig-Metadata) | | Metadata for this assertion. |
[]()
### ActionConfig.ColumnDescriptor
[Section titled “ActionConfig.ColumnDescriptor”](#actionconfigcolumndescriptor)
| Field | Type | Label | Description |
| ------------------ | ----------------- | -------- | ----------------------------------------------------------------------- |
| path | [string](#string) | repeated | The identifier for the column, using multiple parts for nested records. |
| description | [string](#string) | | A text description of the column. |
| bigqueryPolicyTags | [string](#string) | repeated | A list of BigQuery policy tags that will be applied to the column. |
| tags | [string](#string) | repeated | A list of tags for this column which will be applied. |
[]()
### ActionConfig.DataPreparationConfig
[Section titled “ActionConfig.DataPreparationConfig”](#actionconfigdatapreparationconfig)
| Field | Type | Label | Description |
| ----------------- | -------------------------------------------------------------------------------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| name | [string](#string) | | The name of the data preparation. |
| dataset | [string](#string) | | The dataset (schema) of the destination table. |
| project | [string](#string) | | The Google Cloud project (database) of the destination table. |
| dependencyTargets | [ActionConfig.Target](#sqlanvil-ActionConfig-Target) | repeated | Targets of actions that this action is dependent on. |
| filename | [string](#string) | | Path to the source file that the contents of the action is loaded from. |
| tags | [string](#string) | repeated | A list of user-defined tags with which the action should be labeled. |
| disabled | [bool](#bool) | | If set to true, this action will not be executed. However, the action can still be depended upon. Useful for temporarily turning off broken actions. |
| description | [string](#string) | | Description of the data preparation. |
| errorTable | [ActionConfig.DataPreparationConfig.ErrorTableConfig](#sqlanvil-ActionConfig-DataPreparationConfig-ErrorTableConfig) | | |
| loadMode | [ActionConfig.LoadModeConfig](#sqlanvil-ActionConfig-LoadModeConfig) | | |
[]()
### ActionConfig.DataPreparationConfig.ErrorTableConfig
[Section titled “ActionConfig.DataPreparationConfig.ErrorTableConfig”](#actionconfigdatapreparationconfigerrortableconfig)
| Field | Type | Label | Description |
| ------------- | ----------------- | ----- | ------------------------------------------------------- |
| name | [string](#string) | | The name of the error table. |
| dataset | [string](#string) | | The dataset (schema) of the error table. |
| project | [string](#string) | | The Google Cloud project (database) of the error table. |
| retentionDays | [int32](#int32) | | |
[]()
### ActionConfig.DeclarationConfig
[Section titled “ActionConfig.DeclarationConfig”](#actionconfigdeclarationconfig)
| Field | Type | Label | Description |
| ----------- | ------------------------------------------------------------------------------------------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| name | [string](#string) | | The name of the declaration. |
| dataset | [string](#string) | | The dataset (schema) of the declaration. |
| project | [string](#string) | | The Google Cloud project (database) of the declaration. |
| description | [string](#string) | | Description of the declaration. |
| columns | [ActionConfig.ColumnDescriptor](#sqlanvil-ActionConfig-ColumnDescriptor) | repeated | Descriptions of columns within the declaration. |
| filename | [string](#string) | | Path to the source file that the contents of the action is loaded from. |
| tags | [string](#string) | repeated | A list of user-defined tags with which the action should be labeled. |
| connection | [string](#string) | | Optional. Name of a connection (from WorkflowSettings.connections) that this declaration reads from. Only valid on declarations. |
| columnTypes | [ActionConfig.DeclarationConfig.ColumnTypesEntry](#sqlanvil-ActionConfig-DeclarationConfig-ColumnTypesEntry) | repeated | Optional. Column name -> SQL type, used to generate the foreign table when `connection` bridges via FDW. Distinct from `columns` (descriptions). |
[]()
### ActionConfig.DeclarationConfig.ColumnTypesEntry
[Section titled “ActionConfig.DeclarationConfig.ColumnTypesEntry”](#actionconfigdeclarationconfigcolumntypesentry)
| Field | Type | Label | Description |
| ----- | ----------------- | ----- | ----------- |
| key | [string](#string) | | |
| value | [string](#string) | | |
[]()
### ActionConfig.ExportConfig
[Section titled “ActionConfig.ExportConfig”](#actionconfigexportconfig)
Configuration for a `type: "export"` action: writes a SELECT result to a Parquet/CSV/JSON file at a cloud or local location.
| Field | Type | Label | Description |
| ----------------- | ------------------------------------------------------------------ | -------- | -------------------------------------------------------------------------------- |
| name | [string](#string) | | The name of the export. |
| dataset | [string](#string) | | The dataset (schema) used to qualify the export’s target name. |
| project | [string](#string) | | The Google Cloud project (database) of the export. |
| dependencyTargets | [ActionConfig.Target](#sqlanvil-ActionConfig-Target) | repeated | Targets of actions that this action is dependent on. |
| filename | [string](#string) | | Path to the source file that the contents of the action is loaded from. |
| tags | [string](#string) | repeated | A list of user-defined tags with which the action should be labeled. |
| disabled | [bool](#bool) | | If set to true, this action will not be executed. |
| description | [string](#string) | | Description of the export. |
| hermetic | [bool](#bool) | | If true, this action only depends on data from explicitly-declared dependencies. |
| export | [ActionConfig.ExportOptions](#sqlanvil-ActionConfig-ExportOptions) | | The export destination + format options (the `export: {}` block). |
[]()
### ActionConfig.ExportOptions
[Section titled “ActionConfig.ExportOptions”](#actionconfigexportoptions)
The user-facing `export: {}` block on a `type: "export"` action.
| Field | Type | Label | Description |
| --------- | -------------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------ |
| location | [string](#string) | | Destination folder/prefix URI: gs\:// |
| format | [string](#string) | | ”parquet" |
| overwrite | [bool](#bool) | | Overwrite an existing object/file. Defaults to true (defaulted in core when absent). |
| filename | [string](#string) | | Output base filename; defaults to the action name. |
| options | [ActionConfig.ExportOptions.OptionsEntry](#sqlanvil-ActionConfig-ExportOptions-OptionsEntry) | repeated | Format-specific passthrough options (e.g. compression, csv header/delimiter). |
[]()
### ActionConfig.ExportOptions.OptionsEntry
[Section titled “ActionConfig.ExportOptions.OptionsEntry”](#actionconfigexportoptionsoptionsentry)
| Field | Type | Label | Description |
| ----- | ----------------- | ----- | ----------- |
| key | [string](#string) | | |
| value | [string](#string) | | |
[]()
### ActionConfig.ForeignWrapperConfig
[Section titled “ActionConfig.ForeignWrapperConfig”](#actionconfigforeignwrapperconfig)
| Field | Type | Label | Description |
| ----------------- | ---------------------------------------------------------------------------------------------------------- | -------- | ----------- |
| name | [string](#string) | | |
| wrapper | [string](#string) | | |
| server | [string](#string) | | |
| options | [ActionConfig.ForeignWrapperConfig.OptionsEntry](#sqlanvil-ActionConfig-ForeignWrapperConfig-OptionsEntry) | repeated | |
| filename | [string](#string) | | |
| dependencyTargets | [ActionConfig.Target](#sqlanvil-ActionConfig-Target) | repeated | |
[]()
### ActionConfig.ForeignWrapperConfig.OptionsEntry
[Section titled “ActionConfig.ForeignWrapperConfig.OptionsEntry”](#actionconfigforeignwrapperconfigoptionsentry)
| Field | Type | Label | Description |
| ----- | ----------------- | ----- | ----------- |
| key | [string](#string) | | |
| value | [string](#string) | | |
[]()
### ActionConfig.IcebergTableConfig
[Section titled “ActionConfig.IcebergTableConfig”](#actionconfigicebergtableconfig)
| Field | Type | Label | Description |
| ------------------ | -------------------------------------------------------------------------------------------------- | ----- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| fileFormat | [ActionConfig.IcebergTableConfig.FileFormat](#sqlanvil-ActionConfig-IcebergTableConfig-FileFormat) | | The file format for the BigQuery table. |
| connection | [string](#string) | | The connection specifying the credentials to be used to read and write to external storage, such as Cloud Storage. The connection can have the form `{project}.{location}.{connection_id}` or \`projects/{project}/locations/{location}/connections/{connection\_id}”, or be set to DEFAULT. |
| bucketName | [string](#string) | | The name of the Cloud Storage bucket where table data is stored. This value is be used to construct the storage URI in the following way: `gs://{bucket_name}/{table_folder_root}/{table_folder_subpath}``. If `storage\_uri\` is provided, this value is ignored. |
| tableFolderRoot | [string](#string) | | The name of the first-level folder inside the Cloud Storage bucket where table data is stored. This value will be used to construct the storage URI in the following way: `gs://{bucket_name}/{table_folder_root}/{table_folder_subpath}``. If `storage\_uri\` is provided, this value is ignored. |
| tableFolderSubpath | [string](#string) | | The path under the first-level folder of the Cloud Storage bucket where table data is stored. This value will be used to construct the storage URI in the following way: `gs://{bucket_name}/{table_folder_root}/{table_folder_subpath}``. If `storage\_uri\` is provided, this value is ignored. |
[]()
### ActionConfig.ImportConfig
[Section titled “ActionConfig.ImportConfig”](#actionconfigimportconfig)
Configuration for a `type: "import"` action: loads a Parquet/CSV/JSON file into a table in the warehouse (the inverse of `type: "export"`). The resulting table is ref()-able.
| Field | Type | Label | Description |
| ----------------- | ------------------------------------------------------------------ | -------- | -------------------------------------------------------------------------------- |
| name | [string](#string) | | The name of the import (the destination table name). |
| dataset | [string](#string) | | The dataset (schema) used to qualify the import’s target name. |
| project | [string](#string) | | The Google Cloud project (database) of the import. |
| dependencyTargets | [ActionConfig.Target](#sqlanvil-ActionConfig-Target) | repeated | Targets of actions that this action is dependent on. |
| filename | [string](#string) | | Path to the source .sqlx file the action is defined in (generated). |
| tags | [string](#string) | repeated | A list of user-defined tags with which the action should be labeled. |
| disabled | [bool](#bool) | | If set to true, this action will not be executed. |
| description | [string](#string) | | Description of the import. |
| hermetic | [bool](#bool) | | If true, this action only depends on data from explicitly-declared dependencies. |
| import | [ActionConfig.ImportOptions](#sqlanvil-ActionConfig-ImportOptions) | | The import source + format options (the `import: {}` block). |
[]()
### ActionConfig.ImportOptions
[Section titled “ActionConfig.ImportOptions”](#actionconfigimportoptions)
The user-facing `import: {}` block on a `type: "import"` action.
| Field | Type | Label | Description |
| --------- | -------------------------------------------------------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| location | [string](#string) | | Source file/glob/URI to read: gs\:// |
| format | [string](#string) | | ”parquet" |
| overwrite | [bool](#bool) | | Replace the destination table (drop + create). Defaults to true. When false, rows are appended (INSERT … SELECT) into an existing table. |
| options | [ActionConfig.ImportOptions.OptionsEntry](#sqlanvil-ActionConfig-ImportOptions-OptionsEntry) | repeated | Format-specific passthrough options (reserved for future reader options). |
[]()
### ActionConfig.ImportOptions.OptionsEntry
[Section titled “ActionConfig.ImportOptions.OptionsEntry”](#actionconfigimportoptionsoptionsentry)
| Field | Type | Label | Description |
| ----- | ----------------- | ----- | ----------- |
| key | [string](#string) | | |
| value | [string](#string) | | |
[]()
### ActionConfig.IncrementalTableConfig
[Section titled “ActionConfig.IncrementalTableConfig”](#actionconfigincrementaltableconfig)
| Field | Type | Label | Description |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| name | [string](#string) | | The name of the incremental table. |
| dataset | [string](#string) | | The dataset (schema) of the incremental table. |
| project | [string](#string) | | The Google Cloud project (database) of the incremental table. |
| dependencyTargets | [ActionConfig.Target](#sqlanvil-ActionConfig-Target) | repeated | Targets of actions that this action is dependent on. |
| filename | [string](#string) | | Path to the source file that the contents of the action is loaded from. |
| tags | [string](#string) | repeated | A list of user-defined tags with which the action should be labeled. |
| disabled | [bool](#bool) | | If set to true, this action will not be executed. However, the action can still be depended upon. Useful for temporarily turning off broken actions. |
| preOperations | [string](#string) | repeated | Queries to run before `query`. This can be useful for granting permissions. |
| postOperations | [string](#string) | repeated | Queries to run after `query`. |
| protected | [bool](#bool) | | If true, prevents the dataset from being rebuilt from scratch. |
| uniqueKey | [string](#string) | repeated | If set, unique key represents a set of names of columns that will act as a the unique key. To enforce this, when updating the incremental table, sqlanvil merges rows with `uniqueKey` instead of appending them. |
| description | [string](#string) | | Description of the incremental table. |
| columns | [ActionConfig.ColumnDescriptor](#sqlanvil-ActionConfig-ColumnDescriptor) | repeated | Descriptions of columns within the table. |
| partitionBy | [string](#string) | | The key by which to partition the table. Typically the name of a timestamp or the date column. See . |
| partitionExpirationDays | [int32](#int32) | | The number of days for which BigQuery stores data in each partition. The setting applies to all partitions in a table, but is calculated independently for each partition based on the partition time. |
| requirePartitionFilter | [bool](#bool) | | Declares whether the partitioned table requires a WHERE clause predicate filter that filters the partitioning column. |
| updatePartitionFilter | [string](#string) | | SQL-based filter for when incremental updates are applied. |
| clusterBy | [string](#string) | repeated | The keys by which to cluster partitions by. See . |
| labels | [ActionConfig.IncrementalTableConfig.LabelsEntry](#sqlanvil-ActionConfig-IncrementalTableConfig-LabelsEntry) | repeated | Key-value pairs for BigQuery labels. |
| additionalOptions | [ActionConfig.IncrementalTableConfig.AdditionalOptionsEntry](#sqlanvil-ActionConfig-IncrementalTableConfig-AdditionalOptionsEntry) | repeated | Key-value pairs of additional options to pass to the BigQuery API. Some options, for example, partitionExpirationDays, have dedicated type/validity checked fields. For such options, use the dedicated fields. |
| dependOnDependencyAssertions | [bool](#bool) | | When set to true, assertions dependent upon any dependency will be add as dedpendency to this action |
| assertions | [ActionConfig.TableAssertionsConfig](#sqlanvil-ActionConfig-TableAssertionsConfig) | | Assertions to be run on the dataset. If configured, relevant assertions will automatically be created and run as a dependency of this dataset. |
| hermetic | [bool](#bool) | | If true, this indicates that the action only depends on data from explicitly-declared dependencies. Otherwise if false, it indicates that the action depends on data from a source which has not been declared as a dependency. |
| onSchemaChange | [ActionConfig.OnSchemaChange](#sqlanvil-ActionConfig-OnSchemaChange) | | Defines the action behavior if the selected columns in the query don’t the match columns in the target table. |
| iceberg | [ActionConfig.IcebergTableConfig](#sqlanvil-ActionConfig-IcebergTableConfig) | | Configuration options for an Iceberg table. |
| metadata | [ActionConfig.Metadata](#sqlanvil-ActionConfig-Metadata) | | Metadata for this incremental table. |
| reservation | [string](#string) | | Optional. The BigQuery reservation to use for execution. If unset, the value from workflow\_settings.yaml is used. If neither is set, default BigQuery behavior applies. sqlanvil CLI only (GCP sqlanvil support pending). |
| postgres | [PostgresOptions](#sqlanvil-PostgresOptions) | | Postgres and Supabase specific options. |
| supabase | [SupabaseOptions](#sqlanvil-SupabaseOptions) | | |
| mysql | [MysqlOptions](#sqlanvil-MysqlOptions) | | MySQL/MariaDB specific options (engine/charset/collation + indexes). |
[]()
### ActionConfig.IncrementalTableConfig.AdditionalOptionsEntry
[Section titled “ActionConfig.IncrementalTableConfig.AdditionalOptionsEntry”](#actionconfigincrementaltableconfigadditionaloptionsentry)
| Field | Type | Label | Description |
| ----- | ----------------- | ----- | ----------- |
| key | [string](#string) | | |
| value | [string](#string) | | |
[]()
### ActionConfig.IncrementalTableConfig.LabelsEntry
[Section titled “ActionConfig.IncrementalTableConfig.LabelsEntry”](#actionconfigincrementaltableconfiglabelsentry)
| Field | Type | Label | Description |
| ----- | ----------------- | ----- | ----------- |
| key | [string](#string) | | |
| value | [string](#string) | | |
[]()
### ActionConfig.LoadModeConfig
[Section titled “ActionConfig.LoadModeConfig”](#actionconfigloadmodeconfig)
| Field | Type | Label | Description |
| ----------------- | -------------------------------------------------------- | -------- | --------------------------------------- |
| mode | [ActionConfig.LoadMode](#sqlanvil-ActionConfig-LoadMode) | | |
| incrementalColumn | [string](#string) | | Required when mode is MAXIMUM or UNIQUE |
| uniqueKey | [string](#string) | repeated | required when mode is MERGE |
[]()
### ActionConfig.Metadata
[Section titled “ActionConfig.Metadata”](#actionconfigmetadata)
| Field | Type | Label | Description |
| --------------- | ------------------------------------------------- | ----- | ------------------------------------------ |
| overview | [string](#string) | | A detailed description of the data object. |
| extraProperties | [google.protobuf.Struct](#google-protobuf-Struct) | | Extra properties of the data object. |
[]()
### ActionConfig.NotebookConfig
[Section titled “ActionConfig.NotebookConfig”](#actionconfignotebookconfig)
| Field | Type | Label | Description |
| ---------------------------- | ---------------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| name | [string](#string) | | The name of the notebook. |
| location | [string](#string) | | The Google Cloud location of the notebook. |
| project | [string](#string) | | The Google Cloud project (database) of the notebook. |
| dependencyTargets | [ActionConfig.Target](#sqlanvil-ActionConfig-Target) | repeated | Targets of actions that this action is dependent on. |
| filename | [string](#string) | | Path to the source file that the contents of the action is loaded from. |
| tags | [string](#string) | repeated | A list of user-defined tags with which the action should be labeled. |
| disabled | [bool](#bool) | | If set to true, this action will not be executed. However, the action can still be depended upon. Useful for temporarily turning off broken actions. |
| description | [string](#string) | | Description of the notebook. |
| dependOnDependencyAssertions | [bool](#bool) | | When set to true, assertions dependent upon any dependency will be add as dedpendency to this action |
[]()
### ActionConfig.OperationConfig
[Section titled “ActionConfig.OperationConfig”](#actionconfigoperationconfig)
| Field | Type | Label | Description |
| ---------------------------- | ------------------------------------------------------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| name | [string](#string) | | The name of the operation. |
| dataset | [string](#string) | | The dataset (schema) of the operation. |
| project | [string](#string) | | The Google Cloud project (database) of the operation. |
| dependencyTargets | [ActionConfig.Target](#sqlanvil-ActionConfig-Target) | repeated | Targets of actions that this action is dependent on. |
| filename | [string](#string) | | Path to the source file that the contents of the action is loaded from. |
| tags | [string](#string) | repeated | A list of user-defined tags with which the action should be labeled. |
| disabled | [bool](#bool) | | If set to true, this action will not be executed. However, the action can still be depended upon. Useful for temporarily turning off broken actions. |
| hasOutput | [bool](#bool) | | Declares that this action creates a dataset which should be referenceable as a dependency target, for example by using the `ref` function. |
| description | [string](#string) | | Description of the operation. |
| columns | [ActionConfig.ColumnDescriptor](#sqlanvil-ActionConfig-ColumnDescriptor) | repeated | Descriptions of columns within the operation. Can only be set if hasOutput is true. |
| dependOnDependencyAssertions | [bool](#bool) | | When set to true, assertions dependent upon any dependency will be add as dedpendency to this action |
| hermetic | [bool](#bool) | | If true, this indicates that the action only depends on data from explicitly-declared dependencies. Otherwise if false, it indicates that the action depends on data from a source which has not been declared as a dependency. |
| reservation | [string](#string) | | Optional. The BigQuery reservation to use for execution. If unset, the value from workflow\_settings.yaml is used. If neither is set, default BigQuery behavior applies. sqlanvil CLI only (GCP sqlanvil support pending). |
[]()
### ActionConfig.RealtimePublicationConfig
[Section titled “ActionConfig.RealtimePublicationConfig”](#actionconfigrealtimepublicationconfig)
| Field | Type | Label | Description |
| ----------------- | ---------------------------------------------------- | -------- | ----------- |
| name | [string](#string) | | |
| table | [string](#string) | | |
| events | [string](#string) | repeated | |
| filename | [string](#string) | | |
| dependencyTargets | [ActionConfig.Target](#sqlanvil-ActionConfig-Target) | repeated | |
[]()
### ActionConfig.RlsPolicyConfig
[Section titled “ActionConfig.RlsPolicyConfig”](#actionconfigrlspolicyconfig)
| Field | Type | Label | Description |
| ----------------- | ---------------------------------------------------- | -------- | ----------- |
| name | [string](#string) | | |
| table | [string](#string) | | |
| command | [string](#string) | | |
| roles | [string](#string) | repeated | |
| using | [string](#string) | | |
| withCheck | [string](#string) | | |
| filename | [string](#string) | | |
| dependencyTargets | [ActionConfig.Target](#sqlanvil-ActionConfig-Target) | repeated | |
[]()
### ActionConfig.ScriptConfig
[Section titled “ActionConfig.ScriptConfig”](#actionconfigscriptconfig)
Configuration for a script action: an execution-time user script (Python in v1) that stages files / runs glue steps as a first-class DAG node. Language-neutral by design — `language` selects the per-language resolver in the CLI; the friendly `python:` actions.yaml key is sugar that normalizes to `script: { language: "python", ... }`. Scripts never receive warehouse credentials; they produce files, and `type: "import"` is the loading boundary.
| Field | Type | Label | Description |
| ----------------- | ---------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| name | [string](#string) | | The name of the script action (how downstream actions depend on it). |
| dataset | [string](#string) | | The dataset (schema) used to qualify the script’s target name. |
| project | [string](#string) | | The project (database) used to qualify the script’s target name. |
| dependencyTargets | [ActionConfig.Target](#sqlanvil-ActionConfig-Target) | repeated | Targets of actions that this action is dependent on. |
| filename | [string](#string) | | Path to the script file to execute, relative to the project root. |
| tags | [string](#string) | repeated | A list of user-defined tags with which the action should be labeled. |
| disabled | [bool](#bool) | | If set to true, this action will not be executed. |
| description | [string](#string) | | Description of the script action. |
| language | [string](#string) | | Script language (“python” in v1). Set automatically by the `python:` sugar. |
| args | [string](#string) | repeated | Arguments passed verbatim to the script. |
| depsFile | [string](#string) | | Declared dependency manifest, relative to the project root (requirements.txt for python). sqlanvil validates the environment against it but never installs anything. |
| runtimeVersion | [string](#string) | | Runtime version specifier (a PEP 440 specifier for python, e.g. ”>=3.11”). |
| envRoot | [string](#string) | | Environment root directory, relative to the project root (a venv for python) — its interpreter runs the script. When unset, the interpreter is resolved from PATH. |
| timeoutMillis | [int32](#int32) | | Per-action timeout in milliseconds. 0 means the default (30 minutes). |
[]()
### ActionConfig.TableAssertionsConfig
[Section titled “ActionConfig.TableAssertionsConfig”](#actionconfigtableassertionsconfig)
Options for shorthand specifying assertions, useable for some table-based action types.
| Field | Type | Label | Description |
| ------------- | ------------------------------------------------------------------------------------------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| uniqueKey | [string](#string) | repeated | Column(s) which constitute the dataset’s unique key index. If set, the resulting assertion will fail if there is more than one row in the dataset with the same values for all of these column(s). |
| uniqueKeys | [ActionConfig.TableAssertionsConfig.UniqueKey](#sqlanvil-ActionConfig-TableAssertionsConfig-UniqueKey) | repeated | |
| nonNull | [string](#string) | repeated | Column(s) which may never be `NULL`. If set, the resulting assertion will fail if any row contains `NULL` values for these column(s). |
| rowConditions | [string](#string) | repeated | General condition(s) which should hold true for all rows in the dataset. If set, the resulting assertion will fail if any row violates any of these condition(s). |
[]()
### ActionConfig.TableAssertionsConfig.UniqueKey
[Section titled “ActionConfig.TableAssertionsConfig.UniqueKey”](#actionconfigtableassertionsconfiguniquekey)
Combinations of column(s), each of which should constitute a unique key index for the dataset. If set, the resulting assertion(s) will fail if there is more than one row in the dataset with the same values for all of the column(s) in the unique key(s).
| Field | Type | Label | Description |
| --------- | ----------------- | -------- | ----------- |
| uniqueKey | [string](#string) | repeated | |
[]()
### ActionConfig.TableConfig
[Section titled “ActionConfig.TableConfig”](#actionconfigtableconfig)
| Field | Type | Label | Description |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| name | [string](#string) | | The name of the table. |
| dataset | [string](#string) | | The dataset (schema) of the table. |
| project | [string](#string) | | The Google Cloud project (database) of the table. |
| dependencyTargets | [ActionConfig.Target](#sqlanvil-ActionConfig-Target) | repeated | Targets of actions that this action is dependent on. |
| filename | [string](#string) | | Path to the source file that the contents of the action is loaded from. |
| tags | [string](#string) | repeated | A list of user-defined tags with which the action should be labeled. |
| disabled | [bool](#bool) | | If set to true, this action will not be executed. However, the action can still be depended upon. Useful for temporarily turning off broken actions. |
| preOperations | [string](#string) | repeated | Queries to run before `query`. This can be useful for granting permissions. |
| postOperations | [string](#string) | repeated | Queries to run after `query`. |
| description | [string](#string) | | Description of the table. |
| columns | [ActionConfig.ColumnDescriptor](#sqlanvil-ActionConfig-ColumnDescriptor) | repeated | Descriptions of columns within the table. |
| partitionBy | [string](#string) | | The key by which to partition the table. Typically the name of a timestamp or the date column. See . |
| partitionExpirationDays | [int32](#int32) | | The number of days for which BigQuery stores data in each partition. The setting applies to all partitions in a table, but is calculated independently for each partition based on the partition time. |
| requirePartitionFilter | [bool](#bool) | | Declares whether the partitioned table requires a WHERE clause predicate filter that filters the partitioning column. |
| clusterBy | [string](#string) | repeated | The keys by which to cluster partitions by. See . |
| labels | [ActionConfig.TableConfig.LabelsEntry](#sqlanvil-ActionConfig-TableConfig-LabelsEntry) | repeated | Key-value pairs for BigQuery labels. |
| additionalOptions | [ActionConfig.TableConfig.AdditionalOptionsEntry](#sqlanvil-ActionConfig-TableConfig-AdditionalOptionsEntry) | repeated | Key-value pairs of additional options to pass to the BigQuery API. Some options, for example, partitionExpirationDays, have dedicated type/validity checked fields. For such options, use the dedicated fields. |
| dependOnDependencyAssertions | [bool](#bool) | | When set to true, assertions dependent upon any dependency will be add as dedpendency to this action |
| assertions | [ActionConfig.TableAssertionsConfig](#sqlanvil-ActionConfig-TableAssertionsConfig) | | Assertions to be run on the dataset. If configured, relevant assertions will automatically be created and run as a dependency of this dataset. |
| hermetic | [bool](#bool) | | If true, this indicates that the action only depends on data from explicitly-declared dependencies. Otherwise if false, it indicates that the action depends on data from a source which has not been declared as a dependency. |
| iceberg | [ActionConfig.IcebergTableConfig](#sqlanvil-ActionConfig-IcebergTableConfig) | | Configuration options for an Iceberg table. |
| metadata | [ActionConfig.Metadata](#sqlanvil-ActionConfig-Metadata) | | Metadata for this table. |
| reservation | [string](#string) | | Optional. The BigQuery reservation to use for execution. If unset, the value from workflow\_settings.yaml is used. If neither is set, default BigQuery behavior applies. sqlanvil CLI only (GCP sqlanvil support pending). |
| postgres | [PostgresOptions](#sqlanvil-PostgresOptions) | | Postgres and Supabase specific options. |
| supabase | [SupabaseOptions](#sqlanvil-SupabaseOptions) | | |
| mysql | [MysqlOptions](#sqlanvil-MysqlOptions) | | MySQL/MariaDB specific options (engine/charset/collation + indexes). |
[]()
### ActionConfig.TableConfig.AdditionalOptionsEntry
[Section titled “ActionConfig.TableConfig.AdditionalOptionsEntry”](#actionconfigtableconfigadditionaloptionsentry)
| Field | Type | Label | Description |
| ----- | ----------------- | ----- | ----------- |
| key | [string](#string) | | |
| value | [string](#string) | | |
[]()
### ActionConfig.TableConfig.LabelsEntry
[Section titled “ActionConfig.TableConfig.LabelsEntry”](#actionconfigtableconfiglabelsentry)
| Field | Type | Label | Description |
| ----- | ----------------- | ----- | ----------- |
| key | [string](#string) | | |
| value | [string](#string) | | |
[]()
### ActionConfig.Target
[Section titled “ActionConfig.Target”](#actionconfigtarget)
Target represents a unique action identifier.
| Field | Type | Label | Description |
| -------------------------- | ----------------- | ----- | ------------------------------------------------------------------------------------------ |
| project | [string](#string) | | The Google Cloud project (database) of the action. |
| dataset | [string](#string) | | The dataset (schema) of the action. For notebooks, this is the location. |
| name | [string](#string) | | The name of the action. |
| includeDependentAssertions | [bool](#bool) | | flag for when we want to add assertions of this dependency in dependency\_targets as well. |
[]()
### ActionConfig.VectorIndexConfig
[Section titled “ActionConfig.VectorIndexConfig”](#actionconfigvectorindexconfig)
| Field | Type | Label | Description |
| ----------------- | -------------------------------------------------------------------------------------------------- | -------- | ----------- |
| name | [string](#string) | | |
| table | [string](#string) | | |
| column | [string](#string) | | |
| dimensions | [uint32](#uint32) | | |
| indexType | [string](#string) | | |
| params | [ActionConfig.VectorIndexConfig.ParamsEntry](#sqlanvil-ActionConfig-VectorIndexConfig-ParamsEntry) | repeated | |
| filename | [string](#string) | | |
| dependencyTargets | [ActionConfig.Target](#sqlanvil-ActionConfig-Target) | repeated | |
[]()
### ActionConfig.VectorIndexConfig.ParamsEntry
[Section titled “ActionConfig.VectorIndexConfig.ParamsEntry”](#actionconfigvectorindexconfigparamsentry)
| Field | Type | Label | Description |
| ----- | ----------------- | ----- | ----------- |
| key | [string](#string) | | |
| value | [string](#string) | | |
[]()
### ActionConfig.ViewConfig
[Section titled “ActionConfig.ViewConfig”](#actionconfigviewconfig)
| Field | Type | Label | Description |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| name | [string](#string) | | The name of the view. |
| dataset | [string](#string) | | The dataset (schema) of the view. |
| project | [string](#string) | | The Google Cloud project (database) of the view. |
| dependencyTargets | [ActionConfig.Target](#sqlanvil-ActionConfig-Target) | repeated | Targets of actions that this action is dependent on. |
| filename | [string](#string) | | Path to the source file that the contents of the action is loaded from. |
| tags | [string](#string) | repeated | A list of user-defined tags with which the action should be labeled. |
| disabled | [bool](#bool) | | If set to true, this action will not be executed. However, the action can still be depended upon. Useful for temporarily turning off broken actions. |
| preOperations | [string](#string) | repeated | Queries to run before `query`. This can be useful for granting permissions. |
| postOperations | [string](#string) | repeated | Queries to run after `query`. |
| materialized | [bool](#bool) | | Applies the materialized view optimization, see . |
| partitionBy | [string](#string) | | Optional. Applicable only to materialized view. The key by which to partition the materialized view. Typically the name of a timestamp or the date column. See . |
| clusterBy | [string](#string) | repeated | Optional. Applicable only to materialized view. The keys by which to cluster partitions by. See . |
| description | [string](#string) | | Description of the view. |
| columns | [ActionConfig.ColumnDescriptor](#sqlanvil-ActionConfig-ColumnDescriptor) | repeated | Descriptions of columns within the table. |
| labels | [ActionConfig.ViewConfig.LabelsEntry](#sqlanvil-ActionConfig-ViewConfig-LabelsEntry) | repeated | Key-value pairs for BigQuery labels. |
| additionalOptions | [ActionConfig.ViewConfig.AdditionalOptionsEntry](#sqlanvil-ActionConfig-ViewConfig-AdditionalOptionsEntry) | repeated | Key-value pairs of additional options to pass to the BigQuery API. Some options, for example, partitionExpirationDays, have dedicated type/validity checked fields. For such options, use the dedicated fields. |
| dependOnDependencyAssertions | [bool](#bool) | | When set to true, assertions dependent upon any dependency will be add as dedpendency to this action |
| hermetic | [bool](#bool) | | If true, this indicates that the action only depends on data from explicitly-declared dependencies. Otherwise if false, it indicates that the action depends on data from a source which has not been declared as a dependency. |
| assertions | [ActionConfig.TableAssertionsConfig](#sqlanvil-ActionConfig-TableAssertionsConfig) | | Assertions to be run on the dataset. If configured, relevant assertions will automatically be created and run as a dependency of this dataset. |
| metadata | [ActionConfig.Metadata](#sqlanvil-ActionConfig-Metadata) | | Metadata for this view. |
| reservation | [string](#string) | | Optional. The BigQuery reservation to use for execution. If unset, the value from workflow\_settings.yaml is used. If neither is set, default BigQuery behavior applies. sqlanvil CLI only (GCP sqlanvil support pending). |
| postgres | [PostgresOptions](#sqlanvil-PostgresOptions) | | Postgres-native options. For a materialized view (materialized: true), `no_data` (CREATE … WITH NO DATA) and `refresh_policy` (“on\_dependency\_change” → in-place REFRESH instead of drop+recreate) apply; indexes also apply to materialized views. |
| mysql | [MysqlOptions](#sqlanvil-MysqlOptions) | | MySQL/MariaDB-native options. Only meaningful for a materialized view (materialized: true), which MySQL emulates as a refreshed table snapshot — engine/charset/collation/row\_format and indexes apply to that table. |
[]()
### ActionConfig.ViewConfig.AdditionalOptionsEntry
[Section titled “ActionConfig.ViewConfig.AdditionalOptionsEntry”](#actionconfigviewconfigadditionaloptionsentry)
| Field | Type | Label | Description |
| ----- | ----------------- | ----- | ----------- |
| key | [string](#string) | | |
| value | [string](#string) | | |
[]()
### ActionConfig.ViewConfig.LabelsEntry
[Section titled “ActionConfig.ViewConfig.LabelsEntry”](#actionconfigviewconfiglabelsentry)
| Field | Type | Label | Description |
| ----- | ----------------- | ----- | ----------- |
| key | [string](#string) | | |
| value | [string](#string) | | |
[]()
### ActionConfigs
[Section titled “ActionConfigs”](#actionconfigs)
Action configs defines the contents of `actions.yaml` configuration files.
| Field | Type | Label | Description |
| ------- | -------------------------------------- | -------- | ----------- |
| actions | [ActionConfig](#sqlanvil-ActionConfig) | repeated | |
[]()
### BigQueryConnection
[Section titled “BigQueryConnection”](#bigqueryconnection)
BigQueryConnection — connection params for warehouse.kind = “bigquery”. Mirrors the legacy flat fields on WorkflowSettings (default\_project, default\_location, default\_dataset) but namespaced under warehouse.
| Field | Type | Label | Description |
| -------------- | ----------------- | ----- | --------------------------------------------------- |
| project | [string](#string) | | The Google Cloud project (database). |
| location | [string](#string) | | BigQuery location, e.g. “US”, “EU”, “europe-west4”. |
| defaultDataset | [string](#string) | | Default dataset (schema). |
[]()
### ConnectionConfig
[Section titled “ConnectionConfig”](#connectionconfig)
A named connection: the warehouse (read/write target) or a read-only source.
| Field | Type | Label | Description |
| -------------- | ----------------- | ----- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| platform | [string](#string) | | Optional. One of “bigquery”, “postgres”, “supabase”, “mysql”. MySQL/MariaDB sources are runner-extract only (no Postgres FDW for MySQL): the CLI reads the source at run time and materializes it into the write warehouse. `sqlanvil introspect` scaffolds declarations (columnTypes) from a MySQL source. |
| project | [string](#string) | | Optional. BigQuery source default project. |
| dataset | [string](#string) | | Optional. BigQuery source default dataset. |
| saKeyId | [string](#string) | | Optional. Non-secret Vault secret id used in generated BigQuery FDW server DDL. |
| billingProject | [string](#string) | | Optional. BigQuery project that runs/bills the FDW query jobs. Defaults to `project`. Set this to your own project when `project` is a dataset you can read but not bill (e.g. `bigquery-public-data`): the FDW server’s `project_id` becomes the billing project and the foreign table reads the source via a full-FQN subquery. |
| host | [string](#string) | | Optional. Postgres/MySQL source host (non-secret; password lives in .df-credentials.json). |
| port | [uint32](#uint32) | | Optional. Postgres/MySQL source port. |
| database | [string](#string) | | Optional. Postgres/MySQL source database name. |
| defaultSchema | [string](#string) | | Optional. Postgres/Supabase default schema. |
| mode | [string](#string) | | Optional. Source read mode for a cross-warehouse source connection: “fdw” (default for bigquery/postgres/supabase) — a live foreign-data-wrapper table (needs the Vault secret + wrappers). “runner-extract” (default and only mode for mysql; also supported for bigquery) — the CLI reads the source directly at run time and materializes the rows into a plain table (keyless; no Vault secret, no wrappers/postgis on the branch). See the extract action. |
[]()
### DefaultIcebergConfig
[Section titled “DefaultIcebergConfig”](#defaulticebergconfig)
| Field | Type | Label | Description |
| ------------------ | ----------------- | ----- | ---------------------------------------------------------------------------------------------------------------------------- |
| bucketName | [string](#string) | | Optional. Bucket name used to construct a storage URI when creating an Iceberg table. |
| tableFolderRoot | [string](#string) | | Optional. Table folder root used to construct a storage URI when creating an Iceberg table. |
| tableFolderSubpath | [string](#string) | | Optional. Table folder subpath used to construct a storage URI when creating an Iceberg table. |
| connection | [string](#string) | | Optional. The connection specifying the credentials to be used to read and write to external storage, such as Cloud Storage. |
[]()
### Environment
[Section titled “Environment”](#environment)
A named environment (dev/staging/prod). Holds only NON-SECRET overrides plus a pointer to a gitignored credentials file — never secrets themselves.
| Field | Type | Label | Description |
| --------------- | -------------------------------------------------------- | -------- | ----------------------------------------------------------------------------------------- |
| schemaSuffix | [string](#string) | | |
| vars | [Environment.VarsEntry](#sqlanvil-Environment-VarsEntry) | repeated | |
| defaultDatabase | [string](#string) | | |
| defaultLocation | [string](#string) | | |
| credentials | [string](#string) | | Path (relative to the project dir) to this environment’s gitignored .df-credentials file. |
[]()
### Environment.VarsEntry
[Section titled “Environment.VarsEntry”](#environmentvarsentry)
| Field | Type | Label | Description |
| ----- | ----------------- | ----- | ----------- |
| key | [string](#string) | | |
| value | [string](#string) | | |
[]()
### MysqlConnection
[Section titled “MysqlConnection”](#mysqlconnection)
| Field | Type | Label | Description |
| -------- | ----------------- | ----- | ------------------- |
| host | [string](#string) | | |
| port | [uint32](#uint32) | | |
| database | [string](#string) | | |
| user | [string](#string) | | |
| password | [string](#string) | | |
| sslMode | [string](#string) | | SSL mode: “disable" |
[]()
### MysqlOptions
[Section titled “MysqlOptions”](#mysqloptions)
MySQL/MariaDB table options, secondary indexes (plain/unique/fulltext/spatial, with optional per-column prefix lengths), row\_format, and native partitioning.
| Field | Type | Label | Description |
| --------- | ---------------------------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| engine | [string](#string) | | Storage engine, emitted as ENGINE=\ (e.g. “InnoDB”, “MyISAM”). Omit to use the server default. |
| charset | [string](#string) | | Default character set, emitted as DEFAULT CHARSET=\ (e.g. “utf8mb4”). |
| collation | [string](#string) | | Default collation, emitted as COLLATE=\ (e.g. “utf8mb4\_unicode\_ci”). |
| rowFormat | [string](#string) | | Row format, emitted as ROW\_FORMAT=\ (e.g. “DYNAMIC”, “COMPRESSED”, “COMPACT”, “REDUNDANT”). Omit to use the engine default. |
| indexes | [MysqlOptions.Index](#sqlanvil-MysqlOptions-Index) | repeated | |
| partition | [MysqlOptions.Partition](#sqlanvil-MysqlOptions-Partition) | | |
[]()
### MysqlOptions.Index
[Section titled “MysqlOptions.Index”](#mysqloptionsindex)
| Field | Type | Label | Description |
| ------- | ----------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| name | [string](#string) | | Optional; when omitted, derived as \\_\\_idx (or \_key if unique), capped at 63 chars. |
| columns | [string](#string) | repeated | Column names. A column may carry a prefix length in MySQL’s own syntax — “description(50)” — emitted as `description`(50) (required to index BLOB/TEXT, optional elsewhere). |
| unique | [bool](#bool) | | |
| type | [string](#string) | | Index type: "" (default B-tree/engine default), “fulltext”, or “spatial”. Mutually exclusive with `unique`. NB a SPATIAL index requires a NOT NULL SRID-attributed geometry column — CTAS-created columns are nullable, so spatial usually needs an `ALTER TABLE ... MODIFY` pre-step in post\_operations. |
[]()
### MysqlOptions.Partition
[Section titled “MysqlOptions.Partition”](#mysqloptionspartition)
Native MySQL/MariaDB partitioning. NB: MySQL requires every column used in the partitioning expression to be part of every UNIQUE/PRIMARY key — so a partitioned incremental table’s `uniqueKey` must include the partition column(s).
| Field | Type | Label | Description |
| ---------- | ---------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| kind | [MysqlOptions.Partition.Kind](#sqlanvil-MysqlOptions-Partition-Kind) | | |
| expression | [string](#string) | | The expression/columns inside `PARTITION BY <kind> (...)`, emitted verbatim. For RANGE/LIST: a column or expression (e.g. “id”, “YEAR(created\_at)”). For HASH/KEY: a column list. (RANGE COLUMNS / LIST COLUMNS not modeled in v1.) |
| partitions | [MysqlOptions.Partition.Bound](#sqlanvil-MysqlOptions-Partition-Bound) | repeated | |
| count | [uint32](#uint32) | | HASH/KEY partition count, emitted as `PARTITIONS <n>`. Ignored for RANGE/LIST. |
[]()
### MysqlOptions.Partition.Bound
[Section titled “MysqlOptions.Partition.Bound”](#mysqloptionspartitionbound)
RANGE/LIST child partitions. `values` is the raw clause body after the name, e.g. “VALUES LESS THAN (2024)” (range, use MAXVALUE for a catch-all) or “VALUES IN (‘us’, ‘ca’)” (list).
| Field | Type | Label | Description |
| ------ | ----------------- | ----- | ----------- |
| name | [string](#string) | | |
| values | [string](#string) | | |
[]()
### NotebookRuntimeOptionsConfig
[Section titled “NotebookRuntimeOptionsConfig”](#notebookruntimeoptionsconfig)
| Field | Type | Label | Description |
| ----------------------------- | ------------------------------------------------------------------------------------ | ----- | --------------------------------------------------------------------------------------------------------------------------------- |
| outputBucket | [string](#string) | | Storage bucket to output notebooks to after their execution. |
| runtimeTemplateName | [string](#string) | | Colab runtime template (), from which a runtime is created for notebook executions. |
| repositorySnapshotDestination | [RepositorySnapshotDestinationConfig](#sqlanvil-RepositorySnapshotDestinationConfig) | | Storage URI to upload the snapshot to. For empty URI it defaults to the provided output\_bucket. |
[]()
### PostgresConnection
[Section titled “PostgresConnection”](#postgresconnection)
PostgresConnection — libpq-style connection params for warehouse.kind = “postgres”. Standard Postgres host/port/database/user.
| Field | Type | Label | Description |
| ------------- | ----------------- | ----- | ------------------- |
| host | [string](#string) | | |
| port | [uint32](#uint32) | | |
| database | [string](#string) | | |
| user | [string](#string) | | |
| password | [string](#string) | | |
| sslMode | [string](#string) | | SSL mode: “disable" |
| defaultSchema | [string](#string) | | |
[]()
### PostgresOptions
[Section titled “PostgresOptions”](#postgresoptions)
PostgresOptions — Postgres-native table-level options. Mirrors what BigQueryOptions-style fields do in TableConfig but in idiomatic Postgres.
Used as a peer of the existing `bigquery: {...}` shape on action configs: publish(“daily\_orders”, { postgres: { tablespace: “fast\_ssd”, … } })
| Field | Type | Label | Description |
| ------------- | ---------------------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| tablespace | [string](#string) | | Physical storage placement (CREATE TABLE … TABLESPACE \). |
| fillfactor | [uint32](#uint32) | | Storage parameter — fraction of each page to fill on insert (1-100). |
| unlogged | [bool](#bool) | | CREATE UNLOGGED TABLE — faster writes, lost on crash. For staging/temp tables where durability isn’t required. |
| partition | [PostgresOptions.Partition](#sqlanvil-PostgresOptions-Partition) | | |
| indexes | [PostgresOptions.Index](#sqlanvil-PostgresOptions-Index) | repeated | |
| noData | [bool](#bool) | | Materialized view: create WITH NO DATA (empty until first refresh). Default (false) is WITH DATA. Named for the non-default so proto3’s false default means the sensible WITH DATA. |
| refreshPolicy | [string](#string) | | Materialized view refresh on re-run: “on\_dependency\_change” refreshes an existing matview in place (REFRESH MATERIALIZED VIEW) instead of dropping + recreating. Default (unset) drops + recreates each run (safe — also picks up definition changes, which REFRESH does not). |
[]()
### PostgresOptions.Index
[Section titled “PostgresOptions.Index”](#postgresoptionsindex)
Indexes to create alongside the table.
| Field | Type | Label | Description |
| ------- | ---------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| name | [string](#string) | | |
| columns | [string](#string) | repeated | |
| method | [PostgresOptions.Index.Method](#sqlanvil-PostgresOptions-Index-Method) | | |
| where | [string](#string) | | Partial index predicate (WHERE \). |
| unique | [bool](#bool) | | |
| include | [string](#string) | repeated | INCLUDE non-key columns for covering indexes. |
| opclass | [string](#string) | | Operator class applied to each indexed column, e.g. “gin\_trgm\_ops” (pg\_trgm), “jsonb\_path\_ops”, or “vector\_l2\_ops” (pgvector). Required for gin/gist indexes on types without a default opclass. |
[]()
### PostgresOptions.Partition
[Section titled “PostgresOptions.Partition”](#postgresoptionspartition)
Native Postgres declarative partitioning.
| Field | Type | Label | Description |
| -------------- | ---------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| kind | [PostgresOptions.Partition.Kind](#sqlanvil-PostgresOptions-Partition-Kind) | | |
| columns | [string](#string) | repeated | |
| partitions | [PostgresOptions.Partition.Bound](#sqlanvil-PostgresOptions-Partition-Bound) | repeated | |
| includeDefault | [bool](#bool) | | Also create a catch-all DEFAULT partition so rows outside every bound still insert (recommended for declarative full-refresh loads). |
[]()
### PostgresOptions.Partition.Bound
[Section titled “PostgresOptions.Partition.Bound”](#postgresoptionspartitionbound)
Child partitions. `values` is the raw FOR VALUES clause body matching the kind, e.g. “FROM (‘2024-01-01’) TO (‘2025-01-01’)” (range), “IN (‘us’, ‘ca’)” (list), or “WITH (MODULUS 4, REMAINDER 0)” (hash).
| Field | Type | Label | Description |
| ------------ | ---------------------------------------------------------------- | ----- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| name | [string](#string) | | |
| values | [string](#string) | | |
| subPartition | [PostgresOptions.Partition](#sqlanvil-PostgresOptions-Partition) | | Sub-partitioning: make this child a partitioned table in its own right (PARTITION BY …), with its own nested `partitions`. Omit for a leaf child that holds rows directly. |
[]()
### RepositorySnapshotDestinationConfig
[Section titled “RepositorySnapshotDestinationConfig”](#repositorysnapshotdestinationconfig)
| Field | Type | Label | Description |
| --------------------- | ----------------- | ----- | ------------------------------------------------- |
| repositorySnapshotUri | [string](#string) | | Storage URI to upload the repository snapshot to. |
[]()
### SupabaseConnection
[Section titled “SupabaseConnection”](#supabaseconnection)
SupabaseConnection — connection params for warehouse.kind = “supabase”. Supabase projects expose a Postgres connection via project\_ref + service\_role\_key, or a direct connection string for bypassing PostgREST.
| Field | Type | Label | Description |
| ---------------- | ----------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| projectRef | [string](#string) | | From the Supabase dashboard (project URL host before .supabase.co). |
| serviceRoleKey | [string](#string) | | Project service-role JWT. NEVER commit literally — use ${ENV\_VAR} interpolation in workflow\_settings.yaml. |
| defaultSchema | [string](#string) | | |
| connectionString | [string](#string) | | Optional override — direct Postgres URL bypassing the PostgREST proxy. e.g. “postgresql://postgres:${PASSWORD}@db.\.supabase.co:5432/postgres”. If set, takes precedence over project\_ref + service\_role\_key for the direct DB connection. service\_role\_key is still used for RLS bypass. |
[]()
### SupabaseOptions
[Section titled “SupabaseOptions”](#supabaseoptions)
SupabaseOptions — Supabase-specific platform features layered on top of standard Postgres. Used as a peer of `postgres: {...}` for projects targeting `warehouse: { kind: supabase }`.
| Field | Type | Label | Description |
| ----------------- | ---------------------------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| postgres | [PostgresOptions](#sqlanvil-PostgresOptions) | | Standard Postgres options apply. Set these via `postgres:` directly or nest under `supabase.postgres:` — either is accepted. |
| publishToRealtime | [bool](#bool) | | ALTER PUBLICATION supabase\_realtime ADD TABLE \. Implicitly sets REPLICA IDENTITY appropriately. |
| enableRls | [bool](#bool) | | ALTER TABLE \ ENABLE ROW LEVEL SECURITY. Note: only enables RLS — policies are declared via the `rlsPolicy` action type (see Phase 5). |
| ownerRole | [string](#string) | | OWNER TO \. Typically “postgres” or “service\_role”. |
| vectors | [SupabaseOptions.VectorConfig](#sqlanvil-SupabaseOptions-VectorConfig) | repeated | |
[]()
### SupabaseOptions.VectorConfig
[Section titled “SupabaseOptions.VectorConfig”](#supabaseoptionsvectorconfig)
pgvector convenience config. Equivalent to declaring a PostgresOptions.Index with method=HNSW or method=GIST + ivfflat ops, but more ergonomic for RAG pipelines.
| Field | Type | Label | Description |
| ---------- | ---------------------------------------------------------------------------------------------- | -------- | -------------------------------------------------- |
| column | [string](#string) | | |
| dimensions | [uint32](#uint32) | | |
| indexType | [SupabaseOptions.VectorConfig.IndexType](#sqlanvil-SupabaseOptions-VectorConfig-IndexType) | | |
| params | [SupabaseOptions.VectorConfig.ParamsEntry](#sqlanvil-SupabaseOptions-VectorConfig-ParamsEntry) | repeated | ivfflat: { lists }, hnsw: { m, ef\_construction }. |
[]()
### SupabaseOptions.VectorConfig.ParamsEntry
[Section titled “SupabaseOptions.VectorConfig.ParamsEntry”](#supabaseoptionsvectorconfigparamsentry)
| Field | Type | Label | Description |
| ----- | ----------------- | ----- | ----------- |
| key | [string](#string) | | |
| value | [string](#string) | | |
[]()
### WarehouseConfig
[Section titled “WarehouseConfig”](#warehouseconfig)
WarehouseConfig — discriminated union over connection variants. The `kind:` YAML tag selects which `oneof` arm is unmarshalled.
Example YAML: warehouse: kind: postgres host: db.example.com port: 5432 database: analytics user: sqlanvil\_writer password: ${PG\_PASSWORD} ssl\_mode: require default\_schema: public
| Field | Type | Label | Description |
| -------- | -------------------------------------------------- | ----- | ----------- |
| bigquery | [BigQueryConnection](#sqlanvil-BigQueryConnection) | | |
| postgres | [PostgresConnection](#sqlanvil-PostgresConnection) | | |
| supabase | [SupabaseConnection](#sqlanvil-SupabaseConnection) | | |
| mysql | [MysqlConnection](#sqlanvil-MysqlConnection) | | |
[]()
### WorkflowSettings
[Section titled “WorkflowSettings”](#workflowsettings)
Workflow Settings defines the contents of the `workflow_settings.yaml` configuration file.
| Field | Type | Label | Description |
| ----------------------------- | ---------------------------------------------------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| sqlanvilCoreVersion | [string](#string) | | The desired sqlanvil core version to compile against. |
| defaultProject | [string](#string) | | Required. The default Google Cloud project (database). |
| defaultDataset | [string](#string) | | Required. The default dataset (schema). |
| defaultLocation | [string](#string) | | Required. The default BigQuery location to use. For more information on BigQuery locations, see . |
| defaultAssertionDataset | [string](#string) | | Required. The default dataset (schema) for assertions. |
| vars | [WorkflowSettings.VarsEntry](#sqlanvil-WorkflowSettings-VarsEntry) | repeated | Optional. User-defined variables that are made available to project code during compilation. An object containing a list of “key”: value pairs. |
| projectSuffix | [string](#string) | | Optional. The suffix to append to all Google Cloud project references. |
| datasetSuffix | [string](#string) | | Optional. The suffix to append to all dataset references. |
| namePrefix | [string](#string) | | Optional. The prefix to append to all action names. |
| defaultNotebookRuntimeOptions | [NotebookRuntimeOptionsConfig](#sqlanvil-NotebookRuntimeOptionsConfig) | | Optional. Default runtime options for Notebook actions. |
| builtinAssertionNamePrefix | [string](#string) | | Optional. The prefix to append to built-in assertion names. |
| defaultIcebergConfig | [DefaultIcebergConfig](#sqlanvil-DefaultIcebergConfig) | | Optional. Default config options for Iceberg tables. |
| disableAssertions | [bool](#bool) | | Optional. Disables all assertions including built-in assertions (uniqueKey, nonNull, rowConditions) and manual assertions (type: assertion). When true, assertions will still be compiled but marked as disabled. |
| defaultReservation | [string](#string) | | Optional. The default BigQuery reservation to use for execution. If unset, default BigQuery behavior applies. sqlanvil CLI only (GCP sqlanvil support pending). |
| extension | [Extension](#sqlanvil-Extension) | | Optional. An external package that provides an extension. |
| includeTestsInCompiledGraph | [bool](#bool) | | Optional. If set to true, unit tests will be included in the compiled graph. |
| warehouse | [string](#string) | | Optional. The database warehouse to use, e.g. “bigquery”, “postgres”, “supabase”. |
| connections | [WorkflowSettings.ConnectionsEntry](#sqlanvil-WorkflowSettings-ConnectionsEntry) | repeated | Optional. Named connections (warehouse + read-only sources). |
| environments | [WorkflowSettings.EnvironmentsEntry](#sqlanvil-WorkflowSettings-EnvironmentsEntry) | repeated | Optional. Named environments (dev/staging/prod) selected with `--environment`. |
[]()
### WorkflowSettings.ConnectionsEntry
[Section titled “WorkflowSettings.ConnectionsEntry”](#workflowsettingsconnectionsentry)
| Field | Type | Label | Description |
| ----- | ---------------------------------------------- | ----- | ----------- |
| key | [string](#string) | | |
| value | [ConnectionConfig](#sqlanvil-ConnectionConfig) | | |
[]()
### WorkflowSettings.EnvironmentsEntry
[Section titled “WorkflowSettings.EnvironmentsEntry”](#workflowsettingsenvironmentsentry)
| Field | Type | Label | Description |
| ----- | ------------------------------------ | ----- | ----------- |
| key | [string](#string) | | |
| value | [Environment](#sqlanvil-Environment) | | |
[]()
### WorkflowSettings.VarsEntry
[Section titled “WorkflowSettings.VarsEntry”](#workflowsettingsvarsentry)
| Field | Type | Label | Description |
| ----- | ----------------- | ----- | ----------- |
| key | [string](#string) | | |
| value | [string](#string) | | |
[]()
### ActionConfig.IcebergTableConfig.FileFormat
[Section titled “ActionConfig.IcebergTableConfig.FileFormat”](#actionconfigicebergtableconfigfileformat)
Supported file formats for BigQuery tables.
| Name | Number | Description |
| ------------------------- | ------ | ---------------------- |
| FILE\_FORMAT\_UNSPECIFIED | 0 | Default value. |
| PARQUET | 1 | Apache Parquet format. |
[]()
### ActionConfig.LoadMode
[Section titled “ActionConfig.LoadMode”](#actionconfigloadmode)
| Name | Number | Description |
| -------------- | ------ | ----------------------------------------------------------------------------------------------------------------- |
| REPLACE\_TABLE | 0 | Replace existing table (default). |
| APPEND | 1 | Insert into destination table. |
| MAXIMUM | 2 | Insert only records where the specified column value exceeds the existing maximum value in the destination table. |
| UNIQUE | 3 | Insert only records where the specified column value is not already present in the destination column values. |
| MERGE | 4 | Merge records into the destination table, deduplicating using 1+ unique keys |
[]()
### ActionConfig.OnSchemaChange
[Section titled “ActionConfig.OnSchemaChange”](#actionconfigonschemachange)
| Name | Number | Description |
| ----------- | ------ | ------------------------------------------------------------------------------------ |
| IGNORE | 0 | Ignore any schema changes (default). |
| FAIL | 1 | Fails if the query would result in a new column(s) being added, deleted, or renamed. |
| EXTEND | 2 | Does not block any new column(s) from being added. |
| SYNCHRONIZE | 3 | Does not block any new column(s) from being added, deleted or renamed. |
[]()
### MysqlOptions.Partition.Kind
[Section titled “MysqlOptions.Partition.Kind”](#mysqloptionspartitionkind)
| Name | Number | Description |
| ----- | ------ | ----------- |
| RANGE | 0 | |
| LIST | 1 | |
| HASH | 2 | |
| KEY | 3 | |
[]()
### PostgresOptions.Index.Method
[Section titled “PostgresOptions.Index.Method”](#postgresoptionsindexmethod)
| Name | Number | Description |
| ----- | ------ | ----------- |
| BTREE | 0 | |
| HASH | 1 | |
| GIN | 2 | |
| GIST | 3 | |
| BRIN | 4 | |
[]()
### PostgresOptions.Partition.Kind
[Section titled “PostgresOptions.Partition.Kind”](#postgresoptionspartitionkind)
| Name | Number | Description |
| ----- | ------ | ----------- |
| RANGE | 0 | |
| LIST | 1 | |
| HASH | 2 | |
[]()
### SupabaseOptions.VectorConfig.IndexType
[Section titled “SupabaseOptions.VectorConfig.IndexType”](#supabaseoptionsvectorconfigindextype)
| Name | Number | Description |
| ------- | ------ | ----------- |
| IVFFLAT | 0 | |
| HNSW | 1 | |
## Scalar Value Types
[Section titled “Scalar Value Types”](#scalar-value-types)
| .proto Type | Notes | C++ | Java | Python | Go | C# | PHP | Ruby |
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ---------- | ----------- | ------- | ---------- | -------------- | ------------------------------ |
| [double]() | | double | double | float | float64 | double | float | Float |
| [float]() | | float | float | float | float32 | float | float | Float |
| [int32]() | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) |
| [int64]() | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead. | int64 | long | int/long | int64 | long | integer/string | Bignum |
| [uint32]() | Uses variable-length encoding. | uint32 | int | int/long | uint32 | uint | integer | Bignum or Fixnum (as required) |
| [uint64]() | Uses variable-length encoding. | uint64 | long | int/long | uint64 | ulong | integer/string | Bignum or Fixnum (as required) |
| [sint32]() | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) |
| [sint64]() | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s. | int64 | long | int/long | int64 | long | integer/string | Bignum |
| [fixed32]() | Always four bytes. More efficient than uint32 if values are often greater than 2^28. | uint32 | int | int | uint32 | uint | integer | Bignum or Fixnum (as required) |
| [fixed64]() | Always eight bytes. More efficient than uint64 if values are often greater than 2^56. | uint64 | long | int/long | uint64 | ulong | integer/string | Bignum |
| [sfixed32]() | Always four bytes. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) |
| [sfixed64]() | Always eight bytes. | int64 | long | int/long | int64 | long | integer/string | Bignum |
| [bool]() | | bool | boolean | boolean | bool | bool | boolean | TrueClass/FalseClass |
| [string]() | A string must always contain UTF-8 encoded or 7-bit ASCII text. | string | String | str/unicode | string | string | string | String (UTF-8) |
| [bytes]() | May contain any arbitrary sequence of bytes. | string | ByteString | str | \[]byte | ByteString | string | String (ASCII-8BIT) |
# Configs Overview
> SQLAnvil workflow_settings.yaml and action config block reference for BigQuery, Postgres, Supabase, and MySQL/MariaDB.
# Configs Reference
[Section titled “Configs Reference”](#configs-reference)
SQLAnvil actions are configured at two levels:
1. **Project-level** — `workflow_settings.yaml` at the root of your project, defining the warehouse connection and global defaults.
2. **Action-level** — the `config {}` block in SQLX files, or the action entry in `actions.yaml`, defining per-action behavior (type, dependencies, schema, warehouse-specific options).
For the complete proto-generated field reference (all action config fields), see [Configs Proto Reference](/reference/configs).
***
## `workflow_settings.yaml` — warehouse config
[Section titled “workflow\_settings.yaml — warehouse config”](#workflow_settingsyaml--warehouse-config)
`workflow_settings.yaml` holds non-secret settings; `warehouse:` is a string (`bigquery` | `postgres` | `supabase`). The **connection** lives in a separate, gitignored `.df-credentials.json`.
### BigQuery
[Section titled “BigQuery”](#bigquery)
workflow\_settings.yaml
```yaml
warehouse: bigquery
defaultProject: my-gcp-project
defaultLocation: US
defaultDataset: analytics
sqlanvilCoreVersion: 1.24.0 # pin the release you installed — `sqlanvil init` writes this
```
```json
// .df-credentials.json — ADC by default; add a "credentials" service-account key JSON to override
{ "projectId": "my-gcp-project", "location": "US" }
```
### PostgreSQL
[Section titled “PostgreSQL”](#postgresql)
workflow\_settings.yaml
```yaml
warehouse: postgres
defaultDataset: analytics
defaultAssertionDataset: sqlanvil_assertions
sqlanvilCoreVersion: 1.24.0 # pin the release you installed — `sqlanvil init` writes this
```
.df-credentials.json
```json
{
"host": "db.example.com", "port": 5432, "database": "analytics",
"user": "sqlanvil_writer", "password": "...", "sslMode": "require", "defaultSchema": "public"
}
```
### Supabase
[Section titled “Supabase”](#supabase)
workflow\_settings.yaml
```yaml
warehouse: supabase
defaultDataset: public
defaultAssertionDataset: sqlanvil_assertions
sqlanvilCoreVersion: 1.24.0 # pin the release you installed — `sqlanvil init` writes this
```
```json
// .df-credentials.json — Session pooler (copy host verbatim from the dashboard Connect dialog)
{
"host": "aws-1-.pooler.supabase.com", "port": 5432, "database": "postgres",
"user": "postgres.", "password": "...", "sslMode": "require", "defaultSchema": "public"
}
```
### MySQL / MariaDB
[Section titled “MySQL / MariaDB”](#mysql--mariadb)
workflow\_settings.yaml
```yaml
warehouse: mysql # one adapter for MySQL 8 and MariaDB 11
defaultDataset: analytics # the MySQL DATABASE ("schema" == database)
defaultAssertionDataset: sqlanvil_assertions
sqlanvilCoreVersion: 1.5.0 # MySQL landed in core 1.5.0
```
```json
// .df-credentials.json — MysqlConnection (NOTE: no defaultSchema)
{
"host": "localhost", "port": 3306, "database": "analytics",
"user": "root", "password": "", "sslMode": "disable"
}
```
## `workflow_settings.yaml` — named environments
[Section titled “workflow\_settings.yaml — named environments”](#workflow_settingsyaml--named-environments)
Define dev/staging/prod once; select with `--environment ` on `compile`/`run`/`test`. Each environment carries non-secret overrides **plus a pointer to its own gitignored credentials file** — secrets never enter the committed config.
workflow\_settings.yaml
```yaml
environments:
dev:
schemaSuffix: dev # output schemas get a _dev suffix
credentials: .df-credentials.dev.json
prod:
defaultDatabase: prod_db
vars: { region: us-prod }
credentials: .df-credentials.prod.json
```
Per-env fields: `schemaSuffix`, `vars`, `defaultDatabase`, `defaultLocation`, `credentials`. **Precedence: explicit CLI flag > environment > `workflow_settings` defaults** (`vars` merge per-key). See the [Environments guide](/docs/guides/environments/).
***
## Action config blocks
[Section titled “Action config blocks”](#action-config-blocks)
### Common fields (all warehouses)
[Section titled “Common fields (all warehouses)”](#common-fields-all-warehouses)
```sql
config {
type: "table", -- table | view | incremental | assertion | operation | declaration
schema: "my_schema", -- overrides defaultDataset / defaultSchema
database: "my_db", -- overrides default project / database
description: "My table.",
metadata: { -- free-form descriptor metadata (table/view/incremental/assertion)
overview: "longer notes",
extraProperties: { owner: "analytics" }
},
tags: ["daily", "core"],
disabled: false,
hermetic: true,
dependOnDependencyAssertions: true,
dependencies: ["other_table"]
}
```
`metadata` (an `overview` string + arbitrary `extraProperties`) attaches to the action’s descriptor and is accepted on `table`, `view`, `incremental`, and `assertion` configs. Unlike `description`/`columns` (which become warehouse COMMENTs), `metadata` is carried in the compiled graph for tooling/lineage — it is not written to the warehouse.
### BigQuery-specific fields
[Section titled “BigQuery-specific fields”](#bigquery-specific-fields)
```sql
config {
type: "table",
partitionBy: "DATE(created_at)", -- BigQuery only
partitionExpirationDays: 90, -- BigQuery only
clusterBy: ["customer_id", "region"], -- BigQuery only
labels: { team: "analytics" }, -- BigQuery only
additionalOptions: { kms_key_name: "..." }, -- BigQuery only
reservation: "projects/.../reservations/my-res" -- BigQuery only
}
```
### PostgreSQL-specific fields
[Section titled “PostgreSQL-specific fields”](#postgresql-specific-fields)
```sql
config {
type: "table",
postgres: {
tablespace: "fast_ssd",
fillfactor: 80,
unlogged: false,
partition: {
kind: 0, -- numeric enum: RANGE=0, LIST=1, HASH=2
columns: ["order_date"]
},
indexes: [
{
name: "ix_orders_customer",
columns: ["customer_id"],
method: 0, -- numeric enum: BTREE=0, HASH=1, GIN=2, GIST=3, BRIN=4 (omit for btree)
unique: false,
where: "", -- partial index predicate
include: [] -- covering index columns
}
]
}
}
```
### MySQL / MariaDB-specific fields
[Section titled “MySQL / MariaDB-specific fields”](#mysql--mariadb-specific-fields)
```sql
config {
type: "table",
mysql: {
engine: "InnoDB", -- ENGINE=
charset: "utf8mb4", -- DEFAULT CHARSET=
collation: "utf8mb4_unicode_ci", -- COLLATE=
indexes: [
{ name: "ix_label", columns: ["label"], unique: false }
]
}
}
```
Plain B-tree indexes only (no `where`/`include`/`opclass`/`method` — those are Postgres-only). `description:`/`columns:` apply as real comments (tables/incrementals; not views). `type: "view", materialized: true` is emulated as a refreshed table snapshot (drop + CTAS each run). Partitioning, `FULLTEXT`/`SPATIAL`/prefix indexes, and `row_format` use raw DDL in `operations`. See the [MySQL/MariaDB guide](/docs/guides/mysql/).
### Supabase-specific fields
[Section titled “Supabase-specific fields”](#supabase-specific-fields)
The `supabase: {}` block (on `table`/`incremental` configs) plus the `rlsPolicy`, `realtimePublication`, and `vectorIndex` action types. See the [Supabase guide](/docs/guides/supabase/).
```sql
config {
type: "table",
supabase: {
enableRls: true,
publishToRealtime: true,
ownerRole: "postgres",
vectors: [
{
column: "embedding",
dimensions: 1536,
indexType: "hnsw",
params: { m: "16", ef_construction: "64" }
}
]
}
}
```
### Export action (`type: "export"`)
[Section titled “Export action (type: "export")”](#export-action-type-export)
Writes a SELECT result to a Parquet/CSV/JSON file. BigQuery compiles to native `EXPORT DATA` (`gs://` only); Postgres/Supabase export runner-side via DuckDB (`s3://`/`gs://`/local). See the [File Exports guide](/docs/guides/exports/).
```sql
config {
type: "export",
export: {
location: "s3://bucket/orders/", -- gs:// | s3:// | local:// | path
format: "parquet", -- parquet | csv | json
overwrite: true, -- default true
filename: "orders" -- optional; defaults to action name
}
}
SELECT * FROM ${ref("orders")}
```
Cloud credentials live in a `storage` section of the gitignored `.df-credentials.json` (keyed by scheme — `s3`/`gcs`), used only by the DuckDB path; `local://` needs none and BigQuery uses its own GCS access.
***
## Cross-warehouse compatibility
[Section titled “Cross-warehouse compatibility”](#cross-warehouse-compatibility)
| Feature | BigQuery | Postgres | Supabase | MySQL/MariaDB |
| ------------------------------------ | ------------------------------ | ---------------------------- | ---------------------------------- | --------------------------- |
| `partitionBy` / `clusterBy` | ✓ | ✗ (use `postgres.partition`) | ✗ | ✗ |
| `postgres.indexes` | ✗ | ✓ | ✓ | ✗ (use `mysql.indexes`) |
| `mysql.indexes` / `mysql.engine` | ✗ | ✗ | ✗ | ✓ |
| `labels` / `reservation` | ✓ | ✗ | ✗ | ✗ |
| `description:` / `columns:` comments | ✓ | ✓ | ✓ | ✓ (tables, not views) |
| `pre_operations` / `post_operations` | ✓ | ✓ | ✓ | ✓ |
| `materialized` view | ✓ (auto-refresh) | ✓ (manual refresh) | ✓ (manual refresh) | ✓ (emulated table snapshot) |
| named `connections` (FDW) | source | ✓ | ✓ | ✗ |
| `type: "export"` (files) | ✓ (`EXPORT DATA`, gs\:// only) | ✓ (DuckDB) | ✓ (DuckDB, incl. Supabase Storage) | ✗ |
| `supabase.enableRls` | ✗ | ✗ | ✓ | |
SQLAnvil emits a **compilation error** if a warehouse-specific config field is used against the wrong `warehouse.kind`.
# Declaration
> API reference for the Declaration action type in SQLAnvil.
# Class: Declaration
[Section titled “Class: Declaration”](#class-declaration)
You can declare any external table as a data source in sqlanvil. Declaring external data sources that are external to sqlanvil lets you treat those data sources as sqlanvil objects.
Declaring data sources is optional, but can be useful when you want to do the following:
* Reference or resolve declared sources in the same way as any other table in sqlanvil.
* View declared sources in the visualized sqlanvil graph.
* Use sqlanvil to manage the table-level and column-level descriptions of externally created tables.
* Trigger workflow invocations that include all the dependents of an external data source.
You can create declarations in the following ways. Available config options are defined in [DeclarationConfig](configs#sqlanvil-ActionConfig-DeclarationConfig), and are shared across all the followiing ways of creating declarations.
**Using a SQLX file:**
```sql
-- definitions/name.sqlx
config {
type: "declaration"
}
-- Note: no SQL should be present.
```
**Using action configs files:**
definitions/actions.yaml
```yaml
actions:
- declare:
name: name
```
**Using the Javascript API:**
definitions/file.js
```js
declare("name")
```
## Hierarchy
[Section titled “Hierarchy”](#hierarchy)
* ActionBuilder‹Declaration›
↳ **Declaration**
## Index
[Section titled “Index”](#index)
### Methods
[Section titled “Methods”](#methods)
* [columns](#columns)
* [description](#description)
## Methods
[Section titled “Methods”](#methods-1)
### columns
[Section titled “columns”](#columns)
▸ **columns**(`columns`: ColumnDescriptor\[]): *this*
**`deprecated`** Deprecated in favor of [DeclarationConfig.columns](configs#sqlanvil-ActionConfig-DeclarationConfig).
Sets the column descriptors of columns in this table.
**Parameters:**
| Name | Type |
| --------- | ------------------- |
| `columns` | ColumnDescriptor\[] |
**Returns:** *this*
***
### description
[Section titled “description”](#description)
▸ **description**(`description`: string): *this*
**`deprecated`** Deprecated in favor of [DeclarationConfig.description](configs#sqlanvil-ActionConfig-DeclarationConfig).
Sets the description of this assertion.
**Parameters:**
| Name | Type |
| ------------- | ------ |
| `description` | string |
**Returns:** *this*
# Export
> API reference for the Export action type in SQLAnvil.
# Class: Export
[Section titled “Class: Export”](#class-export)
Exports write the result of a SELECT to a Parquet/CSV/JSON file at a cloud or local location.
```sql
-- definitions/dump.sqlx
config {
type: "export",
export: { location: "s3://bucket/orders/", format: "parquet", overwrite: true }
}
SELECT * FROM ${ref("orders")}
```
On BigQuery this compiles to a native `EXPORT DATA` statement (gs\:// only). On Postgres/Supabase the runner performs the export via DuckDB.
## Hierarchy
[Section titled “Hierarchy”](#hierarchy)
* ActionBuilder‹Export›
↳ **Export**
## Index
[Section titled “Index”](#index)
### Methods
[Section titled “Methods”](#methods)
* [query](#query)
## Methods
[Section titled “Methods”](#methods-1)
### query
[Section titled “query”](#query)
▸ **query**(`contextable`: Contextable‹IActionContext, string›): *this*
Sets the SELECT whose result is exported.
**Parameters:**
| Name | Type |
| ------------- | ----------------------------------- |
| `contextable` | Contextable‹IActionContext, string› |
**Returns:** *this*
# Import
> API reference for the Import action type in SQLAnvil.
# Class: Import
[Section titled “Class: Import”](#class-import)
Imports load a Parquet/CSV/JSON file into a table in the warehouse — the inverse of `type: "export"`. The resulting table is `ref()`-able by downstream actions, so an import is a *producer* (unlike export, a terminal sink). It is config-only: no SQL body — the source is the file at `location`, the destination is the action’s target.
```sql
-- definitions/orders_in.sqlx
config {
type: "import",
import: { location: "s3://bucket/orders/*.parquet", format: "parquet", overwrite: true }
}
-- Note: no SQL should be present.
```
On BigQuery this compiles to a native `LOAD DATA` statement (gs\:// only). On Postgres/Supabase the runner performs the load via DuckDB (read the file, write into the warehouse). Data is loaded as-is (no transform) — shape it in a downstream model.
## Hierarchy
[Section titled “Hierarchy”](#hierarchy)
* ActionBuilder‹Import›
↳ **Import**
# Incremental Table
> API reference for the IncrementalTable action type in SQLAnvil.
# Class: IncrementalTable
[Section titled “Class: IncrementalTable”](#class-incrementaltable)
When you define an incremental table, sqlanvil builds the incremental table from scratch only for the first time. During subsequent executions, sqlanvil only inserts or merges new rows into the incremental table according to the conditions that you configure.
You can create incremental tables in the following ways. Available config options are defined in [IncrementalTableConfig](configs#sqlanvil-ActionConfig-IncrementalTableConfig), and are shared across all the following ways of creating tables.
**Using a SQLX file:**
```sql
-- definitions/name.sqlx
config {
type: "incremental"
}
-- This inserts `1` the first time running, and `2` on subsequent runs.
SELECT ${when(incremental(), 1, 2) }
```
**Using the Javascript API:**
definitions/file.js
```js
publish("name", { type: "incremental" }).query(
ctx => `SELECT ${ctx.when(ctx.incremental(), 1, 2) }`
)
```
Note: When using the Javascript API, methods in this class can be accessed by the returned value. This is where `query` comes from.
## Hierarchy
[Section titled “Hierarchy”](#hierarchy)
* ActionBuilder‹Table›
↳ **IncrementalTable**
## Index
[Section titled “Index”](#index)
### Methods
[Section titled “Methods”](#methods)
* [assertions](#assertions)
* [bigquery](#bigquery)
* [columns](#columns)
* [database](#database)
* [dependencies](#dependencies)
* [description](#description)
* [disabled](#disabled)
* [hermetic](#hermetic)
* [postOps](#postops)
* [preOps](#preops)
* [protected](#protected)
* [query](#query)
* [schema](#schema)
* [setDependOnDependencyAssertions](#setdependondependencyassertions)
* [tags](#tags)
* [type](#type)
* [uniqueKey](#uniquekey)
## Methods
[Section titled “Methods”](#methods-1)
### assertions
[Section titled “assertions”](#assertions)
▸ **assertions**(`tableAssertionsConfig`: TableAssertionsConfig): *IncrementalTable*
**`deprecated`** Deprecated in favor of [IncrementalTableConfig.assertions](configs#sqlanvil-ActionConfig-IncrementalTableConfig).
Sets in-line assertions for this incremental table.
**Parameters:**
| Name | Type |
| ----------------------- | --------------------- |
| `tableAssertionsConfig` | TableAssertionsConfig |
**Returns:** *IncrementalTable*
***
### bigquery
[Section titled “bigquery”](#bigquery)
▸ **bigquery**(`bigquery`: IBigQueryOptions): *this*
**`deprecated`** Deprecated in favor of options available directly on [IncrementalTableConfig](configs#sqlanvil-ActionConfig-IncrementalTableConfig). For example: `publish("name", { type: "table", partitionBy: "column" }`) for BigQuery, or the `postgres:` config block for Postgres.
Sets bigquery options for the action.
**Parameters:**
| Name | Type |
| ---------- | ---------------- |
| `bigquery` | IBigQueryOptions |
**Returns:** *this*
***
### columns
[Section titled “columns”](#columns)
▸ **columns**(`columns`: ColumnDescriptor\[]): *this*
**`deprecated`** Deprecated in favor of [IncrementalTableConfig.columns](configs#sqlanvil-ActionConfig-IncrementalTableConfig).
Sets the column descriptors of columns in this incremental table.
**Parameters:**
| Name | Type |
| --------- | ------------------- |
| `columns` | ColumnDescriptor\[] |
**Returns:** *this*
***
### database
[Section titled “database”](#database)
▸ **database**(`database`: string): *this*
**`deprecated`** Deprecated in favor of [IncrementalTableConfig.project](configs#sqlanvil-ActionConfig-IncrementalTableConfig).
Sets the Sets the database in which to create the output of this action. For BigQuery targets this is the Google Cloud project ID; for Postgres/Supabase targets this is the database name in `workflow_settings.yaml`.
**Parameters:**
| Name | Type |
| ---------- | ------ |
| `database` | string |
**Returns:** *this*
***
### dependencies
[Section titled “dependencies”](#dependencies)
▸ **dependencies**(`value`: Resolvable | Resolvable\[]): *this*
**`deprecated`** Deprecated in favor of [IncrementalTableConfig.dependencies](configs#sqlanvil-ActionConfig-IncrementalTableConfig).
Sets dependencies of the incremental table.
**Parameters:**
| Name | Type |
| ------- | --------------------------- |
| `value` | Resolvable \| Resolvable\[] |
**Returns:** *this*
***
### description
[Section titled “description”](#description)
▸ **description**(`description`: string): *this*
**`deprecated`** Deprecated in favor of [IncrementalTableConfig.description](configs#sqlanvil-ActionConfig-IncrementalTableConfig).
Sets the description of this incremental table.
**Parameters:**
| Name | Type |
| ------------- | ------ |
| `description` | string |
**Returns:** *this*
***
### disabled
[Section titled “disabled”](#disabled)
▸ **disabled**(`disabled`: boolean): *this*
**`deprecated`** Deprecated in favor of [IncrementalTableConfig.disabled](configs#sqlanvil-ActionConfig-IncrementalTableConfig).
If called with `true`, this action is not executed. The action can still be depended upon. Useful for temporarily turning off broken actions.
**Parameters:**
| Name | Type | Default |
| ---------- | ------- | ------- |
| `disabled` | boolean | true |
**Returns:** *this*
***
### hermetic
[Section titled “hermetic”](#hermetic)
▸ **hermetic**(`hermetic`: boolean): *void*
**`deprecated`** Deprecated in favor of [IncrementalTableConfig.hermetic](configs#sqlanvil-ActionConfig-IncrementalTableConfig).
If true, this indicates that the action only depends on data from explicitly-declared dependencies. Otherwise if false, it indicates that the action depends on data from a source which has not been declared as a dependency.
**Parameters:**
| Name | Type |
| ---------- | ------- |
| `hermetic` | boolean |
**Returns:** *void*
***
### postOps
[Section titled “postOps”](#postops)
▸ **postOps**(`posts`: Contextable‹ITableContext, string | string\[]›): *this*
Sets a post-operation to run after the query is run. This is often used for revoking temporary permissions granted to access source tables.
Example:
definitions/file.js
```js
publish("example")
.preOps(ctx => `GRANT \`roles/bigquery.dataViewer\` ON TABLE ${ctx.ref("other_table")} TO "group:automation@example.com"`)
.query(ctx => `SELECT * FROM ${ctx.ref("other_table")}`)
.postOps(ctx => `REVOKE \`roles/bigquery.dataViewer\` ON TABLE ${ctx.ref("other_table")} TO "group:automation@example.com"`)
```
**Parameters:**
| Name | Type |
| ------- | ----------------------------------------------- |
| `posts` | Contextable‹ITableContext, string \| string\[]› |
**Returns:** *this*
***
### preOps
[Section titled “preOps”](#preops)
▸ **preOps**(`pres`: Contextable‹ITableContext, string | string\[]›): *this*
Sets a pre-operation to run before the query is run. This is often used for temporarily granting permission to access source tables.
Example:
definitions/file.js
```js
publish("example")
.preOps(ctx => `GRANT \`roles/bigquery.dataViewer\` ON TABLE ${ctx.ref("other_table")} TO "group:automation@example.com"`)
.query(ctx => `SELECT * FROM ${ctx.ref("other_table")}`)
.postOps(ctx => `REVOKE \`roles/bigquery.dataViewer\` ON TABLE ${ctx.ref("other_table")} TO "group:automation@example.com"`)
```
**Parameters:**
| Name | Type |
| ------ | ----------------------------------------------- |
| `pres` | Contextable‹ITableContext, string \| string\[]› |
**Returns:** *this*
***
### protected
[Section titled “protected”](#protected)
▸ **protected**(`isProtected`: boolean): *this*
**`deprecated`** Deprecated in favor of [IncrementalTableConfig.protected](configs#sqlanvil-ActionConfig-IncrementalTableConfig).
If called with `true`, prevents the dataset from being rebuilt from scratch.
**Parameters:**
| Name | Type |
| ------------- | ------- |
| `isProtected` | boolean |
**Returns:** *this*
***
### query
[Section titled “query”](#query)
▸ **query**(`query`: Contextable‹ITableContext, string›): *this*
Sets the query to generate the table from.
**Parameters:**
| Name | Type |
| ------- | ---------------------------------- |
| `query` | Contextable‹ITableContext, string› |
**Returns:** *this*
***
### schema
[Section titled “schema”](#schema)
▸ **schema**(`schema`: string): *this*
**`deprecated`** Deprecated in favor of [IncrementalTableConfig.dataset](configs#sqlanvil-ActionConfig-IncrementalTableConfig).
Sets the schema (BigQuery dataset / Postgres schema) in which to create the output of this action.
**Parameters:**
| Name | Type |
| -------- | ------ |
| `schema` | string |
**Returns:** *this*
***
### setDependOnDependencyAssertions
[Section titled “setDependOnDependencyAssertions”](#setdependondependencyassertions)
▸ **setDependOnDependencyAssertions**(`dependOnDependencyAssertions`: boolean): *this*
**`deprecated`** Deprecated in favor of [IncrementalTableConfig.dependOnDependencyAssertions](configs#sqlanvil-ActionConfig-IncrementalTableConfig).
When called with `true`, assertions dependent upon any dependency will be add as dedpendency to this action.
**Parameters:**
| Name | Type |
| ------------------------------ | ------- |
| `dependOnDependencyAssertions` | boolean |
**Returns:** *this*
***
### tags
[Section titled “tags”](#tags)
▸ **tags**(`value`: string | string\[]): *this*
**`deprecated`** Deprecated in favor of [IncrementalTableConfig.tags](configs#sqlanvil-ActionConfig-IncrementalTableConfig).
Sets a list of user-defined tags applied to this action.
**Parameters:**
| Name | Type |
| ------- | ------------------- |
| `value` | string \| string\[] |
**Returns:** *this*
***
### type
[Section titled “type”](#type)
▸ **type**(`type`: TableType): *this*
**`deprecated`** Deprecated in favor of action type can being set in the configs passed to action constructor functions, for example `publish("name", { type: "incremental" })`.
**Parameters:**
| Name | Type |
| ------ | --------- |
| `type` | TableType |
**Returns:** *this*
***
### uniqueKey
[Section titled “uniqueKey”](#uniquekey)
▸ **uniqueKey**(`uniqueKey`: string\[]): *void*
**`deprecated`** Deprecated in favor of [IncrementalTableConfig.uniqueKey](configs#sqlanvil-ActionConfig-IncrementalTableConfig).
If set, unique key represents a set of names of columns that will act as a the unique key. To enforce this, when updating the incremental table, sqlanvil merges rows with `uniqueKey` instead of appending them.
**Parameters:**
| Name | Type |
| ----------- | --------- |
| `uniqueKey` | string\[] |
**Returns:** *void*
# Notebook
> API reference for the Notebook action type in SQLAnvil.
# Class: Notebook
[Section titled “Class: Notebook”](#class-notebook)
Notebooks run Jupyter Notebook files, and can output content to the storage buckets defined in `workflow_settings.yaml` files.
You can create notebooks in the following ways. Available config options are defined in [NotebookConfig](configs#sqlanvil-ActionConfig-NotebookConfig), and are shared across all the following ways of creating notebooks.
**Using action configs files:**
definitions/actions.yaml
```yaml
actions:
- notebook:
filename: name.ipynb
```
definitions/name.ipynb
```ipynb
{ "cells": [] }
```
**Using the Javascript API:**
definitions/file.js
```js
notebook("name", { filename: "name.ipynb" })
```
definitions/name.ipynb
```ipynb
{ "cells": [] }
```
## Hierarchy
[Section titled “Hierarchy”](#hierarchy)
* ActionBuilder‹Notebook›
↳ **Notebook**
## Index
[Section titled “Index”](#index)
### Methods
[Section titled “Methods”](#methods)
* [ipynb](#ipynb)
## Methods
[Section titled “Methods”](#methods-1)
### ipynb
[Section titled “ipynb”](#ipynb)
▸ **ipynb**(`contents`: object): *Notebook*
Sets or overrides the contents of the notebook to run. Not recommended in general; using separate `.ipynb` files for notebooks is preferred.
**Parameters:**
| Name | Type |
| ---------- | ------ |
| `contents` | object |
**Returns:** *Notebook*
# Operation
> API reference for the Operation action type in SQLAnvil.
# Class: Operation
[Section titled “Class: Operation”](#class-operation)
Operations define custom SQL operations that don’t fit into the sqlanvil model of publishing a table or writing an assertion.
You can create operations in the following ways. Available config options are defined in [OperationConfig](configs#sqlanvil-ActionConfig-OperationConfig), and are shared across all the following ways of creating operations.
**Using a SQLX file:**
```sql
-- definitions/name.sqlx
config {
type: "operations"
}
DELETE FROM dataset.table WHERE country = 'GB'
```
**Using action configs files:**
definitions/actions.yaml
```yaml
actions:
- operation:
filename: name.sql
```
```sql
-- definitions/name.sql
DELETE FROM dataset.table WHERE country = 'GB'
```
**Using the Javascript API:**
definitions/file.js
```js
operate("name").query("DELETE FROM dataset.table WHERE country = 'GB'")
```
Note: When using the Javascript API, methods in this class can be accessed by the returned value. This is where `query` comes from.
## Hierarchy
[Section titled “Hierarchy”](#hierarchy)
* ActionBuilder‹Operation›
↳ **Operation**
## Index
[Section titled “Index”](#index)
### Methods
[Section titled “Methods”](#methods)
* [columns](#columns)
* [database](#database)
* [dependencies](#dependencies)
* [description](#description)
* [disabled](#disabled)
* [hasOutput](#hasoutput)
* [hermetic](#hermetic)
* [queries](#queries)
* [schema](#schema)
* [setDependOnDependencyAssertions](#setdependondependencyassertions)
* [tags](#tags)
## Methods
[Section titled “Methods”](#methods-1)
### columns
[Section titled “columns”](#columns)
▸ **columns**(`columns`: ColumnDescriptor\[]): *this*
**`deprecated`** Deprecated in favor of [OperationConfig.columns](configs#sqlanvil-ActionConfig-OperationConfig).
Sets the column descriptors of columns in this table.
**Parameters:**
| Name | Type |
| --------- | ------------------- |
| `columns` | ColumnDescriptor\[] |
**Returns:** *this*
***
### database
[Section titled “database”](#database)
▸ **database**(`database`: string): *this*
**`deprecated`** Deprecated in favor of [OperationConfig.project](configs#sqlanvil-ActionConfig-OperationConfig).
Sets the database in which to create the corresponding view for this operation. For BigQuery targets this is the Google Cloud project ID; for Postgres/Supabase targets this is the database name in `workflow_settings.yaml`.
**Parameters:**
| Name | Type |
| ---------- | ------ |
| `database` | string |
**Returns:** *this*
***
### dependencies
[Section titled “dependencies”](#dependencies)
▸ **dependencies**(`value`: Resolvable | Resolvable\[]): *this*
**`deprecated`** Deprecated in favor of [OperationConfig.dependencies](configs#sqlanvil-ActionConfig-OperationConfig).
Sets dependencies of the table.
**Parameters:**
| Name | Type |
| ------- | --------------------------- |
| `value` | Resolvable \| Resolvable\[] |
**Returns:** *this*
***
### description
[Section titled “description”](#description)
▸ **description**(`description`: string): *this*
**`deprecated`** Deprecated in favor of [OperationConfig.description](configs#sqlanvil-ActionConfig-OperationConfig).
Sets the description of this assertion.
**Parameters:**
| Name | Type |
| ------------- | ------ |
| `description` | string |
**Returns:** *this*
***
### disabled
[Section titled “disabled”](#disabled)
▸ **disabled**(`disabled`: boolean): *this*
**`deprecated`** Deprecated in favor of [OperationConfig.disabled](configs#sqlanvil-ActionConfig-OperationConfig).
If called with `true`, this action is not executed. The action can still be depended upon. Useful for temporarily turning off broken actions.
**Parameters:**
| Name | Type | Default |
| ---------- | ------- | ------- |
| `disabled` | boolean | true |
**Returns:** *this*
***
### hasOutput
[Section titled “hasOutput”](#hasoutput)
▸ **hasOutput**(`hasOutput`: boolean): *this*
**`deprecated`** Deprecated in favor of [OperationConfig.hasOutput](configs#sqlanvil-ActionConfig-OperationConfig).
Declares that this action creates a dataset which should be referenceable as a dependency target, for example by using the `ref` function.
**Parameters:**
| Name | Type |
| ----------- | ------- |
| `hasOutput` | boolean |
**Returns:** *this*
***
### hermetic
[Section titled “hermetic”](#hermetic)
▸ **hermetic**(`hermetic`: boolean): *void*
**`deprecated`** Deprecated in favor of [OperationConfig.hermetic](configs#sqlanvil-ActionConfig-OperationConfig).
If true, this indicates that the action only depends on data from explicitly-declared dependencies. Otherwise if false, it indicates that the action depends on data from a source which has not been declared as a dependency.
**Parameters:**
| Name | Type |
| ---------- | ------- |
| `hermetic` | boolean |
**Returns:** *void*
***
### queries
[Section titled “queries”](#queries)
▸ **queries**(`queries`: Contextable‹IActionContext, string | string\[]›): *this*
Sets the query/queries to generate the operation from.
**Parameters:**
| Name | Type |
| --------- | ------------------------------------------------ |
| `queries` | Contextable‹IActionContext, string \| string\[]› |
**Returns:** *this*
***
### schema
[Section titled “schema”](#schema)
▸ **schema**(`schema`: string): *this*
**`deprecated`** Deprecated in favor of [OperationConfig.dataset](configs#sqlanvil-ActionConfig-OperationConfig).
Sets the schema (BigQuery dataset / Postgres schema) in which to create the output of this action.
**Parameters:**
| Name | Type |
| -------- | ------ |
| `schema` | string |
**Returns:** *this*
***
### setDependOnDependencyAssertions
[Section titled “setDependOnDependencyAssertions”](#setdependondependencyassertions)
▸ **setDependOnDependencyAssertions**(`dependOnDependencyAssertions`: boolean): *this*
**`deprecated`** Deprecated in favor of [OperationConfig.dependOnDependencyAssertions](configs#sqlanvil-ActionConfig-OperationConfig).
When called with `true`, assertions dependent upon any dependency will be add as dedpendency to this action.
**Parameters:**
| Name | Type |
| ------------------------------ | ------- |
| `dependOnDependencyAssertions` | boolean |
**Returns:** *this*
***
### tags
[Section titled “tags”](#tags)
▸ **tags**(`value`: string | string\[]): *this*
**`deprecated`** Deprecated in favor of [OperationConfig.tags](configs#sqlanvil-ActionConfig-OperationConfig).
Sets a list of user-defined tags applied to this action.
**Parameters:**
| Name | Type |
| ------- | ------------------- |
| `value` | string \| string\[] |
**Returns:** *this*
# Session
> API reference for the Session class in SQLAnvil.
# Class: Session
[Section titled “Class: Session”](#class-session)
Contains methods that are published globally, so can be invoked anywhere in the `/definitions` folder of a sqlanvil project.
## Hierarchy
[Section titled “Hierarchy”](#hierarchy)
* **Session**
## Index
[Section titled “Index”](#index)
### Properties
[Section titled “Properties”](#properties)
* [projectConfig](#projectconfig)
### Methods
[Section titled “Methods”](#methods)
* [assert](#assert)
* [declare](#declare)
* [notebook](#notebook)
* [operate](#operate)
* [publish](#publish)
* [test](#test)
## Properties
[Section titled “Properties”](#properties-1)
### projectConfig
[Section titled “projectConfig”](#projectconfig)
• **projectConfig**: *ProjectConfig*
Stores the sqlanvil project configuration of the current sqlanvil project. Can be accessed via the `sqlanvil` global variable.
Example:
```js
sqlanvil.projectConfig.vars.myVariableName === "myVariableValue"
```
## Methods
[Section titled “Methods”](#methods-1)
### assert
[Section titled “assert”](#assert)
▸ **assert**(`name`: string, `queryOrConfig?`: AContextable‹string› | AssertionConfig): *[Assertion](assertion)*
Adds a sqlanvil assertion the compiled graph.
Available only in the `/definitions` directory.
**`see`** [assertion](Assertion) for examples on how to use.
**Parameters:**
| Name | Type |
| ---------------- | --------------------------------------- |
| `name` | string |
| `queryOrConfig?` | AContextable‹string› \| AssertionConfig |
**Returns:** *[Assertion](assertion)*
***
### declare
[Section titled “declare”](#declare)
▸ **declare**(`config`: DeclarationConfig | any): *[Declaration](declaration)*
Declares the dataset as a sqlanvil data source.
Available only in the `/definitions` directory.
**`see`** [Declaration](Declaration) for examples on how to use.
**Parameters:**
| Name | Type |
| -------- | ------------------------ |
| `config` | DeclarationConfig \| any |
**Returns:** *[Declaration](declaration)*
***
### notebook
[Section titled “notebook”](#notebook)
▸ **notebook**(`config`: NotebookConfig): *[Notebook](notebook)*
Creates a Notebook action.
Available only in the `/definitions` directory.
**`see`** [Notebook](Notebook) for examples on how to use.
**Parameters:**
| Name | Type |
| -------- | -------------- |
| `config` | NotebookConfig |
**Returns:** *[Notebook](notebook)*
***
### operate
[Section titled “operate”](#operate)
▸ **operate**(`name`: string, `queryOrConfig?`: Contextable‹IActionContext, string | string\[]› | OperationConfig): *[Operation](operation)*
Defines a SQL operation.
Available only in the `/definitions` directory.
**`see`** [operation](Operation) for examples on how to use.
**Parameters:**
| Name | Type |
| ---------------- | ------------------------------------------------------------------- |
| `name` | string |
| `queryOrConfig?` | Contextable‹IActionContext, string \| string\[]› \| OperationConfig |
**Returns:** *[Operation](operation)*
***
### publish
[Section titled “publish”](#publish)
▸ **publish**(`name`: string, `queryOrConfig?`: Contextable‹ITableContext, string› | TableConfig | ViewConfig | IncrementalTableConfig | ILegacyTableConfig | any): *[Table](table) | [IncrementalTable](incrementaltable) | [View](view)*
Creates a table, view, or incremental table.
Available only in the `/definitions` directory.
**`see`** [Operation](Operation) for examples on how to make tables.
**`see`** [View](View) for examples on how to make views.
**`see`** [IncrementalTable](IncrementalTable) for examples on how to make incremental tables.
**Parameters:**
| Name | Type |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `name` | string |
| `queryOrConfig?` | Contextable‹ITableContext, string› \| TableConfig \| ViewConfig \| IncrementalTableConfig \| ILegacyTableConfig \| any |
**Returns:** *[Table](table) | [IncrementalTable](incrementaltable) | [View](view)*
***
### test
[Section titled “test”](#test)
▸ **test**(`name`: string): *[Test](test)*
Creates a Test action.
Available only in the `/definitions` directory.
**`see`** [Test](Test) for examples on how to use.
**Parameters:**
| Name | Type |
| ------ | ------ |
| `name` | string |
**Returns:** *[Test](test)*
# Table
> API reference for the Table action type in SQLAnvil.
# Class: Table
[Section titled “Class: Table”](#class-table)
Tables are the fundamental building block for storing data when using sqlanvil. sqlanvil compiles your sqlanvil core code into SQL, executes the SQL code, and creates your defined tables in BigQuery.
You can create tables in the following ways. Available config options are defined in [TableConfig](configs#sqlanvil-ActionConfig-TableConfig), and are shared across all the following ways of creating tables.
**Using a SQLX file:**
```sql
-- definitions/name.sqlx
config {
type: "table"
}
SELECT 1
```
**Using action configs files:**
definitions/actions.yaml
```yaml
actions:
- table:
filename: name.sql
```
```sql
-- definitions/name.sql
SELECT 1
```
**Using the Javascript API:**
definitions/file.js
```js
table("name", { type: "table" }).query("SELECT 1 AS TEST")
```
Note: When using the Javascript API, methods in this class can be accessed by the returned value. This is where `query` comes from.
## Hierarchy
[Section titled “Hierarchy”](#hierarchy)
* ActionBuilder‹Table›
↳ **Table**
## Index
[Section titled “Index”](#index)
### Methods
[Section titled “Methods”](#methods)
* [assertions](#assertions)
* [bigquery](#bigquery)
* [columns](#columns)
* [database](#database)
* [dependencies](#dependencies)
* [description](#description)
* [disabled](#disabled)
* [hermetic](#hermetic)
* [postOps](#postops)
* [preOps](#preops)
* [query](#query)
* [schema](#schema)
* [setDependOnDependencyAssertions](#setdependondependencyassertions)
* [tags](#tags)
* [type](#type)
## Methods
[Section titled “Methods”](#methods-1)
### assertions
[Section titled “assertions”](#assertions)
▸ **assertions**(`tableAssertionsConfig`: TableAssertionsConfig): *Table*
**`deprecated`** Deprecated in favor of [TableConfig.assertions](configs#sqlanvil-ActionConfig-TableConfig).
Sets in-line assertions for this table.
**Parameters:**
| Name | Type |
| ----------------------- | --------------------- |
| `tableAssertionsConfig` | TableAssertionsConfig |
**Returns:** *Table*
***
### bigquery
[Section titled “bigquery”](#bigquery)
▸ **bigquery**(`bigquery`: IBigQueryOptions): *this*
**`deprecated`** Deprecated in favor of options available directly on [TableConfig](configs#sqlanvil-ActionConfig-TableConfig). For example: `publish("name", { type: "table", partitionBy: "column" }`).
Sets bigquery options for the action.
**Parameters:**
| Name | Type |
| ---------- | ---------------- |
| `bigquery` | IBigQueryOptions |
**Returns:** *this*
***
### columns
[Section titled “columns”](#columns)
▸ **columns**(`columns`: ColumnDescriptor\[]): *this*
**`deprecated`** Deprecated in favor of [TableConfig.columns](configs#sqlanvil-ActionConfig-TableConfig).
Sets the column descriptors of columns in this table.
**Parameters:**
| Name | Type |
| --------- | ------------------- |
| `columns` | ColumnDescriptor\[] |
**Returns:** *this*
***
### database
[Section titled “database”](#database)
▸ **database**(`database`: string): *this*
**`deprecated`** Deprecated in favor of [TableConfig.project](configs#sqlanvil-ActionConfig-TableConfig).
Sets the database in which to create the output of this action. For BigQuery targets this is the Google Cloud project ID; for Postgres/Supabase targets this is the database name in `workflow_settings.yaml`.
**Parameters:**
| Name | Type |
| ---------- | ------ |
| `database` | string |
**Returns:** *this*
***
### dependencies
[Section titled “dependencies”](#dependencies)
▸ **dependencies**(`value`: Resolvable | Resolvable\[]): *this*
**`deprecated`** Deprecated in favor of [TableConfig.dependencies](configs#sqlanvil-ActionConfig-TableConfig).
Sets dependencies of the table.
**Parameters:**
| Name | Type |
| ------- | --------------------------- |
| `value` | Resolvable \| Resolvable\[] |
**Returns:** *this*
***
### description
[Section titled “description”](#description)
▸ **description**(`description`: string): *this*
**`deprecated`** Deprecated in favor of [TableConfig.description](configs#sqlanvil-ActionConfig-TableConfig).
Sets the description of this assertion.
**Parameters:**
| Name | Type |
| ------------- | ------ |
| `description` | string |
**Returns:** *this*
***
### disabled
[Section titled “disabled”](#disabled)
▸ **disabled**(`disabled`: boolean): *this*
**`deprecated`** Deprecated in favor of [TableConfig.disabled](configs#sqlanvil-ActionConfig-TableConfig).
If called with `true`, this action is not executed. The action can still be depended upon. Useful for temporarily turning off broken actions.
**Parameters:**
| Name | Type | Default |
| ---------- | ------- | ------- |
| `disabled` | boolean | true |
**Returns:** *this*
***
### hermetic
[Section titled “hermetic”](#hermetic)
▸ **hermetic**(`hermetic`: boolean): *void*
**`deprecated`** Deprecated in favor of [TableConfig.hermetic](configs#sqlanvil-ActionConfig-TableConfig).
If true, this indicates that the action only depends on data from explicitly-declared dependencies. Otherwise if false, it indicates that the action depends on data from a source which has not been declared as a dependency.
**Parameters:**
| Name | Type |
| ---------- | ------- |
| `hermetic` | boolean |
**Returns:** *void*
***
### postOps
[Section titled “postOps”](#postops)
▸ **postOps**(`posts`: Contextable‹ITableContext, string | string\[]›): *this*
Sets a post-operation to run after the query is run. This is often used for revoking temporary permissions granted to access source tables.
Example:
definitions/file.js
```js
publish("example")
.preOps(ctx => `GRANT \`roles/bigquery.dataViewer\` ON TABLE ${ctx.ref("other_table")} TO "group:automation@example.com"`)
.query(ctx => `SELECT * FROM ${ctx.ref("other_table")}`)
.postOps(ctx => `REVOKE \`roles/bigquery.dataViewer\` ON TABLE ${ctx.ref("other_table")} TO "group:automation@example.com"`)
```
**Parameters:**
| Name | Type |
| ------- | ----------------------------------------------- |
| `posts` | Contextable‹ITableContext, string \| string\[]› |
**Returns:** *this*
***
### preOps
[Section titled “preOps”](#preops)
▸ **preOps**(`pres`: Contextable‹ITableContext, string | string\[]›): *this*
Sets a pre-operation to run before the query is run. This is often used for temporarily granting permission to access source tables.
Example:
definitions/file.js
```js
publish("example")
.preOps(ctx => `GRANT \`roles/bigquery.dataViewer\` ON TABLE ${ctx.ref("other_table")} TO "group:automation@example.com"`)
.query(ctx => `SELECT * FROM ${ctx.ref("other_table")}`)
.postOps(ctx => `REVOKE \`roles/bigquery.dataViewer\` ON TABLE ${ctx.ref("other_table")} TO "group:automation@example.com"`)
```
**Parameters:**
| Name | Type |
| ------ | ----------------------------------------------- |
| `pres` | Contextable‹ITableContext, string \| string\[]› |
**Returns:** *this*
***
### query
[Section titled “query”](#query)
▸ **query**(`query`: Contextable‹ITableContext, string›): *this*
Sets the query to generate the table from.
**Parameters:**
| Name | Type |
| ------- | ---------------------------------- |
| `query` | Contextable‹ITableContext, string› |
**Returns:** *this*
***
### schema
[Section titled “schema”](#schema)
▸ **schema**(`schema`: string): *this*
**`deprecated`** Deprecated in favor of [TableConfig.dataset](configs#sqlanvil-ActionConfig-TableConfig).
Sets the schema (BigQuery dataset / Postgres schema) in which to create the output of this action.
**Parameters:**
| Name | Type |
| -------- | ------ |
| `schema` | string |
**Returns:** *this*
***
### setDependOnDependencyAssertions
[Section titled “setDependOnDependencyAssertions”](#setdependondependencyassertions)
▸ **setDependOnDependencyAssertions**(`dependOnDependencyAssertions`: boolean): *this*
**`deprecated`** Deprecated in favor of [TableConfig.dependOnDependencyAssertions](configs#sqlanvil-ActionConfig-TableConfig).
When called with `true`, assertions dependent upon any dependency will be add as dedpendency to this action.
**Parameters:**
| Name | Type |
| ------------------------------ | ------- |
| `dependOnDependencyAssertions` | boolean |
**Returns:** *this*
***
### tags
[Section titled “tags”](#tags)
▸ **tags**(`value`: string | string\[]): *this*
**`deprecated`** Deprecated in favor of [TableConfig.tags](configs#sqlanvil-ActionConfig-TableConfig).
Sets a list of user-defined tags applied to this action.
**Parameters:**
| Name | Type |
| ------- | ------------------- |
| `value` | string \| string\[] |
**Returns:** *this*
***
### type
[Section titled “type”](#type)
▸ **type**(`type`: TableType): *this*
**`deprecated`** Deprecated in favor of action type can being set in the configs passed to action constructor functions, for example `publish("name", { type: "table" })`.
**Parameters:**
| Name | Type |
| ------ | --------- |
| `type` | TableType |
**Returns:** *this*
# Test
> API reference for the Test action type in SQLAnvil.
# Class: Test
[Section titled “Class: Test”](#class-test)
sqlanvil test actions can be used to write unit tests for your generated SQL
You can create unit tests in the following ways.
**Using a SQLX file:**
```sql
-- definitions/name.sqlx
config {
type: "test"
}
input "foo" {
SELECT 1 AS bar
}
SELECT 1 AS bar
```
**Using the Javascript API:**
definitions/file.js
```js
test("name")
.input("sample_data", `SELECT 1 AS bar`)
.expect(`SELECT 1 AS bar`);
publish("sample_data", { type: "table" }).query("SELECT 1 AS bar")
```
Note: When using the Javascript API, methods in this class can be accessed by the returned value. This is where `input` and `expect` come from.
## Hierarchy
[Section titled “Hierarchy”](#hierarchy)
* ActionBuilder‹Test›
↳ **Test**
## Index
[Section titled “Index”](#index)
### Methods
[Section titled “Methods”](#methods)
* [dataset](#dataset)
* [expect](#expect)
* [input](#input)
## Methods
[Section titled “Methods”](#methods-1)
### dataset
[Section titled “dataset”](#dataset)
▸ **dataset**(`ref`: Resolvable): *this*
Sets the schema (BigQuery dataset / Postgres schema) in which to create the output of this action.
**Parameters:**
| Name | Type |
| ----- | ---------- |
| `ref` | Resolvable |
**Returns:** *this*
***
### expect
[Section titled “expect”](#expect)
▸ **expect**(`contextableQuery`: Contextable‹IActionContext, string›): *this*
Sets the expected output of the query to being tested against.
**Parameters:**
| Name | Type |
| ------------------ | ----------------------------------- |
| `contextableQuery` | Contextable‹IActionContext, string› |
**Returns:** *this*
***
### input
[Section titled “input”](#input)
▸ **input**(`refName`: string | string\[], `contextableQuery`: Contextable‹IActionContext, string›): *this*
Sets the input query to unit test against.
**Parameters:**
| Name | Type |
| ------------------ | ----------------------------------- |
| `refName` | string \| string\[] |
| `contextableQuery` | Contextable‹IActionContext, string› |
**Returns:** *this*
# View
> API reference for the View action type in SQLAnvil.
# Class: View
[Section titled “Class: View”](#class-view)
Views are virtualised tables. They are useful for creating a new structured table without having to copy the original data to it, which can result in significant cost savings for avoiding data processing and storage.
You can create views in the following ways. Available config options are defined in [ViewConfig](configs#sqlanvil-ActionConfig-ViewConfig), and are shared across all the following ways of creating tables.
**Using a SQLX file:**
```sql
-- definitions/name.sqlx
config {
type: "view"
}
SELECT column FROM someTable
```
**Using action configs files:**
definitions/actions.yaml
```yaml
actions:
- view:
filename: name.sql
```
```sql
-- definitions/name.sql
SELECT column FROM someTable
```
**Using the Javascript API:**
definitions/file.js
```js
table("name", { type: "view" }).query("SELECT column FROM someTable")
```
Note: When using the Javascript API, methods in this class can be accessed by the returned value. This is where `query` comes from.
## Hierarchy
[Section titled “Hierarchy”](#hierarchy)
* ActionBuilder‹Table›
↳ **View**
## Index
[Section titled “Index”](#index)
### Methods
[Section titled “Methods”](#methods)
* [assertions](#assertions)
* [bigquery](#bigquery)
* [columns](#columns)
* [database](#database)
* [dependencies](#dependencies)
* [description](#description)
* [disabled](#disabled)
* [hermetic](#hermetic)
* [materialized](#materialized)
* [mysql](#mysql)
* [postOps](#postops)
* [preOps](#preops)
* [query](#query)
* [schema](#schema)
* [setDependOnDependencyAssertions](#setdependondependencyassertions)
* [tags](#tags)
* [type](#type)
## Methods
[Section titled “Methods”](#methods-1)
### assertions
[Section titled “assertions”](#assertions)
▸ **assertions**(`tableAssertionsConfig`: TableAssertionsConfig): *View*
**`deprecated`** Deprecated in favor of [ViewConfig.assertions](configs#sqlanvil-ActionConfig-ViewConfig).
Sets in-line assertions for this view.
**Parameters:**
| Name | Type |
| ----------------------- | --------------------- |
| `tableAssertionsConfig` | TableAssertionsConfig |
**Returns:** *View*
***
### bigquery
[Section titled “bigquery”](#bigquery)
▸ **bigquery**(`bigquery`: IBigQueryOptions): *this*
**`deprecated`** Deprecated in favor of options available directly on [ViewConfig](configs#sqlanvil-ActionConfig-ViewConfig).
Sets bigquery options for the action.
**Parameters:**
| Name | Type |
| ---------- | ---------------- |
| `bigquery` | IBigQueryOptions |
**Returns:** *this*
***
### columns
[Section titled “columns”](#columns)
▸ **columns**(`columns`: ColumnDescriptor\[]): *this*
**`deprecated`** Deprecated in favor of [ViewConfig.columns](configs#sqlanvil-ActionConfig-ViewConfig).
Sets the column descriptors of columns in this view.
**Parameters:**
| Name | Type |
| --------- | ------------------- |
| `columns` | ColumnDescriptor\[] |
**Returns:** *this*
***
### database
[Section titled “database”](#database)
▸ **database**(`database`: string): *this*
**`deprecated`** Deprecated in favor of [ViewConfig.project](configs#sqlanvil-ActionConfig-ViewConfig).
Sets the Sets the database in which to create the output of this action. For BigQuery targets this is the Google Cloud project ID; for Postgres/Supabase targets this is the database name in `workflow_settings.yaml`.
**Parameters:**
| Name | Type |
| ---------- | ------ |
| `database` | string |
**Returns:** *this*
***
### dependencies
[Section titled “dependencies”](#dependencies)
▸ **dependencies**(`value`: Resolvable | Resolvable\[]): *this*
**`deprecated`** Deprecated in favor of [ViewConfig.dependencies](configs#sqlanvil-ActionConfig-ViewConfig).
Sets dependencies of the view.
**Parameters:**
| Name | Type |
| ------- | --------------------------- |
| `value` | Resolvable \| Resolvable\[] |
**Returns:** *this*
***
### description
[Section titled “description”](#description)
▸ **description**(`description`: string): *this*
**`deprecated`** Deprecated in favor of [ViewConfig.description](configs#sqlanvil-ActionConfig-ViewConfig).
Sets the description of this view.
**Parameters:**
| Name | Type |
| ------------- | ------ |
| `description` | string |
**Returns:** *this*
***
### disabled
[Section titled “disabled”](#disabled)
▸ **disabled**(`disabled`: boolean): *this*
**`deprecated`** Deprecated in favor of [ViewConfig.disabled](configs#sqlanvil-ActionConfig-ViewConfig).
If called with `true`, this action is not executed. The action can still be depended upon. Useful for temporarily turning off broken actions.
**Parameters:**
| Name | Type | Default |
| ---------- | ------- | ------- |
| `disabled` | boolean | true |
**Returns:** *this*
***
### hermetic
[Section titled “hermetic”](#hermetic)
▸ **hermetic**(`hermetic`: boolean): *void*
**`deprecated`** Deprecated in favor of [ViewConfig.hermetic](configs#sqlanvil-ActionConfig-ViewConfig).
If true, this indicates that the action only depends on data from explicitly-declared dependencies. Otherwise if false, it indicates that the action depends on data from a source which has not been declared as a dependency.
**Parameters:**
| Name | Type |
| ---------- | ------- |
| `hermetic` | boolean |
**Returns:** *void*
***
### materialized
[Section titled “materialized”](#materialized)
▸ **materialized**(`materialized`: boolean): *void*
**`deprecated`** Deprecated in favor of [ViewConfig.materialized](configs#sqlanvil-ActionConfig-ViewConfig).
Applies the materialized view optimization, see .
**Parameters:**
| Name | Type |
| -------------- | ------- |
| `materialized` | boolean |
**Returns:** *void*
***
### mysql
[Section titled “mysql”](#mysql)
▸ **mysql**(`mysql`: IMysqlOptions): *void*
Sets MySQL/MariaDB-native options. Only meaningful for a materialized view (`materialized: true`), which MySQL emulates as a refreshed table snapshot — table options and indexes apply to that table. Mirrors [ViewConfig.mysql](configs#sqlanvil-ActionConfig-ViewConfig).
**Parameters:**
| Name | Type |
| ------- | ------------- |
| `mysql` | IMysqlOptions |
**Returns:** *void*
***
### postOps
[Section titled “postOps”](#postops)
▸ **postOps**(`posts`: Contextable‹ITableContext, string | string\[]›): *this*
Sets a post-operation to run after the query is run. This is often used for revoking temporary permissions granted to access source tables.
Example:
definitions/file.js
```js
publish("example")
.preOps(ctx => `GRANT \`roles/bigquery.dataViewer\` ON TABLE ${ctx.ref("other_table")} TO "group:automation@example.com"`)
.query(ctx => `SELECT * FROM ${ctx.ref("other_table")}`)
.postOps(ctx => `REVOKE \`roles/bigquery.dataViewer\` ON TABLE ${ctx.ref("other_table")} TO "group:automation@example.com"`)
```
**Parameters:**
| Name | Type |
| ------- | ----------------------------------------------- |
| `posts` | Contextable‹ITableContext, string \| string\[]› |
**Returns:** *this*
***
### preOps
[Section titled “preOps”](#preops)
▸ **preOps**(`pres`: Contextable‹ITableContext, string | string\[]›): *this*
Sets a pre-operation to run before the query is run. This is often used for temporarily granting permission to access source tables.
Example:
definitions/file.js
```js
publish("example")
.preOps(ctx => `GRANT \`roles/bigquery.dataViewer\` ON TABLE ${ctx.ref("other_table")} TO "group:automation@example.com"`)
.query(ctx => `SELECT * FROM ${ctx.ref("other_table")}`)
.postOps(ctx => `REVOKE \`roles/bigquery.dataViewer\` ON TABLE ${ctx.ref("other_table")} TO "group:automation@example.com"`)
```
**Parameters:**
| Name | Type |
| ------ | ----------------------------------------------- |
| `pres` | Contextable‹ITableContext, string \| string\[]› |
**Returns:** *this*
***
### query
[Section titled “query”](#query)
▸ **query**(`query`: Contextable‹ITableContext, string›): *this*
Sets the query to generate the table from.
**Parameters:**
| Name | Type |
| ------- | ---------------------------------- |
| `query` | Contextable‹ITableContext, string› |
**Returns:** *this*
***
### schema
[Section titled “schema”](#schema)
▸ **schema**(`schema`: string): *this*
**`deprecated`** Deprecated in favor of [ViewConfig.dataset](configs#sqlanvil-ActionConfig-ViewConfig).
Sets the schema (BigQuery dataset / Postgres schema) in which to create the output of this action.
**Parameters:**
| Name | Type |
| -------- | ------ |
| `schema` | string |
**Returns:** *this*
***
### setDependOnDependencyAssertions
[Section titled “setDependOnDependencyAssertions”](#setdependondependencyassertions)
▸ **setDependOnDependencyAssertions**(`dependOnDependencyAssertions`: boolean): *this*
**`deprecated`** Deprecated in favor of [ViewConfig.dependOnDependencyAssertions](configs#sqlanvil-ActionConfig-ViewConfig).
When called with `true`, assertions dependent upon any dependency will be add as dedpendency to this action.
**Parameters:**
| Name | Type |
| ------------------------------ | ------- |
| `dependOnDependencyAssertions` | boolean |
**Returns:** *this*
***
### tags
[Section titled “tags”](#tags)
▸ **tags**(`value`: string | string\[]): *this*
**`deprecated`** Deprecated in favor of [ViewConfig.tags](configs#sqlanvil-ActionConfig-ViewConfig).
Sets a list of user-defined tags applied to this action.
**Parameters:**
| Name | Type |
| ------- | ------------------- |
| `value` | string \| string\[] |
**Returns:** *this*
***
### type
[Section titled “type”](#type)
▸ **type**(`type`: TableType): *this*
**`deprecated`** Deprecated in favor of action type can being set in the configs passed to action constructor functions, for example `publish("name", { type: "table" })`.
**Parameters:**
| Name | Type |
| ------ | --------- |
| `type` | TableType |
**Returns:** *this*
# Troubleshooting
> Fixes for the most common SQLAnvil failures — install, credentials, connections, and the compile / run / validate loop — plus where to get help.
The errors people actually hit, in the order they usually hit them. If your problem isn’t here, see [Getting help](#getting-help) at the bottom.
## Install
[Section titled “Install”](#install)
**`npm install -g @sqlanvil/cli` fails with EACCES / permission errors** — your global npm prefix needs root. Either run without installing (`npx @sqlanvil/cli compile .`) or [change npm’s default directory](https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally).
**`sqlanvil: command not found` after install** — the npm global bin directory isn’t on your `PATH`. `npm bin -g` shows where it is.
**Node version errors** — SQLAnvil needs **Node 20 LTS or later**. Check with `node --version`.
## Credentials and connections
[Section titled “Credentials and connections”](#credentials-and-connections)
**`ENOTFOUND db..supabase.co`** — the classic Supabase one. The direct `db.[` host is **IPv6-only**; most networks can’t reach it. Use the **Session pooler** instead: in the Supabase dashboard’s Connect dialog, copy the pooler host verbatim — `aws-N-].pooler.supabase.com`, port `5432`, user **`postgres.`** (the ref suffix on the user is required through the pooler).
**Authentication failed (Postgres/Supabase)** — check the user: through the Session pooler it’s `postgres.`, not plain `postgres`. And `sslMode` should be `"require"` for any managed database.
**`.df-credentials.json` seems ignored or “invalid”** — SQLAnvil uses a **flat** shape (`host`/`port`/`database`/`user`/`password`/`sslMode`), not Dataform’s nested one. Warehouse examples: [Getting Started](/docs/getting-started/#create-a-project). Two shape gotchas: BigQuery uses `projectId` + `location` (auth via `gcloud auth application-default login`, or add a `"credentials"` field with a service-account key); MySQL has **no `defaultSchema`** — the `defaultDataset` in `workflow_settings.yaml` *is* the database.
**Credentials file committed by accident?** `init` writes it gitignored — if you moved or recreated it, re-add it to `.gitignore` and rotate the password.
## The compile / run / validate loop
[Section titled “The compile / run / validate loop”](#the-compile--run--validate-loop)
Three verbs, three different failure classes:
* **`sqlanvil compile .`** needs no database at all. If compile fails, the problem is in your project code: an unresolved `${ref("...")}` (no action with that name — check the target schema/name, not just the filename), a config error, or a JS error in `includes/`.
* **`sqlanvil run .`** needs credentials and real tables. The sample project from `init` compiles immediately but won’t run until the `app_orders` declaration points at a table that exists (and the BigQuery source has credentials — or delete that pair).
* **`sqlanvil validate .`** is the “what’s still missing” check — it verifies every action against the warehouse (EXPLAIN-based on Postgres/Supabase, dry-run on BigQuery) without writing anything, and names exactly what’s broken. When in doubt, run validate first.
## Authoring gotchas
[Section titled “Authoring gotchas”](#authoring-gotchas)
* Statements in an action body are separated with **`---`** on its own line — not `;`.
* The settings key is **`sqlanvilCoreVersion`**, not `dataformCoreVersion` (the [migrator](/docs/guides/migrate-dataform/) converts this for you).
* Warehouse-specific config lives in `postgres: {}` / `mysql: {}` / `bigquery: {}` blocks — don’t hand-roll DDL for indexes or partitions in SQL.
* Each warehouse guide ends with its own error table — see [MySQL](/docs/guides/mysql/) in particular.
## SQLAnvil Cloud
[Section titled “SQLAnvil Cloud”](#sqlanvil-cloud)
**Opened a PR, no CI check appeared** — the SQLAnvil GitHub App isn’t installed on that repository, or the repo isn’t connected to the project. Both steps: [Connect a repo](/docs/cloud/connect-a-repo/).
**Branch CI fails immediately on Supabase** — branch CI creates an ephemeral **Supabase branch**, which requires the warehouse project to be on the **Pro plan**. See [Connect Supabase](/docs/cloud/connections/supabase/).
**Supabase connection stopped working after an update** — OAuth scopes are granted per-authorization; if the app’s scopes changed you need to disconnect and reconnect once.
**Debugging a failed run** — the run page shows each action’s executed SQL and the verbatim warehouse error; **Copy failure context** bundles it for pasting into a coding agent. If your agent is connected over [MCP](/docs/cloud/mcp/), `get_run_failure` fetches the same bundle directly.
## Getting help
[Section titled “Getting help”](#getting-help)
SQLAnvil is deliberately **self-service** — there’s no ticket queue, and these docs are the support system. When they’re not enough:
* **Bugs** → [GitHub Issues](https://github.com/SQLAnvil/sqlanvil/issues)
* **Questions and ideas** → [GitHub Discussions](https://github.com/SQLAnvil/sqlanvil/discussions)
* **Cloud account matters** (billing, account deletion) → — see [Billing & your account](/docs/cloud/billing/)
Found a fix that belongs on this page? Docs PRs are welcome — every page has an “Edit page” link.
# Enrich Supabase with BigQuery public data
> Join your Supabase operational tables with Google's free BigQuery public datasets — geo, demographics, currency — using a live Foreign Data Wrapper, no ETL pipeline required.
Your operational data lives in Supabase. Some of the most useful *reference* data doesn’t — it lives in [Google’s BigQuery public datasets](https://cloud.google.com/bigquery/public-data): US ZIP/postal boundaries, census demographics, weather, currency rates, and more, all free to query. SQLAnvil lets you **join the two in place** and write the enriched result back into Supabase — without standing up a separate ETL pipeline to copy those datasets around.
## Why you’d want this
[Section titled “Why you’d want this”](#why-youd-want-this)
* **Geospatial enrichment** — map customer ZIPs to coordinates, compute distances, resolve postal boundaries (e.g. “customers within 25 miles of a store”).
* **Reference/lookup data** — currency exchange rates, public holidays, census demographics, NAICS codes — data you shouldn’t have to maintain a copy of.
* **One source of truth** — query the public dataset live (always current) instead of forking it into your own schema and keeping it in sync.
## How it works
[Section titled “How it works”](#how-it-works)
SQLAnvil’s [`wrapper()` action](/docs/guides/foreign-wrappers/) declares a live **BigQuery Foreign Data Wrapper** — one call sets up the connection and exposes the BigQuery table as a `ref()`-able foreign table. From there it’s plain Supabase/Postgres SQL:
```plaintext
bigquery-public-data (live FDW query)
│
▼
foreign table ──► staging view ──► cached table (materialized in Supabase)
│
your operational tables ──────────────────► join + transform ──► enriched result
```
A typical pipeline:
1. **Declare the bridge** — `wrapper({ provider: "bigquery", foreignTables: [...] })` in a `.js` file.
2. **Materialize once** — `SELECT * FROM ${ref("the_foreign_table")}` into a Supabase table, so downstream joins don’t re-hit BigQuery on every run.
3. **Join + enrich** — join the cached reference data with your operational tables and land the result back in a Supabase schema your app reads from.
See the **[Foreign Data Wrappers guide](/docs/guides/foreign-wrappers/)** for the full mechanics — provider presets, credentials, and the compiled DDL.
## Worked example
[Section titled “Worked example”](#worked-example)
The **[supabase\_bigquery\_mailing\_list](https://github.com/SQLAnvil/sqlanvil/tree/main/examples/supabase_bigquery_mailing_list)** example does exactly this end to end: it builds a proximity mailing list of customers who purchased in the last year and live within a radius of a target ZIP, by joining Supabase orders with Google’s `bigquery-public-data.geo_us_boundaries.zip_codes` over a live FDW and computing distance with PostGIS. Clone it, point it at your Supabase project, and run.
## Getting started
[Section titled “Getting started”](#getting-started)
Note
You’ll need a Supabase project (with the `wrappers` extension) and a GCP service-account key from a billing-enabled project. Querying `bigquery-public-data` is free up to BigQuery’s monthly tier, but BigQuery still requires a billing project to run the query.
```bash
npm install -g @sqlanvil/cli
sqlanvil init my-project --warehouse supabase
```
Then follow the [Foreign Data Wrappers guide](/docs/guides/foreign-wrappers/) to add the BigQuery bridge and store your service-account key in Vault. Once `sqlanvil run` goes green, the enriched result is an ordinary table in your Supabase schema — your app reads it like any other. To keep the reference data fresh without a cron server, [SQLAnvil Cloud](/docs/cloud/) can run the pipeline on a schedule.
# What's New
> Recent SQLAnvil releases and notable changes.
Release notes for the `@sqlanvil/cli` and `@sqlanvil/core` packages. Full notes live on [GitHub Releases](https://github.com/SQLAnvil/sqlanvil/releases).
## 1.24.0 — Keep BigQuery when leaving Dataform; every project ships agent guidance
[Section titled “1.24.0 — Keep BigQuery when leaving Dataform; every project ships agent guidance”](#1240--keep-bigquery-when-leaving-dataform-every-project-ships-agent-guidance)
* **[`migrate-dataform --target-warehouse bigquery`](/docs/guides/migrate-dataform/)** — the tooling swap: *“I don’t want to use Dataform anymore — convert the code and keep running against the warehouse I already have in BigQuery.”* SQL bodies, `bigquery: {}` config blocks, and declarations pass through **untouched** (BigQuery is a first-class sqlanvil warehouse, so nothing needs translating); `defaultProject`/`defaultLocation` carry through, dataset casing is preserved, and no Supabase account is involved anywhere. Only the settings file converts (`sqlanvilCoreVersion:`) and the compile global renames. The default (`supabase`, or `postgres`) still *moves* the warehouse as before. The [`init --interactive`](/docs/getting-started/) convert path now asks which you want — keep-BigQuery is its default, and that answer skips the Postgres credentials Q\&A in favor of `gcloud` ADC guidance. `sqlanvil validate` (BigQuery dry-run) is the completion check.
* **Every generated project now includes [`AGENTS.md`](/docs/guides/ai-agents/)** — a warehouse-tailored guide in the cross-agent AGENTS.md standard (read natively by Codex, Cursor, Antigravity, Gemini CLI, and 30+ tools), plus a one-line `CLAUDE.md` bridge for Claude Code. Written by `init` (all modes, including `--bare`), by `migrate-dataform` (with a converted-project addendum pointing at the migration report), and by the Cloud wizard scaffold. Point any coding agent at your repo and it knows the dialect. Existing `AGENTS.md`/`CLAUDE.md` files are never overwritten.
## 1.23.0 — Guided setup: `init --interactive` (and `init --bare`)
[Section titled “1.23.0 — Guided setup: init --interactive (and init --bare)”](#1230--guided-setup-init---interactive-and-init---bare)
* **[`sqlanvil init --interactive`](/docs/getting-started/)** — a guided Q\&A for starting a project. First question: start **fresh**, or **convert an existing Dataform project**?
* The fresh path walks warehouse choice, project directory, default schema, whether to include the sample project (and its cross-warehouse BigQuery source), then a **credentials Q\&A** that writes the gitignored `.df-credentials.json` for you — Supabase defaults to the Session-pooler shape and warns if you paste the IPv6-only direct `db.[` host. It ends with your numbered next steps (`compile`, `validate`).
* The convert path runs [`migrate-dataform`](/docs/guides/migrate-dataform/) (source stays read-only), then offers the same credentials Q\&A and points you at the migration report.
* Explicit flag only — plain `sqlanvil init` is unchanged for scripts and CI, and `--interactive` fails fast with guidance when there’s no terminal to talk to.
* **`sqlanvil init --bare`** — scaffold the directory structure and config only, skipping the sample project files. For when you’re about to write (or generate) your own models.
## 1.22.0 — Migrate from Dataform
[Section titled “1.22.0 — Migrate from Dataform”](#1220--migrate-from-dataform)
* **[`sqlanvil migrate-dataform ] `](/docs/guides/migrate-dataform/)** — convert a Dataform/BigQuery project into a SQLAnvil-on-Postgres project. The source directory is **read-only**; the output gets a converted project plus `migration-report.{md,json}`. Declarations stay in BigQuery as named runner-extract [connections](/docs/guides/foreign-wrappers/) (one per source GCP project); target SQL gets safe lexical rewrites plus inline `SQLANVIL-MIGRATE:` markers on everything needing dialect review; credentials in the source are never copied. Proven against a 1,404-file production project: 745 declarations → 6 connections, zero compile errors on the converted output.
* **Runner-extract declarations: `schema:` now does the whole job.** A declared `schema:` overrides the connection’s dataset (so one connection per source project serves many datasets) **and** names the Postgres schema the extract materializes into — `ods.zip_code` stays `ods.zip_code`, keeping schema-qualified `ref()`s working after a migration. Declarations without a `schema:` keep the previous `_ext` placement.
* **Empty `columnTypes` now compiles** on runner-extract declarations (introspect them incrementally); the extract fails at **run** time — before touching or billing the source — with the exact `sqlanvil introspect` command to fix it. FDW-mode declarations still require `columnTypes` at compile (a foreign table can’t exist without its columns).
## 1.21.0 — A real starter project from `sqlanvil init`
[Section titled “1.21.0 — A real starter project from sqlanvil init”](#1210--a-real-starter-project-from-sqlanvil-init)
* **`sqlanvil init` now generates a working sample project** in the shape real ones take: source **declarations** (`app_orders` — a table your app already has — plus Google’s public ZIP data from BigQuery), **staging views** over them in `intermediate/`, **output tables** built from the staging layer (`daily_sales`, `product_revenue`, and `orders_by_region` joining both sources), and a business-rule **assertion**. `compile` is green out of the box on every warehouse; [`validate`](/docs/guides/validate/) pinpoints exactly which sources still need wiring before `run`.
* On Postgres/Supabase the BigQuery source rides a `bigquery_public` [connection](/docs/guides/foreign-wrappers/) in `mode: runner-extract` — no Vault secret, no `wrappers` extension. On BigQuery it’s a native declaration; MySQL skips it (cross-warehouse connections need a Postgres-family warehouse).
* The Supabase credentials template now points at the **Session pooler** (`aws-1-.pooler.supabase.com`, user `postgres.`) — the direct `db.[.supabase.co` host is IPv6-only and unreachable from most networks.
* Postgres/Supabase projects default `defaultDataset` to `public`; scaffold directories survive the first commit (`.gitkeep`); `--warehouse mysql` documented in `init` help.
* **Selective runs no longer execute unrelated extracts.** `run --actions`/`--tags` previously ran *every* `runner-extract` source in the project regardless of selection — re-reading (and re-billing) sources nothing selected depended on, and failing runs that never touched them. Extracts now prune like every other action; `--include-deps` still pulls one in when a selected model reads it, and unselective runs are unchanged.
## 1.20.1 — Relative import/export paths are project-relative
[Section titled “1.20.1 — Relative import/export paths are project-relative”](#1201--relative-importexport-paths-are-project-relative)
* Fix: a relative local `location:` on [imports](/docs/guides/imports/)/[exports](/docs/guides/exports/) now resolves against the **project directory**, not the CLI’s own working directory. Previously `sqlanvil run ]` from anywhere else couldn’t find files a [script action](/docs/guides/python-actions/) had just staged (scripts run with cwd = project dir); the two now always agree. Absolute paths and `s3://`/`gs://` URIs are unaffected.
## 1.20.0 — Python script actions
[Section titled “1.20.0 — Python script actions”](#1200--python-script-actions)
* **[`python:` script actions](/docs/guides/python-actions/)** — run Python file-staging and glue steps as first-class DAG nodes, declared in `actions.yaml` against a plain `.py` file. The ingestion slot Dataform/dbt leave outside the pipeline: download → `import` → staging → marts in one `sqlanvil run`, ordered by dependencies and covered by run history.
* **Execution-time only** — compile never runs Python (compiles stay hermetic and deterministic). Compile checks config shape, file existence, and that `pythonVersion` is a well-formed specifier.
* **No warehouse credentials injected** — scripts stage *files*; [`type: "import"`](/docs/guides/imports/) is the loading boundary.
* **You own the environment; SQLAnvil validates it** — [`sqlanvil validate`](/docs/guides/validate/) checks the interpreter against `pythonVersion`, `requirements:` against the *installed* packages (offline, nothing is installed), and the script’s syntax — without executing it. Failures block dependents, like models.
* Contract: cwd = project dir, `SA_VARS` (your vars as JSON) + `SA_ACTION_NAME` env, optional `venv:` interpreter, `args:`, exit 0 = success, 30-min default timeout (`timeoutMillis` overrides).
* Language-neutral underneath (`script:` with a `language` field — `python:` is sugar), so future runtimes are additive.
* The [mailing\_list example](https://github.com/SQLAnvil/sqlanvil/tree/main/examples/supabase_bigquery_mailing_list)’s OpenAddresses ingestion is now fully in-DAG: a `python:` action stages the CSV (bundled sample, or a real openaddresses.io download via the `oa_source_url` var) and the import depends on it.
* **SQLAnvil Cloud:** hosted runs reject script actions at compile time (like local file paths) — user code doesn’t execute on the shared runner. Local CLI and your own CI both run them today.
## 1.19.0 — The `mysql: {}` block is feature-complete
[Section titled “1.19.0 — The mysql: {} block is feature-complete”](#1190--the-mysql--block-is-feature-complete)
* **`FULLTEXT` / `SPATIAL` indexes** — `indexes: [{ columns: ["title"], type: "fulltext" }]` (mutually exclusive with `unique`; a spatial index needs a `NOT NULL` SRID geometry column, which usually means an `ALTER TABLE … MODIFY` in `post_operations` first).
* **Index prefix lengths** — a column may carry MySQL’s own prefix syntax, `"body(50)"` → `` `body`(50) `` (required to index `TEXT`/`BLOB` columns).
* **`rowFormat`** table option — emitted as `ROW_FORMAT=` (e.g. `DYNAMIC`, `COMPRESSED`).
* Fix: the `mysql: {}` block on a **materialized view** (`type: "view", materialized: true`) now compiles through — earlier versions silently ignored it there, despite the docs showing it.
* With these, [the `mysql: {}` block](/docs/guides/mysql/) covers table options, all index forms, and partitioning — closing [issue #35](https://github.com/SQLAnvil/sqlanvil/issues/35).
## 1.18.0 — MySQL/MariaDB as a read-only source
[Section titled “1.18.0 — MySQL/MariaDB as a read-only source”](#1180--mysqlmariadb-as-a-read-only-source)
* **MySQL/MariaDB cross-warehouse sources** — a `platform: mysql` [connection](/docs/guides/foreign-wrappers/) can now feed a Postgres/Supabase warehouse. Declare a source with `connection:` and SQLAnvil reads `database.table` at run time and **materializes** it as a plain `ref()`-able table — the same `runner-extract` mechanism as keyless BigQuery sources (capped at 1M rows / 512 MB). MySQL sources are runner-extract **only** (there is no Postgres FDW for MySQL); an explicit `mode: fdw` is a compile-time error. Source credentials (`host`, `port`, `user`, `password`) live in `.df-credentials.json`’s `connections` map. Validated against MySQL 8 and MariaDB 11.
* **`sqlanvil introspect` maps MySQL types to Postgres types** — scaffolded `columnTypes` from a MySQL source now arrive as warehouse-valid types (`datetime` → `timestamp`, `double` → `double precision`, `json` → `jsonb`, blobs → `bytea`, …), since they define the extract table’s columns in the warehouse.
* Fix: re-running an extract over an existing plain table no longer fails on the leftover `drop foreign table` (drops are now relation-kind-aware; this affected BigQuery extracts too).
* Fix: an explicit `schema:` on a connection’d declaration was silently ignored — both the FDW foreign-table bridge and extracts now honor it as the source schema/database.
## 1.17.1 — `run --graph` works from any directory
[Section titled “1.17.1 — run --graph works from any directory”](#1171--run---graph-works-from-any-directory)
* Fix: `run --graph` no longer requires the working directory to contain a `workflow_settings.yaml` — with a stored graph, the directory is only used for credentials and artifacts, so a bare directory works (as the 1.17.0 notes intended).
## 1.17.0 — `run --graph` (execute a stored compiled graph)
[Section titled “1.17.0 — run --graph (execute a stored compiled graph)”](#1170--run---graph-execute-a-stored-compiled-graph)
* **`sqlanvil run --graph `** — run the JSON emitted by `compile --json` directly, without compiling the project. What executes is exactly what was compiled — environment overrides are baked in at compile time — which makes a stored graph a true **release artifact**: compile once, run the same graph unchanged later or elsewhere. Selection flags (`--actions`, `--tags`, `--include-deps`, `--include-dependents`), `--full-refresh`, and BigQuery `--dry-run` all apply to the loaded graph.
* Guards: `--environment` with `--graph` is rejected (compile with `--environment` instead, and pass `--credentials` explicitly); `--dry-run` on Postgres/Supabase/MySQL is rejected (`validate` needs the project source). The CLI warns when the graph was compiled by a different core `major.minor`.
* This powers **pinned releases** in [SQLAnvil Cloud](/docs/cloud/workflows/): scheduled workflow runs can execute the exact stored snapshot a release captured.
## 1.16.0 — Synced to upstream Dataform 3.0.61
[Section titled “1.16.0 — Synced to upstream Dataform 3.0.61”](#1160--synced-to-upstream-dataform-3061)
* **BigQuery column types**: `BIGNUMERIC`, `JSON`, and `INTERVAL` source columns now map correctly (to numeric/string) when reading table schemas.
* **`getContents(path)`** — read a file’s raw contents inside a compilation (e.g. inline a `.sql` or Markdown snippet); paths are resolved relative to the calling file and confined to the project directory.
* **Assertion `metadata`** — assertions accept a `metadata` block, carried through to the action descriptor.
* **`actions.yaml` guard** — referencing a `.sqlx` file from `actions.yaml` now raises a clear error; `.sqlx` files are compiled directly from `definitions/`.
* Internal: the build migrated to **Bazel 7.3.2 + bzlmod** (no user-facing change).
## 1.15.0 — `runner-extract` connection mode (keyless cross-warehouse sources)
[Section titled “1.15.0 — runner-extract connection mode (keyless cross-warehouse sources)”](#1150--runner-extract-connection-mode-keyless-cross-warehouse-sources)
* **`mode: runner-extract`** on a BigQuery source [connection](/docs/guides/foreign-wrappers/) — an alternative to the live Foreign Data Wrapper. Instead of a foreign table, SQLAnvil reads the source at run time and **materializes** the rows into a plain table (`ref()`-able just like the FDW bridge). It needs **no Vault secret and no `wrappers`/`postgis` extensions**, so it works on bare/ephemeral databases where an FDW can’t be provisioned. Source auth (a short-lived `accessToken`, a key, or ADC) goes in `.df-credentials.json`’s `connections` map. Keep the default `fdw` mode when you want a live foreign table on a persistent warehouse. See the [Foreign Data Wrappers guide](/docs/guides/foreign-wrappers/).
## 1.14.0 — Keyless BigQuery via access token
[Section titled “1.14.0 — Keyless BigQuery via access token”](#1140--keyless-bigquery-via-access-token)
* **`accessToken` on BigQuery credentials** — a new optional field in `.df-credentials.json` lets BigQuery authenticate with a short-lived OAuth2 access token instead of a JSON key. Precedence is `accessToken` → JSON `credentials` → Application Default Credentials, so existing setups are unaffected. This enables **keyless** runs: a caller that already runs inside GCP (e.g. SQLAnvil Cloud’s runner) can mint a token by impersonating a service account and hand it to the CLI — no key is ever written. Tokens are short-lived (≈1h), suited to a single run.
## 1.13.0 — Cross-project BigQuery FDW (`billingProject`)
[Section titled “1.13.0 — Cross-project BigQuery FDW (billingProject)”](#1130--cross-project-bigquery-fdw-billingproject)
* **`billingProject`** on a BigQuery [named connection](/docs/guides/foreign-wrappers/) lets you read a dataset you can **read but not bill** — e.g. `bigquery-public-data` — by billing the FDW query jobs to *your own* GCP project. Set `project` to where the data lives and `billingProject` to your project; SQLAnvil bills yours and reads the source via a full-FQN subquery. Without it, reading a public/other dataset failed with `bigquery.jobs.create` permission denied. The SA needs `bigquery.jobUser` on the billing project. See the [Foreign Data Wrappers guide](/docs/guides/foreign-wrappers/).
## 1.12.0 — File imports (`type: "import"`)
[Section titled “1.12.0 — File imports (type: "import")”](#1120--file-imports-type-import)
* **`type: "import"`** loads a **Parquet/CSV/JSON** file into a table in the warehouse — the symmetric inverse of [`type: "export"`](#180--file-exports-type-export). The loaded table is `ref()`-able by downstream models. On **BigQuery** it compiles to native `LOAD DATA OVERWRITE|INTO … FROM FILES(…)` (gs\:// only); on **Postgres/Supabase** the CLI loads it via the bundled **DuckDB** bridge (read the file, write into the warehouse) — filling the gap where managed Postgres/Supabase can’t read Parquet itself. `overwrite` defaults true (replace), `false` appends. MySQL is not supported yet. See the [Imports guide](/docs/guides/imports/).
## 1.11.0 — MySQL partitioning + introspect
[Section titled “1.11.0 — MySQL partitioning + introspect”](#1110--mysql-partitioning--introspect)
* **MySQL/MariaDB native partitioning** via the `mysql: {}` block: `partition: { kind, expression, partitions, count }` — **RANGE/LIST** (explicit child partitions, e.g. `values less than (2024)` / `values in (...)`) and **HASH/KEY** (`PARTITIONS `). See the [MySQL guide](/docs/guides/mysql/).
* **`sqlanvil introspect` now supports MySQL** — scaffold a `declaration` from an existing MySQL/MariaDB table’s columns, types, and comments (`platform: mysql` connection).
## 1.10.0 — Queryable artifacts + catalog
[Section titled “1.10.0 — Queryable artifacts + catalog”](#1100--queryable-artifacts--catalog)
* **Queryable Parquet artifacts.** `compile` writes a catalog and `run` writes run history to `target/` as Parquet (via the bundled DuckDB) — portable and readable by DuckDB, pandas, agents, or external tables. Best-effort; `--no-artifacts` to disable.
* **`sqlanvil query ""`** runs SQL over the artifacts (views: `actions`, `dependencies`, `columns`, `runs`); **`sqlanvil inspect`** prints counts + latest-run summary + failures; **`sqlanvil docs`** generates a self-contained HTML catalog at `target/docs/index.html`. See the [Artifacts & Catalog guide](/docs/guides/artifacts/). (Add `target/` to `.gitignore`.)
## 1.9.0 — `sqlanvil validate`
[Section titled “1.9.0 — sqlanvil validate”](#190--sqlanvil-validate)
* **New command: `sqlanvil validate`** — catch broken SQL before you run it. It walks the project DAG in dependency order and validates every model against the warehouse planner (`EXPLAIN` on PostgreSQL/Supabase/MySQL, dry-run on BigQuery) **without executing**, using an isolated, auto-dropped shadow schema so whole-project validation works even before any model is built. Results are **PASS / FAIL / BLOCK / SKIP** with error locations; non-zero exit on failures, so it drops into CI. Works on all four warehouses. See the [Validate guide](/docs/guides/validate/).
* **Fix:** `run --dry-run` on PostgreSQL/Supabase/MySQL now **validates** instead of executing — previously it applied changes while claiming “no changes will be applied”. BigQuery keeps its native server-side dry-run.
## 1.8.3 — MySQL pre/post-ops + DuckDB security migration
[Section titled “1.8.3 — MySQL pre/post-ops + DuckDB security migration”](#183--mysql-prepost-ops--duckdb-security-migration)
* **MySQL/MariaDB now runs `pre_operations` / `post_operations`** (and the incremental variants). They were silently ignored before — a `pre_operations`/`post_operations` block compiled and ran but never executed. They now bracket every build path, matching BigQuery/Postgres. ([#44](https://github.com/SQLAnvil/sqlanvil/issues/44))
* **Security:** the DuckDB binding behind Postgres/Supabase file exports moved from the classic `duckdb` package to the official **`@duckdb/node-api`**. The old package’s `node-gyp → tar` chain reported 5 high-severity `npm audit` advisories (no fix available); the new client audits clean. No behavior change. ([#40](https://github.com/SQLAnvil/sqlanvil/issues/40))
## 1.8.2 — Upstream 3.0.60 sync + assertion metadata
[Section titled “1.8.2 — Upstream 3.0.60 sync + assertion metadata”](#182--upstream-3060-sync--assertion-metadata)
* **`metadata` on assertions:** the `assertion` config block now accepts a `metadata` field (mirroring `table`/`view`), letting you attach an `overview` and arbitrary key/value `extraProperties` that land on the assertion’s action descriptor. ([#2208](https://github.com/SQLAnvil/sqlanvil/pull/2208))
* **Synced to upstream Dataform core 3.0.60** (`sqlanvil --version` now reports `Dataform core 3.0.60`).
* **Security:** bumped `vm2` 3.11.3 → 3.11.4 and `protobufjs` 7.5.5 → 7.5.8.
## 1.8.1 — Multiline `rowConditions` fix
[Section titled “1.8.1 — Multiline rowConditions fix”](#181--multiline-rowconditions-fix)
* **Bug fix:** a multiline `rowConditions` assertion entry no longer compiles to invalid SQL on **BigQuery**. The condition label is now escaped to a single-line string literal (BigQuery single-quoted literals can’t span lines), so conditions formatted across multiple lines for readability work. Postgres/Supabase and MySQL were unaffected. ([#42](https://github.com/SQLAnvil/sqlanvil/issues/42))
## 1.8.0 — File exports (`type: "export"`)
[Section titled “1.8.0 — File exports (type: "export")”](#180--file-exports-type-export)
* **`type: "export"`** writes a query result to a **Parquet/CSV/JSON** file. On BigQuery it compiles to native `EXPORT DATA` (gs\:// only); on **Postgres/Supabase** the CLI exports runner-side via **DuckDB** (`COPY (SELECT … FROM postgres_query('pg', …)) TO …`) to `s3://`, `gs://`, **Supabase Storage**, or a local path — filling the gap where managed Supabase can’t write files itself.
* Cloud credentials live in a new `storage` section of the gitignored `.df-credentials.json`. The DuckDB dependency ships with the CLI and loads only when an export runs. See the [File Exports guide](/docs/guides/exports/).
## 1.7.0 — Named environments
[Section titled “1.7.0 — Named environments”](#170--named-environments)
* **`--environment `** (on `compile`/`run`/`test`) loads a named environment from an `environments:` block in `workflow_settings.yaml` — its `schemaSuffix`, `vars`, `defaultDatabase`/`defaultLocation`, and **its own credentials file**. Precedence: explicit CLI flag > environment > `workflow_settings` defaults (`vars` merge per-key). Secrets stay in gitignored per-env `.df-credentials*.json` files. See the [Environments guide](/docs/guides/environments/).
## 1.6.0 — MySQL `mysql:{}` block, comments, materialized views
[Section titled “1.6.0 — MySQL mysql:{} block, comments, materialized views”](#160--mysql-mysql-block-comments-materialized-views)
* **`mysql: {}` config block** — secondary indexes + table options (`engine`/`charset`/`collation`) on `table`/`incremental` models.
* **COMMENT metadata** — `description:`/`columns:` apply as real MySQL table/column comments (full column-definition reconstruction so nothing is dropped), read back from `information_schema`.
* **Materialized views** — `type: "view", materialized: true` is emulated as a refreshed table snapshot (drop + CTAS each run).
## 1.5.0 — MySQL / MariaDB adapter
[Section titled “1.5.0 — MySQL / MariaDB adapter”](#150--mysql--mariadb-adapter)
* New **`mysql` warehouse adapter** — one adapter for MySQL 8 and MariaDB 11, generating portable MySQL DDL/DML (CTAS tables, `CREATE OR REPLACE VIEW`, `ON DUPLICATE KEY UPDATE` incremental upserts). `sqlanvil init --warehouse mysql`. See the [MySQL/MariaDB guide](/docs/guides/mysql/).
## 1.1.0 — Named connections + schema introspection
[Section titled “1.1.0 — Named connections + schema introspection”](#110--named-connections--schema-introspection)
Read foreign sources declaratively, without hand-writing FDW boilerplate.
* **Named connections:** define `connections:` in `workflow_settings.yaml` and tag a `declaration` with `connection:` — SQLAnvil auto-generates the read-only FDW bridge (BigQuery or Postgres source) and the table becomes `ref()`-able. `warehouse:` can name a connection; non-secret connection defs live in `workflow_settings.yaml`, secrets in `.df-credentials.json`. See the [Foreign Data Wrappers guide](/docs/guides/foreign-wrappers/).
* **`sqlanvil introspect`:** reads a source table’s schema and writes the declaration with `columnTypes` (and descriptions) filled in, mapping source types to your warehouse dialect.
* **`sqlanvil init` now defaults to `--warehouse supabase`** (was `bigquery`), fitting the Postgres/Supabase-first focus. Pass `--warehouse postgres|bigquery` to override.
## 1.0.3 — Cross-warehouse Foreign Data Wrappers
[Section titled “1.0.3 — Cross-warehouse Foreign Data Wrappers”](#103--cross-warehouse-foreign-data-wrappers)
First-class **Foreign Data Wrapper** support in the `wrapper()` action — query another warehouse in place and join it with your operational data.
* **Provider presets:** `wrapper({ provider: "bigquery", server, serverOptions, credential, foreignTables })` emits a complete, correct BigQuery FDW bridge (enables the `wrappers` extension, registers the FDW idempotently, creates the server). Generic FDW remains reachable on any Postgres via explicit `wrapper`/`handler`/`validator`.
* **Ref-able foreign tables:** each `foreignTables[]` entry becomes a `ref()`-able table that depends on the server setup, so downstream models consume external data like any other source.
* **Safe credentials:** references a pre-existing Supabase Vault secret by id — the service-account key JSON never enters SQLAnvil. Postgres-first design; only the credential path is Supabase-specific.
* **New example:** [`supabase_bigquery_mailing_list`](https://github.com/SQLAnvil/sqlanvil/tree/main/examples/supabase_bigquery_mailing_list) — a proximity mailing list joining Supabase orders with Google’s `bigquery-public-data` ZIP geo data via a live FDW + PostGIS.
See the [Foreign Data Wrappers guide](/docs/guides/foreign-wrappers/).
## 1.0.2 — Packaging fixes
[Section titled “1.0.2 — Packaging fixes”](#102--packaging-fixes)
* The published npm packages now include `README.md`, `LICENSE`, and `NOTICE`, so the npm package pages render the README and the Apache-2.0 license ships in the artifact.
## 1.0.0 — First public release
[Section titled “1.0.0 — First public release”](#100--first-public-release)
* Open-source SQL workflow tool for **BigQuery, PostgreSQL, and Supabase**, forked from Dataform OSS and extended with first-class Postgres/Supabase support.
* Idiomatic Postgres DDL/DML (native partitioning, `INSERT … ON CONFLICT` upserts, btree/gin/gist/brin indexes, materialized views).
* Supabase-native action types: RLS policies, Realtime publications, pgvector indexes.
* Flat `warehouse:` config + gitignored `.df-credentials.json` connection.