Bladeren bron

refactor(nestedset): Using qb or db APIs inplace of raw SQLs

version-14
Gavin D'souza 3 jaren geleden
bovenliggende
commit
3358fdf9a9
1 gewijzigde bestanden met toevoegingen van 65 en 54 verwijderingen
  1. +65
    -54
      frappe/utils/nestedset.py

+ 65
- 54
frappe/utils/nestedset.py Bestand weergeven

@@ -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):


Laden…
Annuleren
Opslaan