Data transformation in 2026: revisit dbt Core v1, dbt v2 and SQLMesh before you write another SQL model

Almost every reporting database has a folder of SQL that nobody fully owns. It starts with one view for daily sales. Then a second view joins the first one. Then somebody writes a cron job that drops a table at 2 AM and creates it again from a long query, because the view became slow. After two years there are sixty such queries, three cron jobs, a spreadsheet that says which one runs first, and a dashboard that is wrong on some mornings for reasons nobody can explain quickly.

That folder is a transformation layer. It just does not have the parts a transformation layer needs: a clear order of models, tests that run every time, a way to change one model without rebuilding everything, a safe way to try a change before the business sees it, and a record of which column came from where. This post compares the options that Indian data teams look at in 2026 for that job: dbt Core v1, the new dbt v2 (the engine earlier called dbt Fusion), and SQLMesh. We keep the comparison practical and try to be fair about the money and the licences behind each one, because 2026 changed both.

This post sits next to several earlier ones and tries not to repeat them. Data ingestion in 2026 covers getting data into the warehouse; here we assume the raw tables already exist. Data pipeline orchestration in 2026 covers who triggers what and when; the tools here are what those schedulers usually call. Data catalogs in 2026 covers where lineage and ownership are shown to the wider company; transformation tools are one of the best sources of that lineage. In-process analytics in 2026 explains DuckDB, which we use for the drill. If your models write to open table formats or to a fast analytics store, Lakehouse table formats in 2026 and Real-time OLAP in 2026 are the neighbours.

We end with a small local drill on DuckDB: dbt Core 1.12.6, dbt-oss 2.0.8, the full dbt 2.0.9 and SQLMesh 0.236.4 running the same three small models over invented data. We add late rows, change a column, make a typo and push a bad value, and we write down what each tool actually did. We say clearly what we did not run.

A scenario to keep us honest

Imagine a seller of Banarasi silk sarees, dupattas and home furnishings in Varanasi. The company is invented for this post. It buys from weaver families in the clusters around the city, sells through its own website, two online marketplaces and two showrooms in Varanasi, and ships across India and to a few customers abroad. Silk sarees are high value, so returns, exchanges and cancelled orders matter a lot to the margin.

The setup today:

  • The website orders live in a PostgreSQL database in a cloud region in Mumbai.
  • A small dlt pipeline copies website orders, marketplace order exports, marketplace settlement files and showroom billing data into a separate PostgreSQL reporting database every night.
  • About sixty SQL views and “create table as” scripts in a Git repository turn those raw tables into sales, returns, stock and weaver payment reports. A shell script runs them in a fixed order from cron at 2 AM.
  • Metabase dashboards read the final tables. The owner and the finance head look at them every morning.

During the last Diwali season three things went wrong. First, marketplace settlement files arrive late, sometimes by more than a week. The nightly script only rebuilt the last few days of the sales summary to save time, so settlements for older dates never reached the summary, and the margin report was quietly wrong. Second, an analyst renamed a column in one staging query; four downstream scripts failed at 2 AM, and the dashboards showed yesterday’s numbers until noon. Third, a new sales channel value, “whatsapp”, appeared when the showroom team started taking orders on chat. A check that somebody ran after the nightly build found it, but by then the finance head had already exported the wrong channel split to a spreadsheet.

The team is one data engineer, two analysts who write good SQL, and a freelance consultant who used dbt at a previous company. They want four things before the next wedding season:

  1. Models that run in the right order, with tests that run every night, without a hand-written shell script.
  2. Late settlements for older dates should reach the summary tables in a controlled way, not by luck.
  3. A change to one model should be tried somewhere safe first, and the dashboards should not see it until it is checked.
  4. When a check fails, the bad data should not reach the tables the dashboards read.

They are also thinking of moving from the reporting PostgreSQL to a cloud warehouse next year, so they want a tool that will not trap them. We will come back to Varanasi in the worked plan.

The short version

  • dbt Core v1 is the Python tool most teams know. It is Apache-2.0. The latest patch releases on 14 October 2026 were 1.12.6 and 1.11.16, and a 1.13 beta also came out that day. dbt Labs says 1.13 will be the last minor release of the 1.x line and expects to give it critical fixes for several years. It has the largest set of adapters, packages and people who already know it. It runs your SQL in order and runs tests after each model. It does not understand your SQL; it treats it as text with Jinja templates.
  • dbt v2 is the Rust rewrite that was announced as the dbt Fusion engine in May 2025. Version 2.0.0 was tagged on 14 September 2026 (US time; early on 15 September in India). It comes in two forms. dbt is the full distribution, free to use, under the proprietary dbt Product Licensing Agreement. dbt-oss is an Apache-2.0 subset. The full distribution can parse and type check your SQL before it runs. In our drill it caught a typo and a type mismatch that the other tools let through. The open subset does not include that engine.
  • SQLMesh is an Apache-2.0 framework that also understands SQL, through the SQLGlot library. Its model is closer to Terraform: you make a plan, look at what will change, and apply it. It keeps state about which time intervals each model has processed. It creates cheap development environments as views over versioned tables, and promoting a change to production can be a view swap with no rebuild. Fivetran bought its maker, Tobiko Data, in September 2025 and contributed SQLMesh to the Linux Foundation in March 2026. The latest release is 0.236.4 from 23 October 2026 (India time).
  • The vendor picture changed. Fivetran and dbt Labs completed an all-stock merger on 1 June 2026. The same company group now sponsors dbt and gave SQLMesh to a neutral foundation. Read licences and governance, not logos.
  • None of them fixes late data by itself. In our drill, a two-day lookback in dbt v1 silently missed a late row for an older date. SQLMesh needed an explicit restatement, and that restatement had to start from the right upstream model. You still have to design for late data.

Where things stand on 30 October 2026

The names are the first thing to get right, because they changed in 2026 and many blog posts still use the old ones.

  • dbt Core used to mean the open source Python CLI. In dbt Labs’ own wording at dbt Summit 2026, dbt Core is now “simply renamed as the previous version, dbt v1”. The licensing FAQ says the dbt Core name no longer applies to any dbt v2 distribution. We still write “dbt Core v1” in this post, because that is what most people will search for.
  • dbt Fusion was the name of the Rust engine from May 2025. On 1 June 2026, dbt Labs relicensed the source code that had been under the Elastic License 2.0 to Apache 2.0 and published it as dbt Core v2.0 alpha. The compiled Fusion binary moved to a new dbt Product Licensing Agreement. On 15 September 2026 the names changed again: the full product is now called dbt, and the Apache-2.0 subset is dbt OSS, installed as dbt-oss.
  • The repository moved. The old GitHub address dbt-labs/dbt-core now redirects to dbt-labs/dbt. Its README says the main branch holds the Apache 2.0 source of v2, a ground-up rewrite in Rust, and that v1 development moved to the 1.latest branch. The separate dbt-fusion repository is marked as an archive.
  • dbt v2 adapters. At dbt Summit 2026, dbt Labs listed BigQuery, Databricks, DuckDB, Redshift and Snowflake as generally available on v2, ClickHouse and Spark in beta, and Athena, Fabric and Postgres as coming soon. If your warehouse is PostgreSQL, as in Varanasi, v2 is not an option yet. dbt v1 with the dbt-postgres adapter still is.
  • SQLMesh moved from the Tobiko Data GitHub organisation to github.com/SQLMesh/sqlmesh. Its governance file describes a Technical Steering Committee, with Toby Mao as chair. The first TSC meeting was on 10 March 2026. Version numbers are still 0.x. That is a versioning choice, but it also means you should read release notes before each upgrade.
  • SQLGlot, the SQL parser and transpiler under SQLMesh, is MIT licensed. Its latest release on PyPI is 30.26.0, from 29 October 2026.

Two more 2026 items matter for planning. dbt Labs announced that dbt State is generally available. It is a paid dbt platform feature that skips models whose inputs have not changed, and it works with dbt v1.7 through v2. dbt Labs says customers see an “average 15-30%+ reduction” in warehouse compute; that is the vendor’s own figure, and we did not test it. dbt Labs also announced Lake Compute in private beta, a single-node engine based on DuckDB that works over Iceberg tables. We did not try it either.

The first question: what kind of change do you make most often?

Before comparing features, look at the last three months of changes in your SQL repository. Most teams fall into one of four groups, and each group points at a different strength.

Group 1: new models, rarely changed

You mostly add new reports. Old models stay as they are. Tables are small enough that a full rebuild every night takes minutes. Here, the order of models and the tests matter most. Any of the three tools will do the job, and dbt v1 has the gentlest learning curve because so much has been written about it.

Group 2: frequent edits to shared models

Analysts change staging models that many other models depend on. Mistakes are typos, renamed columns and wrong types. Here, a tool that understands SQL before running it saves real time: the full dbt v2 distribution and SQLMesh both parse SQL, and they catch different things in different ways.

Group 3: large incremental tables with late data

You have a few big fact tables that you cannot rebuild every night, and rows for older dates keep arriving: settlements, returns, refunds, corrections. Here, how a tool tracks which time ranges have been processed, and how you reprocess a range, matters more than anything else. SQLMesh was designed around this. dbt v1 has the incremental materialization, and from v1.9 a microbatch strategy that works in time batches.

Group 4: many people changing things at once

Several analysts change models in parallel, and each wants to see their change on real data without paying for a full copy of the warehouse. Here, cheap environments and safe promotion matter. SQLMesh’s virtual data environments are its main idea. In the dbt world, people use --defer and state:modified selection in v1, and the paid dbt platform adds more.

A quick test

Look at your last ten production incidents in the reports. If most were “a model failed because of a typo or a renamed column”, you are in group 2. If most were “the numbers for an old date were wrong”, you are in group 3. If most were “someone’s change reached the dashboard before it was checked”, you are in group 4. Varanasi had one of each, which is common, and which is why the plan later does not pick a tool just on features.

What each tool is

dbt Core v1

