dbt-labs / dbt-core

dbt enables data analysts and engineers to transform their data using the same practices that software engineers use to build applications.
https://getdbt.com
Apache License 2.0
9.49k stars 1.58k forks source link

[CT-705] Review `dbt_utils` "helper" methods: _is_relation + _is_ephemeral #5316

Open jtcohen6 opened 2 years ago

jtcohen6 commented 2 years ago

Many dbt_utils macros need to access information about an underlying table, by running adapter.get_columns_in_relation or a custom query.

Those macros expect to be passed a ref, source, or other Relation object, and for it to represent an object that really exists in the database. To ensure that's the case, and raise a helpful error if it isn't, they first call "internal helper" macros, _is_relation and _is_ephemeral, to check that the passed argument (a) really is a Relation, (b) isn't ephemeral (= exists as a database object, assuming it's already been run).

Because this is Jinja, we can't just call isinstance, or classmethods. We have to check meta properties.

dbt_utils._is_relation

{% macro _is_relation(obj, macro) %}
    {%- if not (obj is mapping and obj.get('metadata', {}).get('type', '').endswith('Relation')) -%}
        {%- do exceptions.raise_compiler_error("Macro " ~ macro ~ " expected a Relation but received the value: " ~ obj) -%}
    {%- endif -%}
{% endmacro %}

dbt_utils._is_ephemeral

There is a node method, is_ephemeral, but nothing available on the actual Relation object returned by ref... except the is_cte property:

{% macro _is_ephemeral(obj, macro) %}
    {%- if obj.is_cte -%}
        {% set ephemeral_prefix = api.Relation.add_ephemeral_prefix('') %}
        {% if obj.name.startswith(ephemeral_prefix) %}
            {% set model_name = obj.name[(ephemeral_prefix|length):] %}
        {% else %}
            {% set model_name = obj.name %}
        {%- endif -%}
        {% set error_message %}
The `{{ macro }}` macro cannot be used with ephemeral models, as it relies on the information schema.

`{{ model_name }}` is an ephemeral model. Consider making it a view or table instead.
        {% endset %}
        {%- do exceptions.raise_compiler_error(error_message) -%}
    {%- endif -%}
{% endmacro %}

Questions

nathaniel-may commented 2 years ago

Would we consider exposing a python type function like isinstance() or type() to the jinja context directly?

whisperstream commented 1 year ago

Chiming in here - I would find isinstance/type useful for a use case I have. I'm processing data in macro that comes from the config/meta dictionaries and being able to understand the the data type of the value in the dictionary would be useful.