diff --git a/frappe/utils/nestedset.py b/frappe/utils/nestedset.py index 2517761c45..f1ebf8eeea 100644 --- a/frappe/utils/nestedset.py +++ b/frappe/utils/nestedset.py @@ -16,6 +16,9 @@ import frappe from frappe import _ from frappe.model.document import Document from frappe.query_builder import DocType, Order +from frappe.query_builder.functions import Coalesce, Max +from frappe.query_builder.utils import DocType + class NestedSetRecursionError(frappe.ValidationError): pass class NestedSetMultipleRootsError(frappe.ValidationError): pass @@ -51,87 +54,91 @@ def update_add_node(doc, parent, parent_field): """ insert a new node """ - doctype = doc.doctype name = doc.name + Table = DocType(doctype) # get the last sibling of the parent if parent: - left, right = frappe.db.sql("select lft, rgt from `tab{0}` where name=%s for update" - .format(doctype), parent)[0] + left, right = frappe.db.get_value(doctype, {"name": parent}, ["lft", "rgt"], for_update=True) validate_loop(doc.doctype, doc.name, left, right) else: # root - right = frappe.db.sql(""" - SELECT COALESCE(MAX(rgt), 0) + 1 FROM `tab{0}` - WHERE COALESCE(`{1}`, '') = '' - """.format(doctype, parent_field))[0][0] + + right = frappe.qb.from_(Table).select( + Coalesce(Max(Table.rgt), 0) + ).where(Coalesce(Table[parent_field], "") == "").run(pluck=True)[0] + right = right or 1 # update all on the right - frappe.db.sql("update `tab{0}` set rgt = rgt+2 where rgt >= %s" - .format(doctype), (right,)) - frappe.db.sql("update `tab{0}` set lft = lft+2 where lft >= %s" - .format(doctype), (right,)) + frappe.qb.update(Table).set(Table.rgt, Table.rgt + 2).where(Table.rgt >= right).run() + frappe.qb.update(Table).set(Table.lft, Table.lft + 2).where(Table.lft >= right).run() - # update index of new node - if frappe.db.sql("select * from `tab{0}` where lft=%s or rgt=%s".format(doctype), (right, right+1)): - frappe.msgprint(_("Nested set error. Please contact the Administrator.")) - raise Exception + if frappe.qb.from_(Table).select("*").where((Table.lft == right) | (Table.rgt == right + 1)).run(): + frappe.throw(_("Nested set error. Please contact the Administrator.")) - frappe.db.sql("update `tab{0}` set lft=%s, rgt=%s where name=%s".format(doctype), - (right,right+1, name)) + # update index of new node + frappe.qb.update(Table).set(Table.lft, right).set(Table.rgt, right + 1).where(Table.name == name).run() return right -def update_move_node(doc, parent_field): - parent = doc.get(parent_field) +def update_move_node(doc: Document, parent_field: str): + parent: str = doc.get(parent_field) + Table = DocType(doc.doctype) if parent: - new_parent = frappe.db.sql("""select lft, rgt from `tab{0}` - where name = %s for update""".format(doc.doctype), parent, as_dict=1)[0] + new_parent = frappe.qb.from_(Table).select( + Table.lft, Table.rgt + ).where(Table.name == parent).for_update().run(as_dict=True)[0] validate_loop(doc.doctype, doc.name, new_parent.lft, new_parent.rgt) # move to dark side - frappe.db.sql("""update `tab{0}` set lft = -lft, rgt = -rgt - where lft >= %s and rgt <= %s""".format(doc.doctype), (doc.lft, doc.rgt)) + frappe.qb.update(Table).set(Table.lft, - Table.lft).set(Table.rgt, - Table.rgt).where( + (Table.lft >= doc.lft) & (Table.rgt <= doc.rgt) + ).run() # shift left diff = doc.rgt - doc.lft + 1 - frappe.db.sql("""update `tab{0}` set lft = lft -%s, rgt = rgt - %s - where lft > %s""".format(doc.doctype), (diff, diff, doc.rgt)) + frappe.qb.update(Table).set(Table.lft, Table.lft - diff).set(Table.rgt, Table.rgt - diff).where( + Table.lft > doc.rgt + ).run() # shift left rgts of ancestors whose only rgts must shift - frappe.db.sql("""update `tab{0}` set rgt = rgt - %s - where lft < %s and rgt > %s""".format(doc.doctype), (diff, doc.lft, doc.rgt)) + frappe.qb.update(Table).set(Table.rgt, Table.rgt - diff).where( + (Table.lft < doc.lft) & (Table.rgt > doc.rgt) + ).run() if parent: - new_parent = frappe.db.sql("""select lft, rgt from `tab%s` - where name = %s for update""" % (doc.doctype, '%s'), parent, as_dict=1)[0] - # set parent lft, rgt - frappe.db.sql("""update `tab{0}` set rgt = rgt + %s - where name = %s""".format(doc.doctype), (diff, parent)) + frappe.qb.update(Table).set(Table.rgt, Table.rgt + diff).where(Table.name == parent).run() # shift right at new parent - frappe.db.sql("""update `tab{0}` set lft = lft + %s, rgt = rgt + %s - where lft > %s""".format(doc.doctype), (diff, diff, new_parent.rgt)) + frappe.qb.update(Table).set(Table.lft, Table.lft + diff).set(Table.rgt, Table.rgt + diff).where( + (Table.lft >= new_parent.lft) & (Table.lft <= new_parent.rgt) + ).run() - # shift right rgts of ancestors whose only rgts must shift - frappe.db.sql("""update `tab{0}` set rgt = rgt + %s - where lft < %s and rgt > %s""".format(doc.doctype), - (diff, new_parent.lft, new_parent.rgt)) + frappe.qb.update(Table).set(Table.lft, Table.lft + diff).set(Table.rgt, Table.rgt + diff).where( + Table.lft > new_parent.rgt + ).run() + # shift right rgts of ancestors whose only rgts must shift + frappe.qb.update(Table).set(Table.rgt, Table.rgt + diff).where( + (Table.lft < new_parent.lft) & (Table.rgt > new_parent.rgt) + ).run() new_diff = new_parent.rgt - doc.lft else: # new root - max_rgt = frappe.db.sql("""select max(rgt) from `tab{0}`""".format(doc.doctype))[0][0] + max_rgt = frappe.qb.from_(Table).select(Max(Table.rgt)).run(pluck=True)[0] new_diff = max_rgt + 1 - doc.lft # bring back from dark side - frappe.db.sql("""update `tab{0}` set lft = -lft + %s, rgt = -rgt + %s - where lft < 0""".format(doc.doctype), (new_diff, new_diff)) + frappe.qb.update(Table).set( + Table.lft, -Table.lft + new_diff + ).set( + Table.rgt, -Table.rgt + new_diff + ).where(Table.lft < 0).run() @frappe.whitelist() @@ -197,10 +204,10 @@ def rebuild_node(doctype, parent, left, parent_field): def validate_loop(doctype, name, lft, rgt): """check if item not an ancestor (loop)""" - if name in frappe.db.sql_list("""select name from `tab{0}` where lft <= %s and rgt >= %s""" - .format(doctype), (lft, rgt)): + if name in frappe.get_all(doctype, filters={"lft": ["<=", lft], "rgt": [">=", rgt]}, pluck="name"): frappe.throw(_("Item cannot be added to its own descendents"), NestedSetRecursionError) + class NestedSet(Document): def __setup__(self): if self.meta.get("nsm_parent_field"): @@ -232,9 +239,7 @@ class NestedSet(Document): raise def validate_if_child_exists(self): - has_children = frappe.db.sql("""select count(name) from `tab{doctype}` - where `{nsm_parent_field}`=%s""".format(doctype=self.doctype, nsm_parent_field=self.nsm_parent_field), - (self.name,))[0][0] + has_children = frappe.db.count(self.doctype, filters={self.nsm_parent_field: self.name}) if has_children: frappe.throw(_("Cannot delete {0} as it has child nodes").format(self.name), NestedSetChildExistsError) @@ -251,8 +256,7 @@ class NestedSet(Document): parent_field = self.nsm_parent_field # set old_parent for children - frappe.db.sql("update `tab{0}` set old_parent=%s where {1}=%s" - .format(self.doctype, parent_field), (newdn, newdn)) + frappe.db.set_value(self.doctype, {"old_parent": newdn}, {parent_field: newdn}, update_modified=False, for_update=False) if merge: rebuild_tree(self.doctype, parent_field) @@ -269,8 +273,7 @@ class NestedSet(Document): def validate_ledger(self, group_identifier="is_group"): if hasattr(self, group_identifier) and not bool(self.get(group_identifier)): - if frappe.db.sql("""select name from `tab{0}` where {1}=%s and docstatus!=2""" - .format(self.doctype, self.nsm_parent_field), (self.name)): + if frappe.get_all(self.doctype, {self.nsm_parent_field: self.name, "docstatus": ("!=", 2)}): frappe.throw(_("{0} {1} cannot be a leaf node as it has children").format(_(self.doctype), self.name)) def get_ancestors(self): @@ -291,10 +294,18 @@ class NestedSet(Document): def get_root_of(doctype): """Get root element of a DocType with a tree structure""" - result = frappe.db.sql("""select t1.name from `tab{0}` t1 where - (select count(*) from `tab{1}` t2 where - t2.lft < t1.lft and t2.rgt > t1.rgt) = 0 - and t1.rgt > t1.lft""".format(doctype, doctype)) + from frappe.query_builder.functions import Count + Table = DocType(doctype) + t1 = Table.as_("t1") + t2 = Table.as_("t2") + + subq = frappe.qb.from_(t2).select(Count("*")).where( + (t2.lft < t1.lft) & (t2.rgt > t1.rgt) + ) + result = frappe.qb.from_(t1).select(t1.name).where( + (subq == 0) & (t1.rgt > t1.lft) # depends on https://github.com/frappe/frappe/pull/16107 + ).run() + return result[0][0] if result else None def get_ancestors_of(doctype, name, order_by="lft desc", limit=None):