From fbab774a5ecb57edc3a15ee7d296d169bff35e64 Mon Sep 17 00:00:00 2001 From: "ram.yamasani@reachlocal.com" Date: Fri, 22 May 2026 10:20:23 -0700 Subject: [PATCH 1/9] DBE-3763 Fix for known issues --- data_diff/databases/mysql.py | 3 +++ data_diff/utils.py | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/data_diff/databases/mysql.py b/data_diff/databases/mysql.py index 1ee04460b..72665deee 100644 --- a/data_diff/databases/mysql.py +++ b/data_diff/databases/mysql.py @@ -8,6 +8,7 @@ Float, Decimal, Integer, + JSON, Text, TemporalType, FractionalType, @@ -68,6 +69,8 @@ class Dialect(BaseDialect): "tinytext": Text, # Boolean "boolean": Boolean, + # JSON + "json": JSON, } def quote(self, s: str) -> str: diff --git a/data_diff/utils.py b/data_diff/utils.py index 1d1405fdb..5c0ab2d0b 100644 --- a/data_diff/utils.py +++ b/data_diff/utils.py @@ -513,6 +513,13 @@ def diff_int_dynamic_color_template(diff_value: int) -> str: def _jsons_equiv(a: str, b: str): + # Treat Python None (DB null) as the JSON null literal so that a NULL on + # the MySQL side matches a 'null' string produced by TO_JSON_STRING(NULL) + # on the BigQuery side (or any other DB that serializes NULL as 'null'). + if a is None: + a = "null" + if b is None: + b = "null" try: return json.loads(a) == json.loads(b) except (ValueError, TypeError, json.decoder.JSONDecodeError): # not valid jsons From 15a6f81dbfec63201e02cbe10007413691744bbb Mon Sep 17 00:00:00 2001 From: "ram.yamasani@reachlocal.com" Date: Tue, 26 May 2026 13:59:19 -0700 Subject: [PATCH 2/9] Fix for '0000-00-00 00:00:00' mysql timestamp value --- data_diff/databases/mysql.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/data_diff/databases/mysql.py b/data_diff/databases/mysql.py index 72665deee..b3e7e8054 100644 --- a/data_diff/databases/mysql.py +++ b/data_diff/databases/mysql.py @@ -109,6 +109,15 @@ def md5_as_hex(self, s: str) -> str: return f"md5({s})" def normalize_timestamp(self, value: str, coltype: TemporalType) -> str: + # MySQL zero-date equivalences vs BigQuery: + # TIMESTAMP '0000-00-00 00:00:00' -> '1970-01-01 00:00:00.000000' (Unix epoch) + # DATETIME '0000-00-00 00:00:00' -> NULL + if isinstance(coltype, Timestamp): + epoch = "cast('1970-01-01 00:00:00' as datetime(6))" + value = f"IF({value} = '0000-00-00 00:00:00', {epoch}, {value})" + elif isinstance(coltype, Datetime): + value = f"NULLIF({value}, '0000-00-00 00:00:00')" + if coltype.rounds: return self.to_string(f"cast( cast({value} as datetime({coltype.precision})) as datetime(6))") From 4ee614ab042511decf871240deff1dda20d643c6 Mon Sep 17 00:00:00 2001 From: Ram Yamasani Date: Tue, 26 May 2026 14:14:39 -0700 Subject: [PATCH 3/9] Update mysql.py epoch timestamp value testing --- data_diff/databases/mysql.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/data_diff/databases/mysql.py b/data_diff/databases/mysql.py index b3e7e8054..2ee4602f6 100644 --- a/data_diff/databases/mysql.py +++ b/data_diff/databases/mysql.py @@ -112,11 +112,11 @@ def normalize_timestamp(self, value: str, coltype: TemporalType) -> str: # MySQL zero-date equivalences vs BigQuery: # TIMESTAMP '0000-00-00 00:00:00' -> '1970-01-01 00:00:00.000000' (Unix epoch) # DATETIME '0000-00-00 00:00:00' -> NULL - if isinstance(coltype, Timestamp): - epoch = "cast('1970-01-01 00:00:00' as datetime(6))" - value = f"IF({value} = '0000-00-00 00:00:00', {epoch}, {value})" - elif isinstance(coltype, Datetime): - value = f"NULLIF({value}, '0000-00-00 00:00:00')" + #if isinstance(coltype, Timestamp): + # epoch = "cast('1970-01-01 00:00:00' as datetime(6))" + # value = f"IF({value} = '0000-00-00 00:00:00', {epoch}, {value})" + #elif isinstance(coltype, Datetime): + # value = f"NULLIF({value}, '0000-00-00 00:00:00')" if coltype.rounds: return self.to_string(f"cast( cast({value} as datetime({coltype.precision})) as datetime(6))") From 7d6b79a3d9703a1c53d76b9507f8a1112b15b836 Mon Sep 17 00:00:00 2001 From: Ram Yamasani Date: Tue, 26 May 2026 15:11:35 -0700 Subject: [PATCH 4/9] Update mysql.py reverted comments. --- data_diff/databases/mysql.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/data_diff/databases/mysql.py b/data_diff/databases/mysql.py index 2ee4602f6..9059c770f 100644 --- a/data_diff/databases/mysql.py +++ b/data_diff/databases/mysql.py @@ -109,14 +109,14 @@ def md5_as_hex(self, s: str) -> str: return f"md5({s})" def normalize_timestamp(self, value: str, coltype: TemporalType) -> str: - # MySQL zero-date equivalences vs BigQuery: - # TIMESTAMP '0000-00-00 00:00:00' -> '1970-01-01 00:00:00.000000' (Unix epoch) - # DATETIME '0000-00-00 00:00:00' -> NULL - #if isinstance(coltype, Timestamp): - # epoch = "cast('1970-01-01 00:00:00' as datetime(6))" - # value = f"IF({value} = '0000-00-00 00:00:00', {epoch}, {value})" - #elif isinstance(coltype, Datetime): - # value = f"NULLIF({value}, '0000-00-00 00:00:00')" + MySQL zero-date equivalences vs BigQuery: + TIMESTAMP '0000-00-00 00:00:00' -> '1970-01-01 00:00:00.000000' (Unix epoch) + DATETIME '0000-00-00 00:00:00' -> NULL + if isinstance(coltype, Timestamp): + epoch = "cast('1970-01-01 00:00:00' as datetime(6))" + value = f"IF({value} = '0000-00-00 00:00:00', {epoch}, {value})" + elif isinstance(coltype, Datetime): + value = f"NULLIF({value}, '0000-00-00 00:00:00')" if coltype.rounds: return self.to_string(f"cast( cast({value} as datetime({coltype.precision})) as datetime(6))") From 19cb09d4c971ea2a3a9a06e427b2d0d9ec2e37ee Mon Sep 17 00:00:00 2001 From: "ram.yamasani@reachlocal.com" Date: Tue, 26 May 2026 15:19:11 -0700 Subject: [PATCH 5/9] Fix missing # on comment lines in normalize_timestamp --- data_diff/databases/mysql.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data_diff/databases/mysql.py b/data_diff/databases/mysql.py index 9059c770f..b3e7e8054 100644 --- a/data_diff/databases/mysql.py +++ b/data_diff/databases/mysql.py @@ -109,9 +109,9 @@ def md5_as_hex(self, s: str) -> str: return f"md5({s})" def normalize_timestamp(self, value: str, coltype: TemporalType) -> str: - MySQL zero-date equivalences vs BigQuery: - TIMESTAMP '0000-00-00 00:00:00' -> '1970-01-01 00:00:00.000000' (Unix epoch) - DATETIME '0000-00-00 00:00:00' -> NULL + # MySQL zero-date equivalences vs BigQuery: + # TIMESTAMP '0000-00-00 00:00:00' -> '1970-01-01 00:00:00.000000' (Unix epoch) + # DATETIME '0000-00-00 00:00:00' -> NULL if isinstance(coltype, Timestamp): epoch = "cast('1970-01-01 00:00:00' as datetime(6))" value = f"IF({value} = '0000-00-00 00:00:00', {epoch}, {value})" From e98e32f24b465118fe94c0f3bc3e7a084ccfa9ea Mon Sep 17 00:00:00 2001 From: "ram.yamasani@reachlocal.com" Date: Wed, 27 May 2026 11:58:05 -0700 Subject: [PATCH 6/9] Adding tests for timestamp normalization --- tests/test_mysql_normalize_timestamp.py | 107 ++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 tests/test_mysql_normalize_timestamp.py diff --git a/tests/test_mysql_normalize_timestamp.py b/tests/test_mysql_normalize_timestamp.py new file mode 100644 index 000000000..0babb4b2a --- /dev/null +++ b/tests/test_mysql_normalize_timestamp.py @@ -0,0 +1,107 @@ +""" +Unit tests for MySQL Dialect.normalize_timestamp, covering zero-date equivalences: + - TIMESTAMP '0000-00-00 00:00:00' -> epoch '1970-01-01 00:00:00.000000' + - DATETIME '0000-00-00 00:00:00' -> NULL +""" +import unittest + +from data_diff.abcs.database_types import Timestamp, Datetime, Date +from data_diff.databases.mysql import Dialect + +DIALECT = Dialect() + + +class TestMySQLNormalizeTimestampZeroDate(unittest.TestCase): + """Verify zero-date handling for Timestamp and Datetime column types.""" + + # ------------------------------------------------------------------ + # TIMESTAMP columns -> zero-date becomes Unix epoch + # ------------------------------------------------------------------ + + def test_timestamp_zero_date_rounds_true(self): + """TIMESTAMP(0) with rounds=True: zero-date wrapped in IF -> epoch.""" + coltype = Timestamp(precision=0, rounds=True) + col = "`ddt_email_notified`" + sql = DIALECT.normalize_timestamp(col, coltype) + self.assertIn("IF(`ddt_email_notified` = '0000-00-00 00:00:00'", sql) + self.assertIn("cast('1970-01-01 00:00:00' as datetime(6))", sql) + # NULL branch: original column still present as the ELSE arm + self.assertIn("`ddt_email_notified`)", sql) + + def test_timestamp_zero_date_rounds_false(self): + """TIMESTAMP(6) with rounds=False: zero-date wrapped in IF -> epoch.""" + coltype = Timestamp(precision=6, rounds=False) + col = "`ts_col`" + sql = DIALECT.normalize_timestamp(col, coltype) + self.assertIn("IF(`ts_col` = '0000-00-00 00:00:00'", sql) + self.assertIn("cast('1970-01-01 00:00:00' as datetime(6))", sql) + + def test_timestamp_no_nullif(self): + """TIMESTAMP column must NOT use NULLIF (that is only for Datetime).""" + coltype = Timestamp(precision=0, rounds=True) + sql = DIALECT.normalize_timestamp("`ts`", coltype) + self.assertNotIn("NULLIF", sql) + + # ------------------------------------------------------------------ + # DATETIME columns -> zero-date becomes NULL + # ------------------------------------------------------------------ + + def test_datetime_zero_date_rounds_true(self): + """DATETIME(0) with rounds=True: zero-date replaced with NULLIF.""" + coltype = Datetime(precision=0, rounds=True) + col = "`ddt_date_processed`" + sql = DIALECT.normalize_timestamp(col, coltype) + self.assertIn(f"NULLIF(`ddt_date_processed`, '0000-00-00 00:00:00')", sql) + + def test_datetime_zero_date_rounds_false(self): + """DATETIME(6) with rounds=False: zero-date replaced with NULLIF.""" + coltype = Datetime(precision=6, rounds=False) + col = "`dt_col`" + sql = DIALECT.normalize_timestamp(col, coltype) + self.assertIn("NULLIF(`dt_col`, '0000-00-00 00:00:00')", sql) + + def test_datetime_no_if_epoch(self): + """DATETIME column must NOT use IF->epoch (that is only for Timestamp).""" + coltype = Datetime(precision=0, rounds=True) + sql = DIALECT.normalize_timestamp("`dt`", coltype) + self.assertNotIn("1970-01-01", sql) + + # ------------------------------------------------------------------ + # DATE columns -> zero-date logic must NOT be applied + # ------------------------------------------------------------------ + + def test_date_unaffected(self): + """DATE columns must not have zero-date replacement applied.""" + coltype = Date(precision=0, rounds=False) + col = "`some_date`" + sql = DIALECT.normalize_timestamp(col, coltype) + self.assertNotIn("0000-00-00 00:00:00", sql) + self.assertNotIn("NULLIF", sql) + self.assertNotIn("1970-01-01", sql) + + # ------------------------------------------------------------------ + # SQL structure checks + # ------------------------------------------------------------------ + + def test_timestamp_rounds_true_output_structure(self): + """TIMESTAMP(0) rounds=True: output is cast(cast(IF(...) as datetime(0)) as datetime(6)).""" + coltype = Timestamp(precision=0, rounds=True) + sql = DIALECT.normalize_timestamp("`col`", coltype) + # Should end with cast to datetime(6) via to_string + self.assertIn("cast( cast(", sql) + self.assertIn("as datetime(0)", sql) + self.assertIn("as datetime(6)", sql) + + def test_timestamp_rounds_false_output_structure(self): + """TIMESTAMP(6) rounds=False: output uses RPAD pattern.""" + coltype = Timestamp(precision=6, rounds=False) + sql = DIALECT.normalize_timestamp("`col`", coltype) + self.assertIn("RPAD", sql) + + def test_datetime_rounds_true_output_structure(self): + """DATETIME(0) rounds=True: NULLIF feeds into cast chain.""" + coltype = Datetime(precision=0, rounds=True) + sql = DIALECT.normalize_timestamp("`col`", coltype) + self.assertIn("NULLIF(", sql) + self.assertIn("as datetime(0)", sql) + self.assertIn("as datetime(6)", sql) From 593b056c61fb1e5cd2d2569aef2692f271d147aa Mon Sep 17 00:00:00 2001 From: "ram.yamasani@reachlocal.com" Date: Wed, 27 May 2026 12:31:47 -0700 Subject: [PATCH 7/9] Removed normalize tests --- tests/test_mysql_normalize_timestamp.py | 107 ------------------------ 1 file changed, 107 deletions(-) delete mode 100644 tests/test_mysql_normalize_timestamp.py diff --git a/tests/test_mysql_normalize_timestamp.py b/tests/test_mysql_normalize_timestamp.py deleted file mode 100644 index 0babb4b2a..000000000 --- a/tests/test_mysql_normalize_timestamp.py +++ /dev/null @@ -1,107 +0,0 @@ -""" -Unit tests for MySQL Dialect.normalize_timestamp, covering zero-date equivalences: - - TIMESTAMP '0000-00-00 00:00:00' -> epoch '1970-01-01 00:00:00.000000' - - DATETIME '0000-00-00 00:00:00' -> NULL -""" -import unittest - -from data_diff.abcs.database_types import Timestamp, Datetime, Date -from data_diff.databases.mysql import Dialect - -DIALECT = Dialect() - - -class TestMySQLNormalizeTimestampZeroDate(unittest.TestCase): - """Verify zero-date handling for Timestamp and Datetime column types.""" - - # ------------------------------------------------------------------ - # TIMESTAMP columns -> zero-date becomes Unix epoch - # ------------------------------------------------------------------ - - def test_timestamp_zero_date_rounds_true(self): - """TIMESTAMP(0) with rounds=True: zero-date wrapped in IF -> epoch.""" - coltype = Timestamp(precision=0, rounds=True) - col = "`ddt_email_notified`" - sql = DIALECT.normalize_timestamp(col, coltype) - self.assertIn("IF(`ddt_email_notified` = '0000-00-00 00:00:00'", sql) - self.assertIn("cast('1970-01-01 00:00:00' as datetime(6))", sql) - # NULL branch: original column still present as the ELSE arm - self.assertIn("`ddt_email_notified`)", sql) - - def test_timestamp_zero_date_rounds_false(self): - """TIMESTAMP(6) with rounds=False: zero-date wrapped in IF -> epoch.""" - coltype = Timestamp(precision=6, rounds=False) - col = "`ts_col`" - sql = DIALECT.normalize_timestamp(col, coltype) - self.assertIn("IF(`ts_col` = '0000-00-00 00:00:00'", sql) - self.assertIn("cast('1970-01-01 00:00:00' as datetime(6))", sql) - - def test_timestamp_no_nullif(self): - """TIMESTAMP column must NOT use NULLIF (that is only for Datetime).""" - coltype = Timestamp(precision=0, rounds=True) - sql = DIALECT.normalize_timestamp("`ts`", coltype) - self.assertNotIn("NULLIF", sql) - - # ------------------------------------------------------------------ - # DATETIME columns -> zero-date becomes NULL - # ------------------------------------------------------------------ - - def test_datetime_zero_date_rounds_true(self): - """DATETIME(0) with rounds=True: zero-date replaced with NULLIF.""" - coltype = Datetime(precision=0, rounds=True) - col = "`ddt_date_processed`" - sql = DIALECT.normalize_timestamp(col, coltype) - self.assertIn(f"NULLIF(`ddt_date_processed`, '0000-00-00 00:00:00')", sql) - - def test_datetime_zero_date_rounds_false(self): - """DATETIME(6) with rounds=False: zero-date replaced with NULLIF.""" - coltype = Datetime(precision=6, rounds=False) - col = "`dt_col`" - sql = DIALECT.normalize_timestamp(col, coltype) - self.assertIn("NULLIF(`dt_col`, '0000-00-00 00:00:00')", sql) - - def test_datetime_no_if_epoch(self): - """DATETIME column must NOT use IF->epoch (that is only for Timestamp).""" - coltype = Datetime(precision=0, rounds=True) - sql = DIALECT.normalize_timestamp("`dt`", coltype) - self.assertNotIn("1970-01-01", sql) - - # ------------------------------------------------------------------ - # DATE columns -> zero-date logic must NOT be applied - # ------------------------------------------------------------------ - - def test_date_unaffected(self): - """DATE columns must not have zero-date replacement applied.""" - coltype = Date(precision=0, rounds=False) - col = "`some_date`" - sql = DIALECT.normalize_timestamp(col, coltype) - self.assertNotIn("0000-00-00 00:00:00", sql) - self.assertNotIn("NULLIF", sql) - self.assertNotIn("1970-01-01", sql) - - # ------------------------------------------------------------------ - # SQL structure checks - # ------------------------------------------------------------------ - - def test_timestamp_rounds_true_output_structure(self): - """TIMESTAMP(0) rounds=True: output is cast(cast(IF(...) as datetime(0)) as datetime(6)).""" - coltype = Timestamp(precision=0, rounds=True) - sql = DIALECT.normalize_timestamp("`col`", coltype) - # Should end with cast to datetime(6) via to_string - self.assertIn("cast( cast(", sql) - self.assertIn("as datetime(0)", sql) - self.assertIn("as datetime(6)", sql) - - def test_timestamp_rounds_false_output_structure(self): - """TIMESTAMP(6) rounds=False: output uses RPAD pattern.""" - coltype = Timestamp(precision=6, rounds=False) - sql = DIALECT.normalize_timestamp("`col`", coltype) - self.assertIn("RPAD", sql) - - def test_datetime_rounds_true_output_structure(self): - """DATETIME(0) rounds=True: NULLIF feeds into cast chain.""" - coltype = Datetime(precision=0, rounds=True) - sql = DIALECT.normalize_timestamp("`col`", coltype) - self.assertIn("NULLIF(", sql) - self.assertIn("as datetime(0)", sql) - self.assertIn("as datetime(6)", sql) From 30534db00cfd04c0c2039cc62292ef85c5950241 Mon Sep 17 00:00:00 2001 From: "ram.yamasani@reachlocal.com" Date: Thu, 28 May 2026 11:40:50 -0700 Subject: [PATCH 8/9] Adding test --- data_diff/utils.py | 7 +++++-- tests/test_utils.py | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/data_diff/utils.py b/data_diff/utils.py index 5c0ab2d0b..1ad9fbac2 100644 --- a/data_diff/utils.py +++ b/data_diff/utils.py @@ -512,7 +512,7 @@ def diff_int_dynamic_color_template(diff_value: int) -> str: return "0" -def _jsons_equiv(a: str, b: str): +def _jsons_equiv(a: Optional[str], b: Optional[str]): # Treat Python None (DB null) as the JSON null literal so that a NULL on # the MySQL side matches a 'null' string produced by TO_JSON_STRING(NULL) # on the BigQuery side (or any other DB that serializes NULL as 'null'). @@ -520,9 +520,12 @@ def _jsons_equiv(a: str, b: str): a = "null" if b is None: b = "null" + # Fast-path: identical strings don't need JSON parsing. + if a == b: + return True try: return json.loads(a) == json.loads(b) - except (ValueError, TypeError, json.decoder.JSONDecodeError): # not valid jsons + except (ValueError, TypeError): # covers json.JSONDecodeError (subclass of ValueError) return False diff --git a/tests/test_utils.py b/tests/test_utils.py index 712f3467c..613c0b032 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -11,6 +11,7 @@ columns_removed_template, columns_added_template, columns_type_changed_template, + _jsons_equiv, ) from data_diff.__main__ import _remove_passwords_in_dict @@ -211,3 +212,43 @@ def test_columns_type_changed_template(self): output = columns_type_changed_template({"column1", "column2"}) self.assertIn("Type changed [2]: [green]", output) self.assertEqual(self.extract_columns_set(output), {"column1", "column2"}) + + +class TestJsonsEquiv(unittest.TestCase): + # --- None / null equivalence --- + def test_both_none(self): + """Two DB NULLs are equivalent.""" + self.assertTrue(_jsons_equiv(None, None)) + + def test_none_vs_json_null_string(self): + """DB NULL on one side, JSON 'null' string on the other, are equivalent.""" + self.assertTrue(_jsons_equiv(None, "null")) + self.assertTrue(_jsons_equiv("null", None)) + + def test_none_vs_json_string_null(self): + """DB NULL must NOT equal the JSON string literal \"null\".""" + self.assertFalse(_jsons_equiv(None, '"null"')) + self.assertFalse(_jsons_equiv('"null"', None)) + + # --- Identical strings fast-path --- + def test_identical_strings(self): + self.assertTrue(_jsons_equiv('{"a": 1}', '{"a": 1}')) + + # --- Semantic JSON equivalence --- + def test_equivalent_objects_different_whitespace(self): + self.assertTrue(_jsons_equiv('{"a":1,"b":2}', '{"b": 2, "a": 1}')) + + def test_equivalent_arrays(self): + self.assertTrue(_jsons_equiv("[1, 2, 3]", "[1,2,3]")) + + def test_different_values(self): + self.assertFalse(_jsons_equiv('{"a": 1}', '{"a": 2}')) + + def test_different_types(self): + self.assertFalse(_jsons_equiv("1", '"1"')) + + # --- Invalid JSON --- + def test_invalid_json_returns_false(self): + # Different invalid-JSON strings → False (can't parse either side) + self.assertFalse(_jsons_equiv("not-json", "also-not-json")) + self.assertFalse(_jsons_equiv('{"a": 1}', "not-json")) From 0029ab161c9af5d7a1984590c5bdbc3636e1fca3 Mon Sep 17 00:00:00 2001 From: "ram.yamasani@reachlocal.com" Date: Thu, 28 May 2026 11:48:00 -0700 Subject: [PATCH 9/9] fix --- data_diff/databases/mysql.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/data_diff/databases/mysql.py b/data_diff/databases/mysql.py index b3e7e8054..2ee4602f6 100644 --- a/data_diff/databases/mysql.py +++ b/data_diff/databases/mysql.py @@ -112,11 +112,11 @@ def normalize_timestamp(self, value: str, coltype: TemporalType) -> str: # MySQL zero-date equivalences vs BigQuery: # TIMESTAMP '0000-00-00 00:00:00' -> '1970-01-01 00:00:00.000000' (Unix epoch) # DATETIME '0000-00-00 00:00:00' -> NULL - if isinstance(coltype, Timestamp): - epoch = "cast('1970-01-01 00:00:00' as datetime(6))" - value = f"IF({value} = '0000-00-00 00:00:00', {epoch}, {value})" - elif isinstance(coltype, Datetime): - value = f"NULLIF({value}, '0000-00-00 00:00:00')" + #if isinstance(coltype, Timestamp): + # epoch = "cast('1970-01-01 00:00:00' as datetime(6))" + # value = f"IF({value} = '0000-00-00 00:00:00', {epoch}, {value})" + #elif isinstance(coltype, Datetime): + # value = f"NULLIF({value}, '0000-00-00 00:00:00')" if coltype.rounds: return self.to_string(f"cast( cast({value} as datetime({coltype.precision})) as datetime(6))")