* fix(query): check standard field definitions Signed-off-by: Akhil Narang <me@akhilnarang.dev> * fix(postgres): fix order_by problem in pg * fix(postgres): fix order_by in get_all for _test_connection_query * fix: add check to a proper numeric fallback in _get_ifnull_fallback * test(postgres): fix pg query used in assertion in test_permission_query * fix(postgres): fix order_by in get_all for possible_link * fix(postgres): fix order_by in get_all for set_modules * fix(postgres): fix pg query count * * fix(postgres): fix order_by in get_all for ask_pass_update * fix(postgres): fix order_by statement in search_widget * fix(postgres): fix order_by in get_list for get_stats * test(postgres): normalize_sql for pg queries in test_arithmetic_operators_in_fields * test(postgres): normalize_sql for pg queries in test_field_alias_in_group_by * test(postgres): normalize_sql for pg queries in test_field_alias_permission_check * test(postgres): fix order_by statement in get_all for test_db_keywords_as_fields * test(postgres): fix order_by statement in get_all for test_prepare_select_args * fix(treeview): use 0 instead of false to check since check field is an integer * fix(postgres): fix order_by in get_all for sync_communication * fix(postgres): fix order_by in get_all for get_references_across_doctypes_by_dynamic_link_field * test(postgres): fix order_by in get_all for test_list_summary * fix(postgres): fix order_by in get_all for email queries * test(postgres): use order_by none and update assertion for postgres * fix(postgres): use ILIKE to support case insensitive search in postgres * test(test_query): update pg specific query assert to use ILIKE * test(test_query): update test_nested_filters to use ilike instead for PG * test(postgres): update pg query in assert to test updated qb query * fix(search): update query to be db-agnostic * test(postgres): normalize query for pg in test_build_match_conditions * fix(postgres): suppress ORDER BY when SELECT DISTINCT in query for postgres specific behavior * fix(postgres): suppress ORDER BY when GROUP BY is explicitly asked for pg specific behavior * test(postgres): fix test behavior for pg ORDER BY drop when used with GROUP BY * refactor: reducing noise in code by formatting code * fix(query): use Star() to handle SQL wildcard character * correctly * fix(postgres): display warning for ORDER BY fields that will be dropped --------- Signed-off-by: Akhil Narang <me@akhilnarang.dev> Co-authored-by: Akhil Narang <me@akhilnarang.dev>
159 lines
3.3 KiB
Python
159 lines
3.3 KiB
Python
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
|
# MIT License. See license.txt
|
|
|
|
import operator
|
|
from collections.abc import Callable
|
|
|
|
import frappe
|
|
from frappe.database.utils import NestedSetHierarchy
|
|
from frappe.model.db_query import get_timespan_date_range
|
|
from frappe.query_builder import Field
|
|
from frappe.query_builder.functions import Coalesce
|
|
|
|
|
|
def like(key: Field, value: str) -> frappe.qb:
|
|
"""Wrapper method for `LIKE`
|
|
|
|
Args:
|
|
key (str): field
|
|
value (str): criterion
|
|
|
|
Return:
|
|
frappe.qb: `frappe.qb` object with `LIKE`
|
|
"""
|
|
return key.like(value)
|
|
|
|
|
|
def ilike(key: Field, value: str) -> frappe.qb:
|
|
"""Wrapper method for `ILIKE`
|
|
Args:
|
|
key (str): field
|
|
value (str): criterion
|
|
Return:
|
|
frappe.qb: `frappe.qb` object with `ILIKE`
|
|
"""
|
|
return key.ilike(value)
|
|
|
|
|
|
def func_in(key: Field, value: list | tuple) -> frappe.qb:
|
|
"""Wrapper method for `IN`.
|
|
|
|
Args:
|
|
key (str): field
|
|
value (Union[int, str]): criterion
|
|
|
|
Return:
|
|
frappe.qb: `frappe.qb` object with `IN`
|
|
"""
|
|
if isinstance(value, str):
|
|
value = value.split(",")
|
|
return key.isin(value)
|
|
|
|
|
|
def not_like(key: Field, value: str) -> frappe.qb:
|
|
"""Wrapper method for `NOT LIKE`.
|
|
|
|
Args:
|
|
key (str): field
|
|
value (str): criterion
|
|
|
|
Return:
|
|
frappe.qb: `frappe.qb` object with `NOT LIKE`
|
|
"""
|
|
return key.not_like(value)
|
|
|
|
|
|
def func_not_in(key: Field, value: list | tuple | str):
|
|
"""Wrapper method for `NOT IN`.
|
|
|
|
Args:
|
|
key (str): field
|
|
value (Union[int, str]): criterion
|
|
|
|
Return:
|
|
frappe.qb: `frappe.qb` object with `NOT IN`
|
|
"""
|
|
if isinstance(value, str):
|
|
value = value.split(",")
|
|
return key.notin(value)
|
|
|
|
|
|
def func_regex(key: Field, value: str) -> frappe.qb:
|
|
"""Wrapper method for `REGEX`
|
|
|
|
Args:
|
|
key (str): field
|
|
value (str): criterion
|
|
|
|
Return:
|
|
frappe.qb: `frappe.qb` object with `REGEX`
|
|
"""
|
|
return key.regex(value)
|
|
|
|
|
|
def func_between(key: Field, value: list | tuple) -> frappe.qb:
|
|
"""Wrapper method for `BETWEEN`.
|
|
|
|
Args:
|
|
key (str): field
|
|
value (Union[int, str]): criterion
|
|
|
|
Return:
|
|
frappe.qb: `frappe.qb` object with `BETWEEN`
|
|
"""
|
|
return key[slice(*value)]
|
|
|
|
|
|
def func_is(key, value):
|
|
"Wrapper for IS"
|
|
|
|
match value.lower():
|
|
case "set":
|
|
return key != ""
|
|
case "not set":
|
|
return key.isnull() | (key == "")
|
|
case _:
|
|
raise ValueError("`is` operator only supports `set` and `not set` as value")
|
|
|
|
|
|
def func_timespan(key: Field, value: str) -> frappe.qb:
|
|
"""Wrapper method for `TIMESPAN`.
|
|
|
|
Args:
|
|
key (str): field
|
|
value (str): criterion
|
|
|
|
Return:
|
|
frappe.qb: `frappe.qb` object with `TIMESPAN`
|
|
"""
|
|
|
|
return func_between(key, get_timespan_date_range(value))
|
|
|
|
|
|
# default operators
|
|
OPERATOR_MAP: dict[str, Callable] = {
|
|
"+": operator.add,
|
|
"=": operator.eq,
|
|
"-": operator.sub,
|
|
"!=": operator.ne,
|
|
"<": operator.lt,
|
|
">": operator.gt,
|
|
"<=": operator.le,
|
|
"=<": operator.le,
|
|
">=": operator.ge,
|
|
"=>": operator.ge,
|
|
"/": operator.truediv,
|
|
"*": operator.mul,
|
|
"in": func_in,
|
|
"not in": func_not_in,
|
|
"like": like,
|
|
"ilike": ilike,
|
|
"not like": not_like,
|
|
"regex": func_regex,
|
|
"between": func_between,
|
|
"is": func_is,
|
|
"timespan": func_timespan,
|
|
# TODO: Add support for custom operators (WIP) - via filters_config hooks
|
|
}
|
|
|
|
NESTED_SET_OPERATORS = frozenset(NestedSetHierarchy)
|