Skip to content

Commit e3858d4

Browse files
authored
Fix(table_diff): Allow diffing of empty tables (#4347)
1 parent c42bce4 commit e3858d4

3 files changed

Lines changed: 57 additions & 1 deletion

File tree

sqlmesh/core/console.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2181,6 +2181,12 @@ def show_schema_diff(self, schema_diff: SchemaDiff) -> None:
21812181
def show_row_diff(
21822182
self, row_diff: RowDiff, show_sample: bool = True, skip_grain_check: bool = False
21832183
) -> None:
2184+
if row_diff.empty:
2185+
self.console.print(
2186+
"\n[b][red]Neither the source nor the target table contained any records[/red][/b]"
2187+
)
2188+
return
2189+
21842190
source_name = row_diff.source
21852191
if row_diff.source_alias:
21862192
source_name = row_diff.source_alias.upper()

sqlmesh/core/table_diff.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,15 @@ def target_count(self) -> int:
8383
"""Count of the target."""
8484
return int(self.stats["t_count"])
8585

86+
@property
87+
def empty(self) -> bool:
88+
return (
89+
self.source_count == 0
90+
and self.target_count == 0
91+
and self.s_only_count == 0
92+
and self.t_only_count == 0
93+
)
94+
8695
@property
8796
def count_pct_change(self) -> float:
8897
"""The percentage change of the counts."""
@@ -446,7 +455,7 @@ def name(e: exp.Expression) -> str:
446455

447456
summary_query = exp.select(*summary_sums).from_(table)
448457

449-
stats_df = self.adapter.fetchdf(summary_query, quote_identifiers=True)
458+
stats_df = self.adapter.fetchdf(summary_query, quote_identifiers=True).fillna(0)
450459
stats_df["s_only_count"] = stats_df["s_count"] - stats_df["join_count"]
451460
stats_df["t_only_count"] = stats_df["t_count"] - stats_df["join_count"]
452461
stats = stats_df.iloc[0].to_dict()

tests/core/test_table_diff.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -788,3 +788,44 @@ def test_data_diff_forward_only(sushi_context_fixed_date, capsys, caplog):
788788
assert row_diff.stats["distinct_count_t"] == 2
789789
assert row_diff.s_sample.shape == (2, 2)
790790
assert row_diff.t_sample.shape == (2, 2)
791+
792+
793+
def test_data_diff_empty_tables():
794+
engine_adapter = DuckDBConnectionConfig().create_engine_adapter()
795+
796+
columns_to_types_src = {
797+
"key": exp.DataType.build("int"),
798+
"value": exp.DataType.build("varchar"),
799+
}
800+
columns_to_types_target = {
801+
"key": exp.DataType.build("int"),
802+
"value2": exp.DataType.build("varchar"),
803+
}
804+
805+
engine_adapter.create_table("table_diff_source", columns_to_types_src)
806+
engine_adapter.create_table("table_diff_target", columns_to_types_target)
807+
808+
table_diff = TableDiff(
809+
adapter=engine_adapter,
810+
source="table_diff_source",
811+
target="table_diff_target",
812+
source_alias="dev",
813+
target_alias="prod",
814+
on=["key"],
815+
)
816+
817+
# should show the schema diff
818+
schema_diff = table_diff.schema_diff()
819+
assert len(schema_diff.added) == 1
820+
assert schema_diff.added[0][0] == "value2"
821+
assert len(schema_diff.removed) == 1
822+
assert schema_diff.removed[0][0] == "value"
823+
824+
# should not error on the row diff
825+
row_diff = table_diff.row_diff()
826+
assert row_diff.empty
827+
828+
output = capture_console_output("show_row_diff", row_diff=row_diff)
829+
assert (
830+
strip_ansi_codes(output) == "Neither the source nor the target table contained any records"
831+
)

0 commit comments

Comments
 (0)