# Demonstrate closure tables. Deletion of nodes is left as an exercise to the reader.
from gluon import DAL, Field
db=DAL('sqlite://closure.db')
db.define_table( 'thing', db.Field('name'))db.thing.truncate()
db.define_table( 'closure', db.Field('parent', type='reference thing'), db.Field('child', type='reference thing'), db.Field('depth', type='integer'))db.closure.truncate()
def link(parent_id,child_id): """ link(1,3) """ p = db.closure.with_alias('p') c = db.closure.with_alias('c') rows = db((p.child==parent_id) & (c.parent==child_id)).select( p.parent.with_alias('parent'), c.child.with_alias('child'), (p.depth+c.depth+1).with_alias('depth')) for row in rows: db.closure.insert(parent=row.parent, child=row.child, depth=row.depth) def add_node(name,parent_name): """ add_node('Fruit','Food') """ child_id=db.thing.insert(name=name) db.closure.insert(parent=child_id, child=child_id, depth=0) if parent_name is not None: parent_id=db(db.thing.name==parent_name).select().first().id link(parent_id, child_id) def ancestors(name): """ print ancestors('Red')""" node=db(db.thing.name==name).select().first() db.thing.name, left=db.thing.on(db.thing.id==db.closure.parent), orderby=db.closure.depth)
def descendants(name): """ print descendants('Fruit')""" node=db(db.thing.name==name).select().first() db.thing.name, left=db.thing.on(db.thing.id==db.closure.child), orderby=db.closure.depth)
def closure(): """ print closure() """ parent = db.thing.with_alias('parent') child = db.thing.with_alias('child') left=(parent.on(parent.id == db.closure.parent), child.on(child.id == db.closure.child)))
def test(): add_node('Food',None) db.commit() print closure()
add_node('Vehicle',None) db.commit() print closure()
add_node('Fruit','Food') db.commit() print closure()
add_node('Meat','Food') db.commit() print closure()
add_node('Red','Fruit') db.commit() print closure()
add_node('Chevy','Vehicle') db.commit() print closure()
print "descendants of 'Food'" print descendants('Food')
print "ancestors of 'Red'" print ancestors('Red')
test()
I'm just trying to find a good solid way of doing the BOM pattern using the DAL, and pretty much all of the decent articles I've found say the Closure Table method is the best trade-off, especially for large-ish and deep-ish BOM structures.
But, I'm not dogmatic. How would you code up a version using "with recursive" queries using the DAL? If you post a running example it would be great at informing the group!
On Wednesday, November 21, 2018 at 9:56:48 AM UTC-8, Val K wrote:Why do you have to use this crutches (despite they are genius)? Now, even Sqlite3 supports 'with recursive' queries.And what do you mean under BOM and large tree? If we are talking about BOM of real (physical) object like a car or even an aircraft carrier, I think it is not large treeonly if you don't want to have BOAOM (bill of atoms of materials)
# fake table in which result of recursive select will be temporary stored
# id-values will be inherited from parent_child tabledb.define_table('entry_collector', Field('child', 'integer'), Field('xpath', 'json'), # array of ids, xpath[0] == root, xpath[-1] == child Field('root', 'integer'), Field('xdepth', 'integer'), migrate_enabled = False, fake_migrate = True )
def with_recursive(parent, child, roots_select, q, *fields, **select_kwargs): """ parent, child - fields obj ( like db.parent_child.parent, db.parent_child.child ) roots_select - sql string (like 'select 123 as id' or db(db.person.id.belongs([11,22,33])._select(db.person.id)) q, fields, select_kwargs - args that will pass to dal: db(q).select(*fields, **select_kwargs) select_kwargs may include 'entry_collector' - name of fake table for recursive (default is 'entry_collector')
returns a regular rows dal object (nothing new) """
entry_collector = select_kwargs.pop('entry_collector', 'entry_collector') args = Storage( entry = parent.table._tablename, parent = parent.name, child = child.name, entry_collector = entry_collector, roots = roots_select )
rec_sql_s = \ """ WITH RECURSIVE %(entry_collector)s(id, child, xpath, root, xdepth) AS (SELECT NULL, id, "[" || id || "]", id, 0 FROM (%(roots)s) UNION SELECT %(entry)s.id, %(entry)s.%(child)s, rtrim(xpath,"]") || "," || %(entry)s.%(child)s || "]", %(entry_collector)s.root, %(entry_collector)s.xdepth + 1 FROM %(entry_collector)s JOIN %(entry)s ON NOT instr(%(entry_collector)s.xpath, %(entry)s.%(parent)s || "," ) AND %(entry)s.%(parent)s = %(entry_collector)s.child ORDER BY 5 DESC /* means BY xdepth */
) """ % args
q = db(q) dal_select = q._db._adapter._select_aux def patch_select(*args, **kwargs): if args: is_recursive = False for fld in args[1]: if fld.table._tablename == entry_collector: is_recursive = True break if is_recursive: args = list(args) args[0] = rec_sql_s + args[0] print 'with rec: ', args[0] return dal_select(*args, **kwargs)
q._db._adapter._select_aux = patch_select try: ret = q.select(*(fields + (db[entry_collector].id,)), **select_kwargs) finally: q._db._adapter._select_aux = dal_select return ret
--
Resources:
- http://web2py.com
- http://web2py.com/book (Documentation)
- http://github.com/web2py/web2py (Source code)
- https://code.google.com/p/web2py/issues/list (Report Issues)
---
You received this message because you are subscribed to the Google Groups "web2py-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to web2py+un...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.