dbt v1 is a Python program. You write each model as a select statement in a .sql file. You refer to other models with {{ ref('name') }} and to raw tables with {{ source('schema', 'table') }}. dbt reads all the files, builds a graph from those references, and runs the models in order, creating tables or views in your warehouse. YAML files next to the models describe columns and tests.

The incremental model in the drill looked like this:

{{ config(materialized='incremental',
          unique_key=['order_date','channel'],
          incremental_strategy='delete+insert') }}
select order_date, channel, sum(qty) as units, sum(amount_inr) as revenue_inr
from {{ ref('stg_orders') }}
where status = 'paid'
{% if is_incremental() %}
  and order_date >= (select max(order_date) - interval 2 day from {{ this }})
{% endif %}
group by 1, 2

The first run builds the whole table. Later runs only select rows from the last two days before the latest date already in the table, delete the matching keys, and insert the new rows. This “lookback” pattern is very common. Its weak spot is that dbt does not remember which dates were processed. It only looks at what is in the target table now, so a late row older than the window is never seen until somebody runs a full refresh.

Things dbt v1 does well:

  • One way of working that many people already know. Most analytics engineers in India who have used a modern data stack have seen a dbt project. Hiring and handover are easier.
  • Tests that are easy to write. not_null, unique, accepted_values and relationships come built in, and packages add many more. Unit tests, where you give fixed input rows and expect fixed output rows, arrived in v1.8.
  • Selection by state. If you keep the manifest.json from your production run, dbt build --select state:modified+ --state <path> builds only the changed models and everything downstream. --defer lets a development run read unchanged parents from production instead of building them.
  • Adapters and packages. dbt v1 has adapters for many databases, including PostgreSQL, and a large package hub.

Things to know:

  • dbt v1 does not read your SQL. Jinja is rendered into text, and the database is the first thing that checks it. A misspelled column fails at run time, and a type mismatch that your database accepts is never flagged.
  • Tests run after the model is written. In dbt build, a model is created first, then its tests run. If a test fails, models downstream of it are skipped, but the failing model itself has already replaced the old table. In our drill, the bad “whatsapp” row was already in the final table when the test failed. You can build a write, audit and publish pattern by hand, with a staging schema and a swap, but dbt does not do it for you.
  • The 1.x line is ending. dbt Labs says 1.13 is the last minor release and that critical support will continue for an expected three to five years, without a hard date. That is enough time to plan, but it is not a reason to start a new large project on v1 without a plan for v2 or an alternative.

dbt v2 (the engine once called Fusion)

dbt v2 keeps the same project format: models, ref, source, YAML, macros and packages. The program underneath is a new Rust binary. It is installed with pip install dbt (full distribution) or pip install dbt-oss (open subset). In our drill, the dbt-oss 2.0.8 build ran our unchanged v1 project: five of five nodes succeeded.

Things dbt v2 adds, according to dbt Labs’ upgrade guide:

  • Static analysis. The full distribution can resolve column names and types for every model before anything runs. There are three modes. Off does no analysis. Baseline is the default, and its findings are warnings. Strict resolves all column types and is needed for column-level lineage. In our drill, the default compile with the full distribution let a typo through, and strict mode caught it as error dbt0227, “No column skuu found”. Strict mode also flagged, as dbt0407, a date compared with a timestamp in our incremental filter, which DuckDB had happily run four times.
  • The open subset does not include that engine. With dbt-oss 2.0.8, --static-analysis strict printed a warning that “this distribution of dbt OSS does not include the static analysis engine”, and the help text says lint “requires the full dbt distribution”. This is the line that separates the free and open parts.
  • Stricter parsing. v2 removes behaviour that was deprecated in v1 and rejects unknown keys in YAML and config. The upgrade guide says you must clear all deprecation warnings first, and dbt Labs provides a dbt-autofix helper. dbt 1.12 has a --use-v2-parser flag so you can see the v2 parser’s complaints while still on v1.
  • Mixed running. v2 writes a manifest in the same version 12 format as v1, so state:modified and --defer can work across a team where some people are on v1 and some on v2.
  • Other features. A local information schema written as Parquet files, a dbt check command for project rules, dbt lint in the full distribution, batch tests (dbt Labs says its internal testing showed 40% shorter test runtime and 65% fewer queries; those are the vendor’s own figures), and model freshness settings. In dbt build, unit tests now run first.
  • Drivers come from the internet on first run. v2 connects through ADBC drivers that it downloads from the dbt Labs CDN on first use and caches. In our drill it saved a 68 MB DuckDB driver, version 1.5.4, under ~/.cache/com.getdbt. Plan for this in locked-down CI runners and offline servers.

Things to know:

  • The full distribution is not open source. It is free to use, including internally at any size, and the old 15-user cap on the VS Code extension is gone. The restriction is on redistribution as a managed service, where the provider must let users log in to dbt platform features. For most companies running their own pipelines, this is fine. For a consulting firm or a SaaS company that wants to offer dbt to its own customers, read the agreement with a lawyer.
  • The adapter list is shorter for now. PostgreSQL is “coming soon” on v2. Many community adapters exist only for v1.
  • The v2 alpha had real security bugs, now fixed. We cover them in the security section below.

