get_node()   B
last analyzed

Complexity

Conditions 3

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 3
c 2
b 0
f 0
dl 0
loc 25
rs 8.8571
1
# Copyright (c) 2016 Fabian Kochem
2
3
4
from libtree import exceptions
5
from libtree.core.node_data import NodeData
6
from libtree.utils import vectorize_nodes
7
8
9
def get_tree_size(cur):
10
    """
11
    Return the total amount of tree nodes.
12
    """
13
    sql = """
14
      SELECT
15
        COUNT(*)
16
      FROM
17
        nodes;
18
    """
19
    cur.execute(sql)
20
    result = cur.fetchone()
21
    return result['count']
22
23
24
def get_root_node(cur):
25
    """
26
    Return root node. Raise ``ValueError`` if root node doesn't exist.
27
    """
28
    sql = """
29
        SELECT
30
          *
31
        FROM
32
          nodes
33
        WHERE
34
          parent IS NULL;
35
    """
36
    cur.execute(sql)
37
    result = cur.fetchone()
38
39
    if result is None:
40
        raise exceptions.NoRootNode()
41
    else:
42
        return NodeData(**result)
43
44
45
def get_node(cur, id):
46
    """
47
    Return ``NodeData`` object for given ``id``. Raises ``ValueError``
48
    if ID doesn't exist.
49
50
    :param uuid4 id: Database ID
51
    """
52
    sql = """
53
        SELECT
54
          *
55
        FROM
56
          nodes
57
        WHERE
58
          id = %s;
59
    """
60
    if not isinstance(id, str):
61
        raise TypeError('ID must be type string (UUID4).')
62
63
    cur.execute(sql, (id, ))
64
    result = cur.fetchone()
65
66
    if result is None:
67
        raise exceptions.NodeNotFound(id)
68
    else:
69
        return NodeData(**result)
70
71
72
def get_node_at_position(cur, node, position):
73
    """
74
    Return node at ``position`` in the children of ``node``.
75
76
    :param node:
77
    :type node: Node or uuid4
78
    :param int position:
79
    """
80
    sql = """
81
      SELECT
82
        *
83
      FROM
84
        nodes
85
      WHERE
86
        parent=%s
87
      AND
88
        position=%s
89
    """
90
91
    cur.execute(sql, (str(node), position))
92
    result = cur.fetchone()
93
94
    if result is None:
95
        raise ValueError('Node does not exist.')
96
    else:
97
        return NodeData(**result)
98
99
100
def get_children(cur, node):
101
    """
102
    Return an iterator that yields a ``NodeData`` object of every
103
    immediate child.
104
105
    :param node:
106
    :type node: Node or uuid4
107
    """
108
    sql = """
109
        SELECT
110
          *
111
        FROM
112
          nodes
113
        WHERE
114
          parent=%s
115
        ORDER BY
116
          position;
117
    """
118
    cur.execute(sql, (str(node), ))
119
    for result in cur:
120
        yield NodeData(**result)
121
122
123
def get_child_ids(cur, node):
124
    """
125
    Return an iterator that yields the ID of every immediate child.
126
127
    :param node:
128
    :type node: Node or uuid4
129
    """
130
    sql = """
131
        SELECT
132
          id
133
        FROM
134
          nodes
135
        WHERE
136
          parent=%s
137
        ORDER BY
138
          position;
139
    """
140
    cur.execute(sql, (str(node), ))
141
    for result in cur:
142
        yield str(result['id'])
143
144
145
def get_children_count(cur, node):
146
    """
147
    Get amount of immediate children.
148
149
    :param node: Node
150
    :type node: Node or uuid4
151
    """
152
    sql = """
153
      SELECT
154
        COUNT(*)
155
      FROM
156
        nodes
157
      WHERE
158
        parent=%s;
159
    """
160
    cur.execute(sql, (str(node), ))
161
    result = cur.fetchone()
162
    return result['count']
163
164
165
def get_ancestors(cur, node, sort=True):
166
    """
167
    Return an iterator which yields a ``NodeData`` object for every
168
    node in the hierarchy chain from ``node`` to root node.
169
170
    :param node:
171
    :type node: Node or uuid4
172
    :param bool sort: Start with closest node and end with root node.
173
                      (default: True). Set to False if order is
174
                      unimportant.
175
    """
176
    # TODO: benchmark if vectorize_nodes() or WITH RECURSIVE is faster
177
    sql = """
178
        SELECT
179
          nodes.*
180
        FROM
181
          ancestors
182
        INNER JOIN
183
          nodes
184
        ON
185
          ancestors.ancestor=nodes.id
186
        WHERE
187
          ancestors.node=%s;
188
    """
189
    cur.execute(sql, (str(node), ))
190
191
    if sort:
192
        make_node = lambda r: NodeData(**r)
193
        for node in vectorize_nodes(map(make_node, cur))[::-1]:
194
            yield node
195
    else:
196
        for result in cur:
197
            yield NodeData(**result)
198
199
200
def get_ancestor_ids(cur, node):
201
    """
202
    Return an iterator that yields the ID of every element while
203
    traversing from ``node`` to the root node.
204
205
    :param node:
206
    :type node: Node or uuid4
207
    """
208
    # TODO: add sort parameter
209
    sql = """
210
        SELECT
211
          ancestor
212
        FROM
213
          ancestors
214
        WHERE
215
          node=%s;
216
    """
217
    cur.execute(sql, (str(node), ))
218
    for result in cur:
219
        yield str(result['ancestor'])
220
221
222
def get_descendants(cur, node):
223
    """
224
    Return an iterator that yields the ID of every element while
225
    traversing from ``node`` to the root node.
226
227
    :param node:
228
    :type node: Node or uuid4
229
    """
230
    sql = """
231
        SELECT
232
          nodes.*
233
        FROM
234
          ancestors
235
        INNER JOIN
236
          nodes
237
        ON
238
          ancestors.node=nodes.id
239
        WHERE
240
          ancestors.ancestor=%s;
241
    """
242
    cur.execute(sql, (str(node), ))
243
    for result in cur:
244
        yield NodeData(**result)
245
246
247
def get_descendant_ids(cur, node):
248
    """
249
    Return an iterator that yields a ``NodeData`` object of each element
250
    in the nodes subtree. Be careful when converting this iterator to an
251
    iterable (like list or set) because it could contain billions of
252
    objects.
253
254
    :param node:
255
    :type node: Node or uuid4
256
    """
257
    sql = """
258
        SELECT
259
          node
260
        FROM
261
          ancestors
262
        WHERE
263
          ancestor=%s;
264
    """
265
    cur.execute(sql, (str(node), ))
266
    for result in cur:
267
        yield str(result['node'])
268