Models
Configuration
MODEL() header fields and SQL-analysis controls.
Every MODEL() key must be one of the fields on this page. Any other key fails compilation with
D002, naming the key and its file and line and suggesting the nearest supported key. SQL comments
(-- ... and /* ... */) inside a header are ignored:
error[D002]: MODEL() in 'models/fct_orders.sql:3' has unsupported keys: descripton = help: did you mean 'description'?[defaults] and [path_defaults.*] in sqlbuild_project.toml reject unknown keys in the same way
with D001. Values that a custom materialization reads belong inside its config (...) block,
which accepts any keys.
Common fields
Section titled “Common fields”| Field | Description |
|---|---|
materialized |
view, table, incremental, snapshot, or a custom materialization name |
tags |
Tags used by selectors |
critical |
true marks the model as critical, so sqb review builds and compares it whenever something upstream of it changes (default false) |
description |
Required human-readable model description; see Descriptions |
columns |
Model-local column declarations, inherited-column audit augmentation, or column renames with migrate_from (incremental and snapshot models; see Column migrations). On a table or view model that declares its own migrate_from, a column migrate_from only names the old column in the compatibility view |
model_schema |
Reusable column schema name |
audits |
Model-level audit instances |
enums |
Model-local enum declarations; names must begin with _ |
constants |
Model-local scalar, list, set, or object constants; names must begin with _. Use constant(...) for an explicit decimal type or collection rendering override. |
schema |
Destination warehouse schema override |
database |
Destination database override |
alias |
Destination relation-name override |
pre_hooks |
Ordered inline_sql(...), sql("name", ...), or python("name", ...) hooks before materialization |
post_hooks |
Ordered inline_sql(...), sql("name", ...), or python("name", ...) hooks after materialization |
enabled |
Set to false to disable the model |
contract |
none for an open statically checked declaration, or enforced for an exact declaration |
sql_analysis |
Per-model SQL-analysis override |
dynamic_columns |
Named pivot column families with pivot_column, value_column, aggregate, type, and name_pattern |
audit_factories |
Audit factories that generate audit cases for this model; see Audits |
max_duration |
Time limit for one build attempt of this model, or "disabled"; see Execution time limit |
Descriptions
Section titled “Descriptions”Every named resource must have a non-empty description. A missing description is a P010 compile
error, so sqb plan and sqb build stop too. There is no setting to turn the check off. One
compile reports every missing description, each at the resource’s declaration with the exact text
to add:
error[P010]: model 'orders_summary' has no description --> models/marts/orders_summary.sql:1:1 = help: to describe model 'orders_summary', add this to the MODEL header in models/marts/orders_summary.sql: MODEL ( description "What one row of orders_summary represents", ... );| Resource | Where the description goes |
|---|---|
| Model | MODEL (description "..."), or the description of its model_schema |
| Scenario | SCENARIO (description "...") |
| Seed | description: on its entry in seeds/*.yml |
| Source | description: on its entry in sources/*.yml (or on the dlt resource) |
| SQL function or table function | FUNCTION (description "...") |
| Python UDF | @udf(description="...") or the function docstring |
| Named SQL hook | HOOK (description "...") |
| Python hook, task, asset, check | description="..." on the decorator or the function docstring |
| Loader | @loader(description="...") or the function docstring; a loader that feeds a declared source may instead rely on the source’s YAML description |
| Provider | The provider class docstring |
SQL tests, audits, macros, enums, constants, materializations and event or output sinks do not need a
description. Descriptions do not change fingerprints, so adding or editing one does not rebuild a
model or redeploy a function (a Python UDF docstring is part of the deployed body, so editing it
does). sqb format --check also fails when a MODEL, SCENARIO, FUNCTION or HOOK
header has no description (a MODEL header that uses model_schema is left to compile), and
sqb format still moves a leading comment into an empty model description.
Header strings
Section titled “Header strings”A quoted value in any SQLBuild header (MODEL, SCENARIO, SCHEMA, FUNCTION, HOOK, MACRO,
TEST, AUDIT, ENUM, CONSTANT) is written as "..." or '...'. Text that contains a double
quote goes between triple quotes, on one line or across several. Inside """...""", " and '
need no escaping:
MODEL ( description """Matches "Miss" and "Mrs." titles, with or without a dot.""", ...);A " inside a "..." string ends it, so it is an error with its line and column rather than the
start of an escape. A description has no \" escape and no "" doubling: in a "..."
description a backslash is an ordinary character, so "Files under C:\" ends with a backslash and
"Path C:\\temp" stores both backslashes. In a '...' description, \' and '' both stand for
one ', as in 'it\'s' and 'it''s'. sqb format --fix rewrites a description that escapes its
quotes as \" or doubles them as "" into a """...""" string holding real quotes:
error[D002]: MODEL(...) in 'models/customers.sql:2:34' contains invalid SQLBuild header syntax: a double quote ends this "..." string early; use """…""" for text containing double quotes = help: write the text between triple quotes, where quotes need no escaping: description """Matches "Miss"."""; sqb format --fix rewrites a description that escapes its quotes as \" or doubles them as ""A """...""" string ends at the first three double quotes in a row. Up to two quotes just before
them belong to the text, so the text may end with a quote, but it cannot contain three in a row.
A """ inside the text and a """ that is never closed fail with the position of the offending
quote or of the opening """.
Folded descriptions
Section titled “Folded descriptions”Descriptions are prose, so SQLBuild stores them folded: the same text gives the same description whether it is written on one line or wrapped, and at any indentation.
- Text on one line is stored exactly as written.
- Otherwise the spaces at either end are dropped, a line break and the spaces around it become one
space, and a blank line starts a new paragraph (stored as
\n\n).
MODEL ( description """ Customers with a "woman's title" flag, one row each.
Built from the raw seed. """,);stores Customers with a "woman's title" flag, one row each., a paragraph break, and Built from the raw seed.
These strings are folded prose:
| String | Where |
|---|---|
description |
The top level of every header. MODEL, SCENARIO, SCHEMA, FUNCTION, HOOK and MACRO headers take one; AUDIT, ENUM, CONSTANT and TEST headers reject the key |
Column description |
Entries of columns (...) in MODEL and SCHEMA headers |
Audit description |
The description option of an audit in audits [...] |
Exception reason |
reason of [[rules.rule_exceptions]], [[rules.graph_edge_exceptions]], [[rules.rule_ignores]], [[rules.select_star_allow]] and [[rules.threshold_overrides]] in sqlbuild_project.toml |
Every other string means exactly what it says and keeps its text as written, line breaks
included: SQL such as audit predicate and where_condition arguments or inline_sql(...) hooks,
regular expressions such as name_pattern, literal values in meta, constants, placeholders
and audit arguments, and names and paths. In these strings a backslash still escapes the character
after it inside "..." and '...'; a """...""" string takes its text exactly as written,
backslashes included. sqb format lays descriptions out in one canonical form; see
Line width and descriptions.
SQL analysis
Section titled “SQL analysis”SQL analysis has project, invocation, and model gates:
settings.sql_analysismust be enabled.--no-sql-analysismust not be present.MODEL (sql_analysis false)can disable analysis for that model. If omitted, the project setting is used.
A model cannot re-enable analysis when either broader gate disables it. The older sql_validation
and --no-sql-validation spellings remain compatibility aliases; use sql_analysis for new
configuration.
Requiring SQL analysis
Section titled “Requiring SQL analysis”A project can stop opt-outs from hiding real findings:
[settings]require_sql_analysis = true # default falseWith this setting, sql_analysis false from a MODEL header or a [path_defaults] entry is accepted
only on a model whose SQL the parser cannot read: its query or one of its SQL hooks. On a model that
parses, compile reports a P009
error at the header key or [path_defaults] entry, with the findings the opt-out was hiding counted
by kind:
error[P009]: `sql_analysis false` is not needed for model 'orders_summary' --> models/marts/orders_summary.sql:4:3 = note: sqlbuild_project.toml sets [settings] require_sql_analysis = true, which only allows `sql_analysis false` on SQL that SQLBuild cannot parse; this model parses successfully. = help: remove `sql_analysis false` and fix the findings it was hiding: 2 unknown columns, 1 type mismatch (run `sqb compile` to see them) = help: to allow `sql_analysis false` on any model, SQL test or audit, set this in sqlbuild_project.toml: [settings] require_sql_analysis = falseTEST(...) and AUDIT(...) headers (singular and generic audits) also accept
sql_analysis false, which skips compile-time SQL checks on that test or audit; the SQL still runs.
SQL tests of a model with sql_analysis false, and audits attached to it, are not analysed either.
require_sql_analysis = true applies the same rule to these headers: the opt-out is accepted only
when that test or audit SQL cannot be parsed, otherwise compile reports P009 at the header key.
[settings] sql_analysis = false in sqlbuild_local.toml is then a D001 error, and
require_sql_analysis itself can only be set in sqlbuild_project.toml. --no-sql-analysis still
turns analysis off for one run, including the checks on audits, SQL tests and SQL hooks.
When the parser cannot read a model, the P001 syntax error shows the exact MODEL header entry
(sql_analysis false,) and asks you to report the SQL so the parser can support it.
Table fields
Section titled “Table fields”| Field | Description |
|---|---|
table_type |
Snowflake table type: permanent, transient, or inherit |
time_travel_retention |
Managed time travel retention such as 7d, inherit, or disabled |
engine, order_by, partition_by, primary_key, table_settings |
ClickHouse physical layout of table and incremental models; see ClickHouse |
run_despite_unchanged |
Removed with virtual environments. It is rejected in MODEL headers, project defaults, and path defaults. |
Table promotion mode is a project setting rather than a MODEL() field. Staged promotion is the default. Immediate promotion is incompatible with model type enforcement and exact contracts; see Materializations.
Incremental fields
Section titled “Incremental fields”| Field | Description |
|---|---|
incremental_strategy |
append, delete_insert, or merge |
cursor |
Output column used to track incremental position |
cursor_type |
timestamp or integer |
cursor_grain |
Timestamp grain such as second, hour, or day |
cursor_start |
Lower cursor bound |
cursor_end |
Upper cursor bound |
cursor_start_max_ahead |
Largest distance a discovered automatic start may sit ahead of the invocation time, or disabled |
cursor_start_max_action |
cap or error when the start exceeds cursor_start_max_ahead |
cursor_future_max_distance |
Largest distance a discovered cursor watermark may sit in the future, or disabled |
cursor_future_action |
cap or error when a watermark exceeds cursor_future_max_distance |
cursor_inputs |
Upstream names mapped to cursor columns |
unique_key |
Merge or delete/insert matching columns |
incremental_mode |
Set to microbatch for batched execution |
microbatch_strategy |
Required in microbatch mode: watermark or rolling_window |
cursor_watermark_mode |
Watermark strategy policy: all or any |
batch_size |
Timestamp duration string such as 1d or 1h; use a numeric string such as "1000" for an integer cursor |
batch_concurrency |
Concurrent batch workers; values above 1 require delete_insert and the project concurrency gate |
microbatch_limit |
Nested max_batches and action policy for watermark execution |
max_microbatches |
Legacy scalar batch-count guard; use microbatch_limit for new models |
unaccounted_partition_policy |
Handling for microbatch partitions with no recorded completion: synthesize, recover_empty, or recover_all |
lookback |
Backward replay extension |
append_cursor_inclusive |
Include (true, default) or exclude (false) the current append-cursor boundary |
merge_exclude_columns |
Columns left unchanged by matched-row merge updates |
full_refresh |
Optional model execution override: false always runs incrementally, true always full-refreshes, and omission follows the command |
on_schema_change |
append_new_columns, sync_all_columns, ignore, or fail |
replay_on_change |
forward, full, or bounded-<duration> |
migrate_from |
Old model name (or schema.name) whose data this model takes over; see Model migrations. Also valid on snapshot, table, and view models |
migrate_force |
true replaces a destination that already has its own build history during a migration. Incremental and snapshot models only |
old_name_view |
How long a compatibility view keeps the old name working after a migration, such as 7d, or false for none. Overrides [migrations] old_name_views; valid on every model |
See Incremental for full semantics.
Unknown on_schema_change or replay_on_change values fail compilation, in MODEL() headers, [defaults] and [path_defaults]. The error names the value and lists the valid ones. bounded-<duration> needs a positive duration such as bounded-14d.
Snapshot fields
Section titled “Snapshot fields”| Field | Description |
|---|---|
unique_key |
Columns identifying one logical record |
snapshot_strategy |
timestamp or check |
updated_at |
Source update timestamp for the timestamp strategy |
check_columns |
Columns compared by the check strategy, or [*] |
observed_at |
Observation timestamp for historical inputs |
historical_input |
snapshot or changes |
initial_valid_from |
Initial validity policy for first-seen rows |
invalidate_hard_deletes |
Close records that disappear from current-state input |
valid_from_column |
Override the generated valid-from column name |
valid_to_column |
Override the generated valid-to column name |
snapshot_full_refresh |
Snapshot full-refresh safety policy |
snapshot_schema_change |
Snapshot schema-change policy |
See Snapshots for strategy combinations, defaults, and safety behavior.
Custom materialization fields
Section titled “Custom materialization fields”| Field | Description |
|---|---|
config |
Arbitrary values passed to ctx.config |
placeholders |
Defaults for runtime @@@placeholder tokens |
Diff fields
Section titled “Diff fields”| Field | Description |
|---|---|
row_diff_exclude_columns |
Columns excluded from row-level comparison |
row_diff_tolerances |
Numeric comparison tolerances |
row_diff_sample_rows |
Deterministic unique-key sample size; 0 disables inherited sampling |
row_diff_sample_seed |
Integer seed used for deterministic key hashing |
Execution time limit
Section titled “Execution time limit”max_duration bounds how long one build attempt of a model may run. It has no default: a model
without it, or with max_duration "disabled", runs until it finishes. Set it in the usual layers,
with the MODEL() header taking precedence over the nearest path default, and the path default over
[defaults]:
[defaults]max_duration = "30m"
[path_defaults."marts/order_history"]max_duration = "2h"MODEL ( description 'Daily customer order counts.', materialized table, max_duration "10m");The limit is a positive fixed duration such as "90s", "15m" or "1h30m". One deadline covers the
whole attempt, starting when the model begins executing: its hooks, statements, attached audits and
every microbatch. Time spent waiting for upstream models or for a free worker does not count.
At the deadline SQLBuild cancels the model’s running statements through the adapter (see the adapter table), runs no further statements for the attempt, and reports the model as timed out with the elapsed time, the limit, where it was set, the cancellation outcome and the query IDs it saw. Statements that already completed are not undone, but the attempt stops before any later promotion or swap, so a staged table is not published. Models that depend on it are skipped as for any failure, and independent models continue. A model that finishes successfully just after its limit keeps its successful result.
The per-model limit is separate from the target’s whole-build
max_duration. When both apply, each
statement is bounded by whichever deadline comes first.