You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
import_tables now covers every table-shaped TABLE_TYPE not handled by another
import_* flag:
- BASE TABLE (permanent / transient / temporary; CTAS-created tables show as
BASE TABLE too — Snowflake doesn't preserve the SELECT)
- ICEBERG TABLE
- HYBRID TABLE (Unistore)
- EVENT TABLE
(External / materialized / dynamic tables still on their own flags.)
Dual-mode emission, mirroring dt_modeling shape:
table_modeling: "observable" (default) → @observable_source_asset, polls
row_count + last_altered, stable data_version, no click-to-materialize.
table_modeling: "asset" → @asset that runs
`CREATE OR REPLACE TABLE <name> AS <materialize_sql>`. Each imported table
MUST carry a `materialize_sql:` under `assets_by_name.<name>:` — ValueError
at component build time if missing. Snowflake's GET_DDL('TABLE',...) returns
schema only (no SELECT — even for CTAS-created tables), so the customer
supplies the SELECT, same as a dbt model file. MaterializeResult emits the
same row_count + last_altered metadata + the executed query_id.
view_modeling: "asset" (default) → @asset that runs
`CREATE OR REPLACE VIEW <name> AS <VIEW_DEFINITION>`. Zero customer input —
Snowflake stores the SELECT in INFORMATION_SCHEMA.VIEWS.VIEW_DEFINITION.
Click materialize = recompile against current upstream schema, catches
schema drift. Per-view `materialize_sql:` override pins a known-good SELECT
if you'd rather not trust the stored copy.
view_modeling: "observable" → falls back to observation-only.
Fallback safety: if view_modeling=asset but no VIEW_DEFINITION available
AND no override, that specific view falls back to observable mode with a
warning log entry — no hard failure for secure views / privilege gaps.
Shared helpers (_fetch_table_state, _build_metadata) factor out the
INFORMATION_SCHEMA round-trip so observable and asset paths emit identical
metadata + data_version signatures. Downstream eager only cascades when
the table actually moves regardless of which mode emitted the event.
README:
- Features list: tables + views entries updated to reflect dual-mode.
- New "Tables and views" section between assets_by_name and the stored
procedure section. Covers what TABLE_TYPEs each flag pulls in,
table_modeling vs view_modeling semantics, the materialize_sql:
pattern, and the dbt-parallel framing for why tables need the SELECT
supplied (CTAS doesn't preserve it).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: integrations/snowflake_workspace/README.md
+84-4Lines changed: 84 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -36,8 +36,8 @@ uv run dg dev # auto-loads .env + .env.secrets
36
36
37
37
## Features
38
38
39
-
-**Tables**: Observe base tables (row_count + last_altered, stable data_version)
40
-
-**Views**: Observe non-materialized views (same shape as tables)
39
+
-**Tables**: Observe (default) or materialize regular / Iceberg / Hybrid / Event tables (row_count + last_altered, stable data_version). Materialize via customer-supplied `materialize_sql:` (see [Tables and views](#tables-and-views))
40
+
-**Views**: Materialize (default — re-runs `CREATE OR REPLACE VIEW`) or observe (toggle via `view_modeling`)
41
41
-**Tasks**: Execute Snowflake tasks on demand from Dagster
42
42
-**Stored Procedures**: Call stored procedures from Dagster
43
43
-**Dynamic Tables**: Trigger manual refreshes for dynamic tables
@@ -101,6 +101,7 @@ uv run dg dev # auto-loads .env + .env.secrets
101
101
102
102
| Field | Type | Default | Description |
103
103
|---|---|---|---|
104
+
|`table_modeling`|`str`|`"observable"`| How imported tables are emitted: 'observable' (default; @observable_source_asset, polls row_count + last_altered via INFORMATION_SCHEMA, no click-to-materialize) or 'asset' (@asset that runs CREATE OR REPLACE TABLE <name> AS <materialize_sql>). 'asset' requires per-table `materialize_sql:` in `assets_by_name`; ValueError at component build time if any imported table is missing the SQL. Snowflake doesn't store the SELECT used at CTAS time — so the customer must supply it, same as a dbt model file. |
104
105
|`filter_by_name_pattern`|`str`| — | Regex pattern to filter entities by name |
105
106
106
107
### Other
@@ -119,8 +120,9 @@ uv run dg dev # auto-loads .env + .env.secrets
119
120
|`import_external_tables`|`bool`|`false`| Import external tables as materializable assets (trigger refresh) |
|`import_openflow_flows`|`bool`|`false`| Import OpenFlow data integration flows as observable assets (monitor via telemetry) |
122
-
|`import_tables`|`bool`|`false`| Import base tables (TABLE_TYPE='BASE TABLE') as observable source assets. Each table becomes an @observable_source_asset that polls row_count + last_altered via INFORMATION_SCHEMA.TABLES and emits a stable data_version signature. Useful for lineage display + freshness checks against tables the team owns out-of-band (manually loaded, ETL'd by external tools, etc.). Iceberg + Hybrid tables are not included here — use dedicated components for those. |
123
-
|`import_views`|`bool`|`false`| Import non-materialized views (TABLE_TYPE='VIEW') as observable source assets. Same shape as import_tables — row_count + last_altered observation, stable data_version. Materialized views are a separate concept; use import_materialized_views. |
123
+
|`import_tables`|`bool`|`false`| Import tables as Dagster assets. Covers every table-shaped object that doesn't have its own dedicated import_* flag: regular base tables (BASE TABLE — permanent / transient / temporary), Iceberg tables, Hybrid tables (Unistore), and Event tables. Default behavior is observation (row_count + last_altered, stable data_version) — opt into materialization via `table_modeling: asset` + a per-table `materialize_sql:` in `assets_by_name`. External / materialized / dynamic tables have their own flags and are not included here. |
124
+
|`import_views`|`bool`|`false`| Import non-materialized views (TABLE_TYPE='VIEW') as Dagster assets. Default behavior is materializable — re-runs the view's stored definition via CREATE OR REPLACE VIEW (recompiles against current upstream schema, catches schema drift). Opt back into observation-only via `view_modeling: observable`. Materialized views are a separate concept; use `import_materialized_views`. |
125
+
|`view_modeling`|`str`|`"asset"`| How imported views are emitted: 'asset' (default; @asset that runs CREATE OR REPLACE VIEW <name> AS <VIEW_DEFINITION pulled from INFORMATION_SCHEMA.VIEWS> — zero config, Snowflake already stores the SELECT) or 'observable' (@observable_source_asset, lineage-display-only). |
124
126
|`exclude_name_pattern`|`str`| — | Regex pattern to exclude entities by name |
125
127
|`task_filter_by_state`|`str`| — | Filter tasks by state (STARTED, SUSPENDED). If not specified, imports all tasks. |
126
128
|`generate_sensor`|`bool`|`true`| Create a sensor to observe task runs and dynamic table refreshes |
This works for `instances[].config_schema:` too — useful when one Snowflake task underlies N intentionally-distinct assets (e.g. by-region), each with its own overridable knobs.
267
269
270
+
## Tables and views
271
+
272
+
`import_tables`and `import_views` surface table-shaped objects from `INFORMATION_SCHEMA.TABLES` as Dagster assets. Both support dual-mode emission — pure observation (default for tables) or materializable @asset (default for views).
273
+
274
+
### What each flag covers
275
+
276
+
| `TABLE_TYPE` (Snowflake) | Imported by | Notes |
277
+
|---|---|---|
278
+
| `BASE TABLE` (permanent / transient / temporary) | `import_tables` | Regular tables. CTAS-created tables show as `BASE TABLE` too — Snowflake doesn't store the SELECT used at create time. |
| `MATERIALIZED VIEW` | `import_materialized_views` | Separate flag. |
284
+
| `DYNAMIC TABLE` | `import_dynamic_tables` | Separate flag. |
285
+
| `EXTERNAL TABLE` | `import_external_tables` | Separate flag. |
286
+
287
+
### Table modeling
288
+
289
+
`table_modeling:` controls how every imported table is emitted:
290
+
291
+
| Value | What you get | When to use |
292
+
|---|---|---|
293
+
| `observable` (default) | `@observable_source_asset`. Polls `INFORMATION_SCHEMA.TABLES.{ROW_COUNT, LAST_ALTERED}` on each tick. Emits `ObserveResult` with `data_version = f"{row_count}:{last_altered}"` so downstream eager only fires on real change. No click-to-materialize. | Catalog discoverability, lineage display, freshness checks against tables an external system writes. |
294
+
| `asset` | `@asset` that runs `CREATE OR REPLACE TABLE <name> AS <materialize_sql>` on materialize. Emits `MaterializeResult` with the same row_count + last_altered metadata plus the executed query_id. | "Rebuild this table from SQL" — the customer-supplied SELECT plays the same role as a dbt model's `.sql` file. |
295
+
296
+
When `table_modeling: asset` is set, each imported table **must** carry a `materialize_sql:` in `assets_by_name`:
297
+
298
+
```yaml
299
+
import_tables: true
300
+
table_modeling: asset
301
+
assets_by_name:
302
+
DAILY_REVENUE:
303
+
materialize_sql: |
304
+
SELECT REGION,
305
+
COUNT(*) AS ORDER_COUNT,
306
+
SUM(TOTAL) AS REVENUE
307
+
FROM RAW.ORDERS
308
+
WHERE STATUS = 'paid'
309
+
GROUP BY REGION
310
+
CUSTOMER_LIFETIME_VALUE:
311
+
materialize_sql: |
312
+
SELECT CUSTOMER_ID,
313
+
SUM(TOTAL) AS LIFETIME_VALUE,
314
+
MAX(ORDER_DATE) AS LAST_ORDER
315
+
FROM RAW.ORDERS
316
+
WHERE STATUS IN ('paid', 'delivered')
317
+
GROUP BY CUSTOMER_ID
318
+
```
319
+
320
+
The component raises `ValueError` at build time if any imported table is missing `materialize_sql:` while `table_modeling: asset` is on — fail loud, no surprises at materialize time. Why is the SELECT a customer input? Snowflake's `GET_DDL('TABLE', …)` returns schema only (no SELECT), so the workspace can't auto-supply it. Same constraint dbt's `materialized='table'` works under.
321
+
322
+
### View modeling
323
+
324
+
`view_modeling:` controls how every imported view is emitted:
325
+
326
+
| Value | What you get | When to use |
327
+
|---|---|---|
328
+
| `asset` (default) | `@asset` that runs `CREATE OR REPLACE VIEW <name> AS <VIEW_DEFINITION>`. The SELECT is auto-loaded from `INFORMATION_SCHEMA.VIEWS.VIEW_DEFINITION` — zero customer input. Click materialize = recompile against current upstream schema, catches schema drift. | Default. Views are pure DDL; re-running is safe and useful. |
329
+
| `observable` | `@observable_source_asset`. Lineage-display only, no click-to-materialize. | When you don't want Dagster touching the view DDL. |
330
+
331
+
To override the stored view definition (e.g. pin a known-good SELECT from source control rather than trusting Snowflake's stored copy), set `materialize_sql:` on the view's `assets_by_name` entry — same field as tables.
332
+
333
+
```yaml
334
+
import_views: true
335
+
view_modeling: asset # default — shown for clarity
336
+
assets_by_name:
337
+
V_PAID_ORDERS:
338
+
# Optional: pin the SELECT explicitly. Without this, the workspace
339
+
# uses whatever VIEW_DEFINITION Snowflake currently has stored.
340
+
materialize_sql: |
341
+
SELECT ORDER_ID, CUSTOMER_ID, TOTAL, ORDER_DATE
342
+
FROM RAW.ORDERS
343
+
WHERE STATUS = 'paid'
344
+
```
345
+
346
+
If `view_modeling: asset` is set but Snowflake doesn't return a `VIEW_DEFINITION` for a given view (privilege issue, secure view) and no override is supplied, that view falls back to observable mode with a warning log entry.
347
+
268
348
## Stored procedure `args:` and `config_schema:`
269
349
270
350
Snowflake stored procedures are called positionally — `CALL <proc>(arg1, arg2, …)`. `assets_by_name` exposes the same dual shape as tasks for wiring those args: hardcoded via `args:`, or launchpad-overridable via `config_schema:`. They're mutually exclusive on the same asset (setting both raises `ValueError` at component build time).
0 commit comments