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

Update DbtTemplater to use JinjaTracer #1788

Merged
Show file tree
Hide file tree
Changes from 109 commits
Commits
Show all changes
113 commits
Select commit Hold shift + click to select a range
6cd3175
Issue 1437: Implement a robust Jinja raw <---> templated mapping
Oct 16, 2021
fd61d38
Copy PythonTemplater.slice_file() to JinjaTemplater
Oct 16, 2021
1c48c41
Prototype new Jinja template mapping approach
Oct 16, 2021
a619497
Handle "templated" type by evaluating but outputting a UUID
Oct 17, 2021
012b575
Rename the field: kindred_raw -> unique_alternate
Oct 17, 2021
b15be0c
Tidying, renaming
Oct 17, 2021
2d71c6c
Add angle brackets around UUID, fix bug where it wasn't returning UUID
Oct 17, 2021
e7cb0d8
Add and popuate RawFileSlice.next_slice_indices
Oct 17, 2021
82018cb
First prototype of TemplateTracer
Oct 17, 2021
9f5a2af
Passes a test!
Oct 17, 2021
b445b0b
Add handling for the case where multiple templated parts are returned…
Oct 18, 2021
eaa5c5b
More bug fixes
Oct 18, 2021
cbba906
Get all the test__templater_jinja_slices tests updated & working
Oct 18, 2021
a66c5f9
Fix test__templater_jinja_error_variable
Oct 18, 2021
d6483bd
Fix a subtle bug that confounded int length with actual templated text
Oct 18, 2021
74dfd12
Fix bug handling "raw" Jinja blocks
Oct 18, 2021
4538be2
Bug fix: Not popping stack on "endif" or "endfor"
Oct 18, 2021
241bbf5
Fix bugs involving {% set %}
Oct 19, 2021
5faa136
Handle both flavors of Jinja "set" command
Oct 19, 2021
99c77c7
Handle where Jinja gives us multiple sections of output at once
Oct 19, 2021
b779eff
Fix bug checking templated file slice
Oct 19, 2021
0546af9
More rework of how to generate and process alt template output
Oct 20, 2021
27fba8d
Split alt template output on NUL ("\0") characters
Oct 20, 2021
7f0ad57
Fix right whitespace stripping case
Oct 20, 2021
ea42fdd
All the test__templater_jinja_slice_template tests are now working
Oct 20, 2021
f7d8cb9
Fix a few tests
Oct 20, 2021
5d74488
Fix some more tests
Oct 20, 2021
45294f0
Update a test with new results (more refined than before)
Oct 20, 2021
c163d96
Update a test whose results have (I think legitimately) changed
Oct 20, 2021
5201022
When looping, don't repeat the {% for %} TemplatedFileSlice
Oct 20, 2021
ccdc06b
Fix some integration issues related to RawFileSlice switching from Na…
Oct 20, 2021
2e2928e
Merge branch 'main' into bhart-issue_1437_robust_jinja_raw_templated_…
barrywhart Oct 20, 2021
c8b01f0
Linter
Oct 20, 2021
51508c3
Update Python tests affected by RawFileSlice changes
Oct 20, 2021
198f533
Updated test result -- looks better now!
Oct 20, 2021
5846b9a
Merge branch 'main' into bhart-issue_1437_robust_jinja_raw_templated_…
barrywhart Oct 20, 2021
c7ccb14
Move the internal error check _inside_ the loop
Oct 21, 2021
0d31e2d
Merge branch 'bhart-issue_1437_robust_jinja_raw_templated_mapping' of…
Oct 21, 2021
e5b9084
Generate nicer placeholder strings for omitted code
Oct 21, 2021
a5119f3
Merge branch 'main' into bhart-issue_1437_robust_jinja_raw_templated_…
barrywhart Oct 21, 2021
3e43762
Tweak placeholder gap output, update expected test output
Oct 21, 2021
863611b
Merge branch 'main' of https://github.com/sqlfluff/sqlfluff
Oct 21, 2021
781c03d
Merge branch 'main' into bhart-issue_1437_robust_jinja_raw_templated_…
Oct 21, 2021
c14e3a7
Indentation improvements for templated code
Oct 22, 2021
9b8cf89
Merge branch 'main' into bhart-issue_1437_robust_jinja_raw_templated_…
Oct 22, 2021
150e756
Tweaks to L003 to avoid breaking non-templated indentation
Oct 22, 2021
dfb69b4
Update existing L003 tests, add a new one
Oct 22, 2021
5841eee
More work on L003
Oct 22, 2021
2e6d28d
Tidying
Oct 24, 2021
f6bcb6d
Streamlining
Oct 24, 2021
d5e4011
Tidying
Oct 24, 2021
46d9f46
Comments, tidying
Oct 24, 2021
3017290
Tidying
Oct 24, 2021
cb595c6
Mypy
Oct 24, 2021
49474d1
PR review
Oct 25, 2021
3ac4346
Rename MetaSegment.template as MetaSegment.is_template
Oct 25, 2021
7e3701e
Refactor: Move tracer class to a new module, tidy up interface with J…
Oct 25, 2021
07c8309
mypy
Oct 25, 2021
c968a8b
Merge branch 'main' into bhart-issue_1437_robust_jinja_raw_templated_…
barrywhart Oct 25, 2021
25c616e
Update test result
Oct 25, 2021
f2af390
Update test result
Oct 25, 2021
e932136
mypy
Oct 25, 2021
f422e66
Merge branch 'main' into bhart-issue_1437_robust_jinja_raw_templated_…
Oct 25, 2021
6d9d8c4
Fix DbtTemplater; it needed the old Jinja _slice_template() function
Oct 25, 2021
f3dd85a
Black, mypy
Oct 25, 2021
41d74e6
Linter
Oct 25, 2021
95a2497
mypy
Oct 25, 2021
3559f3c
Merge branch 'main' into bhart-issue_1437_robust_jinja_raw_templated_…
barrywhart Oct 25, 2021
cd737d1
Merge branch 'main' into bhart-issue_1437_robust_jinja_raw_templated_…
Oct 25, 2021
f2ed81f
Merge branch 'bhart-issue_1437_robust_jinja_raw_templated_mapping' of…
Oct 25, 2021
0eb965d
Add a few "no cover" comments
Oct 26, 2021
3be29cc
Merge branch 'main' into bhart-issue_1437_robust_jinja_raw_templated_…
barrywhart Oct 26, 2021
84923fa
Add "no cover" comments, remove unused L003 code
Oct 26, 2021
0831bf6
Merge branch 'bhart-issue_1437_robust_jinja_raw_templated_mapping' of…
Oct 26, 2021
a55201b
Move tracer-specific RawFileSlice fields into a dictionary
Oct 26, 2021
43f457a
Black, mypy
Oct 26, 2021
5cbbb36
mypy
Oct 26, 2021
a02a7f8
Merge branch 'main' into bhart-issue_1437_robust_jinja_raw_templated_…
barrywhart Oct 27, 2021
6485c0f
Merge branch 'main' into bhart-issue_1437_robust_jinja_raw_templated_…
barrywhart Oct 28, 2021
3451f5c
Merge branch 'main' into bhart-issue_1437_robust_jinja_raw_templated_…
barrywhart Oct 29, 2021
5297c5a
Merge branch 'main' into bhart-issue_1437_robust_jinja_raw_templated_…
Oct 29, 2021
5cb6ea7
Merge branch 'bhart-issue_1437_robust_jinja_raw_templated_mapping' of…
Oct 30, 2021
b0a1306
Update DbtTemplater to use JinjaTracer
Oct 31, 2021
06359c9
Merge branch 'main' into bhart-issue_1783_update_dbt_templater_to_use…
barrywhart Oct 31, 2021
238c29d
Add "no cover" comments for code "uncovered" by this PR
Oct 31, 2021
5371383
Merge branch 'bhart-issue_1783_update_dbt_templater_to_use_jinjatrace…
Oct 31, 2021
113781a
Merge branch 'main' into bhart-issue_1783_update_dbt_templater_to_use…
Oct 31, 2021
288549d
Add "no cover" comment
Oct 31, 2021
800daa6
Comments
Oct 31, 2021
e8dbbe1
Linter
Oct 31, 2021
5b272e2
Merge branch 'main' into bhart-issue_1783_update_dbt_templater_to_use…
barrywhart Oct 31, 2021
86ef486
Merge branch 'main' into bhart-issue_1783_update_dbt_templater_to_use…
barrywhart Nov 1, 2021
4e96b4d
Merge branch 'main' into bhart-issue_1783_update_dbt_templater_to_use…
alanmcruickshank Nov 1, 2021
3db451e
Merge branch 'main' into bhart-issue_1783_update_dbt_templater_to_use…
Nov 1, 2021
f53ca89
Merge branch 'bhart-issue_1783_update_dbt_templater_to_use_jinjatrace…
Nov 1, 2021
3e448cc
Merge branch 'main' into bhart-issue_1783_update_dbt_templater_to_use…
Nov 2, 2021
b8f2864
Add missing __init__.py file
Nov 2, 2021
d7170ca
Add docstring
Nov 2, 2021
7bdc061
mypy
Nov 2, 2021
7ee9264
Merge branch 'main' into bhart-issue_1783_update_dbt_templater_to_use…
barrywhart Nov 3, 2021
407e26c
Merge branch 'main' into bhart-issue_1783_update_dbt_templater_to_use…
barrywhart Nov 5, 2021
08f402f
Add 1 1/2 dbt templater tests
Nov 5, 2021
fbb2de6
Merge branch 'bhart-issue_1783_update_dbt_templater_to_use_jinjatrace…
Nov 5, 2021
f8d74b4
Update test so it doesn't require a database
Nov 5, 2021
f35b00e
Merge branch 'main' into bhart-issue_1783_update_dbt_templater_to_use…
barrywhart Nov 6, 2021
801265b
Merge branch 'main' into bhart-issue_1783_update_dbt_templater_to_use…
barrywhart Nov 7, 2021
83bb966
Merge branch 'main' into bhart-issue_1783_update_dbt_templater_to_use…
Nov 7, 2021
e278f20
Fix broken test
Nov 7, 2021
725e87b
Merge branch 'bhart-issue_1783_update_dbt_templater_to_use_jinjatrace…
Nov 7, 2021
807ce6c
Merge branch 'main' into bhart-issue_1783_update_dbt_templater_to_use…
barrywhart Nov 7, 2021
300c5b5
Merge branch 'main' into bhart-issue_1783_update_dbt_templater_to_use…
barrywhart Nov 8, 2021
fae2ce2
Merge branch 'main' into bhart-issue_1783_update_dbt_templater_to_use…
barrywhart Nov 9, 2021
5787fd2
Merge branch 'main' into bhart-issue_1783_update_dbt_templater_to_use…
alanmcruickshank Nov 9, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
65 changes: 63 additions & 2 deletions plugins/sqlfluff-templater-dbt/sqlfluff_templater_dbt/templater.py
Expand Up @@ -19,10 +19,15 @@
CompilationException as DbtCompilationException,
FailedToConnectException as DbtFailedToConnectException,
)
from jinja2 import Environment

