Skip to content

Commit 3111418

Browse files
feat: add DuckDB support with macro dispatches and CI configuration (#935)
1 parent 4527f5d commit 3111418

23 files changed

Lines changed: 241 additions & 6 deletions

.github/workflows/test-all-warehouses.yml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,20 @@ on:
3535
required: false
3636

3737
jobs:
38-
# ── Docker targets ────────────────────────────────────────────────────
38+
# ── Local targets ────────────────────────────────────────────────────
3939
# No secrets needed — run on pull_request (works for forks without approval).
4040
# Skipped on pull_request_target to avoid duplicate runs for internal PRs.
41-
test-docker:
41+
# Includes Docker-based adapters (postgres, clickhouse, trino, dremio) and
42+
# fully in-process adapters (duckdb).
43+
test-local:
4244
if: github.event_name != 'pull_request_target'
4345
strategy:
4446
fail-fast: false
4547
matrix:
4648
dbt-version:
4749
${{ inputs.dbt-version && fromJSON(format('["{0}"]', inputs.dbt-version)) ||
4850
fromJSON('["latest_official", "latest_pre"]') }}
49-
warehouse-type: [postgres, clickhouse, trino, dremio]
51+
warehouse-type: [postgres, clickhouse, trino, dremio, duckdb]
5052
exclude:
5153
# latest_pre is only tested on postgres
5254
- dbt-version: latest_pre
@@ -55,6 +57,8 @@ jobs:
5557
warehouse-type: trino
5658
- dbt-version: latest_pre
5759
warehouse-type: dremio
60+
- dbt-version: latest_pre
61+
warehouse-type: duckdb
5862
uses: ./.github/workflows/test-warehouse.yml
5963
with:
6064
warehouse-type: ${{ matrix.warehouse-type }}

.github/workflows/test-warehouse.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ on:
1717
- trino
1818
- clickhouse
1919
- dremio
20+
- duckdb
2021
elementary-ref:
2122
type: string
2223
required: false

integration_tests/dbt_project/macros/clear_env.sql

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,8 @@
1818
{% do run_query("DROP DATABASE IF EXISTS " ~ schema_name) %}
1919
{% do adapter.commit() %}
2020
{% endmacro %}
21+
22+
{% macro duckdb__edr_drop_schema(database_name, schema_name) %}
23+
{% do run_query("DROP SCHEMA IF EXISTS " ~ schema_name ~ " CASCADE") %}
24+
{% do adapter.commit() %}
25+
{% endmacro %}

integration_tests/profiles/profiles.yml.j2

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ elementary_tests:
22
target: postgres
33
outputs:
44

5-
# ── Docker targets (plaintext, no secrets needed) ─────────────────
5+
# ── Local targets (plaintext, no secrets needed) ─────────────────
66

77
postgres: &postgres
88
type: postgres
@@ -46,6 +46,12 @@ elementary_tests:
4646
schema: {{ schema_name }}
4747
threads: 4
4848

49+
duckdb: &duckdb
50+
type: duckdb
51+
path: ":memory:"
52+
schema: {{ schema_name }}
53+
threads: 8
54+
4955
# ── Cloud targets (secrets substituted at CI time) ─────────────────
5056

5157
snowflake: &snowflake
@@ -106,7 +112,7 @@ elementary_tests:
106112
elementary:
107113
target: postgres
108114
outputs:
109-
{%- set targets = ['postgres', 'clickhouse', 'trino', 'dremio', 'snowflake', 'bigquery', 'redshift', 'databricks_catalog', 'athena'] %}
115+
{%- set targets = ['postgres', 'clickhouse', 'trino', 'dremio', 'duckdb', 'snowflake', 'bigquery', 'redshift', 'databricks_catalog', 'athena'] %}
110116
{%- for t in targets %}
111117
{{ t }}:
112118
<<: *{{ t }}

macros/edr/metadata_collection/get_columns_from_information_schema.sql

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,21 @@
8989
{{ elementary.empty_table([('full_table_name', 'string'), ('database_name', 'string'), ('schema_name', 'string'), ('table_name', 'string'), ('column_name', 'string'), ('data_type', 'string')]) }}
9090
{% endmacro %}
9191

92+
{% macro duckdb__get_columns_from_information_schema(database_name, schema_name, table_name = none) %}
93+
select
94+
upper(table_catalog || '.' || table_schema || '.' || table_name) as full_table_name,
95+
upper(table_catalog) as database_name,
96+
upper(table_schema) as schema_name,
97+
upper(table_name) as table_name,
98+
upper(column_name) as column_name,
99+
data_type
100+
from information_schema.columns
101+
where upper(table_schema) = upper('{{ schema_name }}') and upper(table_catalog) = upper('{{ database_name }}')
102+
{% if table_name %}
103+
and upper(table_name) = upper('{{ table_name }}')
104+
{% endif %}
105+
{% endmacro %}
106+
92107
{% macro athena__get_columns_from_information_schema(database_name, schema_name, table_name = none) %}
93108
{{ elementary.empty_table([('full_table_name', 'string'), ('database_name', 'string'), ('schema_name', 'string'), ('table_name', 'string'), ('column_name', 'string'), ('data_type', 'string')]) }}
94109
{% endmacro %}

macros/edr/metadata_collection/get_tables_from_information_schema.sql

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,29 @@
8787
from information_schema_tables
8888
{% endmacro %}
8989

90+
{% macro duckdb__get_tables_from_information_schema(schema_tuple) %}
91+
{%- set database_name, schema_name = schema_tuple %}
92+
93+
with information_schema_tables as (
94+
95+
select
96+
upper(table_catalog) as database_name,
97+
upper(table_schema) as schema_name,
98+
upper(table_name) as table_name
99+
from information_schema.tables
100+
where upper(table_schema) = upper('{{ schema_name }}') and upper(table_catalog) = upper('{{ database_name }}')
101+
102+
)
103+
104+
select
105+
{{ elementary.full_table_name() }} as full_table_name,
106+
upper(database_name || '.' || schema_name) as full_schema_name,
107+
database_name,
108+
schema_name,
109+
table_name
110+
from information_schema_tables
111+
{% endmacro %}
112+
90113
{% macro databricks__get_tables_from_information_schema(schema_tuple) %}
91114
{%- set database_name, schema_name = schema_tuple %}
92115
{% set schema_relation = api.Relation.create(database=database_name, schema=schema_name).without_identifier() %}

macros/edr/system/system_utils/buckets_cte.sql

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,21 @@
144144
{{ return(complete_buckets_cte) }}
145145
{% endmacro %}
146146

147+
{% macro duckdb__complete_buckets_cte(time_bucket, bucket_end_expr, min_bucket_start_expr, max_bucket_end_expr) %}
148+
{%- set complete_buckets_cte %}
149+
select
150+
unnest(generate_series({{ min_bucket_start_expr }}, {{ max_bucket_end_expr }}, interval '{{ time_bucket.count }} {{ time_bucket.period }}')) as edr_bucket_start
151+
{%- endset %}
152+
{%- set complete_buckets_cte %}
153+
select
154+
edr_bucket_start,
155+
{{ bucket_end_expr }} as edr_bucket_end
156+
from ({{ complete_buckets_cte }}) as _buckets
157+
where {{ bucket_end_expr }} <= {{ max_bucket_end_expr }}
158+
{%- endset %}
159+
{{ return(complete_buckets_cte) }}
160+
{% endmacro %}
161+
147162
{% macro dremio__complete_buckets_cte(time_bucket, bucket_end_expr, min_bucket_start_expr, max_bucket_end_expr) %}
148163
{%- set complete_buckets_cte %}
149164
with integers as (

macros/edr/system/system_utils/full_names.sql

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,20 @@
8888
{% endmacro %}
8989

9090

91+
{% macro duckdb__full_name_split(part_name) %}
92+
{%- if part_name == 'database_name' -%}
93+
{%- set part_index = 1 -%}
94+
{%- elif part_name == 'schema_name' -%}
95+
{%- set part_index = 2 -%}
96+
{%- elif part_name == 'table_name' -%}
97+
{%- set part_index = 3 -%}
98+
{%- else -%}
99+
{{ return('') }}
100+
{%- endif -%}
101+
trim(split_part(full_table_name,'.',{{ part_index }}),'"') as {{ part_name }}
102+
{% endmacro %}
103+
104+
91105
{% macro databricks__full_name_split(part_name) %}
92106
{%- if part_name == 'database_name' -%}
93107
{%- set part_index = 0 -%}
@@ -156,4 +170,4 @@
156170
{% set schemas_tuple = elementary.strings_list_to_tuple(schemas_list) %}
157171
{{ return(schemas_tuple) }}
158172

159-
{% endmacro %}
173+
{% endmacro %}

macros/edr/tests/on_run_end/handle_tests_results.sql

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,11 @@
132132
{%- do elementary.edr_create_table_as(true, temp_relation, test_tables_union_query) %}
133133
{% do elementary.run_query(insert_query) %}
134134
135+
{# DuckDB: commit so insert survives dbt's post-on-run-end ROLLBACK #}
136+
{% if target.type == 'duckdb' %}
137+
{% do adapter.commit() %}
138+
{% endif %}
139+
135140
{% if not elementary.has_temp_table_support() %}
136141
{% do elementary.fully_drop_relation(temp_relation) %}
137142
{% endif %}
@@ -181,6 +186,11 @@
181186
{%- do elementary.edr_create_table_as(true, temp_relation, test_tables_union_query) %}
182187
{% do elementary.run_query(insert_query) %}
183188

189+
{# DuckDB: commit so insert survives dbt's post-on-run-end ROLLBACK #}
190+
{% if target.type == 'duckdb' %}
191+
{% do adapter.commit() %}
192+
{% endif %}
193+
184194
{% if not elementary.has_temp_table_support() %}
185195
{% do elementary.fully_drop_relation(temp_relation) %}
186196
{% endif %}

macros/edr/tests/test_utils/clean_elementary_test_tables.sql

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@
6969
{% do return(elementary.get_transactionless_clean_elementary_test_tables_queries(test_table_relations)) %}
7070
{% endmacro %}
7171

72+
{% macro duckdb__get_clean_elementary_test_tables_queries(test_table_relations) %}
73+
{% do return(elementary.get_transactionless_clean_elementary_test_tables_queries(test_table_relations)) %}
74+
{% endmacro %}
75+
7276
{% macro dremio__get_clean_elementary_test_tables_queries(test_table_relations) %}
7377
{% do return(elementary.get_transactionless_clean_elementary_test_tables_queries(test_table_relations)) %}
7478
{% endmacro %}

0 commit comments

Comments
 (0)