SQLMesh

SQLMesh also lets you write models as SQL files, but each file starts with a MODEL block that holds the configuration. The same incremental model looked like this in the drill:

MODEL (
  name analytics.fct_daily_sales,
  kind INCREMENTAL_BY_TIME_RANGE (
    time_column order_date,
    lookback 2
  ),
  cron '@daily',
  grain (order_date, channel),
  audits (
    not_null(columns := (revenue_inr)),
    accepted_values(column := channel,
                    is_in := ('website','marketplace','store'))
  )
);
SELECT order_date, channel, SUM(qty) AS units, SUM(amount_inr) AS revenue_inr
FROM analytics.stg_orders
WHERE status = 'paid'
  AND order_date BETWEEN @start_ds AND @end_ds
GROUP BY order_date, channel

There is no ref(). SQLMesh parses the SQL with SQLGlot and finds the dependencies from the table names. The @start_ds and @end_ds macros are filled in by SQLMesh with the interval it wants to process. It records which daily intervals have been filled in its state tables, so it knows what is missing.

The main ideas:

  • Plan and apply. sqlmesh plan compares your files with the current state of an environment, shows a text diff of each changed model, says which models are affected, and lists the backfills it will do. You confirm, and it applies the plan. sqlmesh run is the scheduled command that processes new intervals when a model’s cron says they are due.
  • Change categories. Each change to a model is classified. A breaking change needs the model and everything downstream rebuilt. A non-breaking change, like adding a column, needs only the model itself rebuilt; the children are marked “indirect non-breaking”. A forward-only change reuses the existing table and only applies from now on. In our drill, adding a column was correctly marked non-breaking, and only the staging model was backfilled.
  • Virtual data environments. Every version of a model gets its own physical table, with a version suffix, in a separate schema. Environments such as prod and dev are only views pointing to those tables. A development plan builds new versions for the models you changed and points the dev views at them. Promoting the same change to production then needs no rebuild; the production views are switched to the tables already built. In our drill, the production plan printed “SKIP: No physical layer updates to perform”.
  • Audits that block. Built-in audits are blocking by default. During a plan, if an audit fails, the environment is not switched to the new table. Our drill confirmed this: after the “whatsapp” audit failure, both production and development still showed the old 23 rows with no “whatsapp” row. The SQLMesh docs are honest about the other case: during sqlmesh run, the failing data has already been written to the production table, though downstream models are stopped.
  • Restatement. To reprocess a date range, you run a restatement plan, for example sqlmesh plan --restate-model analytics.stg_orders --start 2026-10-01 --end 2026-10-08. It clears those intervals and backfills the model and its children.
  • It can read dbt projects. sqlmesh init -t dbt sets SQLMesh up over an existing dbt project. The docs note that dbt’s usual incremental logic needs small changes to work in SQLMesh’s incremental styles. We did not test this mode.

Things to know:

  • State needs a real database in production. SQLMesh stores snapshots, intervals and environments. The docs recommend a separate state connection for production, with PostgreSQL as the recommended engine. DuckDB works for one user. ClickHouse, Spark, StarRocks and Trino are listed as unsupported for state, even in development. For a team on a cloud warehouse, this is one more small PostgreSQL database to run and back up.
  • The warehouse fills with versioned tables. After a few plans, our DuckDB file had tables like analytics__stg_orders__1967236945 and analytics__stg_orders__3015048966 in a sqlmesh__analytics schema. The analytics schema held only views. SQLMesh has a janitor process that cleans up old versions, but people who browse the database directly need to know where the real tables are.
  • The learning curve is different, not smaller. Analysts who know dbt need to learn plans, categories, intervals, cron and restatements. The ideas are good, but they are new.

Dataform, Coalesce and SDF, briefly

Google’s Dataform is the built-in option for BigQuery users. Coalesce is a commercial, visual tool. We do not cover either here. SDF, a Rust SQL engine from a Seattle startup, was acquired by dbt Labs on 14 January 2025; its technology became the base of the static analysis in dbt v2, so you will not see it as a separate choice any more.

Side by side

We use headed sections instead of a table, so that each point can carry its caveat.

Licence and governance

  • dbt Core v1: Apache-2.0. Owned and steered by dbt Labs, which is now part of the merged Fivetran and dbt Labs company.
  • dbt v2, dbt-oss: Apache-2.0, same repository, same steward.
  • dbt v2, dbt: proprietary dbt Product Licensing Agreement. Free to use, no seat cap, restrictions on redistribution as a service and on bypassing login for platform features. dbt Labs moved MetricFlow, its semantic layer engine, from a source-available licence to Apache 2.0 in October 2025.
  • SQLMesh: Apache-2.0, a project of the Linux Foundation since 25 March 2026, with a Technical Steering Committee. The founding members named in the announcement were Benzinga, CloudKitchens, Harness, Infinite Lambda, Jump AI and Minerva. Foundation governance protects the licence and the decision process; it does not by itself guarantee the pace of work.

