From a3c0ac8fbe294e1371a6313d2e1ab8e8eef368f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alp=20Ar=C4=B1bal?= Date: Sat, 3 Jun 2023 15:02:53 +0100 Subject: [PATCH 1/3] implement date_trunc --- .../sqlite/macros/utils/date_trunc.sql | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 dbt/include/sqlite/macros/utils/date_trunc.sql diff --git a/dbt/include/sqlite/macros/utils/date_trunc.sql b/dbt/include/sqlite/macros/utils/date_trunc.sql new file mode 100644 index 0000000..7727ab0 --- /dev/null +++ b/dbt/include/sqlite/macros/utils/date_trunc.sql @@ -0,0 +1,53 @@ +{% macro sqlite__date_trunc(datepart, date) -%} +{%- set datepart = datepart.lower() -%} + +{#- use the official modifier whenever possible -#} +{%- if datepart == "year" -%} datetime({{ date }}, 'start of year') +{%- elif datepart == "month" -%} datetime({{ date }}, 'start of month') +{%- elif datepart == "day" -%} datetime({{ date }}, 'start of day') + +{%- elif datepart == "quarter" -%} +{#- truncate to start of year, then add necessary number of months -#} +{#- note that we make use of integer division to round down -#} +datetime( + {{ date }}, + 'start of year', + '+' || cast((strftime('%m', {{ date }}) - 1) / 3 * 3 as text) || " month" +) + +{%- elif datepart == "week" -%} +{#- remove {day_no} days and truncate to start of day -#} +{#- note that week starts at Sunday, i.e. Sunday=0 -#} +datetime({{ date }}, ('-' || strftime('%w', {{ date }}) || ' day'), 'start of day') + +{%- elif datepart == "hour" -%} +{#- truncate to start of day, then add back hours -#} +datetime({{ date }}, 'start of day', '+' || strftime('%H', {{ date }}) || " hour") + +{%- elif datepart == "minute" -%} +{#- truncate to start of day, then add back hours and minutes -#} +datetime( + {{ date }}, + 'start of day', + '+' || strftime('%H', {{ date }}) || " hour", + '+' || strftime('%M', {{ date }}) || " minute" +) +{%- elif datepart == "second" -%} +{#- truncate to start of day, then add back hours, minutes, seconds -#} +datetime( + {{ date }}, + 'start of day', + '+' || strftime('%H', {{ date }}) || " hour", + '+' || strftime('%M', {{ date }}) || " minute", + '+' || strftime('%S', {{ date }}) || " second" +) + +{%- else -%} +{#- arithmetics for micro-/nanoseconds is more complicated, skipped for now -#} +{{ + exceptions.raise_compiler_error( + "Unsupported datepart for macro date_trunc in sqlite: {!r}".format(datepart) + ) +}} +{%- endif -%} +{%- endmacro %} From 3e475cf2ac11f511c0c84c8a5f24c6b0b2b5818e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alp=20Ar=C4=B1bal?= Date: Sat, 3 Jun 2023 15:03:03 +0100 Subject: [PATCH 2/3] test date_trunc --- tests/functional/adapter/utils/test_utils.py | 49 ++++++++++++++++++-- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/tests/functional/adapter/utils/test_utils.py b/tests/functional/adapter/utils/test_utils.py index fd40e79..aaf9a17 100644 --- a/tests/functional/adapter/utils/test_utils.py +++ b/tests/functional/adapter/utils/test_utils.py @@ -1,5 +1,6 @@ import pytest from dbt.tests.adapter.utils.base_utils import BaseUtils +from dbt.tests.adapter.utils.fixture_date_trunc import models__test_date_trunc_yml from dbt.tests.adapter.utils.fixture_datediff import ( seeds__data_datediff_csv, models__test_datediff_yml, @@ -14,7 +15,6 @@ from dbt.tests.adapter.utils.test_current_timestamp import BaseCurrentTimestampNaive from dbt.tests.adapter.utils.test_dateadd import BaseDateAdd #from dbt.tests.adapter.utils.test_datediff import BaseDateDiff -from dbt.tests.adapter.utils.test_date_trunc import BaseDateTrunc from dbt.tests.adapter.utils.test_escape_single_quotes import BaseEscapeSingleQuotesQuote from dbt.tests.adapter.utils.test_escape_single_quotes import BaseEscapeSingleQuotesBackslash from dbt.tests.adapter.utils.test_except import BaseExcept @@ -129,9 +129,50 @@ class TestDateDiff(BaseDateDiff): pass -@pytest.mark.skip("TODO: implement date_trunc") -class TestDateTrunc(BaseDateTrunc): - pass +class TestDateTrunc(BaseUtils): + seeds__data_date_trunc_csv = """date_to_trunc,year,quarter,month,week,day,hour,minute,second +1999-12-31 23:59:59.999999,1999-01-01 00:00:00,1999-10-01 00:00:00,1999-12-01 00:00:00,1999-12-26 00:00:00,1999-12-31 00:00:00,1999-12-31 23:00:00,1999-12-31 23:59:00,1999-12-31 23:59:59 +1999-12-31,1999-01-01 00:00:00,1999-10-01 00:00:00,1999-12-01 00:00:00,1999-12-26 00:00:00,1999-12-31 00:00:00,1999-12-31 00:00:00,1999-12-31 00:00:00,1999-12-31 00:00:00 +1999-01-01,1999-01-01 00:00:00,1999-01-01 00:00:00,1999-01-01 00:00:00,1998-12-27 00:00:00,1999-01-01 00:00:00,1999-01-01 00:00:00,1999-01-01 00:00:00,1999-01-01 00:00:00 +1999-05-16 12:21,1999-01-01 00:00:00,1999-04-01 00:00:00,1999-05-01 00:00:00,1999-05-16 00:00:00,1999-05-16 00:00:00,1999-05-16 12:00:00,1999-05-16 12:21:00,1999-05-16 12:21:00 +""" + + models__test_date_trunc_sql = """ + with data as ( + select * from {{ ref('data_date_trunc') }} + ) + """ + " union all ".join( + """ + select + {{{{ date_trunc("{datepart}", "date_to_trunc") }}}} as actual, + {datepart} as expected + from data""".format( + datepart=datepart + ) + for datepart in [ + "year", + "quarter", + "month", + "week", + "day", + "hour", + "minute", + "second", + ] + ) + + @pytest.fixture(scope="class") + def seeds(self): + return {"data_date_trunc.csv": self.seeds__data_date_trunc_csv} + + @pytest.fixture(scope="class") + def models(self): + return { + "test_date_trunc.yml": models__test_date_trunc_yml, + "test_date_trunc.sql": self.interpolate_macro_namespace( + self.models__test_date_trunc_sql, "date_trunc" + ), + } class TestEscapeSingleQuotes(BaseEscapeSingleQuotesQuote): From 3feb4b7be969f3f77bbbc9a786c7f91dd75229b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alp=20Ar=C4=B1bal?= Date: Sat, 3 Jun 2023 15:03:13 +0100 Subject: [PATCH 3/3] document --- dbt/include/sqlite/macros/utils/date_trunc.sql | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/dbt/include/sqlite/macros/utils/date_trunc.sql b/dbt/include/sqlite/macros/utils/date_trunc.sql index 7727ab0..4c42995 100644 --- a/dbt/include/sqlite/macros/utils/date_trunc.sql +++ b/dbt/include/sqlite/macros/utils/date_trunc.sql @@ -1,5 +1,16 @@ {% macro sqlite__date_trunc(datepart, date) -%} -{%- set datepart = datepart.lower() -%} +{#- Truncate a date to a specified datepart. + +`sqlite__date_trunc` always returns a datetime. + +Only supports the following date parts: +"year", "quarter", "month", "week", "day", "hour", "minute", "second" + +Makes use of official datetime modifiers when possible. +For details of date and time functions in sqlite, refer to the official documentation: +https://www.sqlite.org/lang_datefunc.html -#} +{#- datepart can be quoted or unquoted, lower or uppercase -#} +{%- set datepart = datepart.lower().strip("'") -%} {#- use the official modifier whenever possible -#} {%- if datepart == "year" -%} datetime({{ date }}, 'start of year')