Merge pull request #36621 from frappe/fix-empty-list-in-operator-behavior
fix!: Enhance IN/NOT IN operator handling for empty lists
This commit is contained in:
commit
cefac6851e
4 changed files with 42 additions and 3 deletions
|
|
@ -597,6 +597,20 @@ class Engine:
|
|||
v.strip().strip("'") for v in get_between_date_filter(_value, df).split(" AND ")
|
||||
)
|
||||
|
||||
# Handle empty lists for IN/NOT IN operators before conversion
|
||||
# IN with empty list should return 0 results (always False)
|
||||
# NOT IN with empty list should return all results (always True)
|
||||
if _operator.lower() in ("in", "not in"):
|
||||
if isinstance(_value, (list, tuple, set)) and len(_value) == 0:
|
||||
if _operator.lower() == "in":
|
||||
# Return a criterion that always evaluates to False (1=0)
|
||||
# This ensures IN with empty list returns 0 results
|
||||
return RawCriterion("1=0")
|
||||
else: # not in
|
||||
# Return a criterion that always evaluates to True (1=1)
|
||||
# NOT IN with empty set matches all rows since nothing is excluded
|
||||
return RawCriterion("1=1")
|
||||
|
||||
if not _value and isinstance(_value, list | tuple | set):
|
||||
_value = ("",)
|
||||
|
||||
|
|
|
|||
|
|
@ -868,6 +868,15 @@ from {tables}
|
|||
if f.operator.lower() == "in":
|
||||
can_be_null &= not f.value or any(v is None or v == "" for v in f.value)
|
||||
|
||||
# Handle empty lists for IN/NOT IN operators before processing
|
||||
# IN with empty list should return 0 results (always False: 1=0)
|
||||
# NOT IN with empty list should return all results (always True: 1=1)
|
||||
if isinstance(f.value, (list, tuple)) and len(f.value) == 0:
|
||||
if f.operator.lower() == "in":
|
||||
return "1=0"
|
||||
else: # not in
|
||||
return "1=1"
|
||||
|
||||
if value is None:
|
||||
values = f.value or ""
|
||||
if isinstance(values, str):
|
||||
|
|
|
|||
|
|
@ -1050,15 +1050,21 @@ class TestDBQuery(IntegrationTestCase):
|
|||
self.assertNotIn("IF", frappe.get_all("User", {"first_name": ("in", ["a", "b"])}, run=0).get_sql())
|
||||
self.assertIn("IFNULL", frappe.get_all("User", {"first_name": ("in", ["a", None])}, run=0).get_sql())
|
||||
self.assertIn("IFNULL", frappe.get_all("User", {"first_name": ("in", ["a", ""])}, run=0).get_sql())
|
||||
self.assertIn("IFNULL", frappe.get_all("User", {"first_name": ("in", [])}, run=0).get_sql())
|
||||
# Empty list with IN should return 1=0, not use IFNULL
|
||||
self.assertIn("1=0", frappe.get_all("User", {"first_name": ("in", [])}, run=0).get_sql())
|
||||
self.assertNotIn("IFNULL", frappe.get_all("User", {"first_name": ("in", [])}, run=0).get_sql())
|
||||
self.assertIn("IFNULL", frappe.get_all("User", {"first_name": ("not in", ["a"])}, run=0).get_sql())
|
||||
self.assertIn("IFNULL", frappe.get_all("User", {"first_name": ("not in", [])}, run=0).get_sql())
|
||||
# Empty list with NOT IN should return 1=1, not use IFNULL
|
||||
self.assertIn("1=1", frappe.get_all("User", {"first_name": ("not in", [])}, run=0).get_sql())
|
||||
self.assertNotIn("IFNULL", frappe.get_all("User", {"first_name": ("not in", [])}, run=0).get_sql())
|
||||
self.assertIn("IFNULL", frappe.get_all("User", {"first_name": ("not in", [""])}, run=0).get_sql())
|
||||
|
||||
# primary key is never nullable
|
||||
self.assertNotIn("IFNULL", frappe.get_all("User", {"name": ("in", ["a", None])}, run=0).get_sql())
|
||||
self.assertNotIn("IFNULL", frappe.get_all("User", {"name": ("in", ["a", ""])}, run=0).get_sql())
|
||||
self.assertNotIn("IFNULL", frappe.get_all("User", {"name": ("in", (""))}, run=0).get_sql())
|
||||
# Empty tuple with IN should return 1=0, not use IFNULL
|
||||
self.assertIn("1=0", frappe.get_all("User", {"name": ("in", ())}, run=0).get_sql())
|
||||
self.assertNotIn("IFNULL", frappe.get_all("User", {"name": ("in", ())}, run=0).get_sql())
|
||||
|
||||
def test_coalesce_with_datetime_ops(self):
|
||||
|
|
|
|||
|
|
@ -410,12 +410,22 @@ class TestQuery(IntegrationTestCase):
|
|||
"SELECT `name` FROM `tabDocType` WHERE `name` IN ('ToDo','Note')",
|
||||
)
|
||||
|
||||
# Empty list with IN operator should return 0 results (1=0 condition)
|
||||
self.assertQueryEqual(
|
||||
frappe.qb.get_query(
|
||||
"DocType",
|
||||
filters={"name": ("in", [])},
|
||||
).get_sql(),
|
||||
"SELECT `name` FROM `tabDocType` WHERE `name` IN ('')",
|
||||
"SELECT `name` FROM `tabDocType` WHERE 1=0",
|
||||
)
|
||||
|
||||
# Empty list with NOT IN operator should return all results (1=1 condition)
|
||||
self.assertQueryEqual(
|
||||
frappe.qb.get_query(
|
||||
"DocType",
|
||||
filters={"name": ("not in", [])},
|
||||
).get_sql(),
|
||||
"SELECT `name` FROM `tabDocType` WHERE 1=1",
|
||||
)
|
||||
|
||||
self.assertQueryEqual(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue