Skip to content

Modeling Commands

Commands for working with Rocky’s SQL models: compile, lineage, local tests, and CI.


Compile models: resolve dependencies, type-check SQL, validate data contracts, and build the semantic graph.

Terminal window
rocky compile [flags]
Flag Type Default Description
--models <PATH> PathBuf models Directory containing .sql and .toml model files.
--contracts <PATH> PathBuf Directory containing data contract definitions.
--model <NAME> string Restrict the reported result and exit status to one exact model name — whether that model’s own source is valid, not whether its upstreams can be rebuilt. The full project is still loaded and compile-checked internally for dependency and type context.
--expand-macros bool false Expand macros from macros/ and include the expanded SQL in the output.
--target-dialect <DIALECT> dbx | sf | bq | duckdb Run the P001 dialect-portability lint against the chosen target. Non-portable constructs emit error-severity diagnostics. Precedence: flag > [portability] target_dialect in rocky.toml > unset. See Portability linting.
--with-seed bool false Execute data/seed.sql against an in-memory DuckDB and use its information_schema as the source-of-truth for raw source schemas. Turns leaf .sql models from Unknown columns into concrete types. Requires the duckdb feature (enabled by default in the shipped binary).

Compile all models:

Terminal window
rocky compile
{
"version": "1.11.0",
"command": "compile",
"models": 14,
"execution_layers": 4,
"has_errors": false,
"diagnostics": [],
"compile_timings": { "load_ms": 8, "resolve_ms": 2, "typecheck_ms": 42 },
"models_detail": [
{
"name": "fct_revenue",
"strategy": { "type": "full_refresh" },
"target": { "catalog": "acme_warehouse", "schema": "gold", "table": "fct_revenue" },
"freshness": { "max_lag_seconds": 86400, "time_column": "order_date", "severity": "warning" },
"contract_source": "auto",
"incrementality_hint": {
"is_candidate": true,
"recommended_column": "order_date",
"confidence": "medium",
"signals": ["column name 'order_date' ends with '_date' (timestamp pattern)"]
},
"cost_hint": {
"estimated_rows": 10000,
"estimated_bytes": 2560000,
"estimated_cost_usd": 0.0000228,
"confidence": "high"
},
"depends_on": ["stg_orders", "stg_refunds"],
"tags": { "domain": "finance", "tier": "gold", "owner": "analytics", "region": "emea" }
}
/* one entry per model */
]
}

models_detail carries each compiled model’s declarative shape. Four fields are always there: name, the materialization strategy (wire form {"type": "..."}), the target coordinates, and the direct depends_on list.

Four more appear only when they apply:

  • freshness — the model’s freshness expectation.
  • contract_source"auto" for a sibling .contract.toml, "explicit" for one passed via --contracts.
  • incrementality_hint — set on a full_refresh model that has a monotonic-looking column.
  • cost_hint — set when the upstream statistics support an estimate.

The tags object holds the model’s [tags] merged over any config-group baseline, with the sidecar winning. Rocky omits an empty tags, an empty depends_on, and any absent optional field.

Compile a single model with contracts, showing a warning diagnostic:

Terminal window
rocky compile --model fct_revenue --contracts contracts/
{
"version": "1.6.0",
"command": "compile",
"models": 1,
"execution_layers": 1,
"has_errors": false,
"diagnostics": [
{
"severity": "warning",
"code": "W010",
"model": "fct_revenue",
"message": "column 'discount_pct' not declared in contract",
"span": null,
"suggestion": null
}
],
"compile_timings": { "load_ms": 5, "resolve_ms": 1, "typecheck_ms": 12 }
}

Model selection is exact: an unknown name is an error. Rocky still loads and compile-checks the full project internally so the selected model has dependency and type context, but the visible counts, layers, model details, diagnostics, and failure state describe only the selected model.

