Skip to content

Commit

Permalink
trees: start working on CTE tree implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
ergo committed Oct 23, 2016
1 parent 6c52921 commit 8be7d34
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 11 deletions.
43 changes: 34 additions & 9 deletions ziggurat_foundations/models/services/resource.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from __future__ import unicode_literals

from collections import OrderedDict

import sqlalchemy as sa
from . import BaseService
from ..base import get_db_session
Expand Down Expand Up @@ -202,21 +205,43 @@ def subtree_deeper(cls, object_id, limit_depth=1000000, flat=True,
"""
raw_q = """
WITH RECURSIVE subtree AS (
SELECT res.*, 1 as depth, array[ordering] as sorting FROM
resources res WHERE res.resource_id = :id
SELECT res.*, 1 AS depth, res.ordering::CHARACTER VARYING AS sorting,
res.resource_id::CHARACTER VARYING AS path
FROM resources AS res WHERE res.resource_id = :resource_id
UNION ALL
SELECT res_u.*, depth+1 as depth,
(st.sorting || ARRAY[res_u.ordering] ) as sort
SELECT res_u.*, depth+1 AS depth,
(st.sorting::CHARACTER VARYING || '/' || res_u.ordering::CHARACTER VARYING ) AS sorting,
(st.path::CHARACTER VARYING || '/' || res_u.resource_id::CHARACTER VARYING ) AS path
FROM resources res_u, subtree st
WHERE res_u.parent_id = st.resource_id
)
SELECT * FROM subtree WHERE depth<=:depth ORDER BY sorting;
"""
db_session = get_db_session(db_session)
q = db_session.query(cls).from_statement(raw_q).params(id=object_id,
depth=limit_depth)
text_obj = sa.text(raw_q)
q = db_session.query(cls.model, 'depth', 'sorting', 'path').from_statement(
text_obj).params(
resource_id=object_id, depth=limit_depth)
return q

@classmethod
def build_subtree_strut(self, result):
items = list(result)
struct_dict = OrderedDict()
if len(items) == 0:
return struct_dict[0]
root_elem = {'node': items[0].Resource, 'children': OrderedDict()}
for i, node in enumerate(items[1:]):
new_elem = {'node': node.Resource, 'children': OrderedDict()}
path = map(int, node.path.split('/'))
parent_node = root_elem
normalized_path = path[1:-1]
if normalized_path:
for path_part in normalized_path:
parent_node = parent_node['children'][path_part]
parent_node['children'][new_elem['node'].resource_id] = new_elem
return root_elem

@classmethod
def path_upper(cls, object_id, limit_depth=1000000, flat=True,
db_session=None):
Expand All @@ -226,7 +251,7 @@ def path_upper(cls, object_id, limit_depth=1000000, flat=True,
raw_q = """
WITH RECURSIVE subtree AS (
SELECT res.*, 1 as depth FROM resources res
WHERE res.resource_id = :id
WHERE res.resource_id = :resource_id
UNION ALL
SELECT res_u.*, depth+1 as depth
FROM resources res_u, subtree st
Expand All @@ -235,6 +260,6 @@ def path_upper(cls, object_id, limit_depth=1000000, flat=True,
SELECT * FROM subtree WHERE depth<=:depth;
"""
db_session = get_db_session(db_session)
q = db_session.query(cls).from_statement(raw_q).params(id=object_id,
depth=limit_depth)
q = db_session.query(cls.model).from_statement(sa.text(raw_q)).params(
resource_id=object_id, depth=limit_depth)
return q
7 changes: 5 additions & 2 deletions ziggurat_foundations/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,16 @@ def add_user(db_session, user_name='username', email='email',
return user


def add_resource(db_session, resource_id, resource_name='test_resource'):
def add_resource(db_session, resource_id, resource_name='test_resource',
parent_id=None, ordering=None):
Resource.__possible_permissions__ = [
'test_perm', 'test_perm1',
'test_perm2', 'foo_perm',
'group_perm', 'group_perm2']
resource = TestResource(resource_id=resource_id,
resource_name=resource_name)
resource_name=resource_name,
parent_id=parent_id,
ordering=ordering)
db_session.add(resource)
db_session.flush()
return resource
Expand Down
39 changes: 39 additions & 0 deletions ziggurat_foundations/tests/test_resource.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
from __future__ import with_statement, unicode_literals
import pytest

from ziggurat_foundations.permissions import PermissionTuple, ALL_PERMISSIONS

from ziggurat_foundations.tests import (
add_user, check_one_in_other, add_resource, add_resource_b, add_group,
BaseTestCase)
from ziggurat_foundations.tests.conftest import Resource
from ziggurat_foundations.models.services.resource import ResourceService


class TestResources(BaseTestCase):
def test_nesting(self, db_session):
root = add_resource(
db_session, -1, 'root', ordering=1)
res_a = add_resource(
db_session, 1, 'a', parent_id=root.resource_id, ordering=2)
res_aa = add_resource(
db_session, 5, 'aa', parent_id=res_a.resource_id, ordering=1)
res_ab = add_resource(
db_session, 6, 'ab', parent_id=res_a.resource_id, ordering=2)
res_ac = add_resource(
db_session, 7, 'ac', parent_id=res_a.resource_id, ordering=3)
res_aca = add_resource(
db_session, 9, 'aca', parent_id=res_ac.resource_id, ordering=1)
res_ad = add_resource(
db_session, 8, 'ad', parent_id=res_a.resource_id, ordering=4)
res_b = add_resource(
db_session, 2, 'b', parent_id=root.resource_id, ordering=3)
res_ba = add_resource(
db_session, 4, 'ba', parent_id=res_b.resource_id, ordering=1)
res_c = add_resource(
db_session, 3, 'c', parent_id=root.resource_id, ordering=4)

result = ResourceService.subtree_deeper(root.resource_id,
db_session=db_session)
tree_struct = ResourceService.build_subtree_strut(result)

0 comments on commit 8be7d34

Please sign in to comment.