Skip to content

Commit ac3d5ee

Browse files
Merge pull request frappe#38128 from kaulith/fix/qb-in-filter-none-handling
2 parents 359a0ca + c8ce8cd commit ac3d5ee

3 files changed

Lines changed: 70 additions & 1 deletion

File tree

frappe/core/doctype/user/user.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ frappe.ui.form.on("User", {
33
frm.set_query("default_workspace", () => {
44
return {
55
filters: {
6-
for_user: ["in", [null, frappe.session.user]],
6+
for_user: ["in", ["", frappe.session.user]],
77
title: ["!=", "Welcome Workspace"],
88
},
99
};

frappe/database/operator_map.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ def func_in(key: Field, value: list | tuple) -> frappe.qb:
4848
"""
4949
if isinstance(value, str):
5050
value = value.split(",")
51+
52+
value = ["" if v is None else v for v in value]
53+
if "" in value:
54+
return Coalesce(key, "").isin(value)
5155
return key.isin(value)
5256

5357

frappe/tests/test_query_builder.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import frappe
66
from frappe.core.doctype.doctype.test_doctype import new_doctype
7+
from frappe.database.operator_map import func_in
78
from frappe.query_builder import Case
89
from frappe.query_builder.builder import Function
910
from frappe.query_builder.custom import ConstantColumn
@@ -503,3 +504,67 @@ def test_union(self):
503504
roles = frappe.qb.from_(role).select(role.name)
504505

505506
self.assertEqual(set(users.run() + roles.run()), set((users + roles).run()))
507+
508+
509+
class TestOperatorIn(IntegrationTestCase):
510+
def test_func_in_without_empty_values(self):
511+
note = frappe.qb.DocType("Note")
512+
query = func_in(note.name, ["n1", "n2", "n3"])
513+
sql_str = str(query).lower()
514+
515+
self.assertIn("in", sql_str)
516+
self.assertNotIn("coalesce", sql_str)
517+
518+
def test_func_in_with_none_converts_to_empty_string(self):
519+
note = frappe.qb.DocType("Note")
520+
query = func_in(note.name, [None, "user1"])
521+
sql_str = str(query).lower()
522+
523+
self.assertIn("coalesce", sql_str)
524+
self.assertIn("''", sql_str)
525+
526+
def test_func_in_with_empty_string_uses_coalesce(self):
527+
note = frappe.qb.DocType("Note")
528+
query = func_in(note.name, ["", "user1"])
529+
sql_str = str(query).lower()
530+
531+
self.assertIn("coalesce", sql_str)
532+
self.assertIn("''", sql_str)
533+
534+
def test_func_in_with_mixed_none_and_values(self):
535+
note = frappe.qb.DocType("Note")
536+
query = func_in(note.name, ["val1", None, "val2"])
537+
sql_str = str(query).lower()
538+
539+
self.assertIn("coalesce", sql_str)
540+
541+
def test_in_filter_matches_null_and_empty_columns(self):
542+
test_doctype = new_doctype(
543+
fields=[
544+
{
545+
"fieldname": "test_field",
546+
"fieldtype": "Data",
547+
"label": "Test Field",
548+
},
549+
],
550+
)
551+
test_doctype.insert()
552+
self.test_doctype_name = test_doctype.name
553+
self.addCleanup(frappe.delete_doc, "DocType", self.test_doctype_name)
554+
555+
doc_null = frappe.get_doc({"doctype": self.test_doctype_name, "test_field": None})
556+
doc_null.insert()
557+
doc_empty = frappe.get_doc({"doctype": self.test_doctype_name, "test_field": ""})
558+
doc_empty.insert()
559+
doc_user = frappe.get_doc({"doctype": self.test_doctype_name, "test_field": "user1"})
560+
doc_user.insert()
561+
562+
results = frappe.get_all(
563+
self.test_doctype_name,
564+
filters={"test_field": ["in", [None, "user1"]]},
565+
pluck="test_field",
566+
)
567+
568+
self.assertIn(None, results)
569+
self.assertIn("", results)
570+
self.assertIn("user1", results)

0 commit comments

Comments
 (0)