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 its dependencies, materialization strategy, and target table.
Model formats
Section titled “Model formats”Rocky supports two formats for defining models. The sidecar format is recommended.
Sidecar format (recommended)
Section titled “Sidecar format (recommended)”Each model is two files with the same base name:
models/├── fct_orders.sql # Pure SQL└── fct_orders.toml # ConfigurationThe SQL file is plain SQL with full editor support (syntax highlighting, linting, autocompletion). No special syntax or template tags.
Inline format (legacy)
Section titled “Inline format (legacy)”A single SQL file with TOML frontmatter:
---tomlname = "fct_orders"depends_on = ["stg_orders", "dim_customers"]strategy = "full_refresh"
[target]catalog = "acme_warehouse"schema = "analytics"table = "fct_orders"---
SELECT o.order_id, o.customer_id, c.customer_name, o.total_amount, o.order_dateFROM acme_warehouse.staging__us_west__shopify.orders oJOIN acme_warehouse.analytics.dim_customers c ON o.customer_id = c.customer_idThis format works but is not recommended because embedded TOML breaks SQL editor tooling.
Model configuration
Section titled “Model configuration”The TOML configuration (whether sidecar or inline) supports these fields:
| Field | Required | Description |
|---|---|---|
name | Yes | Model identifier, used in depends_on references |
depends_on | No | List of upstream model names (determines execution order) |
strategy | No | Materialization strategy: full_refresh (default), incremental, or merge |
target | Yes | Output table: { catalog, schema, table } |
sources | No | Input tables (for documentation and validation) |
Strategy-specific fields
Section titled “Strategy-specific fields”For incremental:
strategy = "incremental"timestamp_column = "updated_at"For merge:
strategy = "merge"unique_key = ["customer_id"]update_columns = ["customer_name", "email", "updated_at"] # optional, defaults to allExample: sidecar model
Section titled “Example: sidecar model”models/fct_orders.toml
name = "fct_orders"depends_on = ["stg_orders", "dim_customers"]strategy = "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_dateFROM acme_warehouse.staging__us_west__shopify.orders oJOIN acme_warehouse.analytics.dim_customers c ON o.customer_id = c.customer_idWHERE o.order_date >= '2024-01-01'Example: merge model
Section titled “Example: merge model”models/dim_customers.toml
name = "dim_customers"depends_on = ["stg_customers"]strategy = "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_atFROM acme_warehouse.staging__us_west__shopify.customersThis generates a MERGE statement:
MERGE INTO acme_warehouse.analytics.dim_customers AS targetUSING ( SELECT customer_id, customer_name, email, signup_date, current_timestamp() AS updated_at FROM acme_warehouse.staging__us_west__shopify.customers) AS sourceON target.customer_id = source.customer_idWHEN MATCHED THEN UPDATE SET *WHEN NOT MATCHED THEN INSERT *Materialization strategies
Section titled “Materialization strategies”full_refresh (default)
Section titled “full_refresh (default)”Rebuilds the entire table on every run:
CREATE OR REPLACE TABLE target ASSELECT ...Use for small tables, complex transformations, or when you need guaranteed consistency.
incremental
Section titled “incremental”Appends new rows based on a timestamp column:
INSERT INTO targetSELECT ...WHERE updated_at > :watermarkThe watermark is tracked in Rocky’s embedded state store and updated after each successful run. Use for large append-only or append-mostly tables.
Upserts based on a unique key:
MERGE INTO target USING (...) AS sourceON target.key = source.keyWHEN MATCHED THEN UPDATE SET *WHEN NOT MATCHED THEN INSERT *Use for slowly changing dimensions or any table where rows are updated in place.
Loading models
Section titled “Loading models”Rocky loads models from a directory using load_models_from_dir(). It auto-detects the format:
- For each
.sqlfile, check if a matching.tomlfile exists (sidecar format) - If no sidecar, check for
---toml ... ---frontmatter (inline format) - If neither, skip the file
Validation
Section titled “Validation”Run rocky validate to load all models and validate the DAG before execution:
rocky validate --models ./modelsThis checks:
- All model files parse correctly
- All
depends_onreferences point to existing models - No circular dependencies exist
- Target table identifiers pass SQL validation