@@ -43,8 +43,29 @@ def __init__(self, name):
4343 self .data_type = "varchar"
4444
4545
46- def _normalize (sql ):
47- return " " .join (sql .strip ().split ())
46+ def _strip_line_ws (sql ):
47+ """Strip leading and trailing whitespace from each line. SQL semantics
48+ don't depend on indentation outside of string literals, so this lets the
49+ expected heredocs ignore Jinja's incidental indentation while still
50+ asserting line structure (newlines, ordering, content) exactly."""
51+ return "\n " .join (line .strip () for line in sql .splitlines ())
52+
53+
54+ def _assert_sql_equal (actual , expected ):
55+ """Compare SQL line-by-line, ignoring per-line leading/trailing whitespace."""
56+ assert _strip_line_ws (actual ) == _strip_line_ws (expected )
57+
58+
59+ _EXPECTED_MERGE_TAIL = """\
60+ on (target.id = src.id
61+
62+ )
63+
64+ when matched
65+ then update set"msg" = src."msg","color" = src."color"
66+ when not matched
67+ then insert ("id", "msg", "color")
68+ values (src."id", src."msg", src."color")"""
4869
4970
5071class MockAdapter :
@@ -162,11 +183,16 @@ def _render_iceberg_merge(source_sql=None, query_result="OK"):
162183class TestIncrementalInsertWithSubquery :
163184 def test_insert_uses_subquery (self ):
164185 adapter , _ = _render_incremental_insert (source_sql = "SELECT id, msg, color FROM src" )
165- assert _normalize (adapter .last_sql ) == (
166- 'insert into db.schema.tbl ("id", "msg", "color")'
167- " ( select"
168- ' "id", "msg", "color"'
169- " from ( SELECT id, msg, color FROM src ) _dbt_sbq );"
186+ _assert_sql_equal (
187+ adapter .last_sql ,
188+ """\
189+ insert into db.schema.tbl ("id", "msg", "color")
190+ (
191+ select "id", "msg", "color"
192+ from (
193+ SELECT id, msg, color FROM src
194+ ) _dbt_sbq
195+ );""" ,
170196 )
171197
172198 def test_insert_subquery_isolates_trailing_line_comment (self ):
@@ -175,23 +201,28 @@ def test_insert_subquery_isolates_trailing_line_comment(self):
175201 in whitespace/newline handling is caught."""
176202 source_sql = "SELECT id, msg, color FROM src -- trailing comment"
177203 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- " );"
204+ _assert_sql_equal (
205+ adapter .last_sql ,
206+ """\
207+ insert into db.schema.tbl ("id", "msg", "color")
208+ (
209+ select "id", "msg", "color"
210+ from (
211+ SELECT id, msg, color FROM src -- trailing comment
212+ ) _dbt_sbq
213+ );""" ,
186214 )
187215
188216 def test_insert_without_subquery_uses_tmp_relation (self ):
189217 adapter , _ = _render_incremental_insert ()
190- assert _normalize (adapter .last_sql ) == (
191- 'insert into db.schema.tbl ("id", "msg", "color")'
192- " ( select"
193- ' "id", "msg", "color"'
194- " from db.schema.tbl__dbt_tmp );"
218+ _assert_sql_equal (
219+ adapter .last_sql ,
220+ """\
221+ insert into db.schema.tbl ("id", "msg", "color")
222+ (
223+ select "id", "msg", "color"
224+ from db.schema.tbl__dbt_tmp
225+ );""" ,
195226 )
196227
197228 def test_subquery_too_many_partitions_raises_error (self ):
@@ -210,21 +241,18 @@ def test_without_subquery_too_many_partitions_falls_back_to_batch(self):
210241
211242# --- iceberg_merge ---
212243
213- _EXPECTED_MERGE_CLAUSES = (
214- " on (target.id = src.id )"
215- " when matched"
216- ' then update set"msg" = src."msg","color" = src."color"'
217- ' when not matched then insert ("id", "msg", "color")'
218- ' values (src."id", src."msg", src."color")'
219- )
220-
221244
222245class TestIcebergMergeWithSubquery :
223246 def test_merge_uses_subquery (self ):
224247 adapter , _ = _render_iceberg_merge (source_sql = "SELECT id, msg, color FROM src" )
225- assert _normalize (adapter .last_sql ) == (
226- "merge into db.schema.tbl as target"
227- " using ( SELECT id, msg, color FROM src ) as src" + _EXPECTED_MERGE_CLAUSES
248+ _assert_sql_equal (
249+ adapter .last_sql ,
250+ """\
251+ merge into db.schema.tbl as target using (
252+ SELECT id, msg, color FROM src
253+ ) as src
254+ """
255+ + _EXPECTED_MERGE_TAIL ,
228256 )
229257
230258 def test_merge_subquery_isolates_trailing_line_comment (self ):
@@ -233,26 +261,22 @@ def test_merge_subquery_isolates_trailing_line_comment(self):
233261 in whitespace/newline handling is caught."""
234262 source_sql = "SELECT id, msg, color FROM src -- trailing comment"
235263 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")'
264+ _assert_sql_equal (
265+ adapter .last_sql ,
266+ """\
267+ merge into db.schema.tbl as target using (
268+ SELECT id, msg, color FROM src -- trailing comment
269+ ) as src
270+ """
271+ + _EXPECTED_MERGE_TAIL ,
249272 )
250273
251274 def test_merge_without_subquery_uses_tmp_relation (self ):
252275 adapter , _ = _render_iceberg_merge ()
253- assert _normalize (adapter .last_sql ) == (
254- "merge into db.schema.tbl as target"
255- " using db.schema.tbl__dbt_tmp as src" + _EXPECTED_MERGE_CLAUSES
276+ _assert_sql_equal (
277+ adapter .last_sql ,
278+ "merge into db.schema.tbl as target using db.schema.tbl__dbt_tmp as src\n "
279+ + _EXPECTED_MERGE_TAIL ,
256280 )
257281
258282 def test_subquery_too_many_partitions_raises_error (self ):
@@ -292,19 +316,23 @@ class TestEmptySqlSubqueryWrapping:
292316 def test_empty_sql_wraps_compiled_code (self ):
293317 renderings = _render_empty_sql ("SELECT 1 AS id" )
294318 assert renderings , "no empty_sql fragments rendered"
319+ expected = """\
320+ SELECT * FROM (
321+ SELECT 1 AS id
322+ ) _dbt_sbq WITH NO DATA"""
295323 for rendered in renderings :
296- assert rendered == "SELECT * FROM ( \n SELECT 1 AS id \n ) _dbt_sbq WITH NO DATA"
324+ _assert_sql_equal ( rendered , expected )
297325
298326 def test_empty_sql_isolates_trailing_line_comment (self ):
299327 """The closing paren of empty_sql must land on a fresh line so a
300328 trailing -- line comment in compiled_code cannot comment it out."""
301329 compiled_code = "SELECT 1 AS id\n -- trailing comment"
302330 renderings = _render_empty_sql (compiled_code )
303331 assert renderings , "no empty_sql fragments rendered"
332+ expected = """\
333+ SELECT * FROM (
334+ SELECT 1 AS id
335+ -- trailing comment
336+ ) _dbt_sbq WITH NO DATA"""
304337 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- )
338+ _assert_sql_equal (rendered , expected )
0 commit comments