Compare commits

...

2 Commits

Author SHA1 Message Date
Jeremy Cohen
8952617f61 Placeholder changelog for now 2022-05-18 11:46:21 +02:00
Jeremy Cohen
ce88aba97d Initialize lift + shift, dateadd + datediff 2022-05-18 11:39:09 +02:00
10 changed files with 281 additions and 0 deletions

View File

@@ -0,0 +1,7 @@
kind: Features
body: Move cross-db macros from dbt-utils into dbt-core global project
time: 2022-05-18T11:46:04.557104+02:00
custom:
Author: jtcohen6
Issue: "4813"
PR: "5265"

View File

@@ -0,0 +1,14 @@
{% macro dateadd(datepart, interval, from_date_or_timestamp) %}
{{ return(adapter.dispatch('dateadd', 'dbt')(datepart, interval, from_date_or_timestamp)) }}
{% endmacro %}
{% macro default__dateadd(datepart, interval, from_date_or_timestamp) %}
dateadd(
{{ datepart }},
{{ interval }},
{{ from_date_or_timestamp }}
)
{% endmacro %}

View File

@@ -0,0 +1,14 @@
{% macro datediff(first_date, second_date, datepart) %}
{{ return(adapter.dispatch('datediff', 'dbt')(first_date, second_date, datepart)) }}
{% endmacro %}
{% macro default__datediff(first_date, second_date, datepart) -%}
datediff(
{{ datepart }},
{{ first_date }},
{{ second_date }}
)
{%- endmacro %}

View File

@@ -0,0 +1,5 @@
{% macro postgres__dateadd(datepart, interval, from_date_or_timestamp) %}
{{ from_date_or_timestamp }} + ((interval '1 {{ datepart }}') * ({{ interval }}))
{% endmacro %}

View File

@@ -0,0 +1,32 @@
{% macro postgres__datediff(first_date, second_date, datepart) -%}
{% if datepart == 'year' %}
(date_part('year', ({{second_date}})::date) - date_part('year', ({{first_date}})::date))
{% elif datepart == 'quarter' %}
({{ datediff(first_date, second_date, 'year') }} * 4 + date_part('quarter', ({{second_date}})::date) - date_part('quarter', ({{first_date}})::date))
{% elif datepart == 'month' %}
({{ datediff(first_date, second_date, 'year') }} * 12 + date_part('month', ({{second_date}})::date) - date_part('month', ({{first_date}})::date))
{% elif datepart == 'day' %}
(({{second_date}})::date - ({{first_date}})::date)
{% elif datepart == 'week' %}
({{ datediff(first_date, second_date, 'day') }} / 7 + case
when date_part('dow', ({{first_date}})::timestamp) <= date_part('dow', ({{second_date}})::timestamp) then
case when {{first_date}} <= {{second_date}} then 0 else -1 end
else
case when {{first_date}} <= {{second_date}} then 1 else 0 end
end)
{% elif datepart == 'hour' %}
({{ datediff(first_date, second_date, 'day') }} * 24 + date_part('hour', ({{second_date}})::timestamp) - date_part('hour', ({{first_date}})::timestamp))
{% elif datepart == 'minute' %}
({{ datediff(first_date, second_date, 'hour') }} * 60 + date_part('minute', ({{second_date}})::timestamp) - date_part('minute', ({{first_date}})::timestamp))
{% elif datepart == 'second' %}
({{ datediff(first_date, second_date, 'minute') }} * 60 + floor(date_part('second', ({{second_date}})::timestamp)) - floor(date_part('second', ({{first_date}})::timestamp)))
{% elif datepart == 'millisecond' %}
({{ datediff(first_date, second_date, 'minute') }} * 60000 + floor(date_part('millisecond', ({{second_date}})::timestamp)) - floor(date_part('millisecond', ({{first_date}})::timestamp)))
{% elif datepart == 'microsecond' %}
({{ datediff(first_date, second_date, 'minute') }} * 60000000 + floor(date_part('microsecond', ({{second_date}})::timestamp)) - floor(date_part('microsecond', ({{first_date}})::timestamp)))
{% else %}
{{ exceptions.raise_compiler_error("Unsupported datepart for macro datediff in postgres: {!r}".format(datepart)) }}
{% endif %}
{%- endmacro %}