What the exit status does and does not cover. The selector filters diagnostics by exact attribution — a diagnostic is reported only when its owning model name equals the selector — and the exit status follows that filtered set. The dividing line is how a problem is reported, not what kind of problem it is:

  • A hard compilation failure — one that aborts compilation rather than emitting a diagnostic, such as a model whose SQL cannot be parsed — fails the command regardless of the selector, because compilation never gets far enough to filter anything. Failures during semantic-graph construction and contract loading behave the same way.
  • An error diagnostic attributed to another model is filtered out, so the selected model is reported clean. This holds even though that other model genuinely fails to compile: rocky run classifies such a model as a compile error and excludes it from execution. Selecting a model therefore tells you nothing about whether its upstreams compile.

Two consequences worth knowing:

  • Not every diagnostic names a model you can select. Import-level diagnostics (E033, E034, W012) carry the import name, and W011 carries a contract name that need not correspond to a model at all. Because --model requires a real model name, those cannot be surfaced by selecting anything — run without a selector to see them.
  • A diagnostic can be attributed to more than one model. A target collision (E036) attaches an error to every participating model, so selecting any one of them reports it.

To check whether a model can actually be built, compile without a selector. rocky run --model builds only the selected model and needs --defer to resolve references to upstreams you did not build; without it the SQL is left unchanged and the run succeeds only if the reference already resolves in the active namespace.

Note that under --model, execution_layers counts only the layers containing the selected model — so it reports 1, not the model’s depth in the DAG and not how many layers rebuilding it would take.

Every diagnostic carries a severity ("error", "warning", "info"), a code (E### errors, W### warnings, P### portability lints, or V### validation), the owning model, and (when the compiler can locate it) a span and suggestion.

Compile models from a non-default directory:

Terminal window
rocky compile --models src/transformations/

Reject SQL that won’t run on BigQuery (P001 dialect-portability lint):

Terminal window
rocky compile --target-dialect bq
{
"version": "1.11.0",
"command": "compile",
"has_errors": true,
"diagnostics": [
{
"severity": "error",
"code": "P001",
"model": "fct_revenue",
"message": "NVL is not portable to BigQuery (supported by: Snowflake, Databricks)",
"span": { "file": "models/fct_revenue.sql", "line": 1, "col": 1 },
"suggestion": "use COALESCE"
}
]
}

The --target-dialect flag and the [portability] config block (see Configuration) drive the same check. Project-wide allow-lists and per-model -- rocky-allow: … pragmas exempt specific constructs. See Portability linting.

Compile with seeded source schemas so leaf .sql models pick up real types:

Terminal window
rocky compile --with-seed

--with-seed looks for data/seed.sql relative to the project root (one level up from --models). It opens an in-memory DuckDB, runs the seed, and feeds the resulting information_schema.columns back into the compiler so downstream incrementality and type-inference get concrete types instead of RockyType::Unknown. Bails if data/seed.sql is missing or fails to execute.


Show column-level lineage for a model, tracing how each output column is derived from upstream sources.

Terminal window
rocky lineage <target> [flags]
Argument Type Default Description
target string (required) Model name, or model.column to trace a specific column.
Flag Type Default Description
--models <PATH> PathBuf models Directory containing model files.
--column <NAME> string Specific column to trace (alternative to model.column syntax).
--format <FORMAT> string Output format. Use dot for Graphviz DOT output.
--downstream bool false Walk the column-level graph forward (consumers) instead of backward (sources). Mutually exclusive with --upstream.
--upstream bool true Walk the column-level graph backward (sources). Default; the flag exists for explicitness in scripted callers.

Show lineage for a model. Returns the model’s columns, its upstream and downstream models, and every column-level edge with the transform kind:

Terminal window
rocky lineage fct_revenue
{
"version": "1.6.0",
"command": "lineage",
"model": "fct_revenue",
"columns": [
{ "name": "customer_id" },
{ "name": "revenue_amount" }
],
"upstream": ["stg_orders", "stg_refunds"],
"downstream": [],
"edges": [
{
"source": { "model": "stg_orders", "column": "customer_id" },
"target": { "model": "fct_revenue", "column": "customer_id" },
"transform": "direct"
},
{
"source": { "model": "stg_orders", "column": "total_amount" },
"target": { "model": "fct_revenue", "column": "revenue_amount" },
"transform": "expression"
},
{
"source": { "model": "stg_refunds", "column": "refund_amount" },
"target": { "model": "fct_revenue", "column": "revenue_amount" },
"transform": "expression"
}
]
}

Tracing a single column returns a flat trace shape instead. Use either --column or model.column syntax:

Terminal window
rocky lineage fct_revenue --column revenue_amount
{
"version": "1.6.0",
"command": "lineage",
"model": "fct_revenue",
"column": "revenue_amount",
"direction": "upstream",
"trace": [ /* LineageEdgeRecord entries, same shape as edges above */ ]
}

Trace a specific column and export as Graphviz DOT:

Terminal window
rocky lineage fct_revenue --column revenue_amount --format dot
digraph lineage {
rankdir=LR;
"stg_orders.total_amount" -> "fct_revenue.revenue_amount";
"stg_refunds.refund_amount" -> "fct_revenue.revenue_amount";
}

Use the dot syntax shorthand:

Terminal window
rocky lineage fct_revenue.revenue_amount --format dot | dot -Tpng -o lineage.png

Walk downstream to see every consumer of a column (the answer to “what breaks if I change this?”):

Terminal window
rocky lineage stg_orders.customer_id --downstream
{
"version": "1.11.0",
"command": "lineage",
"model": "stg_orders",
"column": "customer_id",
"direction": "downstream",
"trace": [
{
"source": { "model": "stg_orders", "column": "customer_id" },
"target": { "model": "fct_revenue", "column": "customer_id" },
"transform": "direct"
},
{
"source": { "model": "fct_revenue", "column": "customer_id" },
"target": { "model": "mart_ltv", "column": "customer_id" },
"transform": "direct"
}
]
}

Upstream output has "direction": "upstream" (the default shape, unchanged). The transitive walker is backed by an edges_by_source_model index so cost scales with fan-out rather than total edges.


Emit a project-wide column-level lineage snapshot to disk. Walks every model in the SemanticGraph and serializes the result as persisted catalog artifacts (a catalog.json front door plus edges.parquet / assets.parquet) so downstream consumers (BI tools, governance dashboards, AI review bots) can query lineage without re-invoking the engine.

Terminal window
rocky catalog [flags]
Flag Type Default Description
--models <PATH> PathBuf models Directory containing .sql and .toml model files.
--out <PATH> PathBuf .rocky/catalog/ Output directory. catalog.json is written to <out>/catalog.json; the Parquet artifacts to <out>/edges.parquet and <out>/assets.parquet.
--format <FORMAT> json | parquet | both both Which artefact family to emit. json writes only catalog.json; parquet writes only edges.parquet + assets.parquet; both writes all three.
--catalog <NAME> string Scope the snapshot to a single warehouse catalog. Only assets whose FQN sits in the named catalog are emitted, and edges referencing dropped assets are pruned.

By default (--format both) rocky catalog writes <out>/catalog.json, <out>/edges.parquet, and <out>/assets.parquet; pass --format json to write only the JSON front door. The CLI’s stdout is a short summary in the default --output table mode, or the same JSON payload mirrored to stdout in --output json mode.

The artifact contains:

  • assets — one entry per model or upstream source, with columns (name plus inferred type and nullability when known, and a per-column description from the sidecar [columns] table when set), upstream / downstream lists, and the model’s intent description when supplied.
  • edges — one entry per column-level lineage edge: source column, target column, transform kind (direct, cast, expression, aggregation: <fn>), and a confidence grade (High for explicit projections, Medium for star-expanded edges, Low reserved for future use).
  • stats — aggregate counts (asset_count, edge_count, column_count, assets_with_star, orphan_columns, duration_ms).
  • A config_hash fingerprint of rocky.toml so consumers can tell whether the catalog was built against the current configuration.

Build the default snapshot:

Terminal window
rocky catalog
rocky catalog
project: playground
assets: 3
columns: 13
edges: 13
wrote: .rocky/catalog/catalog.json
wrote: .rocky/catalog/edges.parquet
wrote: .rocky/catalog/assets.parquet
duration: 12ms

Pipe the JSON shape directly:

Terminal window
rocky catalog --output json | jq '.stats'

Write to a custom directory (for example, when building a per-PR artifact):

Terminal window
rocky catalog --out build/catalog
  • Per-asset last_run_id and last_materialized_at are populated from the state store when a matching successful run exists; they stay null for assets that have never been materialized (or built before the run history was captured).
  • Lineage extraction inherits the existing extractor’s coverage: window functions, CTEs, set operations, CASE WHEN projections, and join keys are not yet surfaced as edges. Asset-level partial lineage is flagged via stats.assets_with_star.
  • rocky lineage – per-model lineage exploration with --column traces
  • rocky compile – build the semantic graph that the catalog reads

Print the whole project as one graph. Every pipeline stage is a node, the dependencies between stages are edges, and the nodes are grouped into the layers they execute in. rocky run --dag executes that same graph.

Terminal window
rocky dag [flags]
Flag Type Default Description
--models <PATH> PathBuf each pipeline’s own configured location Override the models directory. Omit it and every transformation pipeline uses its own configured models location. rocky run --dag does the same, which keeps the two in agreement. Passing it overrides the location for every transformation pipeline, so Rocky refuses a project that defines more than one rather than build each model twice.
--seeds <PATH> PathBuf Seeds directory.
--contracts <PATH> PathBuf Contracts directory.
--column-lineage bool false Also emit column-level lineage edges. This one requires a compile, so it costs more than the plain graph.

Each node has an id of the form {kind}:{name}, for example transformation:stg_orders. The kind is one of source, load, transformation, quality, snapshot, seed, test, and replication.

A node also carries the fields that apply to its kind:

  • the pipeline name from rocky.toml;
  • the target table;
  • the materialization strategy;
  • the per-model freshness expectation;
  • the partition shape;
  • the ids it depends on.

execution_layers is the graph sorted into runnable groups.

execution_layers what the grouping means
──────────────── ────────────────────────────────────────
layer 0 [ A , B ] A and B depend on nothing, so Rocky can
│ run them at the same time
layer 1 [ C , D ] C and D depend only on earlier layers,
│ never on each other
layer 2 [ E ] E waits for every layer before it

Nodes inside one layer have no dependency on each other, so an orchestrator can run them in parallel. Each layer waits for the layer before it.

column_lineage is empty unless you pass --column-lineage. An empty list on its own does not mean the project has no lineage.

  • You did not pass --column-lineage: nothing was computed, and you can conclude nothing from the empty list.
  • You passed it and column_lineage_unavailable is absent: the list is the complete answer, empty included.
  • You passed it and column_lineage_unavailable is present: it carries a human-readable reason, and the empty list must not be read as “no lineage”.

Print the graph for the whole project:

Terminal window
rocky dag

Include column-level lineage edges:

Terminal window
rocky dag --column-lineage

Render the runnable SQL each transformation model would emit, without a warehouse connection and without running anything. The SQL is generated through the same path rocky run uses, including declared surrogate-key columns wrapped exactly as they are at materialization.

Terminal window
rocky emit-sql [flags]
Flag Type Default Description
--models <PATH> PathBuf models Directory containing .sql and .toml model files.
--model <NAME> string Render a single model by name instead of the whole project.
--out-dir <PATH> PathBuf Write one <model>.sql file per model into this directory, in dependency order. When omitted, the concatenated SQL is printed to stdout (also in dependency order).

The dialect is the project’s configured target adapter type, resolved from rocky.toml without credentials. With no resolvable config it defaults to DuckDB. All models render in this one resolved dialect, so for a project whose models target more than one adapter, the emitted SQL matches rocky run only for the models whose target uses that dialect.

Full-refresh models emit a complete CREATE OR REPLACE TABLE … AS … that runs as-is against a fresh warehouse and matches what a run executes in the resolved dialect. Incremental and merge models emit their steady-state statement instead: a bare INSERT or MERGE that operates on an existing target. rocky run bootstraps the target table on first build and threads the incremental watermark from state, neither of which a static emit can reproduce, so those files carry a leading note to that effect:

-- NOTE: incremental/merge statement — operates on an existing target.
-- `rocky run` bootstraps the table on first build and threads the
-- incremental watermark from state; this static SQL does neither.
MERGE INTO ...

Models that produce no standalone SQL are reported on stderr rather than silently dropped, so you never mistake the emitted set for the complete project. Two cases are skipped this way: ephemeral models (inlined as CTEs upstream, so they have no statement of their own) and strategies that cannot render offline, such as a Snowflake dynamic table that needs a live compute-warehouse name.

Print the whole project’s SQL to stdout in dependency order:

Terminal window
rocky emit-sql
-- model: stg_orders
CREATE OR REPLACE TABLE main.stg_orders AS
SELECT order_id, customer_id, total_amount FROM raw.orders;
-- model: fct_revenue
CREATE OR REPLACE TABLE main.fct_revenue AS
SELECT customer_id, SUM(total_amount) AS revenue_amount FROM main.stg_orders GROUP BY customer_id;

Write one file per model, ready to commit or hand to another tool:

Terminal window
rocky emit-sql --out-dir build/sql/
emit-sql: wrote 2 model(s) to build/sql/ in dependency order

Render a single model, and capture the project-wide SQL into one file:

Terminal window
rocky emit-sql --model fct_revenue
rocky emit-sql > build/all.sql

When some models cannot be emitted as standalone SQL, the skip report goes to stderr:

emit-sql: 1 model(s) not emitted:
- dim_session (ephemeral — inlined as a CTE)
  • rocky dag – inspect the dependency order emit-sql renders in
  • rocky catalog – the same compiled graph, exported as a lineage snapshot rather than runnable SQL
  • No lock-in – the full fallback recipe for stepping away from the engine

Run local model tests via DuckDB without needing warehouse credentials. Validates model SQL, contract compliance, and user-defined test assertions.

Terminal window
rocky test [flags]
Flag Type Default Description
--models <PATH> PathBuf models Directory containing model files.
--contracts <PATH> PathBuf Directory containing data contract definitions.
--model <NAME> string Run tests for a single model only.

Run all model tests:

Terminal window
rocky test
{
"version": "1.6.0",
"command": "test",
"total": 14,
"passed": 12,
"failed": 2,
"failures": [
{ "name": "fct_orders.not_null(order_id)", "error": "found 3 null values" },
{ "name": "fct_orders.unique(order_id)", "error": "found 1 duplicate" }
]
}

Test a single model with contracts:

Terminal window
rocky test --model fct_revenue --contracts contracts/
{
"version": "1.6.0",
"command": "test",
"total": 1,
"passed": 1,
"failed": 0,
"failures": []
}

The default rocky test path also runs fixture-driven [[test]] unit tests declared in model sidecars. Each [[test]] block mocks upstream inputs with inline rows (given) and asserts the model’s output rows (expect), executed in-memory against DuckDB. When at least one model declares a [[test]] block, the JSON output gains a unit_tests summary; the key is omitted entirely when no model declares one. A failing unit test makes rocky test exit non-zero, the same as a failing model assertion.

{
"version": "1.11.0",
"command": "test",
"total": 3,
"passed": 3,
"failed": 0,
"failures": [],
"unit_tests": {
"total": 2,
"passed": 1,
"failed": 1,
"results": [
{ "model": "fct_revenue", "test": "discount_caps_at_total", "passed": true, "error": null, "mismatches": [] },
{
"model": "fct_revenue",
"test": "refunds_subtract",
"passed": false,
"error": "ordered output mismatch (1 expected vs 1 actual row(s))",
"mismatches": [
{ "row_index": 0, "expected": "customer_id=7, revenue_amount=80", "actual": "customer_id=7, revenue_amount=100", "kind": "value_diff" }
]
}
]
}
}

Each results entry carries the model name, the [[test]] block’s test name, a passed flag, an error message (null when the test passed), and the mismatches array of row-level diffs. Each mismatch renders its row as col=val, col=val. A mismatch kind is missing (expected but not produced), extra (produced but not expected), or value_diff (same positional row, differing values, from an ordered expectation).

--declarative is a separate surface: it adds a declarative block summarising [[tests]] (plural) declared in model sidecars, run against the configured warehouse adapter rather than DuckDB. See Testing and Contracts for both surfaces.

rocky test exits 1 when a selector names something the project does not have. This holds with and without --declarative.

What you passed Message on stderr
--model <NAME> naming no model in the project model '<NAME>' not found (no transformation model with that name)
--models <PATH> naming a missing or empty directory no models found in <PATH>

Rocky refuses before it writes any output. Under --output json stdout stays empty, so read the exit code and stderr rather than the payload.

A model that exists but declares no tests is not an error. It still exits 0 and reports total: 0.


Run the full CI pipeline: compile all models and run all tests. Designed for use in CI/CD environments where no warehouse credentials are available. Returns a non-zero exit code if any compilation error or test failure occurs.

Terminal window
rocky ci [flags]
Flag Type Default Description
--models <PATH> PathBuf models Directory containing model files.
--contracts <PATH> PathBuf Directory containing data contract definitions.

Run CI with default paths:

Terminal window
rocky ci
{
"version": "1.6.0",
"command": "ci",
"compile_ok": true,
"tests_ok": true,
"models_compiled": 14,
"tests_passed": 14,
"tests_failed": 0,
"exit_code": 0,
"diagnostics": [],
"failures": []
}

Run CI with contracts in a GitHub Actions workflow. On a compile error, tests_passed / tests_failed are 0 because tests don’t run, and CI short-circuits and returns a non-zero exit_code:

Terminal window
rocky ci --models src/models --contracts src/contracts
{
"version": "1.6.0",
"command": "ci",
"compile_ok": false,
"tests_ok": false,
"models_compiled": 13,
"tests_passed": 0,
"tests_failed": 0,
"exit_code": 1,
"diagnostics": [
{
"severity": "error",
"code": "E001",
"model": "fct_revenue",
"message": "unknown column 'total' in model 'stg_orders'",
"span": null,
"suggestion": "did you mean 'total_amount'?"
}
],
"failures": []
}

Detect which models changed between a base git ref and HEAD, compile both sides, and report added/modified/removed columns. Emits both JSON (for CI pipelines) and a pre-rendered Markdown block suitable for posting as a PR comment.

Terminal window
rocky ci-diff [base_ref] [flags]
Argument Type Default Description
base_ref string main Git ref to compare against. Rocky shells out to git diff --name-only <base_ref> HEAD to find changed .sql, .rocky, and sidecar .toml files.
Flag Type Default Description
--models <PATH> PathBuf models Directory containing model files.
--semantic flag off Also run the typed-IR semantic breaking-change classifier and surface findings under breaking_findings in the JSON output. Informational only — even a Breaking finding does not change ci-diff’s exit code. The hard gate lives on rocky branch promote.

Diff the current branch against main:

Terminal window
rocky ci-diff
{
"version": "1.31.0",
"command": "ci-diff",
"base_ref": "main",
"head_ref": "HEAD",
"summary": {
"added": 1,
"modified": 2,
"removed": 0
},
"models": [
{
"model": "fct_orders",
"status": "modified",
"columns": [
{ "name": "order_status", "change": "added" },
{ "name": "amount_cents", "change": "type_changed", "from": "INT", "to": "BIGINT" }
]
}
],
"markdown": "### Model diff vs `main`\n\n| Model | Status | ... |"
}

Diff against a feature-branch base and a non-default models directory:

Terminal window
rocky ci-diff release/2026-04 --models src/models

The markdown field holds a ready-to-post report; in a GitHub Actions workflow you can jq -r .markdown the JSON output and feed it into gh pr comment.

Run with --semantic to surface classified breaking-change findings alongside the structural diff:

Terminal window
rocky ci-diff --semantic
{
"version": "1.31.0",
"command": "ci-diff",
"base_ref": "main",
"head_ref": "HEAD",
"summary": { "added": 0, "modified": 1, "removed": 0 },
"models": [ /* ... */ ],
"markdown": "...",
"breaking_findings": [
{
"change": {
"kind": "column_type_changed",
"model": "analytics.marts.fct_orders",
"column": "amount_cents",
"old_type": "BIGINT",
"new_type": "INT",
"narrowing": true
},
"severity": "breaking"
}
]
}

The breaking_findings array is omitted from JSON output when empty or when --semantic is not set. Each finding carries a tagged change object (kind discriminator) and a severity (breaking / warning / info). Use --semantic in ci-diff to surface findings on every PR; rely on rocky branch promote to block promotion when severity == "breaking".

The breaking_findings field is JSON-only: --output table still renders the structural diff but does not print the semantic findings list. Use --output json (and pipe through jq) to inspect them.

  • rocky ci – full compile + test for CI
  • rocky compile – compile a single branch without diffing
  • rocky preview – pruned re-run + sampled data diff + cost delta on top of ci-diff’s structural diff
  • rocky branch promote – promote a branch’s tables to production with a hard semantic breaking-change gate

Preview a change before it merges. Rocky re-executes only the changed models and their downstream column lineage on a per-pull-request branch, and copies everything else from the base ref.

Three subcommands compose into one review artifact. preview create runs the workflow, preview diff reports the structural and sampled row-level diff, and preview cost reports the cost delta against base. A fourth, preview rows, is separate: it samples the output rows of a single model.

For the design (why CTAS today and warehouse-native clones tomorrow, how the column-level pruner works, what the sampling window’s correctness ceiling is), see the How Preview Works concept page. For a step-by-step walkthrough on a feature branch, see the Preview a PR how-to.

Terminal window
rocky preview create --base <ref> [--name <branch_name>]
rocky preview diff --name <branch_name> [--base <ref>] [--sample-size <N>]
rocky preview cost --name <branch_name> [--base <ref>]
rocky preview rows --model <name> [--cte <name>] [--limit <N>]

preview create, preview diff, and preview cost accept --output json|markdown. The Markdown form is pre-rendered for posting to a PR comment; the JSON form embeds the same Markdown in a top-level markdown field for orchestrator use.

Compute the prune set, copy the rest from the base schema, run only the prune set against a per-PR branch.

Flag Type Default Description
--base <REF> string main Git ref the change-set is computed against. Rocky shells out to git diff --name-only <base>...HEAD against the models directory.
--name <NAME> string derived from current branch Branch name to register in the state store. The branch’s schema_prefix becomes branch__<name> and is the target schema for the pruned run.
--models <PATH> PathBuf models Directory containing model files.

Example. Diff against main and create a preview branch:

Terminal window
rocky preview create --base main
{
"version": "1.18.0",
"command": "preview-create",
"branch_name": "preview-fix-price",
"branch_schema": "branch__preview-fix-price",
"base_ref": "main",
"head_ref": "HEAD",
"prune_set": [
{ "model_name": "fct_revenue", "reason": "changed", "changed_columns": ["amount_cents"] },
{ "model_name": "rev_by_region", "reason": "downstream_of_changed" }
],
"copy_set": [
{ "model_name": "stg_orders", "source_schema": "main", "target_schema": "branch__preview-fix-price", "copy_strategy": "ctas" },
{ "model_name": "stg_customers", "source_schema": "main", "target_schema": "branch__preview-fix-price", "copy_strategy": "ctas" }
],
"skipped_set": [],
"run_id": "run-20260428-141033-002",
"run_status": "succeeded",
"duration_ms": 4321
}

copy_strategy reports "ctas" for every successful copy regardless of which SQL primitive the adapter actually emitted. As of engine-v1.19.1, Databricks uses SHALLOW CLONE and BigQuery uses CREATE TABLE … COPY (both metadata-only) under the hood; DuckDB and Snowflake fall through to the portable CTAS default. Surfacing the per-adapter strategy in the wire output is a follow-up.

Sampled row-level diff plus structural (column-level) diff for every model in the prune set.

Flag Type Default Description
--name <NAME> string (required) Branch name created by preview create.
--base <REF> string main Git ref to compare against. Must match what preview create was invoked with.
--sample-size <N> usize 1000 Number of rows to sample per model for row-level diffing. Larger windows reduce false-negative risk; see coverage warning.

Example. Render a Markdown report ready to post on a PR:

Terminal window
rocky preview diff --name preview-fix-price --output markdown

The JSON shape (PreviewDiffOutput) carries the same data plus the per-model sampling_window block with coverage_warning, and rocky preview diff --output json | jq -r .markdown reproduces the --output markdown report.

Per-model cost delta between the branch run and the latest base-schema RunRecord.

Flag Type Default Description
--name <NAME> string (required) Branch name created by preview create.
--base <REF> string main Git ref the base run is identified by.

Example.

Terminal window
rocky preview cost --name preview-fix-price --output markdown

The JSON shape (PreviewCostOutput) reports per-model delta_usd, branch_duration_ms, base_duration_ms, and bytes scanned, plus an aggregate summary.delta_usd, summary.savings_from_copy_usd, and models_skipped_via_copy. Underlying cost math is identical to rocky cost (Databricks / Snowflake duration × DBU rate; BigQuery bytes × $/TB; DuckDB zero); fields fall back to null when no base RunRecord exists or when the adapter does not surface USD.

Sample the result rows of one transformation model, or of one CTE inside it. Classified columns are masked inline in the output.

Flag Type Default Description
--model <NAME> string (required) Model to preview.
--cte <NAME> string Preview one named CTE inside the model instead of the model’s final output.
--limit <N> u32 100 Maximum number of rows to return.
--allow-warehouse bool false Permit execution against a warehouse other than DuckDB. That may cost money, so it is opt-in. A local DuckDB project does not need the flag.
--pipeline <NAME> string Pipeline whose target adapter to run against. Required when the project defines more than one pipeline.
--models <PATH> PathBuf models Models directory.
--sql-file <PATH> PathBuf Preview an ad-hoc SQL snippet read from this file instead of the model’s compiled SQL. This backs the editor’s “Preview Selection”. Mutually exclusive with --cte.

--sql-file still needs --model, which names the enclosing model. If that model has any masked column, Rocky refuses the ad-hoc preview rather than risk leaking a pre-mask value.

Example. Peek at a model’s rows during local development:

Terminal window
rocky preview rows --model customer_orders --limit 20

Wire contracts for all four subcommands are exported by rocky export-schemas:

  • schemas/preview_create.schema.json
  • schemas/preview_diff.schema.json
  • schemas/preview_cost.schema.json
  • schemas/preview_rows.schema.json

These back the autogenerated Pydantic and TypeScript bindings. See JSON Output for the codegen pipeline and version compatibility contract.

  • rocky ci-diff – structural diff alone, without the pruned re-run or row-level sampling
  • rocky branch – the schema-prefix branches preview create registers
  • rocky cost – the per-run cost rollup preview cost diffs across base and branch
  • rocky compare – ad-hoc shadow comparison; preview diff extends the same kernel with sampled row-level diffing

Maintain the producer-contract baselines declared in [imports.<name>]. An import is a vendored snapshot of another team’s compiled IR. Your rocky compile diffs the baseline you accepted against the snapshot you vendored, and fails when the producer made a breaking change.

Terminal window
rocky imports update # advance every baseline to its snapshot
rocky imports update --check # CI guard: report and exit non-zero, write nothing

Advance the vendored baselines to the current snapshot. This is the explicit “I reviewed the producer’s current state and accept it” gesture. It also reports any stale pin. It never rewrites rocky.toml.

Flag Type Default Description
--check bool false Read-only CI guard. Report what is out of date, exit non-zero, and write nothing.
producer project consumer project
──────────────── ─────────────────────────────────────
rocky publish-ir [imports.<name>] in rocky.toml
│ baseline = the IR you already accepted
│ writes an IR snapshot = the vendored file
▼ snapshot pin = optional recipe hash
snapshot file ─ vendored ─► │
rocky compile
diffs baseline against snapshot,
fails on a breaking change
rocky imports update
moves the baseline up to the snapshot