Skip to content

Commit 7bb1f5c

Browse files
Merge pull request #3 from AidanHarveyNelson/feature/improved-integration-tests
Cleaned up Materialization Code and helper code. Added and improved integration tests
2 parents 1c2f80e + d488274 commit 7bb1f5c

31 files changed

Lines changed: 640 additions & 1423 deletions

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ jobs:
2222
with:
2323
# snowflake
2424
SNOWFLAKE_USER: ${{ vars.SNOWFLAKE_USER }}
25-
SNOWFLAKE_WAREHOUSE: ${{ vars.SNOWFLAKE_WAREHOUSE }}
2625
SNOWFLAKE_ROLE: ${{ vars.SNOWFLAKE_ROLE }}
2726
SNOWFLAKE_DATABASE: ${{ vars.SNOWFLAKE_DATABASE }}
27+
SNOWFLAKE_WAREHOUSE: ${{ vars.SNOWFLAKE_WAREHOUSE }}
2828
SNOWFLAKE_SCHEMA: "integration_tests_snowflake_${{ github.run_number }}"
2929

3030
secrets:

.pre-commit-config.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Configuration for pre-commit hooks (see https://pre-commit.com/).
2+
3+
# Force all unspecified python hooks to run python 3.12
4+
default_language_version:
5+
python: python3.12
6+
7+
repos:
8+
- repo: https://github.com/pre-commit/pre-commit-hooks
9+
rev: v3.2.0
10+
hooks:
11+
- id: check-yaml
12+
args: [--unsafe]
13+
- id: end-of-file-fixer
14+
- id: trailing-whitespace
15+
exclude_types:
16+
- "markdown"
17+
- id: check-case-conflict

CHANGLOG.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
11
# Changelog
22

3-
## v0.0.2 (TBA)
3+
## v0.0.2 (2026-09-03)
44

5+
- Feature: Rewrite stream_task.sql materialization with proper step ordering: temp view → target table → stream → task → resume task
6+
- Feature: Simplify stream.sql to always set src_table config and return the source relation for column discovery
7+
- Feature: Fix create_table.sql get_missing_columns argument order
8+
- Feature: Rewrite create_task.sql with clean MERGE formatting and proper parameter handling
9+
- Feature: Add 8 integration tests covering table/stream/task existence and data validation
10+
- Feature: Add prep_structure, insert_data, and cleanup helper macros
11+
- Feature: Update sample models to use correct config structure
12+
- Feature: Update run_test.sh with --full-refresh and clear step logging
13+
- Feature: Remove non-Snowflake adapter dependencies from pyproject.toml
14+
- Feature: Update README with usage guide and config reference
515
- Feature: Expanded integration tests to cover all use cases
616

717

README.md

Lines changed: 125 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,126 @@
11
# dbt-snowflake-streams-tasks
2-
A DBT Snowflake Plugin that enables users to create streams and tasks in DBT to build better versions of incremental models that don't need to be scheduled.
2+
3+
A dbt package that enables creating Snowflake [streams](https://docs.snowflake.com/en/sql-reference/sql/create-stream) and [tasks](https://docs.snowflake.com/en/sql-reference/sql/create-task) through dbt, providing an alternative to incremental models that leverages Snowflake's native change data capture.
4+
5+
## How It Works
6+
7+
Instead of scheduling dbt runs to process incremental data, this package creates:
8+
9+
1. **A target table** — with the schema defined by your SQL
10+
2. **A Snowflake stream** — to capture changes on your source table/view
11+
3. **A Snowflake task** — with a MERGE statement that automatically processes stream data into your target table
12+
13+
Once deployed, the stream and task run autonomously in Snowflake — no dbt scheduling required.
14+
15+
## Installation
16+
17+
Add to your `packages.yml`:
18+
19+
```yaml
20+
packages:
21+
- git: "https://github.com/<your-org>/dbt-snowflake-streams-tasks.git"
22+
revision: v0.0.1
23+
```
24+
25+
## Usage
26+
27+
### 1. Write Your Model SQL
28+
29+
Use `stream_ref()` or `stream_source()` to specify the stream source:
30+
31+
```sql
32+
-- models/my_model.sql
33+
select
34+
id,
35+
name,
36+
email,
37+
created_at
38+
from {{ dbt_streams_tasks.stream_ref('upstream_model') }}
39+
```
40+
41+
Or from a source:
42+
43+
```sql
44+
-- models/my_model.sql
45+
select *
46+
from {{ dbt_streams_tasks.stream_source('my_source', 'my_table') }}
47+
```
48+
49+
### 2. Configure the Model
50+
51+
In your YAML file:
52+
53+
```yaml
54+
# models/_models.yml
55+
version: 2
56+
57+
models:
58+
- name: my_model
59+
config:
60+
materialized: stream_task
61+
unique_key:
62+
- id
63+
stream:
64+
name: "my_stream"
65+
description: "Captures changes from the source"
66+
append_only: true
67+
show_initial_rows: true
68+
copy_grants: false
69+
task:
70+
name: "my_task"
71+
description: "Merges stream data into target"
72+
warehouse: "MY_WAREHOUSE"
73+
schedule: "1 MINUTE"
74+
when: "SYSTEM$STREAM_HAS_DATA('my_stream')"
75+
```
76+
77+
### 3. Run dbt
78+
79+
```bash
80+
dbt run # Creates table, stream, and task
81+
dbt run --full-refresh # Recreates everything from scratch
82+
```
83+
84+
## Configuration Reference
85+
86+
### Stream Config
87+
88+
| Parameter | Required | Default | Description |
89+
|-----------|----------|---------|-------------|
90+
| `name` | Yes | - | Name of the Snowflake stream |
91+
| `description` | No | `''` | Comment on the stream |
92+
| `append_only` | No | `true` | Only track inserts (not updates/deletes) |
93+
| `show_initial_rows` | No | `true` | Include existing rows on stream creation |
94+
| `copy_grants` | No | `false` | Copy grants when replacing the stream |
95+
96+
### Task Config
97+
98+
| Parameter | Required | Default | Description |
99+
|-----------|----------|---------|-------------|
100+
| `name` | Yes | - | Name of the Snowflake task |
101+
| `description` | No | `''` | Comment on the task |
102+
| `warehouse` | No* | - | Warehouse to execute the task (*required if not serverless) |
103+
| `schedule` | No | - | How often to run (e.g., `'1 MINUTE'`) |
104+
| `when` | No | - | Condition to check before running |
105+
| `allow_overlapping_execution` | No | - | Allow concurrent executions |
106+
| `user_task_timeout_ms` | No | - | Task timeout in milliseconds |
107+
| `suspend_task_after_num_failures` | No | - | Auto-suspend after N failures |
108+
| `task_auto_retry_attempts` | No | - | Number of auto-retry attempts |
109+
110+
## Running Integration Tests
111+
112+
```bash
113+
# Set up environment variables
114+
cp integration_tests/test.env.sample integration_tests/test.env
115+
cp integration_tests/vars.env.sample integration_tests/vars.env
116+
# Edit both files with your Snowflake credentials
117+
118+
# Run tests
119+
./run_test.sh
120+
```
121+
122+
## Requirements
123+
124+
- dbt-core >= 1.9.1
125+
- dbt-snowflake >= 1.9.0
126+
- Snowflake account with CHANGE_TRACKING enabled on source tables

dbt_project.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
name: 'dbt_snowflake_streams_tasks'
2-
version: '0.0.1'
1+
name: 'dbt_streams_tasks'
2+
version: '0.0.2'
33
config-version: 2
44
require-dbt-version: [">=1.0.0", "<2.0.0"]
55
macro-paths: ["macros"]

integration_tests/dbt_project.yml

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11

2-
name: 'dbt_snowflake_streams_tasks_integration_tests'
2+
name: 'dbt_streams_tasks_integration_tests'
33
version: '1.0'
44

55
profile: 'integration_tests'
@@ -22,13 +22,10 @@ flags:
2222
use_colors: True
2323

2424
models:
25-
dbt_snowflake_streams_tasks_integration_tests:
25+
dbt_streams_tasks_integration_tests:
2626
customers:
2727
+enabled: True
2828

29-
30-
31-
tests:
32-
dbt_snowflake_streams_tasks_integration_tests:
33-
customers:
34-
+enabled: True
29+
dispatch:
30+
- macro_namespace: 'dbt_streams_tasks'
31+
search_order: ['dbt_streams_tasks_integration_tests', 'dbt_streams_tasks']
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{% macro cleanup() %}
2+
{# Clean up all test objects created during integration tests #}
3+
4+
{% set objects_to_drop = [
5+
"TASK transform_from_stream_test",
6+
"TASK transform_from_ref_test",
7+
"STREAM stream_from_source_test",
8+
"STREAM stream_from_ref_test",
9+
"TABLE " ~ [target.database, target.schema, 'STREAM_FROM_SOURCE']|join('.'),
10+
"TABLE " ~ [target.database, target.schema, 'STREAM_FROM_REF']|join('.'),
11+
"VIEW " ~ [target.database, target.schema, 'VIEW_ON_SOURCE']|join('.'),
12+
"TABLE " ~ [target.database, target.schema, 'testing_source']|join('.'),
13+
"TABLE " ~ [target.database, target.schema, 'testing_ref']|join('.')
14+
] %}
15+
16+
{% for obj in objects_to_drop %}
17+
{% set drop_sql = "DROP " ~ obj ~ " IF EXISTS" %}
18+
{% do log('Dropping: ' ~ obj, info=true) %}
19+
{% do run_query(drop_sql) %}
20+
{% endfor %}
21+
22+
{% endmacro %}
Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
{% macro insert_data() %}
22

3-
{% set test_table = [target.database, target.schema, 'testing_source']|join('.') %}
3+
{% set tables = [
4+
[target.database, target.schema, 'testing_source']|join('.'),
5+
[target.database, target.schema, 'testing_ref']|join('.')
6+
] %}
47

5-
{% set insert_data_sql %}
6-
begin;
7-
insert into {{test_table}} values (1, 'test data');
8-
commit;
9-
{% endset %}
8+
{% for table in tables %}
9+
{% set insert_data_sql %}
10+
INSERT INTO {{ table }} VALUES (2, 'new stream data')
11+
{% endset %}
1012

11-
{% do log('Inserting data into testing table ' ~ test_table, info = true) %}
12-
{% do run_query(insert_data_sql) %}
13+
{% do log('Inserting data into ' ~ table, info = true) %}
14+
{% do run_query(insert_data_sql) %}
15+
{% endfor %}
1316

1417
{% endmacro %}
Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,36 @@
11
{% macro prep_structure() %}
22

3-
{% set create_schema_sql = "CREATE SCHEMA IF NOT EXISTS " ~ target.schema %}
3+
{% set create_schema_sql = "CREATE SCHEMA IF NOT EXISTS " ~ target.database ~ "." ~ target.schema %}
44

55
{% do log('Creating schema ' ~ target.schema, info = true) %}
66
{% do run_query(create_schema_sql) %}
77

8-
{% set test_table = [
8+
{% set test_tables = [
99
[target.database, target.schema, 'testing_source']|join('.'),
1010
[target.database, target.schema, 'testing_ref']|join('.'),
1111
] %}
1212

13-
{% for table in test_table %}
13+
{% for table in test_tables %}
1414
{% set create_testing_table_sql %}
15-
begin;
16-
create or replace table {{ table }} (
17-
id integer,
18-
data string
15+
CREATE OR REPLACE TABLE {{ table }} (
16+
id INTEGER,
17+
data STRING
1918
)
2019
CHANGE_TRACKING = TRUE
21-
;
22-
commit;
2320
{% endset %}
2421

2522
{% do log('Creating testing table ' ~ table, info = true) %}
2623
{% do run_query(create_testing_table_sql) %}
2724
{% endfor %}
2825

26+
{# Insert initial data into both source tables #}
27+
{% for table in test_tables %}
28+
{% set insert_data_sql %}
29+
INSERT INTO {{ table }} VALUES (1, 'initial data')
30+
{% endset %}
2931

30-
{% set test_table = [target.database, target.schema, 'testing_ref']|join('.') %}
31-
32-
{% set insert_data_sql %}
33-
begin;
34-
insert into {{test_table}} values (1, 'test data');
35-
commit;
36-
{% endset %}
37-
38-
{% do log('Inserting data into testing table ' ~ test_table, info = true) %}
39-
{% do run_query(insert_data_sql) %}
40-
32+
{% do log('Inserting initial data into ' ~ table, info = true) %}
33+
{% do run_query(insert_data_sql) %}
34+
{% endfor %}
4135

4236
{% endmacro %}

integration_tests/models/customers/_models.yml

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,16 @@ models:
88
- id
99
stream:
1010
name: "stream_from_source_test"
11-
description: "Stream for the customers table"
12-
tags:
13-
- customers
14-
copy_grants: False
15-
append_only: True
11+
description: "Stream for the customers source table"
12+
copy_grants: false
13+
append_only: true
14+
show_initial_rows: true
1615
task:
1716
name: "transform_from_stream_test"
18-
description: "Task for the customers table"
17+
description: "Task to merge stream data into stream_from_source"
18+
warehouse: "{{ env_var('SNOWFLAKE_WAREHOUSE', 'COMPUTE_WH') }}"
1919
when: "SYSTEM$STREAM_HAS_DATA('stream_from_source_test')"
20-
schedule: "1 MINUTES"
20+
schedule: "1 MINUTE"
2121
columns:
2222
- name: id
2323

@@ -28,13 +28,13 @@ models:
2828
- id
2929
stream:
3030
name: "stream_from_ref_test"
31-
description: "Stream for the customers table"
32-
tags:
33-
- customers
34-
copy_grants: True
35-
append_only: False
31+
description: "Stream for the view_on_source view"
32+
copy_grants: false
33+
append_only: true
34+
show_initial_rows: true
3635
task:
3736
name: "transform_from_ref_test"
38-
description: "Task for the customers table"
37+
description: "Task to merge stream data into stream_from_ref"
38+
warehouse: "{{ env_var('SNOWFLAKE_WAREHOUSE', 'COMPUTE_WH') }}"
3939
when: "SYSTEM$STREAM_HAS_DATA('stream_from_ref_test')"
40-
schedule: "1 MINUTES"
40+
schedule: "1 MINUTE"

0 commit comments

Comments
 (0)