Преглед изворни кода

Merge pull request #16542 from saxenabhishek/aks-feat-dbquery_update

feat: multi table support in db.query
version-14
gavin пре 3 година
committed by GitHub
родитељ
комит
ec4e75b640
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
5 измењених фајлова са 101 додато и 36 уклоњено
  1. +6
    -10
      frappe/database/database.py
  2. +46
    -21
      frappe/database/query.py
  3. +2
    -5
      frappe/desk/reportview.py
  4. +27
    -0
      frappe/tests/test_db.py
  5. +20
    -0
      frappe/tests/test_query.py

+ 6
- 10
frappe/database/database.py Прегледај датотеку

@@ -1019,21 +1019,17 @@ class Database(object):

return self.get_value(dt, dn, ignore=True, cache=cache)

def count(self, dt, filters=None, debug=False, cache=False):
def count(self, dt, filters=None, debug=False, cache=False, distinct: bool = True):
"""Returns `COUNT(*)` for given DocType and filters."""
if cache and not filters:
cache_count = frappe.cache().get_value("doctype:count:{}".format(dt))
if cache_count is not None:
return cache_count
query = self.query.get_sql(table=dt, filters=filters, fields=Count("*"))
if filters:
count = self.sql(query, debug=debug)[0][0]
return count
else:
count = self.sql(query, debug=debug)[0][0]
if cache:
frappe.cache().set_value("doctype:count:{}".format(dt), count, expires_in_sec=86400)
return count
query = self.query.get_sql(table=dt, filters=filters, fields=Count("*"), distinct=distinct)
count = query.run(debug=debug)[0][0]
if not filters and cache:
frappe.cache().set_value("doctype:count:{}".format(dt), count, expires_in_sec=86400)
return count

@staticmethod
def format_date(date):


+ 46
- 21
frappe/database/query.py Прегледај датотеку

@@ -4,10 +4,10 @@ from typing import Any, Dict, List, Tuple, Union

import frappe
from frappe import _
from frappe.query_builder import Criterion, Field, Order
from frappe.query_builder import Criterion, Field, Order, Table


def like(key: str, value: str) -> frappe.qb:
def like(key: Field, value: str) -> frappe.qb:
"""Wrapper method for `LIKE`

Args:
@@ -17,10 +17,10 @@ def like(key: str, value: str) -> frappe.qb:
Returns:
frappe.qb: `frappe.qb object with `LIKE`
"""
return Field(key).like(value)
return key.like(value)


def func_in(key: str, value: Union[List, Tuple]) -> frappe.qb:
def func_in(key: Field, value: Union[List, Tuple]) -> frappe.qb:
"""Wrapper method for `IN`

Args:
@@ -30,10 +30,10 @@ def func_in(key: str, value: Union[List, Tuple]) -> frappe.qb:
Returns:
frappe.qb: `frappe.qb object with `IN`
"""
return Field(key).isin(value)
return key.isin(value)


def not_like(key: str, value: str) -> frappe.qb:
def not_like(key: Field, value: str) -> frappe.qb:
"""Wrapper method for `NOT LIKE`

Args:
@@ -43,10 +43,10 @@ def not_like(key: str, value: str) -> frappe.qb:
Returns:
frappe.qb: `frappe.qb object with `NOT LIKE`
"""
return Field(key).not_like(value)
return key.not_like(value)


def func_not_in(key: str, value: Union[List, Tuple]):
def func_not_in(key: Field, value: Union[List, Tuple]):
"""Wrapper method for `NOT IN`

Args:
@@ -56,10 +56,10 @@ def func_not_in(key: str, value: Union[List, Tuple]):
Returns:
frappe.qb: `frappe.qb object with `NOT IN`
"""
return Field(key).notin(value)
return key.notin(value)


def func_regex(key: str, value: str) -> frappe.qb:
def func_regex(key: Field, value: str) -> frappe.qb:
"""Wrapper method for `REGEX`

Args:
@@ -69,10 +69,10 @@ def func_regex(key: str, value: str) -> frappe.qb:
Returns:
frappe.qb: `frappe.qb object with `REGEX`
"""
return Field(key).regex(value)
return key.regex(value)


def func_between(key: str, value: Union[List, Tuple]) -> frappe.qb:
def func_between(key: Field, value: Union[List, Tuple]) -> frappe.qb:
"""Wrapper method for `BETWEEN`

Args:
@@ -82,7 +82,7 @@ def func_between(key: str, value: Union[List, Tuple]) -> frappe.qb:
Returns:
frappe.qb: `frappe.qb object with `BETWEEN`
"""
return Field(key)[slice(*value)]
return key[slice(*value)]


