Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Column and table comments for postgres/redshift (#2333) #2378

Merged
merged 3 commits into from
May 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
- Sources (and therefore freshness tests) can be enabled and disabled via dbt_project.yml ([#2283](https://github.com/fishtown-analytics/dbt/issues/2283), [#2312](https://github.com/fishtown-analytics/dbt/pull/2312), [#2357](https://github.com/fishtown-analytics/dbt/pull/2357))
- schema.yml files are now fully rendered in a context that is aware of vars declared in from dbt_project.yml files ([#2269](https://github.com/fishtown-analytics/dbt/issues/2269), [#2357](https://github.com/fishtown-analytics/dbt/pull/2357))
- Sources from dependencies can be overridden in schema.yml files ([#2287](https://github.com/fishtown-analytics/dbt/issues/2287), [#2357](https://github.com/fishtown-analytics/dbt/pull/2357))
- Implement persist_docs for both `relation` and `comments` on postgres and redshift, and extract them when getting the catalog. ([#2333](https://github.com/fishtown-analytics/dbt/issues/2333), [#2378](https://github.com/fishtown-analytics/dbt/pull/2378))

### Fixes
- When a jinja value is undefined, give a helpful error instead of failing with cryptic "cannot pickle ParserMacroCapture" errors ([#2110](https://github.com/fishtown-analytics/dbt/issues/2110), [#2184](https://github.com/fishtown-analytics/dbt/pull/2184))
Expand Down
26 changes: 23 additions & 3 deletions core/dbt/clients/_jinja_blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,18 @@ def __init__(self, data):
self._parenthesis_stack = []
self.pos = 0

def linepos(self, end=None) -> str:
"""Given an absolute position in the input data, return a pair of
line number + relative position to the start of the line.
"""
end_val: int = self.pos if end is None else end
data = self.data[:end_val]
# if not found, rfind returns -1, and -1+1=0, which is perfect!
last_line_start = data.rfind('\n') + 1
# it's easy to forget this, but line numbers are 1-indexed
line_number = data.count('\n') + 1
return f'{line_number}:{end_val - last_line_start}'

def advance(self, new_position):
self.pos = new_position

Expand Down Expand Up @@ -320,20 +332,28 @@ def find_blocks(self, allowed_blocks=None, collect_raw_data=True):
dbt.exceptions.raise_compiler_error((
'Got an unexpected control flow end tag, got {} but '
'never saw a preceeding {} (@ {})'
).format(tag.block_type_name, expected, tag.start))
).format(
tag.block_type_name,
expected,
self.tag_parser.linepos(tag.start)
))
expected = _CONTROL_FLOW_TAGS[found]
if expected != tag.block_type_name:
dbt.exceptions.raise_compiler_error((
'Got an unexpected control flow end tag, got {} but '
'expected {} next (@ {})'
).format(tag.block_type_name, expected, tag.start))
).format(
tag.block_type_name,
expected,
self.tag_parser.linepos(tag.start)
))

if tag.block_type_name in allowed_blocks:
if self.stack:
dbt.exceptions.raise_compiler_error((
'Got a block definition inside control flow at {}. '
'All dbt block definitions must be at the top level'
).format(tag.start))
).format(self.tag_parser.linepos(tag.start)))
if self.current is not None:
dbt.exceptions.raise_compiler_error(
duplicate_tags.format(outer=self.current, inner=tag)
Expand Down
39 changes: 39 additions & 0 deletions core/dbt/include/global_project/macros/adapters/common.sql
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
as (
{{ sql }}
);

{% endmacro %}

{% macro create_view_as(relation, sql) -%}
Expand Down Expand Up @@ -280,3 +281,41 @@
{% macro set_sql_header(config) -%}
{{ config.set('sql_header', caller()) }}
{%- endmacro %}


{%- macro set_relation_comment(relation) -%}
{%- set raw_persist_docs = config.get('persist_docs', {}) -%}
{%- set comment = get_relation_comment(raw_persist_docs, model) -%}
{%- if comment is not none -%}
{{ alter_relation_comment(relation, comment) }}
{%- endif -%}
{%- endmacro -%}


{%- macro set_column_comments(relation) -%}
{%- set raw_persist_docs = config.get('persist_docs', {}) -%}
{%- set column_dict = get_relation_column_comments(raw_persist_docs, model) -%}
{%- if column_dict is not none -%}
{{ alter_column_comment(relation, column_dict) }}
{%- endif -%}
{%- endmacro -%}


{# copy+pasted from the snowflake PR - delete these 4 on merge #}
{% macro alter_column_comment(relation, column_dict) -%}
{{ return(adapter_macro('alter_column_comment', relation, column_dict)) }}
{% endmacro %}

{% macro default__alter_column_comment(relation, column_dict) -%}
{{ exceptions.raise_not_implemented(
'alter_column_comment macro not implemented for adapter '+adapter.type()) }}
{% endmacro %}

{% macro alter_relation_comment(relation, relation_comment) -%}
{{ return(adapter_macro('alter_relation_comment', relation, relation_comment)) }}
{% endmacro %}

{% macro default__alter_relation_comment(relation, relation_comment) -%}
{{ exceptions.raise_not_implemented(
'alter_relation_comment macro not implemented for adapter '+adapter.type()) }}
{% endmacro %}
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
{% macro table_options() %}
{%- set raw_persist_docs = config.get('persist_docs', {}) -%}

{%- endmacro -%}

{% macro get_relation_comment(persist_docs, model) %}

{%- if persist_docs is not mapping -%}
{{ exceptions.raise_compiler_error("Invalid value provided for 'persist_docs'. Expected dict but got value: " ~ raw_persist_docs) }}
{% endif %}

{% if persist_docs.get('relation', false) %}
{{ return((model.description | tojson)[1:-1]) }}
{{ return(model.description) }}
{%- else -%}
{{ return(none) }}
{% endif %}

{% endmacro %}


{# copy+pasted from the snowflake PR - delete this on merge #}
{% macro get_relation_column_comments(persist_docs, model) %}
{%- if persist_docs is not mapping -%}
{{ exceptions.raise_compiler_error("Invalid value provided for 'persist_docs'. Expected dict but got value: " ~ persist_docs) }}
{% endif %}

{% if persist_docs.get('columns', false) and model.columns|length != 0 %}
{{ return(model.columns) }}
{%- else -%}
{{ return(none) }}
{% endif %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
{% set unique_key = config.get('unique_key') %}
{% set full_refresh_mode = flags.FULL_REFRESH %}

{% set target_relation = this %}
{% set target_relation = this.incorporate(type='table') %}
{% set existing_relation = load_relation(this) %}
{% set tmp_relation = make_temp_relation(this) %}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,9 +210,7 @@
{% if not target_relation_exists %}

{% set build_sql = build_snapshot_table(strategy, model['injected_sql']) %}
{% call statement('main') -%}
{{ create_table_as(False, target_relation, build_sql) }}
{% endcall %}
{% set final_sql = create_table_as(False, target_relation, build_sql) %}

{% else %}

Expand Down Expand Up @@ -245,17 +243,19 @@
{% do quoted_source_columns.append(adapter.quote(column.name)) %}
{% endfor %}

{% call statement('main') %}
{{ snapshot_merge_sql(
target = target_relation,
source = staging_table,
insert_cols = quoted_source_columns
)
}}
{% endcall %}
{% set final_sql = snapshot_merge_sql(
target = target_relation,
source = staging_table,
insert_cols = quoted_source_columns
)
%}

{% endif %}

{% call statement('main') %}
{{ final_sql }}
{% endcall %}

{{ run_hooks(post_hooks, inside_transaction=True) }}

{{ adapter.commit() }}
Expand Down
24 changes: 14 additions & 10 deletions core/dbt/parser/macros.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,22 +48,26 @@ def parse_macro(
def parse_unparsed_macros(
self, base_node: UnparsedMacro
) -> Iterable[ParsedMacro]:
blocks: List[jinja.BlockTag] = [
t for t in
jinja.extract_toplevel_blocks(
base_node.raw_sql,
allowed_blocks={'macro', 'materialization'},
collect_raw_data=False,
)
if isinstance(t, jinja.BlockTag)
]
try:
blocks: List[jinja.BlockTag] = [
t for t in
jinja.extract_toplevel_blocks(
base_node.raw_sql,
allowed_blocks={'macro', 'materialization'},
collect_raw_data=False,
)
if isinstance(t, jinja.BlockTag)
]
except CompilationException as exc:
exc.add_node(base_node)
raise

for block in blocks:
try:
ast = jinja.parse(block.full_block)
except CompilationException as e:
e.add_node(base_node)
raise e
raise

macro_nodes = list(ast.find_all(jinja2.nodes.Macro))

Expand Down
11 changes: 10 additions & 1 deletion plugins/bigquery/dbt/include/bigquery/macros/adapters.sql
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,21 @@

{%- endmacro -%}


{%- macro bigquery_escape_comment(comment) -%}
{%- if comment is not string -%}
{%- do exceptions.raise_compiler_exception('cannot escape a non-string: ' ~ comment) -%}
{%- endif -%}
{%- do return((comment | tojson)[1:-1]) -%}
{%- endmacro -%}


{% macro bigquery_table_options(persist_docs, temporary, kms_key_name, labels) %}
{% set opts = {} -%}

{%- set description = get_relation_comment(persist_docs, model) -%}
{%- if description is not none -%}
{%- do opts.update({'description': "'" ~ description ~ "'"}) -%}
{%- do opts.update({'description': "'" ~ bigquery_escape_comment(description) ~ "'"}) -%}
{%- endif -%}
{%- if temporary -%}
{% do opts.update({'expiration_timestamp': 'TIMESTAMP_ADD(CURRENT_TIMESTAMP(), INTERVAL 12 hour)'}) %}
Expand Down
43 changes: 43 additions & 0 deletions plugins/postgres/dbt/include/postgres/macros/adapters.sql
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,20 @@
as (
{{ sql }}
);

{% set relation = relation.incorporate(type='table') %}
{{ set_relation_comment(relation) }}
{{ set_column_comments(relation) }}
{%- endmacro %}


{% macro postgres__create_view_as(relation, sql) %}
{{ default__create_view_as(relation, sql) }}
{%- set relation = relation.incorporate(type='view') -%}
{{ set_relation_comment(relation) }}
{{ set_column_comments(relation) }}
{% endmacro %}

{% macro postgres__create_schema(database_name, schema_name) -%}
{% if database_name -%}
{{ adapter.verify_database(database_name) }}
Expand Down Expand Up @@ -127,3 +139,34 @@
})) -%}
{% endmacro %}


{#
By using dollar-quoting like this, users can embed anything they want into their comments
(including nested dollar-quoting), as long as they don't use this exact dollar-quoting
label. It would be nice to just pick a new one but eventually you do have to give up.
#}
{% macro postgres_escape_comment(comment) -%}
{% if comment is not string %}
{% do exceptions.raise_compiler_exception('cannot escape a non-string: ' ~ comment) %}
{% endif %}
{%- set magic = '$dbt_comment_literal_block$' -%}
{%- if magic in comment -%}
{%- do exceptions.raise_compiler_exception('The string ' ~ magic ~ ' is not allowed in comments.') -%}
{%- endif -%}
{{ magic }}{{ comment }}{{ magic }}
{%- endmacro %}


{% macro postgres__alter_relation_comment(relation, comment) %}
{% set escaped_comment = postgres_escape_comment(comment) %}
comment on {{ relation.type }} {{ relation }} is {{ escaped_comment }};
{% endmacro %}


{% macro postgres__alter_column_comment(relation, column_dict) %}
{% for column_name in column_dict %}
{% set comment = column_dict[column_name]['description'] %}
{% set escaped_comment = postgres_escape_comment(comment) %}
comment on column {{ relation }}.{{ column_name }} is {{ escaped_comment }};
{% endfor %}
{% endmacro %}
6 changes: 4 additions & 2 deletions plugins/postgres/dbt/include/postgres/macros/catalog.sql
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,18 @@
when 'v' then 'VIEW'
else 'BASE TABLE'
end as table_type,
null::text as table_comment,
tbl_desc.description as table_comment,
col.attname as column_name,
col.attnum as column_index,
pg_catalog.format_type(col.atttypid, col.atttypmod) as column_type,
null::text as column_comment,
col_desc.description as column_comment,
pg_get_userbyid(tbl.relowner) as table_owner

from pg_catalog.pg_namespace sch
join pg_catalog.pg_class tbl on tbl.relnamespace = sch.oid
join pg_catalog.pg_attribute col on col.attrelid = tbl.oid
left outer join pg_catalog.pg_description tbl_desc on (tbl_desc.objoid = tbl.oid and tbl_desc.objsubid = 0)
left outer join pg_catalog.pg_description col_desc on (col_desc.objoid = tbl.oid and col_desc.objsubid = col.attnum)

where (
{%- for schema in schemas -%}
Expand Down
28 changes: 27 additions & 1 deletion plugins/redshift/dbt/include/redshift/macros/adapters.sql
Original file line number Diff line number Diff line change
Expand Up @@ -49,19 +49,34 @@
as (
{{ sql }}
);

{% set relation = relation.incorporate(type='table') %}
{{ set_relation_comment(relation) }}
{{ set_column_comments(relation) }}
{%- endmacro %}


{% macro redshift__create_view_as(relation, sql) -%}
{%- set binding = config.get('bind', default=True) -%}

{% set bind_qualifier = '' if config.get('bind', default=True) else 'with no schema binding' %}
{% set bind_qualifier = '' if binding else 'with no schema binding' %}
{%- set sql_header = config.get('sql_header', none) -%}

{{ sql_header if sql_header is not none }}

create view {{ relation }} as (
{{ sql }}
) {{ bind_qualifier }};

{#
For late-binding views, it's possible to set comments on the view (though they don't seem to end up anywhere).
Unfortunately, setting comments on columns just results in an error.
#}
{% set relation = relation.incorporate(type='view') %}
{{ set_relation_comment(relation) }}
{% if binding %}
{{ set_column_comments(relation) }}
{% endif %}
{% endmacro %}


Expand Down Expand Up @@ -188,3 +203,14 @@
{% macro redshift__make_temp_relation(base_relation, suffix) %}
{% do return(postgres__make_temp_relation(base_relation, suffix)) %}
{% endmacro %}


{% macro redshift__alter_relation_comment(relation, comment) %}
{% do return(postgres__alter_relation_comment(relation, comment)) %}
{% endmacro %}


{% macro redshift__alter_column_comment(relation, column_dict) %}
{% do return(postgres__alter_column_comment(relation, column_dict)) %}
{% endmacro %}

Loading