Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions mindsdb/utilities/render/sqlalchemy_render.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
15 changes: 15 additions & 0 deletions tests/unit/render/test_sqlalchemyrender.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading