Published Build3 min read
Jinja Is Four Jobs In One Costume, And Only One Of Them Needs A Template
Interlace drops templating from its dbt-style models. Its argument: parse the SQL, and the dependency graph you were asked to declare twice is already sitting in the FROM clause.
Written for builders.See today for builders

What happened
- Interlace has no templating language; a model is a .sql file containing SQL or a .py file containing a function, with no {{ }} and no {% %}.
- The article's argument is that Jinja is doing four unrelated jobs, which is why one mechanism doing all four feels both indispensable and awkward; separated, each has a better answer than a templating language.
- The article calls ref() the most common use of Jinja and the least necessary.
- ref() exists because dbt does not parse your SQL; it needs you to declare the edge separately in the text, and then it substitutes the physical name.
- Interlace parses the SQL with sqlglot and reads dependency edges out of the AST.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
Interlace, a transformation tool with a dbt-shaped project layout, ships without a templating language: a model is a .sql file of SQL or a .py file containing a function, with no `{{ }}` and no `{% %}` anywhere [1]. That is only interesting because of the argument attached to it, which is that Jinja in a dbt project is doing four unrelated jobs at once, and that is why the mechanism feels simultaneously indispensable and awkward [2].
Job one is `ref()`, which the project's own write-up calls the most common use and the least necessary [3]. The reasoning is mechanical rather than aesthetic: `ref()` exists because dbt does not parse your SQL, so it needs the edge declared separately in the text and then substitutes the physical name [4]. But the edge is already in the query. It is the FROM clause. Interlace parses the SQL with sqlglot and reads the edges out of the AST [5]. The worked example is a three-table query where one reference sits inside a correlated subquery in the WHERE clause, and `table_references()` returns customers, orders and watermark with nothing declared [6][7]. The side effect matters more than the keystrokes saved: a model file that is bare SQL is valid SQL you can paste into any client, which a Jinja template is not [8].
Job two, loops, is the one described as Jinja's legitimate job, the thing people reach for with fifty tenants or a pivot over payment methods [9]. Here the answer is not a feature but a consequence. Because .py model files are imported and executed when the project loads, registering a model is a function call, so a loop over a list is a loop over a list [10][15]. The published example loops over three tenants and produces three real models, each with its own snapshot table, environment view, fingerprint, plan entry and checks [11]. The plan output shows orders_acme, orders_globex and orders_initech all depending on raw, at 0.12s, 0.08s and 0.05s [12]. The consequential detail is what the tenant list can be: in Jinja it has to be something the templating context can reach, whereas a Python function can query a database, read a file or call an API, and can be unit-tested [13][14].
Job three, macros, is where the project revised itself. An August 2026 update says Interlace has since gained macros, as SQL expressions expanded into the AST rather than as templates [16]. A macro is declared as `CREATE MACRO cents_to_dollars(amount) AS (amount / 100)::numeric(16, 2);` and expanded into the calling model's AST while it compiles, before the fingerprint, before lineage, before transpilation [17][18]. Two things follow from that ordering. One definition covers every engine, because sqlglot renders Postgres's integer-division cast and BigQuery's NUMERIC from the same line, where dbt needs `default__`, `postgres__` and `bigquery__` variants plus a dispatcher [19]. And because expansion precedes fingerprinting, editing a macro re-plans every model that calls it, which a macro registered in the warehouse could not do because the callers' SQL would not have changed [20].
When a macro is really a program, the escape hatch is Python: a function returning a SQL fragment interpolated into a ModelDef, imported from a helper file beside the model, where names starting with `_` are excluded from being models [21]. Job four, configuration, moves to a leading block comment namespaced under `interlace` carrying strategy, key and checks [22].
What to watch is whether the f-string interpolation in the loop example holds up. `f"... WHERE tenant_id = '{tenant}'"` is string concatenation into SQL [11], which is the thing AST-first tooling is supposed to have retired. Also worth watching: how the AST edge reader handles CTEs, table functions and dynamic identifiers, none of which the published example covers [6].
Claim ledger
Ranked by verification strength, evidence, and original report placement.
- [1]
Interlace has no templating language; a model is a .sql file containing SQL or a .py file containing a function, with no {{ }} and no {% %}.
ReportedView cited source - [2]
The article's argument is that Jinja is doing four unrelated jobs, which is why one mechanism doing all four feels both indispensable and awkward; separated, each has a better answer than a templating language.
ReportedView cited source - [3]
The article calls ref() the most common use of Jinja and the least necessary.
ReportedView cited source - [4]
ref() exists because dbt does not parse your SQL; it needs you to declare the edge separately in the text, and then it substitutes the physical name.
ReportedView cited source - [5]
Interlace parses the SQL with sqlglot and reads dependency edges out of the AST.
ReportedView cited source - [6]
The example query SELECT o.id, c.tier FROM orders o JOIN customers c USING (id) WHERE o.d > (SELECT max(d) FROM watermark) yields sorted(table_references(parse(sql))) == ['customers', 'orders', 'watermark'].
ReportedView cited source
Sources & coverage · 1 publisher
The reporting this story was synthesized from, earliest first. Every link goes to the original.
- dev.toMark AlfordAug 15No Jinja: What Replaces Templating When SQL Is an AST
