2424 "incremental" ,
2525 )
2626)
27+ _INCREMENTAL_SQL_PATH = os .path .join (_INCREMENTAL_DIR , "incremental.sql" )
2728
2829
2930class MockRelation :
@@ -165,7 +166,23 @@ def test_insert_uses_subquery(self):
165166 'insert into db.schema.tbl ("id", "msg", "color")'
166167 " ( select"
167168 ' "id", "msg", "color"'
168- " from (SELECT id, msg, color FROM src) _dbt_sbq );"
169+ " from ( SELECT id, msg, color FROM src ) _dbt_sbq );"
170+ )
171+
172+ def test_insert_subquery_isolates_trailing_line_comment (self ):
173+ """A trailing line comment in source_sql must not swallow the closing
174+ paren of the subquery wrapper. Full SQL is asserted so any regression
175+ in whitespace/newline handling is caught."""
176+ source_sql = "SELECT id, msg, color FROM src -- trailing comment"
177+ adapter , _ = _render_incremental_insert (source_sql = source_sql )
178+ assert adapter .last_sql == (
179+ 'insert into db.schema.tbl ("id", "msg", "color")\n '
180+ " (\n "
181+ ' select "id", "msg", "color"\n '
182+ " from (\n "
183+ " SELECT id, msg, color FROM src -- trailing comment\n "
184+ " ) _dbt_sbq\n "
185+ " );"
169186 )
170187
171188 def test_insert_without_subquery_uses_tmp_relation (self ):
@@ -207,7 +224,28 @@ def test_merge_uses_subquery(self):
207224 adapter , _ = _render_iceberg_merge (source_sql = "SELECT id, msg, color FROM src" )
208225 assert _normalize (adapter .last_sql ) == (
209226 "merge into db.schema.tbl as target"
210- " using (SELECT id, msg, color FROM src) as src" + _EXPECTED_MERGE_CLAUSES
227+ " using ( SELECT id, msg, color FROM src ) as src" + _EXPECTED_MERGE_CLAUSES
228+ )
229+
230+ def test_merge_subquery_isolates_trailing_line_comment (self ):
231+ """A trailing line comment in source_sql must not swallow the closing
232+ paren of the subquery wrapper. Full SQL is asserted so any regression
233+ in whitespace/newline handling is caught."""
234+ source_sql = "SELECT id, msg, color FROM src -- trailing comment"
235+ adapter , _ = _render_iceberg_merge (source_sql = source_sql )
236+ assert adapter .last_sql == (
237+ "merge into db.schema.tbl as target using (\n "
238+ " SELECT id, msg, color FROM src -- trailing comment\n "
239+ " ) as src\n "
240+ " on (target.id = src.id\n "
241+ " \n "
242+ " )\n "
243+ " \n "
244+ " when matched \n "
245+ ' then update set"msg" = src."msg","color" = src."color"\n '
246+ " when not matched \n "
247+ ' then insert ("id", "msg", "color")\n '
248+ ' values (src."id", src."msg", src."color")'
211249 )
212250
213251 def test_merge_without_subquery_uses_tmp_relation (self ):
@@ -229,3 +267,44 @@ def test_subquery_too_many_partitions_raises_error(self):
229267 def test_without_subquery_too_many_partitions_falls_back_to_batch (self ):
230268 _ , context = _render_iceberg_merge (query_result = "TOO_MANY_OPEN_PARTITIONS" )
231269 context ["exceptions" ].raise_compiler_error .assert_not_called ()
270+
271+
272+ # --- empty_sql in incremental.sql ---
273+
274+
275+ def _render_empty_sql (compiled_code ):
276+ """Render the same `{% set empty_sql ... %}` Jinja expression that lives in
277+ incremental.sql for the build_with_subquery branches. We extract it from the
278+ real file so the test catches drift if the line is rewritten."""
279+ with open (_INCREMENTAL_SQL_PATH ) as f :
280+ src = f .read ()
281+ matches = re .findall (r"\{%-?\s*set\s+empty_sql\s*=.*?-?%\}" , src )
282+ assert matches , "expected at least one `{% set empty_sql = ... %}` in incremental.sql"
283+ rendered = []
284+ env = jinja2 .Environment (extensions = ["jinja2.ext.do" ])
285+ for fragment in matches :
286+ template_src = fragment + "{{ empty_sql }}"
287+ rendered .append (env .from_string (template_src ).render (compiled_code = compiled_code ))
288+ return rendered
289+
290+
291+ class TestEmptySqlSubqueryWrapping :
292+ def test_empty_sql_wraps_compiled_code (self ):
293+ renderings = _render_empty_sql ("SELECT 1 AS id" )
294+ assert renderings , "no empty_sql fragments rendered"
295+ for rendered in renderings :
296+ assert rendered == "SELECT * FROM (\n SELECT 1 AS id\n ) _dbt_sbq WITH NO DATA"
297+
298+ def test_empty_sql_isolates_trailing_line_comment (self ):
299+ """The closing paren of empty_sql must land on a fresh line so a
300+ trailing -- line comment in compiled_code cannot comment it out."""
301+ compiled_code = "SELECT 1 AS id\n -- trailing comment"
302+ renderings = _render_empty_sql (compiled_code )
303+ assert renderings , "no empty_sql fragments rendered"
304+ for rendered in renderings :
305+ assert rendered == (
306+ "SELECT * FROM (\n "
307+ "SELECT 1 AS id\n "
308+ "-- trailing comment\n "
309+ ") _dbt_sbq WITH NO DATA"
310+ )
0 commit comments