Model and semantics

  • dbt v1: SQL as text plus Jinja. dbt knows the graph from ref and source, not from the SQL.
  • dbt v2 full: same files, but the engine parses SQL in your warehouse’s dialect and knows column names and types.
  • dbt v2 OSS: same files and graph, without that analysis engine.
  • SQLMesh: SQL with a MODEL header, parsed by SQLGlot, with macros; Python models are also supported. Dependencies come from the parsed SQL.

Incremental models and late data

  • dbt v1: the incremental materialization uses whatever filter you write. It does not track processed dates, so late data outside your filter is missed silently. From v1.9 there is a microbatch strategy that splits a model into time batches; read its docs if late data is your main problem.
  • dbt v2: same model files and same behaviour for the filter you write.
  • SQLMesh: INCREMENTAL_BY_TIME_RANGE tracks intervals, and lookback re-processes a few earlier intervals on each run. Anything older needs a restatement plan. Restatements cascade downstream, but you must start from the model whose data is stale. In our drill, restating only the fact table changed nothing, because the full-refresh staging model above it still held the old rows.

Testing and audits

  • dbt v1 and v2: data tests run after the model is built. Unit tests exist from v1.8. v2 adds batch tests and runs unit tests first in dbt build.
  • SQLMesh: audits are tied to the model and block by default; during a plan, a failed audit stops the environment from switching. SQLMesh also has unit tests in YAML and a table_diff command for comparing environments. We did not run those two.

Environments

  • dbt v1 and v2: each developer usually has a target schema, and --defer lets them read unchanged parents from production. A change still has to be rebuilt in production after merge.
  • SQLMesh: environments are views over shared versioned tables. Production promotion can be a pointer switch with no rebuild, as our drill showed. On BigQuery, Databricks and Snowflake the docs say forward-only changes can use zero-copy clones in development.

State, plan and apply

  • dbt v1: no state of its own. You store the manifest.json from production somewhere and pass it with --state.
  • dbt v2: same artifacts, plus local Parquet information schema files. dbt State, the paid platform feature, adds smarter skipping.
  • SQLMesh: state is a set of tables in a database (in our drill, _snapshots, _intervals, _environments and others in a sqlmesh schema). plan shows exactly what will be built before it is built.

Selecting what to rebuild after a change

  • dbt v1 and dbt-oss v2: in our drill, adding one new column to the staging model made state:modified+ select all three models and both tests, including dim_sku, which does not use the new column. That is correct but cautious.
  • SQLMesh: the same change was classified non-breaking. Only the staging model was backfilled; the children were marked indirect non-breaking and not rebuilt.

Column-level lineage

  • dbt v1: not built in. The catalog tools in our Data catalogs post read dbt artifacts and some compute column lineage themselves.
  • dbt v2 full: column-level lineage with strict static analysis, shown in local docs and written into the information schema output. We did not open those views.
  • SQLMesh: column lineage from SQLGlot, shown in the browser UI when you install the web extra. We did not start the UI.

Performance

We did not run a performance test, and our three-model project tells you nothing about speed. dbt Labs says the Rust engine parses and compiles much faster than v1; when it bought SDF, its chief executive wrote about “~2 orders of magnitude” faster compilation. That is the vendor’s own claim. The bigger performance effect in real life is usually warehouse work: how much each run rebuilds. SQLMesh’s interval tracking and virtual promotion, and dbt State on the paid platform, both aim at that. Measure on your own project before you believe any number, including ours.

Ecosystem and adapters

  • dbt v1: the widest list of adapters, including PostgreSQL, and many community packages.
  • dbt v2: BigQuery, Databricks, DuckDB, Redshift and Snowflake are generally available; ClickHouse and Spark are in beta; Athena, Fabric and Postgres are coming soon. Many packages work, but stricter parsing means some old packages need updates.
  • SQLMesh: its docs list Athena, Azure SQL, BigQuery, ClickHouse, Databricks, Db2, DuckDB, Fabric, MotherDuck, MSSQL, MySQL, Postgres, GCP Postgres, Redshift, RisingWave, Snowflake, Spark, StarRocks and Trino as execution engines. Fewer people know it, so fewer answers are a search away.

Operations

  • dbt v1: a Python package. Needs a Python environment per adapter version, plus a place to keep production artifacts. Runs from cron, CI or an orchestrator.
  • dbt v2: a single binary through pip or an installer. Downloads drivers on first run. In our drill it also created a ~/.dbt/leases directory in the home folder. dbt and dbt-oss are separate installs; we used separate virtual environments.
  • SQLMesh: a Python package with optional extras. Needs a state database. sqlmesh run should be called on a schedule by cron or an orchestrator; in our drill it did nothing on the first call because the daily interval had not yet ended, and printed when the next run would be due.

Security

  • dbt v1: GHSA-xwgp-43x8-639j, published 14 October 2026, rated high: a crafted package tarball name could write files outside the target folder during dbt deps. Fixed in 1.12.6 and 1.11.16. GHSA-pxwq-479r-hhwr, rated high: a denial of service in SQL compilation through a pinned sqlparse version, patched in 1.11.4 and 1.12.3. CVE-2024-40637, rated medium: packages could override built-in materializations; fixed in 1.8.0 with patches for 1.6 and 1.7. CVE-2024-36105, rated medium, was about binding to an unrestricted IP address.
  • dbt v2: CVE-2026-100406 (GHSA-h2j2-2r7g-rff8), rated high: in early v2 alphas, dbt docs serve had an unauthenticated endpoint that ran arbitrary SQL in DuckDB, which could read local files. Affected 2.0.0-alpha.0 to alpha.1, fixed by alpha.5. GHSA-h364-2v3g-4xm8, CVSS 8.1: symlink traversal in dbt deps before alpha.5, patched in rc.1. GHSA-wfxc-3pjp-fm6h, medium: a vulnerable tar library used by dbt deps.
  • SQLMesh: the GitHub security advisory list for SQLMesh/sqlmesh was empty when we checked. That means nothing has been published there, not that there are no bugs.

The pattern is clear: dbt deps downloads and unpacks code from the internet, and that path has had bugs in both lines. Pin package versions, review new packages, and run deps in CI rather than on a laptop with production credentials. Bind any docs or UI server to 127.0.0.1.

When a folder of SQL is still fine

Not every team needs a framework. If you have fewer than a dozen queries, one person maintains them, every table is rebuilt in a few minutes, and nobody else changes the SQL, a well-commented script run by your scheduler may be enough. Add tests as plain SQL queries that return zero rows when things are fine. Move to a framework when a second person starts changing models, when one model feeds many others, or when you need incremental tables. Varanasi crossed all three lines long ago.

A worked plan for Varanasi

  1. Decide on the warehouse question first. The reporting database is PostgreSQL. dbt v1 and SQLMesh support it today; dbt v2 lists Postgres as coming soon. If the move to a cloud warehouse is a year away, do not choose a tool only for the future warehouse. Choose one that works now and moves well later.
  2. Start with a pilot of the ten most important models in both dbt v1 and SQLMesh. Two weeks is enough. Pick the sales summary, returns, settlement margin and weaver payments, plus their staging models. Use a copy of the reporting database, not production.
  3. Test the late settlement case on purpose. Insert a settlement row for a date three weeks old. In dbt, see that a lookback misses it, then decide between a longer lookback, the microbatch strategy, or a weekly full refresh of the margin tables. In SQLMesh, write the restatement command the team will run when a late file arrives, and check that it starts from the correct upstream model. Write the steps in a runbook.
  4. Test the bad value case. Insert an order with channel “whatsapp”. In dbt v1, confirm that the test fails after the table is written, and build a simple write, audit, publish step if the finance head must never see such rows: build into a staging schema, run tests, then swap. In SQLMesh, confirm that the plan fails and production views are not switched. Also decide what happens during scheduled sqlmesh run, where the docs say the data is already written.
  5. Test the renamed column case. Rename a column in one staging model. Note how each tool shows the impact before anything runs. If the team later moves to a warehouse that dbt v2 supports, try the full distribution’s strict mode on the same change.
  6. Choose and write down why. If the late settlement and “do not show bad data” problems were the bigger pain, SQLMesh fits Varanasi well. If the team values easy hiring and the consultant’s dbt experience more, and accepts a hand-built audit step, dbt v1 is a fine choice, with a plan to move to v2 when Postgres support is generally available or when the warehouse changes. Either way, record the reasons in the repository.
  7. Put the state and artifacts somewhere safe. For SQLMesh, a small separate PostgreSQL database for state, backed up like any other. For dbt, store the production manifest.json after each run so state:modified works in CI.
  8. Schedule from the orchestrator, not from cron on one laptop. The nightly dlt load, then the transformation run, then the dashboard cache refresh. Our orchestration post covers the options.
  9. Turn on what you can for lineage. Feed dbt artifacts or SQLMesh metadata into the catalog if the company has one. Otherwise, at least keep the model documentation in the repository and up to date.

When each is better

  • dbt Core v1 is better when your warehouse has no v2 adapter yet, when your team already knows dbt, when you rely on community adapters or packages, or when you want the most widely understood option for hiring and handover.
  • dbt v2 (full) is better when your warehouse is on the supported list and you want SQL errors and type mismatches caught before anything runs, with column lineage, and the proprietary licence is acceptable.
  • dbt v2 OSS is better when you need strict Apache-2.0 software and want the new runtime and stricter parsing, and you can live without the static analysis engine.
  • SQLMesh is better when you have large incremental tables with late data, when you want plans that show impact before changes, cheap development environments and blocking audits, and when you prefer a foundation-governed Apache-2.0 project.

When not to adopt each

  • Do not start a large new project on dbt v1 without a plan for what comes after 1.13. Three to five years of critical fixes is expected, not promised with a date.
  • Do not adopt the full dbt v2 if you plan to offer it inside your own product or managed service without reading the licence carefully, or if your warehouse is not on the supported list.
  • Do not expect dbt-oss to give you the type checking you saw in a demo; that is in the full distribution.
  • Do not adopt SQLMesh if nobody on the team has time to learn plans, intervals and restatements, or if you cannot run a separate state database in production. Also be careful if your main users browse the warehouse directly and are confused by versioned physical tables.

