SWITCHING THE DEFAULT TARGET

The default target is where your standard models run — and it's a setting, not a commitment. Because federation models are written in DuckDB dialect and never depend on the default target's engine, swapping the entire lakehouse under a DVT project is a profile change, not a rewrite.

This page is the concept, told through a real migration we ran: a 67-model project moved from a PostgreSQL default target to Databricks. Total code change: 2 profile lines, 1 cast fix, and 4 lines of seed config. All 67 models green on the first full run.

WHY THIS WORKS: THE DUAL-DIALECT DESIGN

A DVT project contains two kinds of SQL:

FEDERATION MODELS — DUCKDB DIALECT

Decomposed, transpiled per source engine, computed in DuckDB, loaded anywhere by Sling. They never touch the default target's SQL dialect — so a target switch can't break them.

STANDARD MODELS — TARGET DIALECT

table / view / incremental models compile and run on the default target through the official dbt adapter — as do the macros, snapshots and tests they carry. This is the only SQL the target's dialect touches — and most ANSI SQL ports cleanly.

So the blast radius of a lakehouse switch is limited to engine-specific syntax in your standard models — typically date functions and casts. Everything else moves untouched.

THE ONE-COMMAND WAY: DVT FLIP-TARGET-TO

One command does the whole move. It makes the new output the default target AND takes every file that runs on it — models, snapshots, tests, analyses, macros — into the new engine's dialect. Since 0.2.59 there is one translator: the AI lane, with machines judging every answer. Files already valid on the new engine are left untouched; your Jinja survives byte-for-byte; federated and target-pinned models are never touched.

dvt flip-target-to dbx_dev --dry-run   # the ledger: what flips, what doesn't
dvt flip-target-to dbx_dev             # flip the files + the default target

Every changed file keeps a timestamped .bak. Each file that needs the new dialect gets its own bounded AI conversation, and no answer is written until the validators accept it — Jinja intact, parses in the new dialect, no function the engine lacks. Then the new engine itself compiles the project as the final judge. A file the lane cannot carry is kept as written and named with its full path. The default target always moves. It's also in Chat with Martin as /flip-target-to.

config-checkup runs first, always: a model declared table whose dependencies are federated is really DuckDB SQL — the checkup fixes its config so the flip correctly skips it instead of translating it into corruption. Two known limits: the flip translates only while the AI-Proxy is connected (without it, the target moves and every file still to translate is named); and on Oracle, custom-schema models need the schema user to exist first — Oracle schemas are users.

That's the concept. The mechanics — the full ledger, role changes, the lane, the engine judge, backups, every flag — live on the command's own page: dvt flip-target-to →

STEP 1 — POINT THE PROFILE AT THE NEW ENGINE

The manual steps below are what the command automates — worth reading once so you know what actually changes. It starts in profiles.yml: change the target: to an output that already exists in your profile (or add one):

my_project:
  target: dbx_dev        # was: pg_dev — this is the whole switch
  outputs:
    pg_dev:
      type: postgres
      # ...
    dbx_dev:
      type: databricks
      catalog: main
      schema: analytics
      host: xxxx.cloud.databricks.com
      http_path: /sql/1.0/warehouses/xxxx
      token: "{{ env_var('DATABRICKS_TOKEN') }}"

Then dvt sync installs the new adapter and drivers, and dvt debug --all verifies every connection on both the adapter and Sling layers before you run anything.

STEP 2 — FIX ENGINE-SPECIFIC SQL (USUALLY CASTS)

Standard models now compile in the new engine's dialect. In our migration exactly one model needed a change: Databricks runs ANSI mode, which rejects implicit string→date casts that PostgreSQL tolerated.

-- before (worked on postgres, implicit cast)
where transaction_date >= '2015-01-01'

-- after (explicit ANSI cast, works everywhere)
where transaction_date >= cast('2015-01-01' as date)

Writing standard models in portable ANSI SQL from day one makes this step a no-op.

STEP 3 — PIN SEED COLUMN TYPES WHERE NEEDED

Engines infer seed column types differently. DVT honors dbt-native +column_types in dbt_project.yml and passes them through Sling — our migration needed 4 lines for two seeds with non-ISO date strings:

seeds:
  my_project:
    transactions_b:
      +column_types:
        transaction_date: date

STEP 4 — RUN

dvt build

dvt build does the rest: seeds load through Sling, standard models run on the new engine, and federation models run exactly as before — same sources, same DuckDB compute, results now landing in the new default target.

Done. PASS=67 WARN=0 ERROR=0 SKIP=0 TOTAL=67

The real migration result: 67 of 67 models green after a 2-line profile change, 1 cast fix, and 4 lines of seed config. The cross-engine catalog (dvt docs) reflected the switch automatically — models that were stamped postgres now stamp databricks.

WHAT THIS MEANS

  • ▸No vendor lock-in at the transformation layer — your models are the asset, the engine is a setting.
  • ▸Trial a warehouse migration on a branch: switch the target, run, compare. Roll back by switching it back.
  • ▸Run dev on a cheap engine (PostgreSQL, DuckDB) and prod on the warehouse — same project, different target.
  • ▸Per-model target= overrides let you migrate gradually instead of all at once.