perf: Avoid coalesce for between filters (#26531)

- Avoid on `between` + date
- Avoid on timestamp fields
- Avoid on `>` and `>=` comparisons
This commit is contained in:
Ankush Menat 2024-05-22 15:02:59 +05:30 committed by GitHub
parent 4fbeb4617a
commit 005e74b20d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 30 additions and 2 deletions

View file

@ -728,7 +728,7 @@ class DatabaseQuery:
df = df[0] if df else None
# primary key is never nullable, modified is usually indexed by default and always present
can_be_null = f.fieldname not in ("name", "modified")
can_be_null = f.fieldname not in ("name", "modified", "creation")
value = None
@ -810,12 +810,20 @@ class DatabaseQuery:
if f.operator.lower() in ("previous", "next", "timespan"):
date_range = get_date_range(f.operator.lower(), f.value)
f.operator = "Between"
f.operator = "between"
f.value = date_range
fallback = f"'{FallBackDateTimeStr}'"
if f.operator.lower() in (">", ">=") and (
f.fieldname in ("creation", "modified")
or (df and (df.fieldtype == "Date" or df.fieldtype == "Datetime"))
):
# Null values can never be greater than any non-null value
can_be_null = False
if f.operator in (">", "<", ">=", "<=") and (f.fieldname in ("creation", "modified")):
value = cstr(f.value)
can_be_null = False
fallback = f"'{FallBackDateTimeStr}'"
elif f.operator.lower() in ("between") and (
@ -823,6 +831,17 @@ class DatabaseQuery:
or (df and (df.fieldtype == "Date" or df.fieldtype == "Datetime"))
):
escape = False
# Between operator never needs to check for null
# Explanation: Consider SQL -> `COLUMN between X and Y`
# Actual computation:
# for row in rows:
# if Y > row.COLUMN > X:
# yield row
# Since Y and X can't be null, null value in column will never match filter, so
# coalesce is extra cost that prevents index usage
can_be_null = False
value = get_between_date_filter(f.value, df)
fallback = f"'{FallBackDateTimeStr}'"

View file

@ -1109,6 +1109,15 @@ class TestDBQuery(FrappeTestCase):
self.assertNotIn("ifnull", frappe.get_all("User", {"name": ("in", (""))}, run=0))
self.assertNotIn("ifnull", frappe.get_all("User", {"name": ("in", ())}, run=0))
def test_coalesce_with_datetime_ops(self):
self.assertNotIn("ifnull", frappe.get_all("User", {"last_active": (">", "2022-01-01")}, run=0))
self.assertNotIn("ifnull", frappe.get_all("User", {"creation": ("<", "2022-01-01")}, run=0))
self.assertNotIn(
"ifnull",
frappe.get_all("User", {"last_active": ("between", ("2022-01-01", "2023-01-01"))}, run=0),
)
self.assertIn("ifnull", frappe.get_all("User", {"last_active": ("<", "2022-01-01")}, run=0))
def test_ambiguous_linked_tables(self):
from frappe.desk.reportview import get