View File

@@ -0,0 +1,34 @@
import os
import pytest
from dbt.tests.util import run_dbt
macros__test_assert_equal_sql = """
{% test assert_equal(model, actual, expected) %}
select * from {{ model }} where {{ actual }} != {{ expected }}
{% endtest %}
"""
class BaseUtils:
# setup
@pytest.fixture(scope="class")
def macros(self):
return {"test_assert_equal.sql": macros__test_assert_equal_sql}
# make it possible to dynamically update the macro call with a namespace
# (e.g.) 'dateadd', 'dbt.dateadd', 'dbt_utils.dateadd'
def macro_namespace(self):
return ""
def interpolate_macro_namespace(self, model_sql, macro_name):
macro_namespace = self.macro_namespace()
return (
model_sql.replace(f"{macro_name}(", f"{macro_namespace}.{macro_name}(")
if macro_namespace
else model_sql
)
# actual test sequence
def test_build_assert_equal(self, project):
run_dbt(["build"]) # seed, model, test

View File

@@ -0,0 +1,40 @@
# dateadd
seeds__data_dateadd_csv = """from_time,interval_length,datepart,result
2018-01-01 01:00:00,1,day,2018-01-02 01:00:00
2018-01-01 01:00:00,1,month,2018-02-01 01:00:00
2018-01-01 01:00:00,1,year,2019-01-01 01:00:00
2018-01-01 01:00:00,1,hour,2018-01-01 02:00:00
,1,day,
"""
models__test_dateadd_sql = """
with data as (
select * from {{ ref('data_dateadd') }}
)
select
case
when datepart = 'hour' then cast({{ dateadd('hour', 'interval_length', 'from_time') }} as {{ api.Column.translate_type('timestamp') }})
when datepart = 'day' then cast({{ dateadd('day', 'interval_length', 'from_time') }} as {{ api.Column.translate_type('timestamp') }})
when datepart = 'month' then cast({{ dateadd('month', 'interval_length', 'from_time') }} as {{ api.Column.translate_type('timestamp') }})
when datepart = 'year' then cast({{ dateadd('year', 'interval_length', 'from_time') }} as {{ api.Column.translate_type('timestamp') }})
else null
end as actual,
result as expected
from data
"""
models__test_dateadd_yml = """
version: 2
models:
- name: test_dateadd
tests:
- assert_equal:
actual: actual
expected: expected
"""

View File

@@ -0,0 +1,66 @@
# datediff
seeds__data_datediff_csv = """first_date,second_date,datepart,result
2018-01-01 01:00:00,2018-01-02 01:00:00,day,1
2018-01-01 01:00:00,2018-02-01 01:00:00,month,1
2018-01-01 01:00:00,2019-01-01 01:00:00,year,1
2018-01-01 01:00:00,2018-01-01 02:00:00,hour,1
2018-01-01 01:00:00,2018-01-01 02:01:00,minute,61
2018-01-01 01:00:00,2018-01-01 02:00:01,second,3601
2019-12-31 00:00:00,2019-12-27 00:00:00,week,-1
2019-12-31 00:00:00,2019-12-30 00:00:00,week,0
2019-12-31 00:00:00,2020-01-02 00:00:00,week,0
2019-12-31 00:00:00,2020-01-06 02:00:00,week,1
,2018-01-01 02:00:00,hour,
2018-01-01 02:00:00,,hour,
"""
models__test_datediff_sql = """
with data as (
select * from {{ ref('data_datediff') }}
)
select
case
when datepart = 'second' then {{ datediff('first_date', 'second_date', 'second') }}
when datepart = 'minute' then {{ datediff('first_date', 'second_date', 'minute') }}
when datepart = 'hour' then {{ datediff('first_date', 'second_date', 'hour') }}
when datepart = 'day' then {{ datediff('first_date', 'second_date', 'day') }}
when datepart = 'week' then {{ datediff('first_date', 'second_date', 'week') }}
when datepart = 'month' then {{ datediff('first_date', 'second_date', 'month') }}
when datepart = 'year' then {{ datediff('first_date', 'second_date', 'year') }}
else null
end as actual,
result as expected
from data
-- Also test correct casting of literal values.
union all select {{ datediff("'1999-12-31 23:59:59.999999'", "'2000-01-01 00:00:00.000000'", "microsecond") }} as actual, 1 as expected
union all select {{ datediff("'1999-12-31 23:59:59.999999'", "'2000-01-01 00:00:00.000000'", "millisecond") }} as actual, 1 as expected
union all select {{ datediff("'1999-12-31 23:59:59.999999'", "'2000-01-01 00:00:00.000000'", "second") }} as actual, 1 as expected
union all select {{ datediff("'1999-12-31 23:59:59.999999'", "'2000-01-01 00:00:00.000000'", "minute") }} as actual, 1 as expected
union all select {{ datediff("'1999-12-31 23:59:59.999999'", "'2000-01-01 00:00:00.000000'", "hour") }} as actual, 1 as expected
union all select {{ datediff("'1999-12-31 23:59:59.999999'", "'2000-01-01 00:00:00.000000'", "day") }} as actual, 1 as expected
union all select {{ datediff("'1999-12-31 23:59:59.999999'", "'2000-01-03 00:00:00.000000'", "week") }} as actual, 1 as expected
union all select {{ datediff("'1999-12-31 23:59:59.999999'", "'2000-01-01 00:00:00.000000'", "month") }} as actual, 1 as expected
union all select {{ datediff("'1999-12-31 23:59:59.999999'", "'2000-01-01 00:00:00.000000'", "quarter") }} as actual, 1 as expected
union all select {{ datediff("'1999-12-31 23:59:59.999999'", "'2000-01-01 00:00:00.000000'", "year") }} as actual, 1 as expected
"""
models__test_datediff_yml = """
version: 2
models:
- name: test_datediff
tests:
- assert_equal:
actual: actual
expected: expected
"""

View File

@@ -0,0 +1,45 @@
import pytest
from dbt.tests.adapter.utils.base_utils import BaseUtils
from dbt.tests.adapter.utils.fixture_dateadd import (
seeds__data_dateadd_csv,
models__test_dateadd_sql,
models__test_dateadd_yml,
)
class BaseDateAdd(BaseUtils):
@pytest.fixture(scope="class")
def project_config_update(self):
return {
"name": "test",
# this is only needed for BigQuery, right?
# no harm having it here until/unless there's an adapter that doesn't support the 'timestamp' type
"seeds": {
"test": {
"data_dateadd": {
"+column_types": {
"from_time": "timestamp",
"result": "timestamp",
},
},
},
},
}
@pytest.fixture(scope="class")
def seeds(self):
return {"data_dateadd.csv": seeds__data_dateadd_csv}
@pytest.fixture(scope="class")
def models(self):
macro_namespace = self.macro_namespace()
return {
"test_dateadd.yml": models__test_dateadd_yml,
"test_dateadd.sql": self.interpolate_macro_namespace(
models__test_dateadd_sql, "dateadd"
),
}
class TestDateAdd(BaseDateAdd):
pass

View File

@@ -0,0 +1,24 @@
import pytest
from dbt.tests.adapter.utils.base_utils import BaseUtils
from dbt.tests.adapter.utils.fixture_datediff import (
seeds__data_datediff_csv,
models__test_datediff_sql,
models__test_datediff_yml,
)
class BaseDateDiff(BaseUtils):
@pytest.fixture(scope="class")
def seeds(self):
return {"data_datediff.csv": seeds__data_datediff_csv}
@pytest.fixture(scope="class")
def models(self):
return {
"test_datediff.yml": models__test_datediff_yml,
"test_datediff.sql": self.interpolate_macro_namespace(models__test_datediff_sql, "datediff"),
}
class TestDateDiff(BaseDateDiff):
pass