Skip to content

Silver Layer (Models)

The silver layer is where you write custom SQL transformations — the equivalent of dbt models. Each model is a SQL query paired with TOML configuration that declares dependencies, materialization strategy, and target table.

Each model is two files with the same base name:

models/
├── fct_orders.sql # Pure SQL — opens cleanly in any SQL editor
└── fct_orders.toml # Configuration

A single SQL file with TOML frontmatter. Supported for backward compatibility; the sidecar format is preferred because embedded TOML breaks SQL editor tooling.

---toml
name = "fct_orders"
depends_on = ["stg_orders"]
[strategy]
type = "full_refresh"
[target]
catalog = "acme_warehouse"
schema = "analytics"
table = "fct_orders"
---
SELECT ...

Model TOML fields (full reference: Model Format):

FieldRequiredDescription
nameYesModel identifier, used in depends_on references
depends_onNoList of upstream model names (execution order)
[strategy]NoMaterialization config (see below) — defaults to full_refresh
[target]YesOutput table: { catalog, schema, table }
[[sources]]NoInput tables (for documentation and lineage)
# Incremental
[strategy]
type = "incremental"
timestamp_column = "updated_at"
# Merge
[strategy]
type = "merge"
unique_key = ["customer_id"]
update_columns = ["name", "email", "updated_at"] # optional, defaults to all non-key columns

models/fct_orders.toml

name = "fct_orders"
depends_on = ["stg_orders", "dim_customers"]
[strategy]
type = "full_refresh"
[target]
catalog = "acme_warehouse"
schema = "analytics"
table = "fct_orders"

models/fct_orders.sql

SELECT
o.order_id,
o.customer_id,
c.customer_name,
o.total_amount,
o.order_date
FROM acme_warehouse.staging__us_west__shopify.orders o
JOIN acme_warehouse.analytics.dim_customers c
ON o.customer_id = c.customer_id
WHERE o.order_date >= '2024-01-01'

models/dim_customers.toml

name = "dim_customers"
depends_on = ["stg_customers"]
[strategy]
type = "merge"
unique_key = ["customer_id"]
[target]
catalog = "acme_warehouse"
schema = "analytics"
table = "dim_customers"

models/dim_customers.sql

SELECT
customer_id,
customer_name,
email,
signup_date,
current_timestamp() AS updated_at
FROM acme_warehouse.staging__us_west__shopify.customers

This generates a MERGE statement:

MERGE INTO acme_warehouse.analytics.dim_customers AS target
USING (
SELECT
customer_id,
customer_name,
email,
signup_date,
current_timestamp() AS updated_at
FROM acme_warehouse.staging__us_west__shopify.customers
) AS source
ON target.customer_id = source.customer_id
WHEN MATCHED THEN UPDATE SET *
WHEN NOT MATCHED THEN INSERT *
StrategyWhen to useAdapters
full_refreshSmall tables, complex transforms, guaranteed consistencyAll
incrementalLarge append-mostly tables, timestamped eventsAll
mergeSCDs, upserts by keyAll
time_intervalPartition-keyed reprocessing with @start_date / @end_dateAll
materialized_viewWarehouse-managed view refreshDatabricks
dynamic_tableTarget-lag managed tablesSnowflake

Rebuilds the entire table on every run:

CREATE OR REPLACE TABLE target AS SELECT ...

Appends new rows past the stored watermark:

INSERT INTO target SELECT ... WHERE updated_at > :watermark

Watermarks live in Rocky’s embedded state store (state management) and advance after each successful run.

Upserts by unique key:

MERGE INTO target USING (...) AS source
ON target.key = source.key
WHEN MATCHED THEN UPDATE SET *
WHEN NOT MATCHED THEN INSERT *

Run rocky validate to load all models and validate the DAG before execution:

Terminal window
rocky validate --models ./models

This checks:

  • All model files parse correctly
  • All depends_on references point to existing models
  • No circular dependencies exist
  • Target table identifiers pass SQL validation