from sqlfluff.core.errors import SQLTemplaterError, SQLTemplaterSkipFile

from sqlfluff.core.templaters.base import RawFileSlice, TemplatedFile
from sqlfluff.core.templaters.base import (
RawFileSlice,
TemplatedFile,
TemplatedFileSlice,
)

from sqlfluff.core.templaters.slicers.heuristic import slice_template
from sqlfluff.core.templaters.jinja import JinjaTemplater
Expand Down Expand Up @@ -374,13 +379,53 @@ def _find_node(self, fname, config=None):
return results[0]

def _unsafe_process(self, fname, in_str=None, config=None):
original_file_path = os.path.relpath(fname, start=os.getcwd())

# Below, we monkeypatch Environment.from_string() to intercept when dbt
# compiles (i.e. runs Jinja) to expand the "node" corresponding to fname.
# We do this to capture the Jinja context at the time of compilation, i.e.:
# - Jinja Environment object
# - Jinja "globals" dictionary
#
# This info is captured by the "make_template()" function, which in
# turn is used by our parent class' (JinjaTemplater) slice_file()
# function.
old_from_string = Environment.from_string
try:
make_template = None

def from_string(*args, **kwargs):
"""Replaces (via monkeypatch) the jinja2.Environment function."""
nonlocal make_template
# Is it processing the node corresponding to fname?
globals = kwargs.get("globals")
if globals:
model = globals.get("model")
if model:
if model.get("original_file_path") == original_file_path:
# Yes. Capture the important arguments and create
# a make_template() function.
env = args[0]
globals = args[2] if len(args) >= 3 else kwargs["globals"]