A short history

dbt began in 2016; the dbt-labs/dbt repository on GitHub was created on 10 March 2016. Through the late 2010s and early 2020s it became the usual way to manage SQL transformations in cloud warehouses, and the company behind it built the paid dbt Cloud, now called the dbt platform. Versions 1.8 (May 2024) added unit tests and 1.9 (December 2024) added microbatch incrementals; 1.10 (June 2025), 1.11 (December 2025) and 1.12 (July 2026) followed.

SQLMesh came from Tobiko Data. Its first PyPI release, 0.1.0, was on 20 February 2023. It was built on SQLGlot, which already had wide use as a SQL parser and transpiler.

In January 2025 dbt Labs bought SDF Labs, whose Rust SQL engine became the core of dbt Fusion, announced in May 2025 under the Elastic License 2.0. In September 2025 Fivetran bought Tobiko Data. In October 2025 Fivetran and dbt Labs announced a merger. In March 2026 Fivetran contributed SQLMesh to the Linux Foundation. In June 2026 the merger completed, and dbt Labs relicensed the Fusion source as Apache 2.0 dbt Core v2.0 alpha. In September 2026 dbt v2.0.0 was released with the new names dbt and dbt OSS.

Decision guide

Start with your warehouse

  • PostgreSQL, MySQL, MSSQL, Trino and many others: dbt v1 or SQLMesh today.
  • BigQuery, Databricks, DuckDB, Redshift or Snowflake: all options are open, including dbt v2.

Then look at your pain

  • Typos and type errors in shared models: full dbt v2 strict mode, or SQLMesh.
  • Late data in big fact tables: SQLMesh, or dbt with a carefully designed incremental strategy and a scheduled full refresh.
  • Bad data reaching dashboards: SQLMesh plans with blocking audits, or dbt with a hand-built write, audit, publish step.
  • Many people changing models: SQLMesh environments, or dbt with --defer and good CI.

Then check licence and team

  • Must be Apache-2.0 end to end: dbt v1, dbt-oss or SQLMesh.
  • Want neutral governance: SQLMesh under the Linux Foundation; dbt is steered by one company.
  • Want the easiest hiring: dbt.

Checklist

  1. List your warehouse and check the current adapter status for each tool.
  2. Count your models and your incremental tables.
  3. Write down your three worst incidents from the last quarter.
  4. Decide your licence rules before the pilot, not after.
  5. Pilot with ten real models on a copy of real data.
  6. Test a late row older than your lookback.
  7. Test a bad value and see where it ends up.
  8. Test a renamed column and see when it is caught.
  9. Decide where state or artifacts live, and back them up.
  10. Pin tool and package versions; read release notes before upgrades.
  11. Run dbt deps or package installs in CI with no production credentials.
  12. Bind docs and UI servers to loopback.
  13. Set telemetry settings to match your company policy.
  14. Write a runbook for late data and for failed checks.
  15. Plan your route off dbt v1 if you choose it today.

Common mistakes

  • Trusting a lookback to catch all late data. It only sees the window. Our drill missed a late row for 2 October with a two-day lookback.
  • Restating the wrong model. In SQLMesh, restating a fact table does not refresh a full-refresh staging model above it. Restate from where the stale data is.
  • Assuming a failed test means the table was not changed. In dbt, tests run after the model is written. In SQLMesh, the same is true during run.
  • Assuming dbt-oss has the full engine. It does not include static analysis.
  • Running v2 without warning in CI. It downloads drivers on first run and writes files in the home directory.
  • Ignoring the deprecation warnings in v1. v2 turns many of them into errors.
  • Browsing physical tables in SQLMesh. Query the environment views, not the versioned tables.
  • Keeping SQLMesh state in DuckDB in production. The docs say it is single user.
  • Treating 0.x as unstable or 2.0 as stable without reading. Read the release notes either way.
  • Comparing speed on a toy project. Our 0.30 s and 0.94 s timings mean nothing.

The drill we ran

We ran everything on 30 October 2026, between about 6:21 PM and 6:27 PM IST, inside Python virtual environments in one working folder. Nothing was installed with apt or dpkg, no system services were created, nothing in /etc or /usr was changed, and no server or network listener was started. The pip installs and the dbt v2 driver download used the internet. We set DO_NOT_TRACK=1, DBT_SEND_ANONYMOUS_USAGE_STATS=false and SQLMESH__DISABLE_ANONYMIZED_ANALYTICS=1, but we did not check network traffic to confirm what each tool sent. This was one run of a toy project, not a benchmark.

Versions: dbt-core 1.12.6 with dbt-duckdb 1.11.1 and duckdb 1.5.6; dbt-oss 2.0.8; dbt 2.0.9; sqlmesh 0.236.4 with sqlglot 30.8.0. When we installed dbt-core 1.12.6, pip also pulled a package called dbt-core-experimental-parser 2.0.8, and dbt --version printed “dbt v2 is available, and is the default for new installations. This is dbt Core v1.”

