Skip to content

Commit 05b4bcc

Browse files
committed
Fix: table_diff - correctly handle nulls in boolean columns when displaying the row diff (#4310)
1 parent d20cd36 commit 05b4bcc

File tree

2 files changed

+56
-1
lines changed

2 files changed

+56
-1
lines changed

sqlmesh/core/console.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2091,6 +2091,10 @@ def _cells_match(x: t.Any, y: t.Any) -> bool:
20912091

20922092
# Convert array-like objects to list for consistent comparison
20932093
def _normalize(val: t.Any) -> t.Any:
2094+
# Convert Pandas null to Python null for the purposes of comparison to prevent errors like the following on boolean fields:
2095+
# - TypeError: boolean value of NA is ambiguous
2096+
if pd.isnull(val):
2097+
val = None
20942098
return list(val) if isinstance(val, (pd.Series, np.ndarray)) else val
20952099

20962100
return _normalize(x) == _normalize(y)

tests/core/test_table_diff.py

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from rich.console import Console
1010
from sqlmesh.core.console import TerminalConsole
1111
from sqlmesh.core.context import Context
12-
from sqlmesh.core.config import AutoCategorizationMode, CategorizerConfig
12+
from sqlmesh.core.config import AutoCategorizationMode, CategorizerConfig, DuckDBConnectionConfig
1313
from sqlmesh.core.model import SqlModel, load_sql_based_model
1414
from sqlmesh.core.table_diff import TableDiff
1515
import numpy as np
@@ -511,3 +511,54 @@ def test_data_diff_array_dict(sushi_context_fixed_date):
511511
stripped_output = strip_ansi_codes(output)
512512
stripped_expected = expected_output.strip()
513513
assert stripped_output == stripped_expected
514+
515+
516+
def test_data_diff_nullable_booleans():
517+
engine_adapter = DuckDBConnectionConfig().create_engine_adapter()
518+
519+
columns_to_types = {"key": exp.DataType.build("int"), "value": exp.DataType.build("boolean")}
520+
521+
engine_adapter.create_table("table_diff_source", columns_to_types)
522+
engine_adapter.create_table("table_diff_target", columns_to_types)
523+
524+
engine_adapter.execute(
525+
"insert into table_diff_source (key, value) values (1, true), (2, false), (3, null)"
526+
)
527+
engine_adapter.execute(
528+
"insert into table_diff_target (key, value) values (1, false), (2, null), (3, true)"
529+
)
530+
531+
table_diff = TableDiff(
532+
adapter=engine_adapter,
533+
source="table_diff_source",
534+
target="table_diff_target",
535+
source_alias="dev",
536+
target_alias="prod",
537+
on=["key"],
538+
)
539+
540+
diff = table_diff.row_diff()
541+
542+
output = capture_console_output("show_row_diff", row_diff=diff)
543+
544+
expected_output = """
545+
Row Counts:
546+
└── PARTIAL MATCH: 3 rows (100.0%)
547+
548+
COMMON ROWS column comparison stats:
549+
pct_match
550+
value 0.0
551+
552+
553+
COMMON ROWS sample data differences:
554+
Column: value
555+
┏━━━━━┳━━━━━━━┳━━━━━━━┓
556+
┃ key ┃ DEV ┃ PROD ┃
557+
┡━━━━━╇━━━━━━━╇━━━━━━━┩
558+
│ 1 │ True │ False │
559+
│ 2 │ False │ <NA> │
560+
│ 3 │ <NA> │ True │
561+
└─────┴───────┴───────┘
562+
"""
563+
564+
assert strip_ansi_codes(output) == expected_output.strip()

0 commit comments

Comments
 (0)