def make_template(in_str):
return env.from_string(in_str, globals=globals)

return old_from_string(*args, **kwargs)

finally:
# Undo the monkeypatch.
Environment.from_string = from_string

node = self._find_node(fname, config)

node = self.dbt_compiler.compile_node(
node=node,
manifest=self.dbt_manifest,
)

Environment.from_string = old_from_string

if hasattr(node, "injected_sql"):
# If injected SQL is present, it contains a better picture
# of what will actually hit the database (e.g. with tests).
Expand Down Expand Up @@ -428,8 +473,24 @@ def _unsafe_process(self, fname, in_str=None, config=None):
node.raw_sql,
compiled_sql,
config=config,
make_template=make_template,
)

if make_template and n_trailing_newlines:
# Update templated_sql as we updated the other strings above. Update
# sliced_file to reflect the mapping of the added character(s) back
# to the raw SQL.
templated_sql = templated_sql + "\n" * n_trailing_newlines
sliced_file.append(
TemplatedFileSlice(
slice_type="literal",
source_slice=slice(
len(node.raw_sql) - n_trailing_newlines, len(node.raw_sql)
),
templated_slice=slice(
len(templated_sql) - n_trailing_newlines, len(templated_sql)
),
)
)
return (
TemplatedFile(
source_str=node.raw_sql,
Expand Down
Expand Up @@ -12,4 +12,4 @@ where not products._fivetran_deleted
{% if true -%}
and products.valid_date_local >= (
select max(valid_date_local) from {{ this }})
{% endif -%}
{% endif %}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This started breaking after the recent L009 PR was merged. IIUC, using whitespace control at the end of the file caused all the trailing newlines to be (correctly) removed by Jinja, thus L009 would report an issue. I feel comfortable with this change, i.e. that the behavior was correct and that this fix makes sense.

Expand Up @@ -8,7 +8,7 @@ final as
(
select
col_name,
{{- echo('col_name') -}} as col_name2
{{- echo('col_name') -}} as col_name2
from
cte_example
)
Expand Down
@@ -0,0 +1,8 @@
with last_day_macro as (

select
{{ dbt_utils.last_day('2021-11-05', 'month') }}

)

select * from last_day_macro
@@ -0,0 +1,2 @@
{# {{ 1 + 2 }} #}
select 1
26 changes: 26 additions & 0 deletions plugins/sqlfluff-templater-dbt/test/fixtures/dbt/last_day.sql
@@ -0,0 +1,26 @@
with last_day_macro as (

select

cast(







date_trunc('month', 2021-11-05)
+ ((interval '1 month') * (1))


+ ((interval '1 day') * (-1))



as date)


)

select * from last_day_macro
@@ -0,0 +1,2 @@

select 1
4 changes: 4 additions & 0 deletions plugins/sqlfluff-templater-dbt/test/templater_test.py
Expand Up @@ -54,6 +54,10 @@ def test__templater_dbt_profiles_dir_expanded(dbt_templater): # noqa: F811
"use_headers.sql",
# var(...)
"use_var.sql",
# {# {{ 1 + 2 }} #}
"templated_inside_comment.sql",
# {{ dbt_utils.last_day(
"last_day.sql",
],
)
def test__templater_dbt_templating_result(
Expand Down
2 changes: 1 addition & 1 deletion src/sqlfluff/core/templaters/jinja.py
Expand Up @@ -345,7 +345,7 @@ def slice_file(
cls, raw_str: str, templated_str: str, config=None, **kwargs
) -> Tuple[List[RawFileSlice], List[TemplatedFileSlice], str]:
"""Slice the file to determine regions where we can fix."""
# The TemplateTracer slicing algorithm is more robust, but it requires
# The JinjaTracer slicing algorithm is more robust, but it requires
# us to create and render a second template (not raw_str) and is only
# enabled if the caller passes a make_template() function. (For now,
# the dbt templater does not.)
Expand Down
31 changes: 14 additions & 17 deletions src/sqlfluff/core/templaters/python.py
Expand Up @@ -563,7 +563,7 @@ def _coalesce_types(elems: List[RawFileSlice]) -> str:
types = {elem.slice_type for elem in elems}
# Replace block types with templated
for typ in list(types):
if typ.startswith("block_"):
if typ.startswith("block_"): # pragma: no cover
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and elsewhere, we see that large areas of older slicing code are no longer used and could potentially be removed in a future PR.

types.remove(typ)
types.add("templated")
# Take the easy route if they're all the same type
Expand Down Expand Up @@ -614,7 +614,7 @@ def _split_uniques_coalesce_rest(
int_file_slice.templated_slice.stop
- int_file_slice.templated_slice.start
== 0
):
): # pragma: no cover
point_combo = int_file_slice.coalesce()
templater_logger.debug(
" Yielding Point Combination: %s", point_combo
Expand All @@ -636,13 +636,13 @@ def _split_uniques_coalesce_rest(
templated_str=templated_str
)
if head_buffer:
yield from head_buffer
yield from head_buffer # pragma: no cover
# Have we consumed the whole thing?
if not int_file_slice.slice_buffer:
continue
continue # pragma: no cover

# Try to yield simply again (post trim)
try:
try: # pragma: no cover
simple_elem = int_file_slice.try_simple()
templater_logger.debug(" Yielding Simple: %s", simple_elem)
yield simple_elem
Expand Down Expand Up @@ -833,25 +833,25 @@ def _split_uniques_coalesce_rest(
# formatting, but this class is also the base for the jinja templater
# (and others?) so it may be used there.
# One way uniques give us landmarks to try and estimate what to do with them.
owu_templ_tuples = cls._sorted_occurrence_tuples(
owu_templ_tuples = cls._sorted_occurrence_tuples( # pragma: no cover
{key: templ_occs[key] for key in one_way_uniques}
)

templater_logger.debug(
templater_logger.debug( # pragma: no cover
" Handling One Way Uniques: %s", owu_templ_tuples
)

# Hang onto out *ending* position too from here.
stops = (
stops = ( # pragma: no cover
int_file_slice.source_slice.stop,
int_file_slice.templated_slice.stop,
)

# OWU in this context refers to "One Way Unique"
this_owu_idx: Optional[int] = None
last_owu_idx: Optional[int] = None
this_owu_idx: Optional[int] = None # pragma: no cover
last_owu_idx: Optional[int] = None # pragma: no cover
# Iterate through occurrence tuples of the one-way uniques.
for raw, template_idx in owu_templ_tuples:
for raw, template_idx in owu_templ_tuples: # pragma: no cover
raw_idx = raw_occs[raw][0]
raw_len = len(raw)

Expand Down Expand Up @@ -907,10 +907,7 @@ def _split_uniques_coalesce_rest(

# If we succeeded in one of the above, we can also recurse
# and be more intelligent with the other sections.
if sub_section: # pragma: no cover
# This assertion makes MyPy happy. In this case, we
# never set source_slice without also setting
# subsection.
if sub_section:
templater_logger.debug(
" Attempting Subsplit [pre]: %s, %r",
sub_section,
Expand Down Expand Up @@ -949,7 +946,7 @@ def _split_uniques_coalesce_rest(
if last_owu_idx is None or last_owu_idx + 1 >= len(
int_file_slice.slice_buffer
):
cur_idx = 0 # pragma: no cover
cur_idx = 0
else:
cur_idx = last_owu_idx + 1

Expand Down Expand Up @@ -1040,7 +1037,7 @@ def _split_uniques_coalesce_rest(
)

# Yield anything from the tail buffer
if tail_buffer:
if tail_buffer: # pragma: no cover
templater_logger.debug(
" Yielding Tail Buffer [end]: %s", tail_buffer
)
Expand Down