Data: an invented raw.orders table with 140 rows from 1 to 7 October 2026, three channels, statuses paid and returned. Three models: stg_orders (full), dim_sku (full) and fct_daily_sales (incremental by date and channel, two-day lookback), with not_null and accepted_values checks.

What we saw with dbt Core 1.12.6:

  • The first dbt build passed all five nodes.
  • We added four rows: two for 8 October, a late row for 6 October and a late row for 2 October. After another dbt build, 8 October appeared and 6 October went from 36 to 37 units, inside the lookback. 2 October stayed at 30 units and 56,300 rupees, while the raw data said 31 units and 58,700. No warning. A --full-refresh of the fact table fixed it.
  • After adding an is_return column to stg_orders, state:modified+ selected all three models and both tests.
  • After inserting a “whatsapp” row, all models built and then accepted_values failed with one row. The bad row was already in the final table.

What we saw with dbt-oss 2.0.8:

  • It ran the same project unchanged, five of five, reporting about 0.94 seconds.
  • state:modified+ after another new column selected the same five nodes.
  • A typo column skuu in dim_sku compiled with no error. Strict mode printed that this distribution does not include the static analysis engine.

What we saw with the full dbt 2.0.9, without logging in:

  • The default compile with the typo succeeded.
  • --static-analysis strict failed with dbt0227 “No column skuu found” and also dbt0407 “Comparison between date and timestamp with >= operator is not supported” on our incremental filter.
  • It created ~/.dbt/leases, and a DuckDB ADBC driver, version 1.5.4, of about 68 MB was cached under ~/.cache/com.getdbt. So the v2 runs used DuckDB 1.5.4 while v1 and SQLMesh used 1.5.6.

What we saw with SQLMesh 0.236.4:

  • A production plan with --end was refused: “The start and end dates can’t be set for a production plan without restatements.” Without dates, it backfilled the fact table from 1 to 29 October and passed both audits.
  • After the same four extra rows, sqlmesh run said no models were ready because the cron interval had not elapsed.
  • Restating only fct_daily_sales for 1 to 8 October re-ran it but changed no numbers, because the full stg_orders above it still held the old rows. Restating stg_orders backfilled all three models, and the values became 31 and 58,700 for 2 October, 37 and 68,050 for 6 October, and 3 and 4,800 for 8 October.
  • Adding is_return in a dev plan was marked “Non-breaking”, and only stg_orders was rebuilt. Promoting to production printed “SKIP: No physical layer updates to perform”.
  • The analytics and analytics__dev schemas held only views; real tables were in sqlmesh__analytics with version suffixes, and state was in a sqlmesh schema.
  • A breaking change plus a “whatsapp” row made the audit fail and the plan stop with “Plan application failed”. Both production and dev still showed 23 rows and no “whatsapp” row.

At the end, pgrep showed no dbt or SQLMesh process running.

Not run: the dbt platform, dbt State, dbt login, Lake Compute, the SQLMesh web UI and its column lineage view, unit tests in either tool, sqlmesh table_diff, SQLMesh’s dbt project mode, any cloud warehouse or PostgreSQL target, and any performance comparison.

Unlearn, re-learn

  • Unlearn “dbt Core is the open source one”. Re-learn that the names are now dbt v1, dbt (full, proprietary, free to use) and dbt OSS (Apache-2.0).
  • Unlearn “a lookback handles late data”. Re-learn that it handles late data inside the window only, and design for the rest.
  • Unlearn “tests stop bad data”. Re-learn where each tool runs its checks: before the switch or after the write.
  • Unlearn “a new column means rebuilding everything downstream”. Re-learn that change categories can tell you what really needs a rebuild.
  • Unlearn “SQL is just text to the tool”. Re-learn that 2026 tools can parse it, and check which distribution gives you that.
  • Unlearn “a company’s logo tells you the licence”. Re-learn to read the licence file and the governance file.

Sources

dbt:

SQLMesh:

Company news:

Related posts on Revisit.Tech: data ingestion, orchestration, data catalogs, in-process analytics, lakehouse table formats, real-time OLAP.

comments powered by Disqus

Releted Posts

Data ingestion in 2026: revisit Airbyte, Meltano and dlt before you write another extract script

Almost every data team has a folder of extract scripts. One pulls orders from the shop platform every night, one reads the payment gateway’s settlement API, one copies a few tables from the ERP database, and one downloads a CSV that somebody uploads to a shared drive.

Read more

Stream processing in 2026: revisit Apache Flink, RisingWave and Arroyo before you write another consumer that keeps state

Most teams that use Kafka have at least one consumer that does more than it should. It started as a simple loop that read events and wrote them somewhere.

Read more

Graph databases in 2026: revisit Neo4j Community, Memgraph and Apache AGE before you model another relationship in SQL joins

Most teams do not decide to adopt a graph database. They arrive at it through a query. Somebody asks “which customers are connected to that blocked customer through a shared phone number, bank account or address?

Read more