Skip to content

Commit 9f592cc

Browse files
jonhopper-dataengineersCortex Code
andauthored
Add Data Metric Function cleanup support (#28)
DMFs are now cleaned as a separate object type to prevent them from being incorrectly dropped by the regular function cleanup macro. .... Generated with [Cortex Code](https://docs.snowflake.com/en/user-guide/cortex-code/cortex-code) Co-authored-by: Cortex Code <noreply@snowflake.com>
1 parent be30aac commit 9f592cc

11 files changed

Lines changed: 89 additions & 6 deletions

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
# Data Engineers Snowflake DataOps Utils Project Changelog
22
This file contains the changelog for the Data Engineers Snowflake DataOps Utils project, detailing updates, fixes, and enhancements made to the project over time.
33

4+
## v1.0.3 - 2026-05-16 - Data Metric Function Cleanup Support
5+
6+
### Added
7+
- Added `clean_data_metric_functions` macro to reconcile dbt-defined Data Metric Functions (DMFs) against deployed DMFs in Snowflake and drop orphaned ones. DMFs are identified via `information_schema.functions` where `is_data_metric = 'YES'` and matched against dbt graph nodes with `config.materialized = 'data_metric_function'`.
8+
- Added `data_metric_functions` as a supported `object_type` in the `clean_objects` orchestrator macro (included in the default list).
9+
10+
### Changed
11+
- Modified `clean_functions` macro to exclude Data Metric Functions (`is_data_metric = 'NO'` filter) so DMFs are not incorrectly dropped by the regular function cleanup. DMFs are now handled exclusively by `clean_data_metric_functions`.
12+
- Added `{% if execute %}` guards to all clean macros (`clean_functions`, `clean_generic`, `clean_models`, `clean_schemas`, `clean_stale_models`) to prevent `run_query()` and `graph.nodes` access during parsing or docs generation.
13+
- Bumped version from 1.0.2 to 1.0.3
14+
415
## v1.0.2 - 2026-05-12 - Clean Functions Signature Fallback Fix
516

617
### Fixed

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
A macro-only [dbt](https://github.com/dbt-labs/dbt) package for Snowflake DataOps. Provides utilities for object lifecycle management, RBAC grant orchestration, dimensional modelling helpers, tagging, shares, and more.
44

5-
- **Version**: 1.0.0
5+
- **Version**: 1.0.3
66
- **dbt**: `>=1.3.0, <3.0.0`
77
- **Dependencies**: None (zero external package dependencies)
88
- **dbt Fusion**: Compatible
@@ -16,7 +16,7 @@ Add the following to your `packages.yml`:
1616
```yaml
1717
packages:
1818
- git: https://github.com/DataEngineersNZ/dbt-snowflake-datops-utils.git
19-
revision: "1.0.0"
19+
revision: "1.0.3"
2020
```
2121
2222
Then run `dbt deps`.
@@ -62,7 +62,8 @@ The following `vars` can be set in your `dbt_project.yml` or via `--vars` on the
6262
| `clean_objects(database, clean_targets, object_types)` | Orchestrate all clean macros for specified object types and environments |
6363
| `clean_schemas(database, dry_run)` | Drop schemas not defined in the dbt project |
6464
| `clean_models(database, dry_run)` | Drop orphaned tables/views/dynamic tables/external tables/materialized views |
65-
| `clean_functions(database, dry_run)` | Drop orphaned UDFs and stored procedures |
65+
| `clean_functions(database, dry_run)` | Drop orphaned UDFs and stored procedures (excludes DMFs) |
66+
| `clean_data_metric_functions(database, dry_run)` | Drop orphaned Data Metric Functions (DMFs) |
6667
| `clean_generic(object_type, database, dry_run)` | Drop orphaned tasks/streams/stages/alerts/file formats/network rules/secrets/semantic views/agents |
6768
| `clean_stale_models(database, schema, days, dry_run)` | Drop models older than N days from a specific schema |
6869

@@ -286,7 +287,7 @@ The `clean_objects` macro orchestrates removal of Snowflake objects not defined
286287
dbt run-operation clean_objects --args '{"clean_targets": ["prod"], "object_types": ["schemas", "tables_and_views", "functions_and_procedures"]}'
287288
```
288289

289-
Supported `object_types`: `schemas`, `functions_and_procedures`, `tasks`, `streams`, `stages`, `tables_and_views`, `alerts`, `file_formats`, `semantic_views`, `agents`.
290+
Supported `object_types`: `schemas`, `functions_and_procedures`, `data_metric_functions`, `tasks`, `streams`, `stages`, `tables_and_views`, `alerts`, `file_formats`, `semantic_views`, `agents`.
290291

291292
All clean macros support `dry_run` mode (default: `true`) to preview drops before executing.
292293

dbt_project.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: 'dbt_dataengineers_utils'
2-
version: '1.0.2'
2+
version: '1.0.3'
33
config-version: 2
44

55
require-dbt-version: [">=1.9.4", "<3.0.0"]

macros/clean/clean.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,18 @@ macros:
5656
description: List of object types to clean up
5757

5858

59+
- name: clean_data_metric_functions
60+
description: Reconcile dbt-defined data metric functions vs deployed; drop any orphaned DMFs in Snowflake.
61+
docs:
62+
show: true
63+
arguments:
64+
- name: database
65+
type: string
66+
description: target database
67+
- name: dry_run
68+
type: boolean
69+
description: specifies if the macro should run in dry run mode
70+
5971
- name: clean_schemas
6072
description: Reconcile schema list vs dbt; drop schemas no longer defined.
6173
docs:
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
{% macro clean_data_metric_functions(database=target.database, dry_run=True) %}
2+
{% if execute %}
3+
{% set snowflake_dmfs_to_drop = [] %}
4+
{% set nodes = graph.nodes.values() if graph.nodes else [] %}
5+
6+
{% set get_snowflake_dmfs %}
7+
SELECT
8+
function_schema AS schema,
9+
function_name AS name,
10+
argument_signature AS argument_signature
11+
FROM {{ database }}.information_schema.functions
12+
WHERE is_data_metric = 'YES'
13+
AND function_schema NOT IN ('INFORMATION_SCHEMA', 'META', 'PUBLIC', 'PUBLIC_META', 'SCHEMACHANGE', 'UNIT_TESTS')
14+
{% endset %}
15+
16+
{% set snowflake_dmf_results = run_query(get_snowflake_dmfs) %}
17+
18+
{% for result in snowflake_dmf_results %}
19+
{% set dbt_models = [] %}
20+
{% set sql_object_schema = result.values()[0] %}
21+
{% set sql_object_name = result.values()[1] %}
22+
{% set sql_arguments = result.values()[2] %}
23+
24+
{# Match against dbt graph nodes with materialized='data_metric_function' #}
25+
{# Uses selectattr for materialized check (consistent with clean_generic) #}
26+
{# Then checks both node.name and override_name (consistent with has_matching_nodes) #}
27+
{% set matching_nodes = nodes
28+
| selectattr("schema", "equalto", sql_object_schema | lower)
29+
| selectattr("config.materialized", "equalto", "data_metric_function")
30+
%}
31+
{% for node in matching_nodes %}
32+
{% set node_name = node.config.get("meta", {}).get("override_name", node.config.get("override_name", node.name)) %}
33+
{% if node_name | lower == sql_object_name | lower %}
34+
{% do dbt_models.append(node.schema ~ "." ~ node.name) %}
35+
{% endif %}
36+
{% endfor %}
37+
38+
{% if dbt_models | length == 0 %}
39+
{% do snowflake_dmfs_to_drop.append(sql_object_schema ~ "." ~ sql_object_name ~ sql_arguments) %}
40+
{% endif %}
41+
{% endfor %}
42+
43+
{% do dbt_dataengineers_utils.drop_object("FUNCTION", database, snowflake_dmfs_to_drop, dry_run) %}
44+
{% endif %}
45+
{% endmacro %}

macros/clean/clean_functions.sql

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{% macro clean_functions(database=target.database, dry_run=True) %}
2+
{% if execute %}
23
{% set snowflake_functions_to_drop = [] %}
34
{% set snowflake_procedures_to_drop = [] %}
45
{% set ns = namespace(sql_arguments="") %}
@@ -13,6 +14,7 @@
1314
argument_signature AS argument_signature
1415
FROM {{ database }}.information_schema.functions
1516
WHERE schema NOT IN ('INFORMATION_SCHEMA', 'META', 'PUBLIC', 'PUBLIC_META', 'SCHEMACHANGE', 'UNIT_TESTS')
17+
AND is_data_metric = 'NO'
1618
UNION ALL
1719
SELECT
1820
'PROCEDURE' AS object_type,
@@ -71,4 +73,5 @@
7173
{% do dbt_dataengineers_utils.drop_object("PROCEDURE", database, snowflake_procedures_to_drop, dry_run) %}
7274
{% do dbt_dataengineers_utils.drop_object("FUNCTION", database, snowflake_functions_to_drop, dry_run) %}
7375

76+
{% endif %}
7477
{% endmacro %}

macros/clean/clean_generic.sql

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{% macro clean_generic(object_type, database=target.database, dry_run=True) %}
2+
{% if execute %}
23
{% set snowflake_objects_to_drop = [] %}
34
{% set nodes = graph.nodes.values() if graph.nodes else [] %}
45
{% set schema_index = 3 %}
@@ -48,4 +49,5 @@
4849
{% endfor %}
4950

5051
{% do dbt_dataengineers_utils.drop_object(object_type, database, snowflake_objects_to_drop, dry_run) %}
52+
{% endif %}
5153
{% endmacro %}

macros/clean/clean_models.sql

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{% macro clean_models(database=target.database, dry_run=True) %}
2+
{% if execute %}
23
{% set snowflake_views_to_drop = [] %}
34
{% set snowflake_materialized_views_to_drop = [] %}
45
{% set snowflake_tables_to_drop = [] %}
@@ -67,4 +68,5 @@
6768
{% do dbt_dataengineers_utils.drop_object("TABLE", database, snowflake_tables_to_drop, dry_run) %}
6869
{% do dbt_dataengineers_utils.drop_object("EXTERNAL TABLE", database, snowflake_external_tables_to_drop, dry_run) %}
6970
{% do dbt_dataengineers_utils.drop_object("DYNAMIC TABLE", database, snowflake_dynamic_tables_to_drop, dry_run) %}
71+
{% endif %}
7072
{% endmacro %}

macros/clean/clean_objects.sql

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{% macro clean_objects(database=target.database, clean_targets=['local-dev', 'unit-test', 'test', 'prod'], object_types= ['schemas', 'functions_and_procedures', 'tasks', 'streams', 'stages', 'tables_and_views', 'alerts', 'file_formats', 'semantic_views', 'agents']) %}
1+
{% macro clean_objects(database=target.database, clean_targets=['local-dev', 'unit-test', 'test', 'prod'], object_types= ['schemas', 'functions_and_procedures', 'data_metric_functions', 'tasks', 'streams', 'stages', 'tables_and_views', 'alerts', 'file_formats', 'semantic_views', 'agents']) %}
22
{%if execute %}
33
{% if flags.WHICH in ('run', 'run-operation') %}
44
{% if target.name in clean_targets %}
@@ -12,6 +12,9 @@
1212
{% if 'functions_and_procedures' in object_types %}
1313
{% do dbt_dataengineers_utils.clean_functions(database, dry_run) %}
1414
{% endif %}
15+
{% if 'data_metric_functions' in object_types %}
16+
{% do dbt_dataengineers_utils.clean_data_metric_functions(database, dry_run) %}
17+
{% endif %}
1518
{% if 'tasks' in object_types %}
1619
{% do dbt_dataengineers_utils.clean_generic("TASK", database, dry_run) %}
1720
{% endif %}

macros/clean/clean_schemas.sql

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{% macro clean_schemas(database=target.database, dry_run=True) %}
2+
{% if execute %}
23
{% set snowflake_schemas_to_drop = [] %}
34
{% set nodes = graph.nodes.values() if graph.nodes else [] %}
45
{% set sources = graph.sources.values() if graph.sources else [] %}
@@ -28,4 +29,5 @@
2829
{% endfor %}
2930

3031
{% do dbt_dataengineers_utils.drop_object("SCHEMA", database, snowflake_schemas_to_drop, dry_run) %}
32+
{% endif %}
3133
{% endmacro %}

0 commit comments

Comments
 (0)