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 transpiles every default-target model into the new engine's dialect — deterministically, with the same SQLGlot machinery federation uses on every run. No AI tokens. 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 models + the default target

Every changed file keeps a timestamped .bak. SQL the deterministic pass can't translate gets one AI rescue attempt through your gateway — validated before anything is written — or its full path printed for you to edit. The default target only flips when every file made it. It's also in Chat with Martin as /flip-target-to.

The transpiler is conservative on purpose: it masks every piece of Jinja as an opaque token, translates only the SQL between the tags, and accepts a translation only when it can prove nothing moved across a tag boundary. Anything it can't prove is named for you — refused rather than guessed at.

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 transpiling it into corruption. One known limit: 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 --agentic lane, backups and rollback, every flag — live on the command's own page: dvt flip-target-to →

NEXT RELEASE — NOT IN 0.2.52

What follows is built and documented ahead of the release that carries it. pip install dvt-core gives you 0.2.52, and 0.2.52 does not do it yet.

On 0.2.52, today: the flip walks your model paths only— macros, snapshots, tests and analyses are not visited, so they neither flip nor refuse. (By doctrine they belong to the target and are chartered to move with it; it's also why macros belong to the target and must never be shared with a federated model.) It masks Jinja expressions and transpiles the SQL around them, so a model file carrying Jinja control flow ({% if %}, {% for %}, {% set %}) is refused outright rather than transpiled span by span — dialect can hide inside a branch, and 0.2.52 will not guess. Refusals still get the AI rescue attempt and the interactive aftercare. And there is no wholesale restore: every rewritten file keeps its timestamped .bak beside it and stays rewritten — what protects you from a half-migrated project is that the default target does not move until every file passes.

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.