From 05a1192eb88c55adc9b11e20002fb20bf7458574 Mon Sep 17 00:00:00 2001 From: Siva dheeraj <141407819+Sivadheeraj5356@users.noreply.github.com> Date: Wed, 28 Oct 2026 06:47:32 +0530 Subject: [PATCH 1/2] fix(render): preserve NOT when rendering negated IS [NOT] NULL predicates Handle SQLAlchemy NULL conditions in expressions. --- mindsdb/utilities/render/sqlalchemy_render.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/mindsdb/utilities/render/sqlalchemy_render.py b/mindsdb/utilities/render/sqlalchemy_render.py index b07c34296d..9342877b5e 100644 --- a/mindsdb/utilities/render/sqlalchemy_render.py +++ b/mindsdb/utilities/render/sqlalchemy_render.py @@ -284,6 +284,15 @@ def to_expression(self, t): arg1 = self.to_expression(t.args[1]) op = t.op.lower() + if ( + op in ("is", "is not") + and isinstance(t.args[1], ast.Constant) + and t.args[1].value is None + ): + # to_expression wraps constants in a label; with a labeled NULL + # operand SQLAlchemy cannot negate the predicate, so NOT (x IS NULL) + # lost its NOT. A bare sa.null() keeps is_/is_not invertible. + arg1 = sa.null() if op in ("in", "not in"): if t.args[1].parentheses: arg1 = [arg1] From 1fbf35e950c34c31f3ac68ddfeff1272200dbe40 Mon Sep 17 00:00:00 2001 From: Siva dheeraj <141407819+Sivadheeraj5356@users.noreply.github.com> Date: Wed, 28 Oct 2026 06:48:32 +0530 Subject: [PATCH 2/2] test: cover NOT with IS [NOT] NULL predicate rendering Add a test to ensure NOT IS NULL predicates render correctly. --- tests/unit/render/test_sqlalchemyrender.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/unit/render/test_sqlalchemyrender.py b/tests/unit/render/test_sqlalchemyrender.py index fd01bd4b2f..fc6de031d3 100644 --- a/tests/unit/render/test_sqlalchemyrender.py +++ b/tests/unit/render/test_sqlalchemyrender.py @@ -17,6 +17,21 @@ class TestMysqlRender: + def test_not_is_null(self): + # NOT must survive rendering of negated IS [NOT] NULL predicates + # (regression: a labeled NULL operand made the inversion a no-op) + cases = { + "SELECT * FROM tb1 WHERE NOT (x IS NULL)": "SELECT * FROM tb1 WHERE x IS NOT NULL", + "SELECT * FROM tb1 WHERE NOT x IS NULL": "SELECT * FROM tb1 WHERE x IS NOT NULL", + "SELECT * FROM tb1 WHERE NOT (x IS NOT NULL)": "SELECT * FROM tb1 WHERE x IS NULL", + "SELECT * FROM tb1 WHERE x IS NULL": "SELECT * FROM tb1 WHERE x IS NULL", + "SELECT * FROM tb1 WHERE x IS NOT NULL": "SELECT * FROM tb1 WHERE x IS NOT NULL", + } + for sql_in, expected in cases.items(): + query = parse_sql(sql_in) + sql = SqlalchemyRender("mysql").get_string(query, with_failback=False) + assert sql.replace("\n", "").replace("\t", "") == expected, sql_in + def test_create_table(self): query = CreateTable( name="tbl1",