def make_function(key: Any, value: Union[int, str]):
@@ -139,7 +139,9 @@ OPERATOR_MAP = {


class Query:
def get_condition(self, table: str, **kwargs) -> frappe.qb:
tables: dict = {}

def get_condition(self, table: Union[str, Table], **kwargs) -> frappe.qb:
"""Get initial table object

Args:
@@ -148,11 +150,20 @@ class Query:
Returns:
frappe.qb: DocType with initial condition
"""
table_object = self.get_table(table)
if kwargs.get("update"):
return frappe.qb.update(table)
return frappe.qb.update(table_object)
if kwargs.get("into"):
return frappe.qb.into(table)
return frappe.qb.from_(table)
return frappe.qb.into(table_object)
return frappe.qb.from_(table_object)

def get_table(self, table_name: Union[str, Table]) -> Table:
if isinstance(table_name, Table):
return table_name
table_name = table_name.strip('"').strip("'")
if table_name not in self.tables:
self.tables[table_name] = frappe.qb.DocType(table_name)
return self.tables[table_name]

def criterion_query(self, table: str, criterion: Criterion, **kwargs) -> frappe.qb:
"""Generate filters from Criterion objects
@@ -217,8 +228,13 @@ class Query:
conditions = conditions.where(_operator(Field(filters[0]), filters[2]))
break
else:
_operator = OPERATOR_MAP[f[1]]
conditions = conditions.where(_operator(Field(f[0]), f[2]))
_operator = OPERATOR_MAP[f[-2]]
if len(f) == 4:
table_object = self.get_table(f[0])
_field = table_object[f[1]]
else:
_field = Field(f[0])
conditions = conditions.where(_operator(_field, f[-1]))

return self.add_conditions(conditions, **kwargs)

@@ -249,7 +265,7 @@ class Query:
if isinstance(value, (list, tuple)):
if isinstance(value[1], (list, tuple)) or value[0] in list(OPERATOR_MAP.keys())[-4:]:
_operator = OPERATOR_MAP[value[0]]
conditions = conditions.where(_operator(key, value[1]))
conditions = conditions.where(_operator(Field(key), value[1]))
else:
_operator = OPERATOR_MAP[value[0]]
conditions = conditions.where(_operator(Field(key), value[1]))
@@ -293,10 +309,19 @@ class Query:
self,
table: str,
fields: Union[List, Tuple],
filters: Union[Dict[str, Union[str, int]], str, int] = None,
filters: Union[Dict[str, Union[str, int]], str, int, List[Union[List, str, int]]] = None,
**kwargs,
):
# Clean up state before each query
self.tables = {}
criterion = self.build_conditions(table, filters, **kwargs)

if len(self.tables) > 1:
primary_table = self.tables[table]
del self.tables[table]
for table_object in self.tables.values():
criterion = criterion.left_join(table_object).on(table_object.parent == primary_table.name)

if isinstance(fields, (list, tuple)):
query = criterion.select(*kwargs.get("field_objects", fields))



+ 2
- 5
frappe/desk/reportview.py Прегледај датотеку

@@ -48,15 +48,12 @@ def get_list():
@frappe.read_only()
def get_count():
args = get_form_params()

if is_virtual_doctype(args.doctype):
controller = get_controller(args.doctype)
data = controller(args.doctype).get_count(args)
else:
distinct = "distinct " if args.distinct == "true" else ""
args.fields = [f"count({distinct}`tab{args.doctype}`.name) as total_count"]
data = execute(**args)[0].get("total_count")

distinct = args["distinct"] == "true"
data = frappe.db.count(args["doctype"], args["filters"], distinct=distinct)
return data




+ 27
- 0
frappe/tests/test_db.py Прегледај датотеку

@@ -482,6 +482,33 @@ class TestDB(unittest.TestCase):

frappe.db.delete("ToDo", {"description": test_body})

def test_count(self):
frappe.db.delete("Note")

frappe.get_doc(doctype="Note", title="note1", content="something").insert()
frappe.get_doc(doctype="Note", title="note2", content="someting else").insert()

# Count with no filtes
self.assertEquals((frappe.db.count("Note")), 2)

# simple filters
self.assertEquals((frappe.db.count("Note", ["title", "=", "note1"])), 1)

frappe.get_doc(doctype="Note", title="note3", content="something other").insert()

# List of list filters with tables
self.assertEquals(
(
frappe.db.count(
"Note",
[["Note", "title", "like", "note%"], ["Note", "content", "like", "some%"]],
)
),
3,
)

frappe.db.rollback()


@run_only_if(db_type_is.MARIADB)
class TestDDLCommandsMaria(unittest.TestCase):


+ 20
- 0
frappe/tests/test_query.py Прегледај датотеку

@@ -0,0 +1,20 @@
import unittest

import frappe
from frappe.tests.test_query_builder import db_type_is, run_only_if


@run_only_if(db_type_is.MARIADB)
class TestQuery(unittest.TestCase):
def test_multiple_tables_in_filters(self):
self.assertEqual(
frappe.db.query.get_sql(
"DocType",
["*"],
[
["BOM Update Log", "name", "like", "f%"],
["DocType", "parent", "=", "something"],
],
).get_sql(),
"SELECT * FROM `tabDocType` LEFT JOIN `tabBOM Update Log` ON `tabBOM Update Log`.`parent`=`tabDocType`.`name` WHERE `tabBOM Update Log`.`name` LIKE 'f%' AND `tabDocType`.`parent`='something'",
)

Loading…
Откажи
Сачувај