Initial publish of dbt_f2dq (f2dq) package v0.12.1.
Clean package repo for Gitea installs via packages.yml git URL. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
|
||||
{% macro get_model_config(monitored) %}
|
||||
{% set model = {} %}
|
||||
{% do model.update({'name': f2dq.row_value(monitored, 'name')}) %}
|
||||
{% do model.update({'schema': f2dq.row_value(monitored, 'schema')}) %}
|
||||
{% do model.update({'database': f2dq.row_value(monitored, 'database')}) %}
|
||||
{% do model.update({'time_filter': f2dq.row_value(monitored, 'time_filter')}) %}
|
||||
{% do model.update({'metrics': fromjson(f2dq.row_value(monitored, 'metrics'))}) %}
|
||||
{% do model.update({'model_name': model.get('database') + '.' + model.get('schema') + '.' + model.get('name')}) %}
|
||||
{% do model.update({'table_name': full_table_name_values(model.get('name'), model.get('schema'), model.get('database'))}) %}
|
||||
|
||||
{% set columns_db = f2dq.row_value(monitored, 'columns') %}
|
||||
|
||||
{% set column_list = fromjson(columns_db) if columns_db is not none else none %}
|
||||
{% set columns_dict = f2dq.dict_from_list(column_list) %}
|
||||
|
||||
{% do model.update({'columns_dict': columns_dict}) %}
|
||||
{% do model.update({'columns_compute_all': columns_dict is none}) %}
|
||||
|
||||
{% set columns_query %}
|
||||
select * from {{ ref('f2dq_columns') }}
|
||||
where name = '{{ model.name }}' and schema = '{{ model.schema }}' and database = '{{ model.database }}'
|
||||
{% endset %}
|
||||
{% set columns = run_query(columns_query) %}
|
||||
|
||||
{% set columns_info = {} %}
|
||||
{% for col in columns %}
|
||||
{% set column_name = f2dq.row_value(col, 'column_name') %}
|
||||
{% set data_type = f2dq.get_column_type(col) %}
|
||||
{% do columns_info.update({column_name: { 'data_type': data_type }}) %}
|
||||
{% endfor %}
|
||||
|
||||
{% do model.update({'columns_info': columns_info}) %}
|
||||
{% do model.update({'columns': columns}) %}
|
||||
|
||||
{{ return(model) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro should_compute_metric(model, column_name) %}
|
||||
{{ return(model.columns_compute_all or model.columns_dict.get(column_name)) }}
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,4 @@
|
||||
{% macro bigquery__quote_column_name(column_name) %}
|
||||
{% set quoted_col_name = '`' + column_name + '`' %}
|
||||
{{ return(quoted_col_name) }}
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
{%- macro bigquery__quote_string(str) %}
|
||||
r"""{{ str }}"""
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,3 @@
|
||||
{% macro bigquery__split_and_return_nth_value(column_name, delimiter, ordinal) %}
|
||||
split({{ f2dq.clean_blacklist(column_name, ['"', '`'], '') }}, '{{ delimiter }}')[ORDINAL( {{ ordinal }} )]
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
{% macro escape_seq_for_json(chr) %}{{adapter.dispatch('escape_seq_for_json', 'f2dq')(chr)}}{% endmacro %}
|
||||
|
||||
{% macro default__escape_seq_for_json(chr) %}'\\\{{chr}}'{% endmacro %}
|
||||
@@ -0,0 +1,10 @@
|
||||
|
||||
{% macro name_in_db(name) %}
|
||||
{% set translated = adapter.dispatch('name_in_db', 'f2dq')(name) %}
|
||||
{{ return(translated) }}
|
||||
|
||||
{% endmacro %}
|
||||
|
||||
{% macro default__name_in_db(name) %}
|
||||
{{ return(name) }}
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,10 @@
|
||||
{% macro quote_column_name(column_name) %}
|
||||
{% set col_name = adapter.dispatch('quote_column_name', 'f2dq')(column_name) %}
|
||||
{{ return(col_name) }}
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro default__quote_column_name(column_name) %}
|
||||
{% set quoted_col_name = '"' + column_name + '"' %}
|
||||
{{ return(quoted_col_name) }}
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
{%- macro quote_string(str) %}
|
||||
{{ adapter.dispatch('quote_string', 'f2dq')(str) }}
|
||||
{% endmacro %}
|
||||
|
||||
{%- macro default__quote_string(str) %}
|
||||
$${{ str }}$$
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{%- macro quote_new_line() %}{{ adapter.dispatch('quote_new_line', 'f2dq')() }}{% endmacro %}
|
||||
|
||||
{%- macro default__quote_new_line() %}'\134\134n'{% endmacro %}
|
||||
@@ -0,0 +1,7 @@
|
||||
{% macro split_and_return_nth_value(column_name, delimiter, ordinal) -%}
|
||||
{{ adapter.dispatch('split_and_return_nth_value', 'f2dq')(column_name, delimiter, ordinal) }}
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro default__split_and_return_nth_value(column_name, delimiter, ordinal) -%}
|
||||
split_part({{ f2dq.clean_blacklist(column_name, ['"', '`'], '') }}, '{{ delimiter }}', {{ ordinal }})
|
||||
{%- endmacro %}
|
||||
@@ -0,0 +1 @@
|
||||
{% macro postgres__escape_seq_for_json(chr) %}'\{{chr}}'{% endmacro %}
|
||||
@@ -0,0 +1,3 @@
|
||||
{% macro postgres_type_db() %}
|
||||
{{ ('postgres', 'greenplum') }}
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
|
||||
{%- macro postgres__quote_new_line() %}'\\n'{% endmacro %}
|
||||
@@ -0,0 +1,2 @@
|
||||
|
||||
{% macro redshift__escape_seq_for_json(chr) %}'\\\{{chr}}'{% endmacro %}
|
||||
@@ -0,0 +1,2 @@
|
||||
|
||||
{%- macro redshift__quote_new_line() %}'\134\134n'{% endmacro %}
|
||||
@@ -0,0 +1,8 @@
|
||||
|
||||
{% macro snowflake__name_in_db(name) %}
|
||||
{% if name %}
|
||||
{{ return (name.upper()) }}
|
||||
{% else %}
|
||||
{{ return (name) }}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,97 @@
|
||||
{% macro pub_monitored_from_graph() %}
|
||||
{% set monitored = [] %}
|
||||
{% set both = []%}
|
||||
{% do both.extend(graph.nodes.values()) %}
|
||||
{% do both.extend(graph.sources.values()) %}
|
||||
{% set owners_config = f2dq.get_owners_config() %}
|
||||
|
||||
{% set select_var = f2dq.f2dq_var('select', none) %}
|
||||
{% set select_all = true %}
|
||||
|
||||
{% set selected_nodes = none %}
|
||||
{% set selected_tags = none %}
|
||||
|
||||
{% if select_var is not none %}
|
||||
{% set select_all = false %}
|
||||
{% set selected_nodes = dict() %}
|
||||
{% set selected_tags = dict() %}
|
||||
|
||||
{% for el in select_var %}
|
||||
{% if el.startswith('tag:') %}
|
||||
{% do selected_tags.update({el[4:]: True}) %}
|
||||
{% else %}
|
||||
{% do selected_nodes.update({el: True}) %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% for el in both %}
|
||||
{% if el.resource_type in ['model', 'seed', 'source'] %}
|
||||
{% if f2dq.f2dq_node_config(el, 'monitored') %}
|
||||
{% set target_name = el.identifier or el.alias or el.name %}
|
||||
|
||||
{% if select_all %}
|
||||
{% set selected = true %}
|
||||
{% else %}
|
||||
{% set selected_name = selected_nodes.get(target_name, false) %}
|
||||
{% set selected_tag = [] %}
|
||||
|
||||
{% for tag in el.tags %}
|
||||
{% if selected_tags.get(tag, false) %}
|
||||
{% do selected_tag.append(true) %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% set selected = selected_name or (selected_tag | length > 0) %}
|
||||
{% endif %}
|
||||
|
||||
{% set metrics_groups = f2dq.f2dq_node_config(el, 'metrics_groups', f2dq.f2dq_var('default_metrics')) %}
|
||||
{% set additional_metrics = f2dq.f2dq_node_config(el, 'metrics', {}) %}
|
||||
|
||||
{% do monitored.append({
|
||||
'name': f2dq.name_in_db(target_name),
|
||||
'schema': f2dq.name_in_db(el.schema),
|
||||
'database': f2dq.name_in_db(el.database),
|
||||
'time_filter': f2dq.f2dq_node_config(el, 'time_filter', none),
|
||||
'metrics_groups': metrics_groups,
|
||||
'additional_metrics': f2dq.metrics_in_db(additional_metrics),
|
||||
'metrics': f2dq.metrics_in_db(f2dq.final_metrics(metrics_groups, additional_metrics)),
|
||||
'columns': f2dq.columns_in_db(f2dq.f2dq_node_config(el, 'columns', none)),
|
||||
'anomaly_detector': f2dq.f2dq_node_config(el, 'anomaly_detector', f2dq.f2dq_var('anomaly_detector', {})),
|
||||
'owners': f2dq.prepare_model_owners(f2dq.f2dq_node_config(el, 'owners', []), owners_config),
|
||||
'selected': selected
|
||||
})
|
||||
%}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{{ return(monitored) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro get_owners_config() %}
|
||||
{% set owners_config = f2dq.f2dq_var('owners_config', {}) %}
|
||||
{{ return (owners_config) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro prepare_model_owners(f2dq_owners, owners_config) %}
|
||||
{% set owners = {} %}
|
||||
{% set seen_identifiers = {} %}
|
||||
{% for owner in f2dq_owners if owners_config.get(owner) %}
|
||||
{% set members = owners_config.get(owner) %}
|
||||
{% for member in members %}
|
||||
{% set identifier = member.get('identifier') %}
|
||||
{% if identifier not in seen_identifiers %}
|
||||
{% do seen_identifiers.update({identifier: true }) %}
|
||||
{% do owners.update({
|
||||
identifier: {
|
||||
'notify_channel': member.get('type'),
|
||||
'owner': owner,
|
||||
'name': member.get('name')
|
||||
}
|
||||
}) %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{{ return (owners) }}
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,32 @@
|
||||
{% macro get_monitored_columns(schema, database) %}
|
||||
{{ adapter.dispatch('get_monitored_columns', 'f2dq')(schema, database) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro default__get_monitored_columns(table_schema, db_name) %}
|
||||
{% set relation = api.Relation.create(database=db_name, schema=table_schema) %}
|
||||
select
|
||||
table_name,
|
||||
table_schema,
|
||||
table_catalog,
|
||||
column_name,
|
||||
data_type,
|
||||
is_nullable
|
||||
from
|
||||
{{ relation.information_schema('COLUMNS') }}
|
||||
where
|
||||
table_schema = '{{ table_schema }}'
|
||||
{% endmacro %}
|
||||
|
||||
{% macro redshift__get_monitored_columns(table_schema, db_name) %}
|
||||
select
|
||||
table_name,
|
||||
table_schema,
|
||||
table_catalog,
|
||||
column_name,
|
||||
data_type,
|
||||
is_nullable
|
||||
from
|
||||
svv_columns
|
||||
where
|
||||
table_schema = '{{ table_schema }}'
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,10 @@
|
||||
{% macro get_tables() %}
|
||||
select *
|
||||
from {{ ref('f2dq_selected') }}
|
||||
order by name, schema, database, time_filter
|
||||
{% endmacro %}
|
||||
|
||||
{% macro get_schemas() %}
|
||||
select distinct schema, database
|
||||
from {{ ref('f2dq_selected') }}
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,17 @@
|
||||
{% macro save_monitored(monitored_path) %}
|
||||
|
||||
{% set monitored_query %}
|
||||
select
|
||||
{{ full_table_name('name', 'schema', 'database') }} as {{ f2dq.quote_column('model') }},
|
||||
time_filter as {{ f2dq.quote_column('time_filter') }},
|
||||
metrics as {{ f2dq.quote_column('metrics') }},
|
||||
columns as {{ f2dq.quote_column('columns') }},
|
||||
anomaly_detector as {{ f2dq.quote_column('anomaly_detector') }},
|
||||
owners as {{ f2dq.quote_column('owners') }}
|
||||
from {{ ref('f2dq_selected') }}
|
||||
{% endset %}
|
||||
{% set query_result = run_query(monitored_query) %}
|
||||
{% set monitored_file_path = monitored_path or 'target/f2dq/monitored.json' %}
|
||||
{% do query_result.to_json(monitored_file_path) %}
|
||||
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,33 @@
|
||||
{% macro full_table_name(table_name, table_schema, table_catalog) %}
|
||||
{{ adapter.dispatch('full_table_name', 'f2dq')(table_name, table_schema, table_catalog) }}
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro default__full_table_name(table_name, table_schema, table_catalog) %}
|
||||
'"' || {{table_catalog}} || '"' || '.' || '"' || {{table_schema}} || '"' || '.' || '"' || {{table_name}} || '"'
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro bigquery__full_table_name(table_name, table_schema, table_catalog) %}
|
||||
'`' || {{table_catalog}} || '`' || '.' || '`' || {{table_schema}} || '`' || '.' || '`' || {{table_name}} || '`'
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro full_table_name_values(table_name, table_schema, table_catalog) %}
|
||||
{% set result = adapter.dispatch('full_table_name_values', 'f2dq')(table_name, table_schema, table_catalog) %}
|
||||
{{ return (result.strip()) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro default__full_table_name_values(table_name, table_schema, table_catalog) %}
|
||||
"{{table_catalog}}"."{{table_schema}}"."{{table_name}}"
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro bigquery__full_table_name_values(table_name, table_schema, table_catalog) %}
|
||||
`{{table_catalog}}`.`{{table_schema}}`.`{{table_name}}`
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro snowflake__full_table_name_values(table_name, table_schema, table_catalog) %}
|
||||
"{{table_catalog|upper}}"."{{table_schema|upper}}"."{{table_name|upper}}"
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,91 @@
|
||||
|
||||
{% macro f2dq_metric_max(context) %}
|
||||
max({{context.column_name}})
|
||||
{% endmacro %}
|
||||
|
||||
{% macro f2dq_metric_min(context) %}
|
||||
min({{context.column_name}})
|
||||
{% endmacro %}
|
||||
|
||||
{% macro f2dq_metric_avg(context) %}
|
||||
avg(cast ({{context.column_name}} as {{ numeric_type() }}))
|
||||
{% endmacro %}
|
||||
|
||||
{% macro f2dq_metric_stddev(context) %}
|
||||
stddev(cast ( {{context.column_name}} as {{ numeric_type() }}))
|
||||
{% endmacro %}
|
||||
|
||||
{% macro f2dq_metric_variance(context) %}
|
||||
variance(cast ( {{context.column_name}} as {{ numeric_type() }}))
|
||||
{% endmacro %}
|
||||
|
||||
{% macro f2dq_metric_max_length(context) %}
|
||||
max(length({{context.column_name}}))
|
||||
{% endmacro %}
|
||||
|
||||
{% macro f2dq_metric_min_length(context) %}
|
||||
min(length({{context.column_name}}))
|
||||
{% endmacro %}
|
||||
|
||||
{% macro f2dq_metric_avg_length(context) %}
|
||||
avg(cast (length( {{context.column_name}} ) as {{ numeric_type() }}))
|
||||
{% endmacro %}
|
||||
|
||||
{% macro f2dq_metric_nulls_count(context) %}
|
||||
coalesce(
|
||||
sum(
|
||||
case when {{context.column_name}} is null
|
||||
then 1
|
||||
else 0
|
||||
end
|
||||
), 0
|
||||
)
|
||||
{% endmacro %}
|
||||
|
||||
{% macro f2dq_metric_missing_count(context) %}
|
||||
coalesce(
|
||||
sum(
|
||||
case
|
||||
when {{context.column_name}} is null
|
||||
then 1
|
||||
when {{context.column_name}} = ''
|
||||
then 1
|
||||
else 0
|
||||
end
|
||||
), 0
|
||||
)
|
||||
{% endmacro %}
|
||||
|
||||
{% macro f2dq_metric_nulls_percent(context) %}
|
||||
{{ percentage_formula(f2dq_metric_nulls_count(context), f2dq_metric_row_count()) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro f2dq_metric_missing_percent(context) %}
|
||||
{{ percentage_formula(f2dq_metric_missing_count(context), f2dq_metric_row_count()) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro f2dq_metric_count_true(context) %}
|
||||
COALESCE(
|
||||
SUM(
|
||||
CASE
|
||||
WHEN {{ context.column_name }} IS TRUE THEN 1
|
||||
ELSE 0
|
||||
END
|
||||
),
|
||||
0
|
||||
)
|
||||
{% endmacro %}
|
||||
|
||||
{% macro f2dq_metric_count_false(context) %}
|
||||
COALESCE(
|
||||
SUM(
|
||||
CASE
|
||||
WHEN {{ context.column_name }} IS FALSE THEN 1
|
||||
ELSE 0
|
||||
END
|
||||
),
|
||||
0
|
||||
)
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
{% macro f2dq_metric_regex_count(column_name, pattern) %}
|
||||
coalesce(
|
||||
sum(
|
||||
case when {{ regex_match_expression(column_name, pattern) }}
|
||||
then 1
|
||||
else 0
|
||||
end
|
||||
), 0
|
||||
)
|
||||
{% endmacro %}
|
||||
|
||||
{% macro f2dq_metric_match_regex(context) %}
|
||||
{{ f2dq_metric_regex_count(context.column_name, context.config.regex) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro f2dq_metric_match_regex_percent(context) %}
|
||||
{{ percentage_formula(f2dq_metric_match_regex(context), f2dq_metric_row_count()) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro f2dq_metric_not_match_regex(context) %}
|
||||
{{ f2dq_metric_row_count() }} - {{ f2dq_metric_regex_count(context.column_name, context.config.regex) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro f2dq_metric_not_match_regex_percent(context) %}
|
||||
{{ percentage_formula(f2dq_metric_not_match_regex(context), f2dq_metric_row_count()) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro f2dq_metric_distinct_values(context) %}
|
||||
{{ distinct_values(context) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro distinct_values(context) %}
|
||||
{{ adapter.dispatch('distinct_values', 'f2dq')(context) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro default__distinct_values(context) %}
|
||||
coalesce(
|
||||
count(distinct {{ context.column_name }} )
|
||||
, 0)
|
||||
{% endmacro %}
|
||||
|
||||
{% macro postgres__distinct_values(context) %}
|
||||
{# /* In postgres, its faster to count distinct values in a column by selecting then counting in separate steps */ #}
|
||||
with temp_table as (
|
||||
select distinct {{ context.column_name }} from {{ context.table_name }}
|
||||
where {{ in_time_window(context.time_filter) }}
|
||||
)
|
||||
select coalesce(count(*), 0) from temp_table
|
||||
{% endmacro %}
|
||||
|
||||
{% macro f2dq_metric_approx_distinct_values(context) %}
|
||||
{{ approx_distinct_values(context) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro approx_distinct_values(context) %}
|
||||
{{ adapter.dispatch('approx_distinct_values', 'f2dq')(context) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro default__approx_distinct_values(context) %}
|
||||
{# /* No approximate distinct count in postgres so we default to using a distinct count */ #}
|
||||
{{ f2dq_metric_distinct_values(context) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro redshift__approx_distinct_values(context) %}
|
||||
approximate {{ f2dq_metric_distinct_values(context.column_name) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro bigquery__approx_distinct_values(context) %}
|
||||
approx_count_distinct({{ context.column_name }})
|
||||
{% endmacro %}
|
||||
|
||||
{% macro snowflake__approx_distinct_values(context) %}
|
||||
approx_count_distinct({{ context.column_name }})
|
||||
{% endmacro %}
|
||||
|
||||
{% macro f2dq_metric_duplicate_values(context) %}
|
||||
with temp_table as (
|
||||
select {{ context.column_name }} from {{ context.table_name }}
|
||||
where {{ in_time_window(context.time_filter) }}
|
||||
group by {{ context.column_name }}
|
||||
having count(1) > 1
|
||||
)
|
||||
select coalesce(count(*), 0) from temp_table
|
||||
{% endmacro %}
|
||||
|
||||
{% macro f2dq_metric_duplicate_rows(context) %}
|
||||
with temp_table as (
|
||||
select {{ context.column_name }}, count(1) as row_count from {{ context.table_name }}
|
||||
where {{ in_time_window(context.time_filter) }}
|
||||
group by {{ context.column_name }}
|
||||
having count(1) > 1
|
||||
)
|
||||
select coalesce(sum(row_count), 0) from temp_table
|
||||
{% endmacro %}
|
||||
|
||||
{% macro f2dq_metric_unique_rows(context) %}
|
||||
with temp_table as (
|
||||
select {{ context.column_name }}, count(1) as row_count from {{ context.table_name }}
|
||||
where {{ in_time_window(context.time_filter) }}
|
||||
group by {{ context.column_name }}
|
||||
having count(1) = 1
|
||||
)
|
||||
select coalesce(sum(row_count), 0) from temp_table
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,7 @@
|
||||
{% macro f2dq_metric_distinct_table_rows(context) %}
|
||||
with temp_table AS (
|
||||
select distinct * from {{ context.table_name }}
|
||||
where {{ in_time_window(context.time_filter) }}
|
||||
)
|
||||
select coalesce(count(*), 0) FROM temp_table
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,33 @@
|
||||
|
||||
{% macro f2dq_metric_row_count(context) %}
|
||||
count(1)
|
||||
{% endmacro %}
|
||||
|
||||
{% macro f2dq_metric_freshness(context) %}
|
||||
{{ freshness_expression(context.time_filter) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro freshness_expression(time_filter) %}
|
||||
{# /* If time_filter is none, we are computing the metric globally. we set the value as null since a table without use of a time filter doesn't really have a freshness metric */ #}
|
||||
{% if time_filter is none %}
|
||||
cast(null as {{ numeric_type() }})
|
||||
{% else %}
|
||||
{{ adapter.dispatch('freshness_expression', 'f2dq')(time_filter) }}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro default__freshness_expression(time_filter) %}
|
||||
EXTRACT(EPOCH FROM ({{time_window_end()}} - max({{time_filter}})))
|
||||
{% endmacro %}
|
||||
|
||||
{% macro bigquery__freshness_expression(time_filter) %}
|
||||
TIMESTAMP_DIFF ( timestamp({{ time_window_end()}}), timestamp(max({{time_filter}})), SECOND)
|
||||
{% endmacro %}
|
||||
|
||||
{% macro snowflake__freshness_expression(time_filter) %}
|
||||
timediff(second, max({{time_filter}}), {{- time_window_end() -}})
|
||||
{% endmacro %}
|
||||
|
||||
{% macro redshift__freshness_expression(time_filter) %}
|
||||
DATEDIFF(second, max({{time_filter}}), {{- time_window_end() -}})
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,102 @@
|
||||
{% macro metrics_base_expressions(model, columns, table_level=False) %}
|
||||
|
||||
{% set col_expr = [] %}
|
||||
|
||||
{% for col in columns %}
|
||||
{% set column_name = f2dq.row_value(col, 'column_name') %}
|
||||
{% do col_expr.extend(f2dq.metrics_base_expression_column_all(model, col)) %}
|
||||
{% endfor %}
|
||||
|
||||
{% if table_level %}
|
||||
{% do col_expr.extend(f2dq.metrics_base_expresion_table_all(model)) %}
|
||||
{% endif %}
|
||||
|
||||
{{ return (col_expr) }}
|
||||
|
||||
{% endmacro %}
|
||||
|
||||
{% macro metrics_base_expression_column_all(model, column) %}
|
||||
|
||||
{%- set col_expr = [] %}
|
||||
{%- set metrics_to_compute = [] %}
|
||||
{% set column_name = f2dq.row_value(column, 'column_name') %}
|
||||
{% set data_type = model.columns_info[column_name].data_type %}
|
||||
{% do metrics_to_compute.extend(model.metrics.get('group').get('column', {}).get(data_type, [])) %}
|
||||
{% do metrics_to_compute.extend(model.metrics.get('additional').get('column', {}).get(column_name, [])) %}
|
||||
|
||||
{% for metric_value in metrics_to_compute %}
|
||||
{% set metric_obj = f2dq.extract_metric_config(metric_value) %}
|
||||
{% set expression = f2dq.metrics_base_expression_column(model, column_name, metric_obj['metric'], metric_obj['config']) %}
|
||||
{% do col_expr.append({ 'expr': expression, 'col_name': column_name, 'metric': metric_obj['metric']}) %}
|
||||
{% endfor %}
|
||||
|
||||
{{ return (col_expr) }}
|
||||
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro metrics_base_expresion_table_all(model) %}
|
||||
{%- set table_expr = [] %}
|
||||
{%- set metrics_to_compute = [] %}
|
||||
{% do metrics_to_compute.extend(model.metrics.get('group').get('table', [])) %}
|
||||
{% do metrics_to_compute.extend(model.metrics.get('additional').get('table', [])) %}
|
||||
|
||||
{% for metric_value in metrics_to_compute %}
|
||||
{% set metric_obj = f2dq.extract_metric_config(metric_value) %}
|
||||
{% set expression = f2dq.metrics_base_expression_table(model, metric_obj['metric'], metric_obj['config']) %}
|
||||
{% do table_expr.append({ 'expr': expression, 'col_name': '', 'metric': metric_obj['metric']}) %}
|
||||
{% endfor %}
|
||||
|
||||
{{ return (table_expr) }}
|
||||
|
||||
{% endmacro %}
|
||||
|
||||
{% macro metrics_base_expression_table(model, metric_name, config) %}
|
||||
{% set metric_macro = f2dq.get_metric_macro(metric_name) %}
|
||||
{% set context = {'time_filter': model.time_filter, 'metric_name': metric_name, 'config': config, 'table_name': model.table_name, 'column_name': none} %}
|
||||
|
||||
{{ metric_macro(context) }}
|
||||
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{%- macro metrics_base_expression_column(model, column_name, metric_name, config) %}
|
||||
{% set metric_macro = f2dq.get_metric_macro(metric_name) %}
|
||||
{% set context = {'time_filter': model.time_filter, 'metric_name': metric_name, 'config': config, 'table_name': model.table_name, 'column_name': f2dq.quote_column_name(column_name)} %}
|
||||
|
||||
{{ metric_macro(context) }}
|
||||
|
||||
{% endmacro %}
|
||||
|
||||
{% macro extract_metric_config(metric_value) %}
|
||||
|
||||
{% set config = none %}
|
||||
|
||||
{% if metric_value is mapping %}
|
||||
{% set metric = metric_value.keys() | first %}
|
||||
{% if metric_value[metric] is none %}
|
||||
{{ exceptions.raise_compiler_error("Empty configuration passed for metric: " ~ metric ~ ". If the metric doesn't use a config, please use the column name as a string.") }}
|
||||
{% endif %}
|
||||
|
||||
{% set config = metric_value[metric] %}
|
||||
{%- else %}
|
||||
{% set metric = metric_value %}
|
||||
{% endif %}
|
||||
|
||||
{{ return ({'metric': metric, 'config': config}) }}
|
||||
|
||||
{% endmacro %}
|
||||
|
||||
{%- macro get_metric_macro(metric_name) %}
|
||||
{% set macro_name = 'f2dq_metric' + '_' + metric_name %}
|
||||
|
||||
{% if context['f2dq'].get(macro_name) %}
|
||||
{% set metric_macro = context['f2dq'][macro_name] %}
|
||||
{%- else %}
|
||||
{% set metric_macro = context[project_name][macro_name] %}
|
||||
{% endif %}
|
||||
|
||||
{{ return (metric_macro) }}
|
||||
|
||||
{% endmacro %}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
{% macro f2dq_last_base_metrics_part() %}
|
||||
|
||||
-- depends_on: {{ ref('f2dq_columns') }}
|
||||
|
||||
{{
|
||||
config(
|
||||
materialized='table',
|
||||
)
|
||||
}}
|
||||
|
||||
{{ f2dq.empty_last_base_metrics() }}
|
||||
|
||||
{% endmacro %}
|
||||
|
||||
{% macro f2dq_last_base_metrics_thread(num) %}
|
||||
{% set part_name = 'f2dq_last_base_metrics_part' ~ num %}
|
||||
{{ f2dq.generate_depends(['f2dq_selected', 'f2dq_monitored', 'f2dq_columns', 'f2dq_run_started_at', part_name]) }}
|
||||
|
||||
{{
|
||||
config(
|
||||
materialized='table',
|
||||
)
|
||||
}}
|
||||
|
||||
{% if not f2dq.in_compile() %}
|
||||
{{ f2dq.metrics_base_compute_for_thread(num, part_name) }}
|
||||
{% endif %}
|
||||
|
||||
{{ f2dq.empty_last_base_metrics() }}
|
||||
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,75 @@
|
||||
{% macro metrics_base_compute_for_thread(thread_value, ref_model) %}
|
||||
{%- set tables = run_query(f2dq.get_tables()) %}
|
||||
{%- for mtable in tables %}
|
||||
-- we are splitting computing metrics to 4 different threads
|
||||
{% set for_loop_mod = (loop.index % 4) %}
|
||||
{% if for_loop_mod == thread_value %}
|
||||
{% set model = get_model_config(mtable) %}
|
||||
|
||||
{% set columns_to_query = [] %}
|
||||
{% set size = 0 %}
|
||||
|
||||
{% for column in model.columns %}
|
||||
{% set column_name = f2dq.row_value(column, 'column_name') %}
|
||||
|
||||
{% if should_compute_metric(model, column_name) %}
|
||||
{% do columns_to_query.append(column) %}
|
||||
{% endif %}
|
||||
|
||||
{% set columns_size = columns_to_query| length %}
|
||||
|
||||
{% if columns_size == var('f2dq:max_columns_in_query') %}
|
||||
{%- set insert_stats_query = f2dq.metrics_base_insert(model, ref_model, columns_to_query) -%}
|
||||
|
||||
{% if insert_stats_query %}
|
||||
{% do run_query(insert_stats_query) %}
|
||||
{% endif %}
|
||||
{% do columns_to_query.clear() %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{%- set insert_stats_query = f2dq.metrics_base_insert(model, ref_model, columns_to_query, table_level=True) -%}
|
||||
{% do run_query(insert_stats_query) %}
|
||||
|
||||
{{ dbt_utils.log_info('[f2dq_log] - finished computing metrics for:' ~ model.model_name) }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro metrics_base_insert(model, ref_model, columns, table_level=False) %}
|
||||
|
||||
{% set col_exprs = f2dq.metrics_base_expressions(model, columns, table_level) %}
|
||||
{% if col_exprs == [] %}
|
||||
{{ return ('') }}
|
||||
{% endif %}
|
||||
|
||||
insert into {{ ref(ref_model) }}
|
||||
with temp_table_metrics as (
|
||||
select
|
||||
{%- for col_expr in col_exprs %}
|
||||
( {{ col_expr.expr }} ) as {{ f2dq.quote_column_name(col_expr.col_name + '___' + col_expr.metric) }}
|
||||
{%- if not loop.last %},{%- endif %}
|
||||
{% endfor %}
|
||||
from
|
||||
{{ model.table_name }}
|
||||
where
|
||||
{{ in_time_window(model.time_filter) }}
|
||||
)
|
||||
|
||||
{%- for col_expr in col_exprs %}
|
||||
{% set final_metric_name = get_final_metric_name(col_expr.metric, model.time_filter) %}
|
||||
|
||||
select '{{model.table_name}}' as table_name, '{{ col_expr.col_name }}' as column_name, '{{ final_metric_name }}' as metric, {{ f2dq.quote_column_name(col_expr.col_name + '___' + col_expr.metric) }} as value
|
||||
from temp_table_metrics
|
||||
{% if not loop.last %}union all{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% endmacro %}
|
||||
|
||||
{% macro get_final_metric_name(metric_name, time_filter) %}
|
||||
{% if time_filter is none %}
|
||||
{{ return ('global__' + metric_name) }}
|
||||
{% else %}
|
||||
{{ return (metric_name) }}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,11 @@
|
||||
{% macro pub_insert_into_f2dq_monitored() %}
|
||||
{% set monitored = f2dq.pub_monitored_from_graph() %}
|
||||
{% do f2dq.insert_list_to_table(
|
||||
this,
|
||||
monitored,
|
||||
['name', 'schema', 'database', 'time_filter', 'metrics_groups', 'additional_metrics', 'metrics', 'columns', 'anomaly_detector', 'owners', 'selected']
|
||||
) %}
|
||||
|
||||
{{ return('') }}
|
||||
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,23 @@
|
||||
{% macro clean_additional_whitespaces(column_name) %}
|
||||
{{ adapter.dispatch('clean_additional_whitespaces', 'f2dq')(column_name) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro default__clean_additional_whitespaces(column_name) %}
|
||||
trim(regexp_replace( {{ column_name }}, '\s\s+', ' '))
|
||||
{% endmacro %}
|
||||
|
||||
{% macro postgres__clean_additional_whitespaces(column_name) %}
|
||||
trim(regexp_replace( {{ column_name }}, '\s\s+', ' ', 'g'))
|
||||
{% endmacro %}
|
||||
|
||||
{% macro redshift__clean_additional_whitespaces(column_name) %}
|
||||
trim(regexp_replace( {{ column_name }}, '\\s\\s+', ' '))
|
||||
{% endmacro %}
|
||||
|
||||
{% macro bigquery__clean_additional_whitespaces(column_name) %}
|
||||
trim(regexp_replace( {{ column_name }}, r'\s\s+', ' '))
|
||||
{% endmacro %}
|
||||
|
||||
{% macro snowflake__clean_additional_whitespaces(column_name) %}
|
||||
trim(regexp_replace( {{ column_name }}, '\\s\\s+', ' '))
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,31 @@
|
||||
{% macro generate_blacklist_pattern(chars_to_blacklist) %}
|
||||
{% set pattern = [] %}
|
||||
{% for char in chars_to_blacklist %}
|
||||
{% set expr = '(' + char + ')' %}
|
||||
{% do pattern.append(expr) %}
|
||||
{% endfor %}
|
||||
|
||||
{{ return(pattern | join('|')) }}
|
||||
{% endmacro %}
|
||||
|
||||
{%- macro clean_blacklist(column_name, chars_to_blacklist, replacement) -%}
|
||||
{% set pattern_string = f2dq.generate_blacklist_pattern(chars_to_blacklist) %}
|
||||
|
||||
{{ adapter.dispatch('clean_blacklist', 'f2dq')(column_name, pattern_string, replacement) }}
|
||||
{%- endmacro -%}
|
||||
|
||||
{%- macro default__clean_blacklist(column_name, pattern_string, replacement) -%}
|
||||
regexp_replace( {{ column_name }}, '{{ pattern_string }}', '{{ replacement }}')
|
||||
{%- endmacro -%}
|
||||
|
||||
{%- macro postgres__clean_blacklist(column_name, pattern_string, replacement) -%}
|
||||
regexp_replace( {{ column_name }}, '{{ pattern_string }}', '{{ replacement }}', 'g')
|
||||
{%- endmacro -%}
|
||||
|
||||
{%- macro redshift__clean_blacklist(column_name, pattern_string, replacement) -%}
|
||||
regexp_replace( {{ column_name }}, '{{ pattern_string }}', '{{ replacement }}')
|
||||
{%- endmacro -%}
|
||||
|
||||
{%- macro bigquery__clean_blacklist(column_name, pattern_string, replacement) -%}
|
||||
regexp_replace( {{ column_name }}, """{{ pattern_string }}""", '{{ replacement }}')
|
||||
{%- endmacro -%}
|
||||
@@ -0,0 +1,3 @@
|
||||
{% macro clean_capitalize_words(column_name) %}
|
||||
initcap( {{column_name}} )
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,23 @@
|
||||
{# https://github.com/fast2dev/f2dq/issues/143 #}
|
||||
|
||||
{#
|
||||
macro returns rows with she same key set (unique_cols)
|
||||
|
||||
along with the fields of the base model duplicates information added:
|
||||
f2dq_duplicate_count - total number of duplicates with the same current key set
|
||||
f2dq_duplicate_row_number - number of a duplicate row inside the group of duplicates with the same current key set
|
||||
#}
|
||||
|
||||
{% macro filter_get_duplicates(relation, unique_cols, sort_columns) %}
|
||||
(
|
||||
with duplication_context as (
|
||||
{{f2dq.add_duplication_context(relation, unique_cols, sort_columns)}}
|
||||
),
|
||||
duplicate_rows as (
|
||||
select * from duplication_context where f2dq_duplicate_group_row_count > 1
|
||||
)
|
||||
{# return surrogate key as well? #}
|
||||
select *
|
||||
from duplicate_rows
|
||||
)
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,15 @@
|
||||
|
||||
|
||||
|
||||
{% macro filter_remove_duplicates(relation, unique_cols, sort_columns) %}
|
||||
(
|
||||
with with_row_num as (
|
||||
{{f2dq.add_duplication_context(relation, unique_cols, sort_columns)}}
|
||||
),
|
||||
one_row_num as (
|
||||
select * from with_row_num where f2dq_duplicate_group_row_number = 1
|
||||
)
|
||||
select {{ dbt_utils.star(from=relation) }}
|
||||
from one_row_num
|
||||
)
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,32 @@
|
||||
{% macro is_dbt_relation(obj) %}
|
||||
{{ return (obj is mapping and obj.get('metadata', {}).get('type', '').endswith('Relation') )}}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro normalize_expression_cte(reference_table) %}
|
||||
with target_table as (
|
||||
{% if f2dq.is_dbt_relation(reference_table) or reference_table is string %}
|
||||
select * from {{ reference_table }}
|
||||
{% elif reference_table is mapping %}
|
||||
{% for key, value in reference_table.items() %}
|
||||
select '{{key}}' as source, '{{value}}' as target
|
||||
{% if not loop.last %}union all{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
)
|
||||
{% endmacro %}
|
||||
|
||||
{%- macro normalize_values(source_relation, column_name, reference_table) -%}
|
||||
(
|
||||
{{ f2dq.normalize_expression_cte(reference_table) }}
|
||||
|
||||
select s.*,
|
||||
case when t.source is null
|
||||
then s.{{column_name}}
|
||||
else t.target
|
||||
end as {{ column_name + '__normalized'}}
|
||||
from {{ source_relation }} s
|
||||
left join target_table t
|
||||
on t.source = s.{{column_name}}
|
||||
)
|
||||
{%- endmacro -%}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
{% macro export_alerts(start_date, end_date, alerts_path=None, monitored_path=None) %}
|
||||
{% set alerts_query %}
|
||||
select
|
||||
type as {{ f2dq.quote_column('type') }},
|
||||
model as {{ f2dq.quote_column('model') }},
|
||||
message as {{ f2dq.quote_column('message') }},
|
||||
value as {{ f2dq.quote_column('value') }},
|
||||
{{ format_timestamp('time_window_end')}} as {{ f2dq.quote_column('time_window_end') }}
|
||||
from {{ ref('f2dq_alerts') }}
|
||||
where
|
||||
case
|
||||
when type = 'anomaly' then {{ in_date_window('time_window_end', start_date, end_date) }}
|
||||
else {{ in_date_window('time_window_end', start_date, none) }}
|
||||
end
|
||||
order by time_window_end desc
|
||||
{% endset %}
|
||||
|
||||
{% set query_result = run_query(alerts_query) %}
|
||||
{% set alerts_file_path = alerts_path or 'target/f2dq/alerts.json' %}
|
||||
{% do query_result.to_json(alerts_file_path) %}
|
||||
{{ save_monitored(monitored_path) }}
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,15 @@
|
||||
{% macro export_table_samples(start_date, end_date, table_samples_path=None) %}
|
||||
{% set table_samples_query %}
|
||||
select
|
||||
lower(table_name) as table_name,
|
||||
sample_data,
|
||||
sampled_on
|
||||
from
|
||||
{{ ref('f2dq_table_samples') }}
|
||||
{% endset %}
|
||||
|
||||
{% set query_result = run_query(table_samples_query) %}
|
||||
{% set table_samples_file_path = table_samples_path or 'target/f2dq/table_samples.json' %}
|
||||
{% do query_result.to_json(table_samples_file_path) %}
|
||||
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,25 @@
|
||||
{% macro export_tests_history(start_date, end_date, tests_history_path=None) %}
|
||||
{% set tests_history_query %}
|
||||
select
|
||||
table_name,
|
||||
column_name,
|
||||
test_name,
|
||||
run_at,
|
||||
status,
|
||||
execution_time,
|
||||
message,
|
||||
failures_count,
|
||||
failures_json,
|
||||
failures_table,
|
||||
severity,
|
||||
compiled_sql
|
||||
from
|
||||
{{ ref('f2dq_test_history') }}
|
||||
where {{ in_date_window('run_at', start_date, end_date) }}
|
||||
{% endset %}
|
||||
|
||||
{% set query_result = run_query(tests_history_query) %}
|
||||
{% set tests_history_file_path = tests_history_path or 'target/f2dq/tests_history.json' %}
|
||||
{% do query_result.to_json(tests_history_file_path) %}
|
||||
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,93 @@
|
||||
|
||||
{% macro overview_select_base(type, timestamp_col) %}
|
||||
'{{ type }}' as {{ f2dq.quote_column('type') }},
|
||||
table_name as {{ f2dq.quote_column('table_name') }},
|
||||
column_name as {{ f2dq.quote_column('column_name') }},
|
||||
{{ timestamp_col }} as {{ f2dq.quote_column('computed_on') }},
|
||||
{% endmacro %}
|
||||
|
||||
{% macro generate_overview(start_date, end_date, interval, overview_path=None, monitored_path=None) %}
|
||||
-- depends_on: {{ ref('f2dq_anomalies') }}
|
||||
-- depends_on: {{ ref('f2dq_base_metrics') }}
|
||||
-- depends_on: {{ ref('f2dq_schema_changes') }}
|
||||
-- depends_on: {{ ref('f2dq_columns') }}
|
||||
|
||||
{# time grain is either days or hour #}
|
||||
{% set time_grain, num_str = interval.split(':') %}
|
||||
{% set num = num_str | int %}
|
||||
{% if time_grain == 'hours' %}
|
||||
{% set interval_length_sec = num * 3600 %}
|
||||
{% elif time_grain == 'days'%}
|
||||
{% set interval_length_sec = num * 3600 * 24 %}
|
||||
{% else %}
|
||||
{{ exceptions.raise_compiler_error("Invalid interval. Got: " ~ interval) }}
|
||||
{% endif %}
|
||||
{{ dbt_utils.log_info('[f2dq] interval length in seconds is ' ~ interval_length_sec) }}
|
||||
{% set overview_query %}
|
||||
with schema_changes_casted as (
|
||||
select id, table_name, operation, column_name, data_type, {{ bool_to_string('is_nullable') }}, prev_column_name, prev_data_type, {{ bool_to_string('prev_is_nullable') }}, detected_time
|
||||
from {{ ref('f2dq_schema_changes') }}
|
||||
),
|
||||
columns_casted as (
|
||||
select {{ full_table_name('name', 'schema', 'database') }} as table_name, column_name, data_type, {{ bool_to_string('is_nullable') }}, computed_on
|
||||
from {{ ref('f2dq_columns') }}
|
||||
)
|
||||
|
||||
(
|
||||
select
|
||||
{{ overview_select_base('metric', 'computed_on')}}
|
||||
{{ to_single_json(['metric', 'value', 'time_window_end', 'interval_length_sec']) }} as {{ f2dq.quote_column('data') }}
|
||||
from
|
||||
{{ ref('f2dq_base_metrics') }}
|
||||
where {{ in_date_window('time_window_end', start_date, end_date) }}
|
||||
and interval_length_sec = {{interval_length_sec}}
|
||||
) union all
|
||||
(
|
||||
select
|
||||
{{ overview_select_base('anomaly', 'computed_on')}}
|
||||
{{ to_single_json(['id', 'metric', 'z_score_value', 'last_value', 'last_avg', 'last_stddev', 'time_window_end', 'interval_length_sec']) }} as {{ f2dq.quote_column('data') }}
|
||||
from
|
||||
{{ ref('f2dq_anomalies') }}
|
||||
where {{ in_date_window('time_window_end', start_date, end_date) }}
|
||||
and interval_length_sec = {{interval_length_sec}}
|
||||
) union all
|
||||
(
|
||||
select
|
||||
{{ overview_select_base('schema_change', 'detected_time')}}
|
||||
{{ to_single_json(['id', 'operation', 'data_type', 'is_nullable', 'prev_column_name', 'prev_data_type', 'prev_is_nullable', 'detected_time']) }} as {{ f2dq.quote_column('data') }}
|
||||
from
|
||||
schema_changes_casted
|
||||
where {{ in_date_window('detected_time', start_date, none) }}
|
||||
) union all
|
||||
(
|
||||
select
|
||||
{{ overview_select_base('schema', 'computed_on')}}
|
||||
{{ to_single_json(['data_type', 'is_nullable']) }} as {{ f2dq.quote_column('data') }}
|
||||
from
|
||||
columns_casted
|
||||
)
|
||||
union all
|
||||
(
|
||||
select
|
||||
'alert' as {{ f2dq.quote_column('type') }},
|
||||
model as {{ f2dq.quote_column('table_name') }},
|
||||
null as {{ f2dq.quote_column('column_name') }},
|
||||
time_window_end as {{ f2dq.quote_column('computed_on') }},
|
||||
{{ to_single_json(['type', 'model', 'message', 'value', 'time_window_end']) }} as {{ f2dq.quote_column('data') }}
|
||||
from
|
||||
{{ ref('f2dq_alerts') }}
|
||||
where
|
||||
case
|
||||
when type = 'anomaly' then {{ in_date_window('time_window_end', start_date, end_date) }}
|
||||
else {{ in_date_window('time_window_end', start_date, none) }}
|
||||
end
|
||||
)
|
||||
order by {{ f2dq.quote_column('computed_on')}} desc
|
||||
{% endset %}
|
||||
|
||||
{% set overview_result = run_query(overview_query) %}
|
||||
{% set overview_file_path = overview_path or '../target/f2dq/overview.json' %}
|
||||
{% do overview_result.to_json(overview_file_path) %}
|
||||
{{ save_monitored(monitored_path) }}
|
||||
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,65 @@
|
||||
{#
|
||||
# This file contains significant part of code licensed under:
|
||||
# Copyright 2020 Soda
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#}
|
||||
|
||||
{% macro get_regex_for(to_validate) %}
|
||||
|
||||
{% set regexp_dict = {
|
||||
'number_whole': '^\-?[0-9]+$',
|
||||
'number_decimal_point': '^\-?[0-9]+\.[0-9]+$',
|
||||
'number_decimal_comma': '^\-?[0-9]+,[0-9]+$',
|
||||
'number_percentage': '^\-?[0-9]+([\.,][0-9]+)? ?%$',
|
||||
'number_percentage_point': '^\-?[0-9]+([\.][0-9]+)? ?%$',
|
||||
'number_percentage_comma': '^\-?[0-9]+([,][0-9]+)? ?%$',
|
||||
'date_eu': '^([1-9]|0[1-9]|[12][0-9]|3[01])[-\./]([1-9]|0[1-9]|1[012])[-\./](19|20)?[0-9][0-9]$',
|
||||
'date_us': '^([1-9]|0[1-9]|1[012])[-\./]([1-9]|0[1-9]|[12][0-9]|3[01])[-\./](19|20)?[0-9][0-9]$',
|
||||
'date_inverse': '^(19|20)[0-9][0-9][-\./]?([1-9]|0[1-9]|1[012])[-\./]?([1-9]|0[1-9]|[12][0-9]|3[01])$',
|
||||
'time_24h': '^([01][0-9]|2[0-3]):([0-5][0-9])$',
|
||||
'time_12h': '^(1[0-2]|0?[1-9]):[0-5][0-9]$',
|
||||
'time': '^([0-9]|1[0-9]|2[0-4])[:-]([0-9]|[0-5][0-9])([:-]([0-9]|[0-5][0-9])(,[0-9]+)?)?$',
|
||||
'date_iso_8601':
|
||||
'^'
|
||||
'([1-9][0-9]{3}-((0[1-9]|1[0-2])-(0[1-9]|1[0-9]|2[0-8])|(0[13-9]|1[0-2])-(29|30)|(0[13578]|1[02])-31)|'
|
||||
'([1-9][0-9](0[48]|[2468][048]|[13579][26])|([2468][048]|[13579][26])00)-02-29)'
|
||||
|
||||
'T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](\.[0-9]+)?'
|
||||
|
||||
'(Z|[+-][01][0-9]:[0-5][0-9])?'
|
||||
'$',
|
||||
'uuid': '^[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}$',
|
||||
'ipv4_address': '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$',
|
||||
'ipv6_address': '^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}))|:)))(%.+)?$',
|
||||
'email': '^[A-Za-z0-9.-_%]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$',
|
||||
} %}
|
||||
|
||||
{% set base_regex = regexp_dict[to_validate] %}
|
||||
{% set qualifed_regex = adapter.dispatch('get_regex_for', 'f2dq')(base_regex) %}
|
||||
{{ return(qualifed_regex) }}
|
||||
|
||||
{% endmacro %}
|
||||
|
||||
{% macro default__get_regex_for(pattern) %}
|
||||
{{ return (pattern) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro redshift__get_regex_for(pattern) %}
|
||||
{% set changed = modules.re.sub('\.', '\\.', pattern) %}
|
||||
{% set changed = modules.re.sub('\-', '\\-', changed) %}
|
||||
{{ return (changed) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro snowflake__get_regex_for(pattern) %}
|
||||
{% set changed = modules.re.sub('\.', '\\.', pattern) %}
|
||||
{% set changed = modules.re.sub('\-', '\\-', changed) %}
|
||||
{{ return (changed) }}
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,93 @@
|
||||
|
||||
|
||||
{% macro valid_regex(column_name, to_validate) %}
|
||||
{% set pattern = f2dq.get_regex_for(to_validate) %}
|
||||
case when
|
||||
{{ column_name }} is null then false
|
||||
else {{ f2dq.regex_match_expression(column_name, pattern) }}
|
||||
end
|
||||
{% endmacro %}
|
||||
|
||||
{% macro valid_email(column_name) %}
|
||||
{{ f2dq.valid_regex(column_name, 'email')}}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro valid_date_eu(column_name) %}
|
||||
{{ f2dq.valid_regex(column_name, 'date_eu')}}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro valid_date_us(column_name) %}
|
||||
{{ f2dq.valid_regex(column_name, 'date_us')}}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro valid_date_inverse(column_name) %}
|
||||
{{ f2dq.valid_regex(column_name, 'date_inverse')}}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro valid_date_iso_8601(column_name) %}
|
||||
{{ f2dq.valid_regex(column_name, 'date_iso_8601')}}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro valid_time_24h(column_name) %}
|
||||
{{ f2dq.valid_regex(column_name, 'time_24h')}}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro valid_time_12h(column_name) %}
|
||||
{{ f2dq.valid_regex(column_name, 'time_12h')}}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro valid_time(column_name) %}
|
||||
{{ f2dq.valid_regex(column_name, 'time')}}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro valid_ip_v4(column_name) %}
|
||||
{{ f2dq.valid_regex(column_name, 'ipv4_address')}}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro valid_ip_v6(column_name) %}
|
||||
{{ f2dq.valid_regex(column_name, 'ipv6_address')}}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro valid_ip(column_name) %}
|
||||
(
|
||||
{{ f2dq.valid_regex(column_name, 'ipv4_address')}}
|
||||
or
|
||||
{{ f2dq.valid_regex(column_name, 'ipv6_address')}}
|
||||
)
|
||||
{% endmacro %}
|
||||
|
||||
{% macro valid_number(column_name) %}
|
||||
{{ f2dq.valid_regex(column_name, 'number_whole')}}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro valid_number_decimal_point(column_name) %}
|
||||
{{ f2dq.valid_regex(column_name, 'number_decimal_point')}}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro valid_number_decimal_comma(column_name) %}
|
||||
{{ f2dq.valid_regex(column_name, 'number_decimal_comma')}}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro valid_number_percentage(column_name) %}
|
||||
{{ f2dq.valid_regex(column_name, 'number_percentage')}}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro valid_number_percentage_point(column_name) %}
|
||||
{{ f2dq.valid_regex(column_name, 'number_percentage_point')}}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro valid_number_percentage_comma(column_name) %}
|
||||
{{ f2dq.valid_regex(column_name, 'number_percentage_comma')}}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro valid_phone(column_name) %}
|
||||
{{ f2dq.valid_regex(column_name, 'phone')}}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro valid_uuid(column_name) %}
|
||||
{{ f2dq.valid_regex(column_name, 'uuid')}}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro valid_credit_card(column_name) %}
|
||||
{{ f2dq.valid_regex(column_name, 'credit_card_number')}}
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,112 @@
|
||||
|
||||
{% macro save_test_history(results) -%}
|
||||
|
||||
{{ adapter.dispatch('save_test_history_impl', 'f2dq') (results) }}
|
||||
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro default__save_test_history_impl(results) %}
|
||||
-- depends_on: {{ ref('f2dq_test_history') }}
|
||||
{% set command = flags.WHICH %}
|
||||
{% if execute and results and command in ('test', 'build') %}
|
||||
{% set tests = [] %}
|
||||
{% for el in results %}
|
||||
{% if el.node.resource_type.value == 'test' %}
|
||||
{% do tests.append(f2dq.test_data_dict(el)) %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if tests %}
|
||||
{% do f2dq.insert_list_to_table(
|
||||
ref('f2dq_test_history'),
|
||||
tests,
|
||||
['table_name', 'column_name', 'test_name', 'status', 'execution_time', 'message', 'failures_count', 'failures_json', 'failures_table', 'severity', 'compiled_sql', 'run_at'],
|
||||
{ 'run_at': timestamp_type() }
|
||||
) %}
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
{{ return ('') }}
|
||||
|
||||
{% endmacro %}
|
||||
|
||||
{% macro test_data_dict(el) %}
|
||||
|
||||
{% set run_started_at_str = run_started_at.strftime('%Y-%m-%d %H:%M:%S') %}
|
||||
|
||||
{% if el.node.to_dict().get('test_metadata') %}
|
||||
{% set any_refs = modules.re.findall("ref\(\'(?P<name>.*)\'\)", el.node.test_metadata.kwargs['model']) %}
|
||||
{% set any_source = modules.re.findall("source\(\'(?P<one>.*)\'\,\s+\'(?P<two>.*)\'\)", el.node.test_metadata.kwargs['model']) %}
|
||||
|
||||
{% if any_refs %}
|
||||
{% set name = any_refs[0] %}
|
||||
{% set node_name = f2dq.priv_full_name_from_depends(el.node, name) %}
|
||||
{% set schema = graph.nodes.get(node_name)['schema'] %}
|
||||
{% set database = graph.nodes.get(node_name)['database'] %}
|
||||
{% set table_name = (database + '.' + schema + '.' + name) | lower %}
|
||||
|
||||
{% elif any_source %}
|
||||
{% set package_name = any_source[0][0] %}
|
||||
{% set name = any_source[0][1] %}
|
||||
{% set node_name = f2dq.priv_full_name_from_depends(el.node, name) %}
|
||||
{% set schema = graph.sources.get(node_name)['schema'] %}
|
||||
{% set database = graph.sources.get(node_name)['database'] %}
|
||||
{% set table_name = (database + '.' + schema + '.' + name) | lower %}
|
||||
{% else %}
|
||||
{% set table_name = none %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% set table_name = none %}
|
||||
{% endif %}
|
||||
|
||||
{% if var.has_var('f2dq:query_test_failures') %}
|
||||
{% set query_failures = var('f2dq:query_test_failures') %}
|
||||
{% else %}
|
||||
{% set query_failures = true %}
|
||||
{% endif %}
|
||||
|
||||
{% if el.failures and el.failures > 0 and el.node.relation_name and query_failures %}
|
||||
{% if var.has_var('f2dq:test_history_failures_limit') %}
|
||||
{% set limit_count = var('f2dq:test_history_failures_limit')%}
|
||||
{% else %}
|
||||
{% set limit_count = 10 %}
|
||||
{% endif %}
|
||||
|
||||
{% set failures_query %}
|
||||
select * from {{ el.node.relation_name}} limit {{ limit_count }}
|
||||
{% endset %}
|
||||
{% set failures_list = f2dq.agate_to_list(run_query(failures_query)) %}
|
||||
{% endif %}
|
||||
|
||||
{% set failures_json = none %}
|
||||
|
||||
{{ return ({
|
||||
'table_name': table_name,
|
||||
'column_name': el.node.column_name or none,
|
||||
'test_name': el.node.name,
|
||||
'status': el.status.name,
|
||||
'execution_time': el.execution_time,
|
||||
'message': el.message,
|
||||
'failures_count': el.failures,
|
||||
'failures_json': '' ~ failures_list,
|
||||
'failures_table': el.node.relation_name or none,
|
||||
'severity': el.node.config.severity,
|
||||
'compiled_sql': el.node.compiled_sql or el.node.compiled_code or none,
|
||||
'run_at': run_started_at_str,
|
||||
})
|
||||
}}
|
||||
|
||||
{% endmacro %}
|
||||
|
||||
{% macro priv_full_name_from_depends(node, name) %}
|
||||
|
||||
{% for full_name in node.depends_on.nodes %}
|
||||
{% set node_name = full_name.split('.')[-1] %}
|
||||
{% if node_name == name %}
|
||||
{{ return(full_name) }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{{ return(none) }}
|
||||
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,54 @@
|
||||
{%- macro order_by_if_time_filter(time_filter) -%}
|
||||
{%- if time_filter is not none -%}
|
||||
order by {{ time_filter }} desc
|
||||
{%- endif -%}
|
||||
{%- endmacro -%}
|
||||
|
||||
|
||||
{% macro f2dq_last_table_samples() %}
|
||||
{{ f2dq.generate_depends(['f2dq_selected', 'f2dq_monitored', 'f2dq_columns', 'f2dq_run_started_at', 'f2dq_last_table_samples_part']) }}
|
||||
|
||||
{{
|
||||
config(
|
||||
materialized='table',
|
||||
)
|
||||
}}
|
||||
|
||||
{% set store_samples = f2dq.f2dq_var('store_table_samples', false) %}
|
||||
{% if not f2dq.in_compile() and store_samples is sameas true %}
|
||||
{%- set tables = run_query(f2dq.get_tables()) %}
|
||||
|
||||
{% set samples_list = [] %}
|
||||
{%- for sample_table in tables %}
|
||||
|
||||
{% set model = get_model_config(sample_table) %}
|
||||
{% set columns_to_sample = [] %}
|
||||
{% for key, value in model.columns_info.items() | sort %}
|
||||
{% if value.data_type in ['numeric', 'text'] %}
|
||||
{% do columns_to_sample.append(key) %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% set samples_query %}
|
||||
select {{ print_list(columns_to_sample)}} from {{ model.table_name }}
|
||||
{{ order_by_if_time_filter(model.time_filter) }}
|
||||
limit 10
|
||||
{% endset %}
|
||||
|
||||
{% set samples = f2dq.agate_to_list(run_query(samples_query)) %}
|
||||
{% do samples_list.append({
|
||||
'table_name': model.model_name,
|
||||
'sample_data': samples,
|
||||
}) %}
|
||||
|
||||
{% endfor %}
|
||||
{% do f2dq.insert_list_to_table(
|
||||
ref('f2dq_last_table_samples_part'),
|
||||
samples_list,
|
||||
['table_name', 'sample_data']
|
||||
) %}
|
||||
{% endif %}
|
||||
|
||||
{{ f2dq.empty_last_table_samples() }}
|
||||
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,42 @@
|
||||
{% macro insert_list_to_table(table, list, params, dtype=None,insert_size=100) %}
|
||||
|
||||
{% set single_insert_list = [] %}
|
||||
{% for el in list %}
|
||||
{% do single_insert_list.append(el) %}
|
||||
{% set single_insert_list_size = single_insert_list | length %}
|
||||
{% if single_insert_list_size == insert_size or loop.last %}
|
||||
|
||||
{% set insert_query %}
|
||||
insert into {{ table }} ({%- for p in params %}{{p}}{% if not loop.last %}, {% endif %}{% endfor %}) values
|
||||
{%- for row in single_insert_list -%}
|
||||
(
|
||||
{%- for p in params -%}
|
||||
{%- if row[p] is none -%}
|
||||
NULL
|
||||
{%- else -%}
|
||||
{%- if row[p] is string -%}
|
||||
{%- if dtype and p in dtype -%}
|
||||
{% set cast_type = dtype[p] %}
|
||||
cast ({{ f2dq.quote_string(row[p]) }} as {{ cast_type }})
|
||||
{%- else %}
|
||||
{{- f2dq.quote_string(row[p]) -}}
|
||||
{%- endif -%}
|
||||
{%- elif row[p] is number -%}
|
||||
{{-row[p]-}}
|
||||
{%- else -%}
|
||||
{{- f2dq.quote_string(tojson(row[p])) -}}
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
{%- if not loop.last -%},{%- endif -%}
|
||||
{%- endfor -%}
|
||||
)
|
||||
{%- if not loop.last -%},{%- endif %}
|
||||
{% endfor -%}
|
||||
{% endset %}
|
||||
|
||||
{% do run_query(insert_query) %}
|
||||
{% do single_insert_list.clear() %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,106 @@
|
||||
|
||||
{% macro metric_expression(table, metric, expression, column_name=None, condition=None) %}
|
||||
select * from {{ref('f2dq_base_metrics')}}
|
||||
where
|
||||
table_name = '{{ f2dq.full_table_name_values(table.identifier, table.schema, table.database)}}' and
|
||||
metric = '{{ metric }}' and
|
||||
{% if condition is not none %}
|
||||
{{ condition }} and
|
||||
{% endif %}
|
||||
{% if column_name is none %}
|
||||
not ( {{ expression }} )
|
||||
{% else %}
|
||||
column_name = '{{ column_name }}' and
|
||||
not ( {{ expression }} )
|
||||
{% endif %}
|
||||
|
||||
{% endmacro %}
|
||||
|
||||
{# old test macros, will be removed after some time #}
|
||||
{% test metric_expression_is_true(model, table, metric, expression, column_name=None, condition=None) %}
|
||||
{{ f2dq.metric_expression(table, metric, expression, column_name=None, condition=None) }}
|
||||
{% endtest %}
|
||||
|
||||
|
||||
{% test metric_equal_to(model, table, metric, value, column_name=None, condition=None) %}
|
||||
{{ f2dq.metric_expression(table, metric, 'value = ' ~ value, column_name, condition) }}
|
||||
{% endtest %}
|
||||
|
||||
|
||||
{% test metric_in_range(model, table, metric, min_value, max_value, column_name=None, condition=None) %}
|
||||
{{ f2dq.metric_expression(table, metric, 'value >= ' ~ min_value ~ ' and value <= ' ~ max_value, column_name, condition) }}
|
||||
{% endtest %}
|
||||
|
||||
{# new test macros #}
|
||||
|
||||
{% test assert_true(model, column_name=None, metric=None, expression=expression, condition=None) %}
|
||||
-- depends_on: {{ ref('f2dq_base_metrics') }}
|
||||
{% if execute %}
|
||||
{{ f2dq.metric_expression(model, metric, expression, column_name, condition) }}
|
||||
{% else %}
|
||||
{{ f2dq.empty_table() }}
|
||||
{% endif %}
|
||||
{% endtest %}
|
||||
|
||||
{% test assert_false(model, column_name=None, metric=None, expression=expression, condition=None) %}
|
||||
-- depends_on: {{ ref('f2dq_base_metrics') }}
|
||||
{% if execute %}
|
||||
{{ f2dq.metric_expression(model, metric, 'not (' ~ expression ~ ')', column_name, condition) }}
|
||||
{% else %}
|
||||
{{ f2dq.empty_table() }}
|
||||
{% endif %}
|
||||
{% endtest %}
|
||||
|
||||
{% test assert_in_range(model, column_name=None, metric=None, min_value=None, max_value=None, condition=None) %}
|
||||
-- depends_on: {{ ref('f2dq_base_metrics') }}
|
||||
{% if execute %}
|
||||
{{ f2dq.metric_expression(model, metric, 'value >= ' ~ min_value ~ ' and value <= ' ~ max_value, column_name, condition) }}
|
||||
{% else %}
|
||||
{{ f2dq.empty_table() }}
|
||||
{% endif %}
|
||||
{% endtest %}
|
||||
|
||||
{% test assert_equal(model, column_name=None, metric=None, value=value, condition=None) %}
|
||||
-- depends_on: {{ ref('f2dq_base_metrics') }}
|
||||
{% if execute %}
|
||||
{{ f2dq.metric_expression(model, metric, 'value = ' ~ value, column_name, condition) }}
|
||||
{% else %}
|
||||
{{ f2dq.empty_table() }}
|
||||
{% endif %}
|
||||
{% endtest %}
|
||||
|
||||
{% test assert_greater(model, column_name=None, metric=None, value=None, condition=None) %}
|
||||
-- depends_on: {{ ref('f2dq_base_metrics') }}
|
||||
{% if execute %}
|
||||
{{ f2dq.metric_expression(model, metric, 'value > ' ~ value, column_name, condition) }}
|
||||
{% else %}
|
||||
{{ f2dq.empty_table() }}
|
||||
{% endif %}
|
||||
{% endtest %}
|
||||
|
||||
{% test assert_greater_equal(model, column_name=None, metric=None, value=None, condition=None) %}
|
||||
-- depends_on: {{ ref('f2dq_base_metrics') }}
|
||||
{% if execute %}
|
||||
{{ f2dq.metric_expression(model, metric, 'value >= ' ~ value, column_name, condition) }}
|
||||
{% else %}
|
||||
{{ f2dq.empty_table() }}
|
||||
{% endif %}
|
||||
{% endtest %}
|
||||
|
||||
{% test assert_less(model, column_name=None, metric=None, value=None, condition=None) %}
|
||||
-- depends_on: {{ ref('f2dq_base_metrics') }}
|
||||
{% if execute %}
|
||||
{{ f2dq.metric_expression(model, metric, 'value < ' ~ value, column_name, condition) }}
|
||||
{% else %}
|
||||
{{ f2dq.empty_table() }}
|
||||
{% endif %}
|
||||
{% endtest %}
|
||||
|
||||
{% test assert_less_equal(model, column_name=None, metric=None, value=None, condition=None) %}
|
||||
-- depends_on: {{ ref('f2dq_base_metrics') }}
|
||||
{% if execute %}
|
||||
{{ f2dq.metric_expression(model, metric, 'value <= ' ~ value, column_name, condition) }}
|
||||
{% else %}
|
||||
{{ f2dq.empty_table() }}
|
||||
{% endif %}
|
||||
{% endtest %}
|
||||
@@ -0,0 +1,20 @@
|
||||
|
||||
{% macro row_value(agate_row, column) %}
|
||||
{{ return (agate_row[f2dq.name_in_db(column)]) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro agate_to_list(table) %}
|
||||
{% set col_names = table.column_names %}
|
||||
{% set query_result = [] %}
|
||||
{% for row in table.rows %}
|
||||
{% set pairs = [] %}
|
||||
{% for col_name in col_names %}
|
||||
{% set value = row.get(col_name) | string %}
|
||||
{% do pairs.append('"' ~ (col_name | lower) ~ '":' ~ '"' ~ (value | replace('"', '\\\"') | replace('\n', '\\n') ) ~ '"') %}
|
||||
{% endfor %}
|
||||
{% set joined_pairs = '{' ~ (pairs | join(',')) ~ '}' %}
|
||||
{% do query_result.append(joined_pairs) %}
|
||||
{% endfor %}
|
||||
{% set query_result = '[' ~ (query_result | join(',')) ~ ']' %}
|
||||
{{ return (query_result) }}
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,8 @@
|
||||
|
||||
{% macro bool_to_string(column) %}
|
||||
(
|
||||
case when {{ column }} = true then 'true'
|
||||
when {{ column }} = false then 'false'
|
||||
end
|
||||
) as {{ column }}
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,102 @@
|
||||
|
||||
{% macro get_column_type(column) %}
|
||||
{% set result = adapter.dispatch('get_column_type', 'f2dq')(column) %}
|
||||
{{ return(result) }}
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro default__get_column_type(column) %}
|
||||
|
||||
{% if column.data_type in [
|
||||
'character varying',
|
||||
'varchar',
|
||||
'character',
|
||||
'char',
|
||||
'text'
|
||||
] %}
|
||||
{{ return('text') }}
|
||||
|
||||
{% elif column.data_type in [
|
||||
'smallint',
|
||||
'integer',
|
||||
'bigint',
|
||||
'decimal',
|
||||
'numeric',
|
||||
'real',
|
||||
'double precision',
|
||||
'enum',
|
||||
] %}
|
||||
{{ return('numeric') }}
|
||||
|
||||
{% elif column.data_type in [ 'boolean', 'bool' ] %}
|
||||
|
||||
{{ return('boolean') }}
|
||||
|
||||
{% else %}
|
||||
{{ return('unknown') }}
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro snowflake__get_column_type(column) %}
|
||||
|
||||
{% if column.DATA_TYPE in [
|
||||
'VARCHAR',
|
||||
'CHAR',
|
||||
'CHARACTER',
|
||||
'STRING',
|
||||
'TEXT'
|
||||
] %}
|
||||
|
||||
{{ return('text') }}
|
||||
|
||||
{% elif column.DATA_TYPE in [
|
||||
'NUMBER',
|
||||
'DECIMAL',
|
||||
'NUMERIC',
|
||||
'INT',
|
||||
'INTEGER',
|
||||
'BIGINT',
|
||||
'SMALLINT',
|
||||
'TINYINT',
|
||||
'BYTEINT',
|
||||
'FLOAT',
|
||||
'FLOAT4',
|
||||
'FLOAT8',
|
||||
'DOUBLE',
|
||||
'DOUBLE PRECISION',
|
||||
'REAL',
|
||||
] %}
|
||||
|
||||
{{ return('numeric') }}
|
||||
|
||||
{% elif column.data_type in [ 'BOOLEAN' ] %}
|
||||
|
||||
{{ return('boolean') }}
|
||||
|
||||
{% else %}
|
||||
|
||||
{{ return('unknown') }}
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro bigquery__get_column_type(column) %}
|
||||
|
||||
{% if column.data_type in [ 'STRING' ] %}
|
||||
{{ return('text') }}
|
||||
|
||||
{% elif column.data_type in [ "INT64", "NUMERIC", "BIGNUMERIC", "FLOAT64", "INTEGER"] %}
|
||||
{{ return('numeric') }}
|
||||
|
||||
{% elif column.data_type in [ "BOOLEAN", "BOOL"] %}
|
||||
{{ return('boolean') }}
|
||||
{% else %}
|
||||
{{ return('unknown') }}
|
||||
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,5 @@
|
||||
{% macro comma_delimited_list(args) %}
|
||||
{%- for arg in args %}
|
||||
{{- arg -}} {{- ", " if not loop.last else "" -}}
|
||||
{% endfor %}
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,5 @@
|
||||
{% macro comparison_text(a, b) %}
|
||||
case when {{a}} > {{b}} then 'greater than'
|
||||
when {{a}} = {{b}} then 'equal to'
|
||||
else 'less than' end
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,58 @@
|
||||
{# Dual-read helpers for migrating from re_data → f2dq without breaking
|
||||
projects that still use re_data_* config keys or re_data:* vars. #}
|
||||
|
||||
{% macro f2dq_var(name, default=none) %}
|
||||
{# Prefer f2dq:<name>; fall back to re_data:<name>; then default.
|
||||
For booleans, either namespace set to true wins (so a project that
|
||||
still sets re_data:store_table_samples: true keeps working even
|
||||
though the package default for f2dq:store_table_samples is false). #}
|
||||
{% set fkey = 'f2dq:' ~ name %}
|
||||
{% set rkey = 're_data:' ~ name %}
|
||||
{% set fval = var(fkey) if var.has_var(fkey) else none %}
|
||||
{% set rval = var(rkey) if var.has_var(rkey) else none %}
|
||||
{% if fval is sameas true or rval is sameas true %}
|
||||
{{ return(true) }}
|
||||
{% elif fval is sameas false and rval is sameas false %}
|
||||
{{ return(false) }}
|
||||
{% elif fval is not none and fval is not sameas false %}
|
||||
{{ return(fval) }}
|
||||
{% elif rval is not none and rval is not sameas false %}
|
||||
{{ return(rval) }}
|
||||
{% elif fval is not none %}
|
||||
{{ return(fval) }}
|
||||
{% elif rval is not none %}
|
||||
{{ return(rval) }}
|
||||
{% else %}
|
||||
{{ return(default) }}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro f2dq_node_config(el, key, default=none) %}
|
||||
{# Read f2dq_<key> / re_data_<key> from node config, then config.meta /
|
||||
node.meta (dbt 1.12 CustomKeyInConfigDeprecation migration path).
|
||||
`key` is the suffix without package prefix, e.g. 'monitored',
|
||||
'time_filter', 'metrics_groups'. #}
|
||||
{% set keys = ['f2dq_' ~ key, 're_data_' ~ key] %}
|
||||
{% for k in keys %}
|
||||
{% if el.config is mapping and el.config.get(k) is not none %}
|
||||
{{ return(el.config.get(k)) }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% set conf_meta = el.config.get('meta', {}) if el.config is mapping else {} %}
|
||||
{% if conf_meta is mapping %}
|
||||
{% for k in keys %}
|
||||
{% if conf_meta.get(k) is not none %}
|
||||
{{ return(conf_meta.get(k)) }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% set node_meta = el.get('meta', {}) if el is mapping else {} %}
|
||||
{% if node_meta is mapping %}
|
||||
{% for k in keys %}
|
||||
{% if node_meta.get(k) is not none %}
|
||||
{{ return(node_meta.get(k)) }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{{ return(default) }}
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,13 @@
|
||||
{% macro add_duplication_context(relation, unique_cols, sort_columns) %}
|
||||
|
||||
select {{ dbt_utils.star(from=relation) }}
|
||||
, count(*) over (
|
||||
partition by {{ f2dq.comma_delimited_list(unique_cols) }}
|
||||
) as f2dq_duplicate_group_row_count
|
||||
, row_number() over (
|
||||
partition by {{ f2dq.comma_delimited_list(unique_cols) }} {% if sort_columns %} order by {{ f2dq.comma_delimited_list(sort_columns) }} {% endif %}
|
||||
) as f2dq_duplicate_group_row_number
|
||||
|
||||
from {{ relation }}
|
||||
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,6 @@
|
||||
{% macro generate_depends(used_tables) %}
|
||||
{% for t in used_tables %}
|
||||
-- depends_on: {{ ref(t) }}
|
||||
{% endfor %}
|
||||
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
{% macro dict_from_list(el_list) %}
|
||||
|
||||
{% if el_list is none %}
|
||||
{{ return (none) }}
|
||||
{% endif %}
|
||||
|
||||
{% set for_cols_dict = {} %}
|
||||
{% for col in el_list %}
|
||||
{% do for_cols_dict.update({col: True})%}
|
||||
{% endfor %}
|
||||
{% do return(for_cols_dict) %}
|
||||
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,40 @@
|
||||
{#
|
||||
# This file contains significant part of code derived from
|
||||
# https://github.com/fivetran/dbt_fivetran_utils/tree/v0.4.0 which is licensed under Apache License 2.0.
|
||||
#}
|
||||
|
||||
{% macro json_extract(string, string_path) -%}
|
||||
|
||||
{{ adapter.dispatch('json_extract','f2dq') (string, string_path) }}
|
||||
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro default__json_extract(string, string_path) %}
|
||||
|
||||
json_extract_path_text({{string}}, {{ "'" ~ string_path ~ "'" }} )
|
||||
|
||||
{% endmacro %}
|
||||
|
||||
{% macro snowflake__json_extract(string, string_path) %}
|
||||
|
||||
json_extract_path_text(try_parse_json( {{string}} ), {{ "'" ~ string_path ~ "'" }} )
|
||||
|
||||
{% endmacro %}
|
||||
|
||||
{% macro redshift__json_extract(string, string_path) %}
|
||||
|
||||
case when is_valid_json( {{string}} ) then json_extract_path_text({{string}}, {{ "'" ~ string_path ~ "'" }} ) else null end
|
||||
|
||||
{% endmacro %}
|
||||
|
||||
{% macro bigquery__json_extract(string, string_path) %}
|
||||
|
||||
json_extract_scalar({{string}}, {{ "'$." ~ string_path ~ "'" }} )
|
||||
|
||||
{% endmacro %}
|
||||
|
||||
{% macro postgres__json_extract(string, string_path) %}
|
||||
|
||||
{{string}}::json->>{{"'" ~ string_path ~ "'" }}
|
||||
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,60 @@
|
||||
{#
|
||||
# This file contains significant part of code derived from
|
||||
# https://github.com/fivetran/dbt_fivetran_utils/tree/v0.4.0 which is licensed under Apache License 2.0.
|
||||
#}
|
||||
|
||||
{% macro percentile(percentile_field, partition_field, percent) -%}
|
||||
|
||||
{{ adapter.dispatch('percentile','f2dq') (percentile_field, partition_field, percent) }}
|
||||
|
||||
{%- endmacro %}
|
||||
|
||||
--percentile calculation specific to Redshift
|
||||
{% macro default__percentile(percentile_field, partition_field, percent) %}
|
||||
|
||||
percentile_cont(
|
||||
{{ percent }} )
|
||||
within group ( order by {{ percentile_field }} )
|
||||
over ( partition by {{ partition_field }} )
|
||||
|
||||
{% endmacro %}
|
||||
|
||||
--percentile calculation specific to Redshift
|
||||
{% macro redshift__percentile(percentile_field, partition_field, percent) %}
|
||||
|
||||
percentile_cont(
|
||||
{{ percent }} )
|
||||
within group ( order by {{ percentile_field }} )
|
||||
over ( partition by {{ partition_field }} )
|
||||
|
||||
{% endmacro %}
|
||||
|
||||
--percentile calculation specific to BigQuery
|
||||
{% macro bigquery__percentile(percentile_field, partition_field, percent) %}
|
||||
|
||||
percentile_cont(
|
||||
{{ percentile_field }},
|
||||
{{ percent }})
|
||||
over (partition by {{ partition_field }}
|
||||
)
|
||||
|
||||
{% endmacro %}
|
||||
|
||||
{% macro postgres__percentile(percentile_field, partition_field, percent) %}
|
||||
|
||||
percentile_cont(
|
||||
{{ percent }} )
|
||||
within group ( order by {{ percentile_field }} )
|
||||
/* have to group by partition field */
|
||||
|
||||
{% endmacro %}
|
||||
|
||||
{% macro spark__percentile(percentile_field, partition_field, percent) %}
|
||||
|
||||
percentile(
|
||||
{{ percentile_field }},
|
||||
{{ percent }})
|
||||
over (partition by {{ partition_field }}
|
||||
)
|
||||
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
{% macro print_list(l) %}
|
||||
{% for el in l %}{{el}}{% if not loop.last %},{% endif %}{% endfor %}
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,10 @@
|
||||
{% macro percentage_formula(summation, total) %}
|
||||
abs(
|
||||
(
|
||||
cast({{ summation }} as {{ numeric_type() }})
|
||||
) /
|
||||
nullif(
|
||||
cast( {{ total }} as {{ numeric_type() }} )
|
||||
, 0) * 100.0
|
||||
)
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,65 @@
|
||||
{% macro generate_anomaly_message(column_name, metric, last_value, last_avg) -%}
|
||||
{{ return(adapter.dispatch('generate_anomaly_message', 'f2dq')(column_name, metric, last_value, last_avg)) }}
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro default__generate_anomaly_message(column_name, metric, last_value, last_avg) %}
|
||||
|
||||
case when {{ column_name }} != '' then metric || '(' || column_name || ')'
|
||||
else metric
|
||||
end
|
||||
|| ' is ' ||
|
||||
{{ to_2dp( percentage_formula('last_value - last_avg', last_avg) ) }}
|
||||
|| '% ' ||
|
||||
{{ comparison_text(last_value, last_avg) }}
|
||||
|| ' average.'
|
||||
{% endmacro %}
|
||||
|
||||
{% macro to_2dp(val) %}
|
||||
{{ adapter.dispatch('to_2dp', 'f2dq')(val) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro default__to_2dp(val) %}
|
||||
trim(to_char({{ val }}, '9999999999999999990D00'))
|
||||
{% endmacro %}
|
||||
|
||||
{% macro bigquery__to_2dp(val) %}
|
||||
format('%.2f', {{ val }})
|
||||
{% endmacro %}
|
||||
|
||||
{% macro seconds_to_hours(val) %}
|
||||
cast({{ val }} as {{ numeric_type() }}) / 3600
|
||||
{% endmacro %}
|
||||
|
||||
{% macro generate_metric_value_text(metric, value) %}
|
||||
case
|
||||
when {{ metric }} = 'freshness'
|
||||
then cast({{ to_2dp(seconds_to_hours(value)) }} as {{ string_type() }}) || ' hours'
|
||||
when {{ regex_match_expression(metric, 'percent') }}
|
||||
then cast({{ to_2dp(value) }} as {{ string_type() }}) || '%'
|
||||
when {{ regex_match_expression(metric, 'count') }}
|
||||
then cast({{ value }} as {{ string_type() }})
|
||||
else cast({{ to_2dp(value) }} as {{ string_type() }})
|
||||
end
|
||||
|
||||
{% endmacro %}
|
||||
|
||||
{% macro generate_schema_change_message(operation, column_name, prev_column_name, prev_data_type, data_type, detected_time) %}
|
||||
case
|
||||
when {{ operation }} = 'column_added'
|
||||
then 'column ' || {{ column_name }} || ' of type ' || {{ data_type }} || ' was added.'
|
||||
when {{ operation }} = 'column_removed'
|
||||
then 'column ' || {{ prev_column_name }} || ' of type ' || {{ prev_data_type }} || ' was removed.'
|
||||
when {{ operation }} = 'type_change'
|
||||
then {{ column_name }} || ' column data type was changed from ' || {{ prev_data_type }} || ' to ' || {{ data_type }} || '.'
|
||||
else ''
|
||||
end
|
||||
{% endmacro %}
|
||||
|
||||
{% macro generate_failed_test_message(test_name, column_name) %}
|
||||
case
|
||||
when {{ column_name }} is null
|
||||
then 'Test ' || {{ test_name }} || ' failed.'
|
||||
else
|
||||
'Test ' || {{ test_name }} || ' failed for column ' || {{ column_name }} || '.'
|
||||
end
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,16 @@
|
||||
|
||||
{% macro get_target_database() %}
|
||||
{{- adapter.dispatch('get_target_database', 'f2dq')() -}}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro default__get_target_database() %}
|
||||
{{- return (target.dbname) -}}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro bigquery__get_target_database() %}
|
||||
{{- return (target.project) -}}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro snowflake__get_target_database() %}
|
||||
{{- return (target.database) -}}
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,16 @@
|
||||
{% macro in_compile() %}
|
||||
|
||||
{%- call statement('in_compile', fetch_result=True) -%}
|
||||
select * from {{ ref('f2dq_run_started_at') }}
|
||||
{%- endcall -%}
|
||||
|
||||
{% if execute %}
|
||||
{%- set result = load_result('in_compile')['data'][0][0] -%}
|
||||
{% if result == run_started_at.timestamp() * 1000000 %}
|
||||
{{ return(False) }}
|
||||
{% else %}
|
||||
{{ return(True) }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,7 @@
|
||||
{% macro is_list(obj) %}
|
||||
{% if not obj %}
|
||||
{{ return (False) }}
|
||||
{% endif %}
|
||||
{% set check = obj is iterable and (obj is not string and obj is not mapping) %}
|
||||
{{ return (check) }}
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,22 @@
|
||||
{% macro to_json_string_value_or_null(column) %}
|
||||
(
|
||||
case
|
||||
when {{ column }} is null then 'null'
|
||||
else '"' ||
|
||||
regexp_replace(
|
||||
replace(cast({{ column }} as {{ string_type() }}), '"', {{ escape_seq_for_json('"') }}),
|
||||
'\n', {{ quote_new_line() }} {% if target.type in postgres_type_db() %}, 'g' {% endif %}
|
||||
) || '"'
|
||||
end
|
||||
)
|
||||
{% endmacro %}
|
||||
|
||||
{% macro to_single_json(columns) %}
|
||||
'{' ||
|
||||
{%- for column in columns %}
|
||||
'"{{ column }}": ' ||
|
||||
{{ to_json_string_value_or_null(column) }}
|
||||
{%- if not loop.last %} || ',' || {%- endif %}
|
||||
{%- endfor %}
|
||||
|| '}'
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,67 @@
|
||||
|
||||
{% macro all_types_select() %}
|
||||
with types_table as (
|
||||
select
|
||||
cast (null as {{ string_type() }}) as string_type,
|
||||
cast (null as {{ long_string_type() }}) as long_string_type,
|
||||
cast (1 as {{ numeric_type() }}) as numeric_type,
|
||||
cast ('2000-01-10' as {{ timestamp_type() }}) as timestamp_type,
|
||||
cast (true as {{ boolean_type() }}) as boolean_type
|
||||
)
|
||||
{% endmacro %}
|
||||
|
||||
{% macro empty_table_generic(list) %}
|
||||
{{ f2dq.all_types_select() }}
|
||||
select
|
||||
{% for name, type in list %}
|
||||
{{ type }}_type as {{ name }}
|
||||
{%- if not loop.last %}, {%- endif %}
|
||||
{% endfor %}
|
||||
from types_table
|
||||
where string_type is not null
|
||||
{% endmacro %}
|
||||
|
||||
{% macro empty_last_base_metrics() %}
|
||||
{{
|
||||
f2dq.empty_table_generic([
|
||||
('table_name', 'string'),
|
||||
('column_name', 'string'),
|
||||
('metric', 'string'),
|
||||
('value', 'numeric')
|
||||
])
|
||||
}}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro empty_last_table_samples() %}
|
||||
{{
|
||||
f2dq.empty_table_generic([
|
||||
('table_name', 'string'),
|
||||
('sample_data', 'string')
|
||||
])
|
||||
}}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro empty_columns_table() %}
|
||||
{{
|
||||
f2dq.empty_table_generic([
|
||||
('name', 'string'),
|
||||
('schema', 'string'),
|
||||
('database', 'string'),
|
||||
('column_name', 'string'),
|
||||
('data_type', 'string'),
|
||||
('is_nullable', 'boolean'),
|
||||
('time_filter', 'string'),
|
||||
('computed_on', 'timestamp')
|
||||
])
|
||||
}}
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro empty_table() %}
|
||||
{{
|
||||
f2dq.empty_table_generic([
|
||||
('name', 'string')
|
||||
])
|
||||
}}
|
||||
{% endmacro %}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
|
||||
|
||||
{% macro columns_in_db(columns) %}
|
||||
{% set translated = [] %}
|
||||
{% if columns is none %}
|
||||
{{ return (none) }}
|
||||
{% endif %}
|
||||
{% for col in columns %}
|
||||
{% do translated.append(f2dq.name_in_db(col))%}
|
||||
{% endfor %}
|
||||
{{ return (translated) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro metrics_in_db(metrics) %}
|
||||
{% set translated = metrics %}
|
||||
{% set column_metrics = {} %}
|
||||
{% for col in metrics.column %}
|
||||
{% do column_metrics.update({f2dq.name_in_db(col): metrics.column[col]}) %}
|
||||
{% endfor %}
|
||||
{% if column_metrics %}
|
||||
{% do metrics.update({'column': column_metrics}) %}
|
||||
{% endif %}
|
||||
{{ return (metrics) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro final_metrics(metrics_groups, additional_metrics) %}
|
||||
{% set final_metrics_dict = dict([('group', {}), ('additional', {})]) %}
|
||||
{% set all_metrics_groups = f2dq.f2dq_var('metrics_groups') %}
|
||||
|
||||
{% for group in metrics_groups %}
|
||||
{% set value = all_metrics_groups.get(group) %}
|
||||
{% do final_metrics_dict['group'].update(value) %}
|
||||
{% endfor %}
|
||||
|
||||
{% do final_metrics_dict['additional'].update(additional_metrics) %}
|
||||
{{ return (final_metrics_dict) }}
|
||||
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
|
||||
{% macro quote_column(col_name) %}
|
||||
{{ adapter.dispatch('quote_column', 'f2dq')(col_name) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro default__quote_column(col_name) %}
|
||||
"{{ col_name }}"
|
||||
{% endmacro %}
|
||||
|
||||
{% macro bigquery__quote_column(col_name) %}
|
||||
`{{ col_name }}`
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,15 @@
|
||||
{% macro regex_match_expression(column_name, pattern) %}
|
||||
{{ adapter.dispatch('regex_match_expression', 'f2dq')(column_name, pattern) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro default__regex_match_expression(column_name, pattern) %}
|
||||
({{column_name}} ~ '{{pattern}}')
|
||||
{% endmacro %}
|
||||
|
||||
{% macro bigquery__regex_match_expression(column_name, pattern) %}
|
||||
regexp_contains({{column_name}}, r'{{pattern}}')
|
||||
{% endmacro %}
|
||||
|
||||
{% macro snowflake__regex_match_expression(column_name, pattern) %}
|
||||
regexp_like({{column_name | upper}}, '{{pattern}}')
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,100 @@
|
||||
|
||||
{% macro time_window_start() %}
|
||||
cast('{{- f2dq.f2dq_var('time_window_start') -}}' as timestamp)
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro time_window_end() %}
|
||||
cast('{{- f2dq.f2dq_var('time_window_end') -}}' as timestamp)
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro anamaly_detection_time_window_start() %}
|
||||
{{ adapter.dispatch('anamaly_detection_time_window_start', 'f2dq')() }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro default__anamaly_detection_time_window_start() %}
|
||||
{{ time_window_start() }} - interval '{{ f2dq.f2dq_var('anomaly_detection_look_back_days') }} days'
|
||||
{% endmacro %}
|
||||
|
||||
{% macro bigquery__anamaly_detection_time_window_start() %}
|
||||
DATE_ADD({{ time_window_start() }}, INTERVAL -{{ f2dq.f2dq_var('anomaly_detection_look_back_days') }} DAY)
|
||||
{% endmacro %}
|
||||
|
||||
{% macro snowflake__anamaly_detection_time_window_start() %}
|
||||
DATEADD('DAY', -{{- f2dq.f2dq_var('anomaly_detection_look_back_days') -}}, {{ time_window_start() }})
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro interval_length_sec(start_timestamp, end_timestamp) %}
|
||||
{{ adapter.dispatch('interval_length_sec', 'f2dq')(start_timestamp, end_timestamp) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro default__interval_length_sec(start_timestamp, end_timestamp) %}
|
||||
EXTRACT(EPOCH FROM ({{ end_timestamp }} - {{ start_timestamp }} ))
|
||||
{% endmacro %}
|
||||
|
||||
{% macro bigquery__interval_length_sec(start_timestamp, end_timestamp) %}
|
||||
TIMESTAMP_DIFF ({{ end_timestamp }}, {{ start_timestamp }}, SECOND)
|
||||
{% endmacro %}
|
||||
|
||||
{% macro snowflake__interval_length_sec(start_timestamp, end_timestamp) %}
|
||||
timediff(second, {{ start_timestamp }}, {{ end_timestamp }})
|
||||
{% endmacro %}
|
||||
|
||||
{% macro redshift__interval_length_sec(start_timestamp, end_timestamp) %}
|
||||
DATEDIFF(second, {{ start_timestamp }}, {{ end_timestamp }})
|
||||
{% endmacro %}
|
||||
|
||||
{%- macro in_time_window(time_column) %}
|
||||
{# /* If not time_filter is specified, we compute the metric over the entire table else we filter for the time frame */ #}
|
||||
{% if time_column is none %}
|
||||
true
|
||||
{% else %}
|
||||
{{ adapter.dispatch('in_time_window', 'f2dq')(time_column) }}
|
||||
{% endif %}
|
||||
{% endmacro -%}
|
||||
|
||||
{% macro default__in_time_window(time_column) %}
|
||||
{{time_column}} >= {{ time_window_start() }} and
|
||||
{{time_column}} < {{ time_window_end() }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro bigquery__in_time_window(time_column) %}
|
||||
cast({{time_column}} as timestamp) >= {{ time_window_start() }} and
|
||||
cast({{time_column}} as timestamp) < {{ time_window_end() }}
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro format_timestamp(column_name) %}
|
||||
{{ adapter.dispatch('format_timestamp', 'f2dq')(column_name) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro default__format_timestamp(column_name) %}
|
||||
to_char({{column_name}}, 'YYYY-MM-DD HH24:MI:SS')
|
||||
{% endmacro %}
|
||||
|
||||
{% macro bigquery__format_timestamp(column_name) %}
|
||||
FORMAT_TIMESTAMP('%Y-%m-%d %H:%I:%S', {{column_name}})
|
||||
{% endmacro %}
|
||||
|
||||
/*
|
||||
provide a common way to compare time vs a range: start_date <= target <= end_date
|
||||
if start_date is none: target <= end_date
|
||||
if end_date is none: target >= start_date
|
||||
think none as infinity
|
||||
*/
|
||||
{%- macro in_date_window(target, start_date, end_date) %}
|
||||
{{ adapter.dispatch('in_date_window','f2dq')(target, start_date, end_date) }}
|
||||
{% endmacro -%}
|
||||
|
||||
{% macro default__in_date_window(target, start_date, end_date) %}
|
||||
{% if start_date is not none and end_date is not none %}
|
||||
date({{target}}) between '{{start_date}}' and '{{end_date}}'
|
||||
{% elif start_date is none %}
|
||||
date({{target}}) <= '{{end_date}}'
|
||||
{% elif end_date is none %}
|
||||
date({{target}}) >= '{{start_date}}'
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
{% macro timestamp_type() %}
|
||||
{{ adapter.dispatch('timestamp_type', 'f2dq')() }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro default__timestamp_type() %}
|
||||
timestamp without time zone
|
||||
{% endmacro %}
|
||||
|
||||
{% macro redshift__timestamp_type() %}
|
||||
TIMESTAMP
|
||||
{% endmacro %}
|
||||
|
||||
{% macro bigquery__timestamp_type() %}
|
||||
TIMESTAMP
|
||||
{% endmacro %}
|
||||
|
||||
{% macro snowflake__timestamp_type() %}
|
||||
TIMESTAMP_NTZ
|
||||
{% endmacro %}
|
||||
|
||||
{% macro string_type() %}
|
||||
{{ adapter.dispatch('string_type', 'f2dq')() }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro default__string_type() %}
|
||||
text
|
||||
{% endmacro %}
|
||||
|
||||
{% macro redshift__string_type() %}
|
||||
varchar(2047)
|
||||
{% endmacro %}
|
||||
|
||||
{% macro bigquery__string_type() %}
|
||||
STRING
|
||||
{% endmacro %}
|
||||
|
||||
{% macro snowflake__string_type() %}
|
||||
STRING
|
||||
{% endmacro %}
|
||||
|
||||
{% macro long_string_type() %}
|
||||
{{ adapter.dispatch('long_string_type', 'f2dq')() }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro default__long_string_type() %}
|
||||
{{ f2dq.string_type() }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro redshift__long_string_type() %}
|
||||
varchar(65535)
|
||||
{% endmacro %}
|
||||
|
||||
{% macro integer_type() %}
|
||||
INTEGER
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro boolean_type() %}
|
||||
{{ adapter.dispatch('boolean_type', 'f2dq')() }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro default__boolean_type() %}
|
||||
BOOLEAN
|
||||
{% endmacro %}
|
||||
|
||||
{% macro redshift__boolean_type() %}
|
||||
boolean
|
||||
{% endmacro %}
|
||||
|
||||
{% macro bigquery__boolean_type() %}
|
||||
BOOLEAN
|
||||
{% endmacro %}
|
||||
|
||||
{% macro snowflake__boolean_type() %}
|
||||
BOOLEAN
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro numeric_type() %}
|
||||
{{ adapter.dispatch('numeric_type', 'f2dq')() }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro default__numeric_type() %}
|
||||
double precision
|
||||
{% endmacro %}
|
||||
|
||||
{% macro redshift__numeric_type() %}
|
||||
DOUBLE PRECISION
|
||||
{% endmacro %}
|
||||
|
||||
{% macro bigquery__numeric_type() %}
|
||||
FLOAT64
|
||||
{% endmacro %}
|
||||
|
||||
{% macro snowflake__numeric_type() %}
|
||||
FLOAT
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user