From f4eaa4a481fdbba2e6428e7269059fb473e9f261 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Wed, 10 Aug 2022 01:18:17 +0530 Subject: [PATCH] feat: joining on tables mentioned in fields --- frappe/database/query.py | 23 ++++++++++++++++++++--- frappe/database/utils.py | 10 ++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/frappe/database/query.py b/frappe/database/query.py index 7ba1e9a162..1e4d8c65ef 100644 --- a/frappe/database/query.py +++ b/frappe/database/query.py @@ -1,8 +1,6 @@ import operator import re -import sys from ast import literal_eval -from fileinput import filename from functools import cached_property from types import BuiltinFunctionType from typing import Any, Callable @@ -13,6 +11,8 @@ from frappe.model.db_query import get_timespan_date_range from frappe.query_builder import Criterion, Field, Order, Table, functions from frappe.query_builder.functions import Function, SqlFunctions from frappe.query_builder.utils import PseudoColumn +from frappe.database.utils import table_from_string +from pypika import functions as f TAB_PATTERN = re.compile("^tab") WORDS_PATTERN = re.compile(r"\w+") @@ -503,7 +503,7 @@ class Engine: return fields - def set_fields(self, table, fields, **kwargs): + def set_fields(self, table, fields, **kwargs) -> list: fields = kwargs.get("pluck") if kwargs.get("pluck") else fields or "name" if isinstance(fields, list) and None in fields and Field not in fields: return None @@ -586,6 +586,23 @@ class Engine: table, kwargs.get("field_objects") or fields, **kwargs ) criterion = self.build_conditions(table, filters, **kwargs) + joined = False + for field in fields: + if "tab" in str(field): + join_on = table_from_string(str(field)) + criterion = criterion.left_join(join_on).on(join_on.parent == getattr(frappe.qb.DocType(table), "name")) + joined = True + if joined: + # Converting all fields to avoid ambiguity. + for field in fields: + if not getattr(field, '__module__', None) == f.__name__: + field = field if isinstance(field, str) else field.get_sql() + fields[fields.index(field)] = getattr(frappe.qb.DocType(table), field) + else: + field.args = [getattr(frappe.qb.DocType(table), arg.get_sql()) for arg in field.args] + field.args[0] = getattr(frappe.qb.DocType(table), field.args[0].get_sql()) + fields[fields.index(field)] = field + if self.linked_doctype and self.fieldname: for field in fields: if "tab" not in str(field): diff --git a/frappe/database/utils.py b/frappe/database/utils.py index c4d8cb4953..531fbbd973 100644 --- a/frappe/database/utils.py +++ b/frappe/database/utils.py @@ -3,10 +3,14 @@ from functools import cached_property from types import NoneType +import typing import frappe from frappe.query_builder.builder import MariaDB, Postgres +if typing.TYPE_CHECKING: + from frappe.query_builder import DocType + Query = str | MariaDB | Postgres QueryValues = tuple | list | dict | NoneType @@ -17,6 +21,12 @@ FallBackDateTimeStr = "0001-01-01 00:00:00.000000" def is_query_type(query: str, query_type: str | tuple[str]) -> bool: return query.lstrip().split(maxsplit=1)[0].lower().startswith(query_type) +def table_from_string(table: str) -> "DocType": + table_name = table.split("`", maxsplit=1)[1].split(".")[0][3:] + if "`" in table_name: + return frappe.qb.DocType(table_name=table_name.replace("`", "")) + else: + return frappe.qb.DocType(table_name=table_name) class LazyString: def _setup(self) -> None: