diff --git a/macros/utils/cross_db_utils/dateadd.sql b/macros/utils/cross_db_utils/dateadd.sql index c0e6ef1c8..209edea8e 100644 --- a/macros/utils/cross_db_utils/dateadd.sql +++ b/macros/utils/cross_db_utils/dateadd.sql @@ -1,7 +1,86 @@ {% macro edr_dateadd(datepart, interval, from_date_or_timestamp) %} + {{ + return( + adapter.dispatch("edr_dateadd", "elementary")( + datepart, interval, from_date_or_timestamp + ) + ) + }} +{% endmacro %} + +{% macro default__edr_dateadd(datepart, interval, from_date_or_timestamp) %} {% set macro = dbt.dateadd or dbt_utils.dateadd %} {% if not macro %} {{ exceptions.raise_compiler_error("Did not find a `dateadd` macro.") }} {% endif %} {{ return(macro(datepart, interval, from_date_or_timestamp)) }} {% endmacro %} + +{# + Override dbt-dremio's dateadd macro which has two bugs: + 1. Calls interval.replace() on the interval parameter, failing when interval is an integer + 2. Wraps result in "select TIMESTAMPADD(...)" which creates a scalar subquery when + embedded in larger SQL expressions, causing $SCALAR_QUERY errors in Dremio + + This override outputs just TIMESTAMPADD(...) as an expression (no "select" prefix). +#} +{% macro dremio__edr_dateadd(datepart, interval, from_date_or_timestamp) %} + {% set datepart = datepart | lower %} + {% if datepart == "year" %} + timestampadd( + year, + cast({{ interval }} as int), + cast({{ from_date_or_timestamp }} as timestamp) + ) + {% elif datepart == "quarter" %} + timestampadd( + quarter, + cast({{ interval }} as int), + cast({{ from_date_or_timestamp }} as timestamp) + ) + {% elif datepart == "month" %} + timestampadd( + month, + cast({{ interval }} as int), + cast({{ from_date_or_timestamp }} as timestamp) + ) + {% elif datepart == "week" %} + timestampadd( + week, + cast({{ interval }} as int), + cast({{ from_date_or_timestamp }} as timestamp) + ) + {% elif datepart == "hour" %} + timestampadd( + hour, + cast({{ interval }} as int), + cast({{ from_date_or_timestamp }} as timestamp) + ) + {% elif datepart == "minute" %} + timestampadd( + minute, + cast({{ interval }} as int), + cast({{ from_date_or_timestamp }} as timestamp) + ) + {% elif datepart == "second" %} + timestampadd( + second, + cast({{ interval }} as int), + cast({{ from_date_or_timestamp }} as timestamp) + ) + {% elif datepart == "day" %} + timestampadd( + day, + cast({{ interval }} as int), + cast({{ from_date_or_timestamp }} as timestamp) + ) + {% else %} + {{ + exceptions.raise_compiler_error( + "dremio__edr_dateadd: unrecognized datepart '" + ~ datepart + ~ "'. Supported: year, quarter, month, week, day, hour, minute, second." + ) + }} + {% endif %} +{% endmacro %} diff --git a/macros/utils/data_types/cast_column.sql b/macros/utils/data_types/cast_column.sql index e11fa02e0..6f89cf93b 100644 --- a/macros/utils/data_types/cast_column.sql +++ b/macros/utils/data_types/cast_column.sql @@ -42,10 +42,29 @@ {%- endmacro -%} -- fmt: on +{# + Dremio's Gandiva (Arrow execution engine) cannot parse ISO 8601 timestamps: + 1. The 'T' date-time separator is not recognized (needs space) + 2. Sub-millisecond precision causes overflow + 3. The 'Z' UTC timezone suffix is rejected as an unknown zone + This normalizes '2024-01-15T12:30:00.123456Z' to '2024-01-15 12:30:00.123'. + Three separate REGEXP_REPLACE calls are needed because each has a different + replacement pattern (space, truncation, removal) that can't be combined into one. +#} {%- macro dremio__edr_cast_as_timestamp(timestamp_field) -%} cast( regexp_replace( - {{ timestamp_field }}, '(\.\d{3})\d+', '$1' + regexp_replace( + regexp_replace( + cast({{ timestamp_field }} as {{ elementary.edr_type_string() }}), + '(\d)T(\d)', + '$1 $2' + ), + '(\.\d{3})\d+', + '$1' + ), + 'Z$', + '' ) as {{ elementary.edr_type_timestamp() }} ) {%- endmacro -%}