Skip to content

Commit f29af30

Browse files
anandgupta42claude
andcommitted
fix: improve Jinja preprocessing robustness
- Use __jinja_expr__ placeholder for adapter.dispatch/return/log instead of empty string to avoid invalid SQL in expression positions - Add Jinja auto-preprocessing to lineage.check and sql.explain - Improve sql.optimize fallback: preserve original SQL when only Jinja preprocessing occurred (no actual rewrites) - Simplify sql.format: drop the SQL comment approach, just format the preprocessed SQL cleanly - Update tests to verify __jinja_expr__ placeholder behavior Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7a9fdb8 commit f29af30

3 files changed

Lines changed: 36 additions & 16 deletions

File tree

packages/altimate-engine/src/altimate_engine/server.py

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,12 @@ def dispatch(request: JsonRpcRequest) -> JsonRpcResponse:
472472
)
473473
)
474474

475-
optimized_sql = rw.get("rewritten_sql", params_obj.sql)
475+
# When Jinja was preprocessed and no rewrites occurred, return
476+
# the original SQL to avoid silently dropping template syntax.
477+
if jinja_preprocessed and not suggestions:
478+
optimized_sql = params_obj.sql
479+
else:
480+
optimized_sql = rw.get("rewritten_sql", params_obj.sql)
476481
if not suggestions and optimized_sql.strip() != params_obj.sql.strip():
477482
suggestions.append(
478483
SqlOptimizeSuggestion(
@@ -500,16 +505,21 @@ def dispatch(request: JsonRpcRequest) -> JsonRpcResponse:
500505
result = SqlOptimizeResult(
501506
success=True,
502507
original_sql=params_obj.sql,
503-
optimized_sql=rw.get("rewritten_sql", params_obj.sql),
508+
optimized_sql=optimized_sql,
504509
suggestions=suggestions,
505510
anti_patterns=anti_patterns,
506511
confidence=opt_confidence,
507512
error=rw.get("error"),
508513
)
509514
elif method == "lineage.check":
510515
p = LineageCheckParams(**params)
516+
sql_for_lineage = p.sql
517+
if contains_jinja(sql_for_lineage):
518+
pp = preprocess_jinja(sql_for_lineage)
519+
if pp.was_preprocessed:
520+
sql_for_lineage = pp.preprocessed_sql
511521
raw = guard_column_lineage(
512-
p.sql,
522+
sql_for_lineage,
513523
dialect=p.dialect or "",
514524
schema_context=_schema_context_to_dict(p.schema_context)
515525
if p.schema_context
@@ -569,28 +579,33 @@ def dispatch(request: JsonRpcRequest) -> JsonRpcResponse:
569579

570580
# Auto-preprocess Jinja if present
571581
sql_to_format = fmt_params.sql
572-
jinja_fmt_note = None
573582
if contains_jinja(sql_to_format):
574583
pp = preprocess_jinja(sql_to_format)
575584
if pp.was_preprocessed:
576585
sql_to_format = pp.preprocessed_sql
577-
jinja_fmt_note = (
578-
"Note: Jinja templates were removed before formatting. "
579-
"The formatted output contains plain SQL only."
580-
)
581586

582587
raw = guard_format_sql(sql_to_format, fmt_params.dialect)
583588
formatted_sql = raw.get("formatted_sql", raw.get("sql"))
584-
if jinja_fmt_note and formatted_sql:
585-
formatted_sql = formatted_sql.rstrip() + "\n\n-- " + jinja_fmt_note + "\n"
586589
result = SqlFormatResult(
587590
success=raw.get("success", True),
588591
formatted_sql=formatted_sql,
589592
statement_count=raw.get("statement_count", 1),
590-
error=raw.get("error"), # Only propagate real errors, not Jinja notes
593+
error=raw.get("error"),
591594
)
592595
elif method == "sql.explain":
593-
result = explain_sql(SqlExplainParams(**params))
596+
explain_params = SqlExplainParams(**params)
597+
sql_to_explain = explain_params.sql
598+
if contains_jinja(sql_to_explain):
599+
pp = preprocess_jinja(sql_to_explain)
600+
if pp.was_preprocessed:
601+
sql_to_explain = pp.preprocessed_sql
602+
result = explain_sql(
603+
SqlExplainParams(
604+
sql=sql_to_explain,
605+
warehouse=explain_params.warehouse,
606+
analyze=explain_params.analyze,
607+
)
608+
)
594609
elif method == "sql.fix":
595610
fix_params = SqlFixParams(**params)
596611
guard_result = guard_fix_sql(fix_params.sql)

packages/altimate-engine/src/altimate_engine/sql/jinja_preprocessor.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
- {{ ref('model') }} → model
88
- {{ source('src', 'table') }} → src__table
99
- {{ config(...) }} → (removed)
10-
- {{ var('name') }} → '__var_name__'
10+
- {{ var('name') }} → '__var_name__' (string literal; may break
11+
identifier contexts like FROM {{ var('t') }})
1112
- {{ var('name', default) }} → '__var_name__'
1213
- {{ this }} → __this__
1314
- {{ this.identifier }} → __this__
@@ -199,9 +200,10 @@ def preprocess_jinja(sql: str) -> JinjaPreprocessResult:
199200
# --- Pass 8: Stub this ---
200201
out = _RE_THIS.sub("__this__", out)
201202

202-
# --- Pass 9: Remove adapter.dispatch, return, log, exceptions ---
203-
out = _RE_ADAPTER_DISPATCH.sub("", out)
204-
out = _RE_UTILITY_CALLS.sub("", out)
203+
# --- Pass 9: Replace adapter.dispatch, return, log, exceptions with placeholder ---
204+
# Use __jinja_expr__ instead of empty string to avoid invalid SQL in expression positions
205+
out = _RE_ADAPTER_DISPATCH.sub("__jinja_expr__", out)
206+
out = _RE_UTILITY_CALLS.sub("__jinja_expr__", out)
205207

206208
# --- Pass 10: Strip block tags (if/elif/else/endif, for/endfor) — keep content ---
207209
out = _RE_IF_OPEN.sub("", out)

packages/altimate-engine/tests/test_jinja_preprocessor.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,12 +402,14 @@ def test_adapter_dispatch(self):
402402
"""
403403
result = preprocess_jinja(sql)
404404
assert "adapter" not in result.preprocessed_sql
405+
assert "__jinja_expr__" in result.preprocessed_sql
405406
assert "SELECT * FROM orders" in result.preprocessed_sql
406407

407408
def test_return(self):
408409
sql = "{{ return([]) }}"
409410
result = preprocess_jinja(sql)
410411
assert "return" not in result.preprocessed_sql
412+
assert "__jinja_expr__" in result.preprocessed_sql
411413

412414
def test_log(self):
413415
sql = """
@@ -416,6 +418,7 @@ def test_log(self):
416418
"""
417419
result = preprocess_jinja(sql)
418420
assert "log(" not in result.preprocessed_sql
421+
assert "__jinja_expr__" in result.preprocessed_sql
419422

420423

421424
# ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)