/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
1
# Copyright (C) 2008 Canonical Ltd
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
17
"""Persistent maps from tuple_of_strings->string using CHK stores.
18
19
Overview and current status:
20
21
The CHKMap class implements a dict from tuple_of_strings->string by using a trie
22
with internal nodes of 8-bit fan out; The key tuples are mapped to strings by
23
joining them by \x00, and \x00 padding shorter keys out to the length of the
24
longest key. Leaf nodes are packed as densely as possible, and internal nodes
3735.11.1 by John Arbash Meinel
Clean up some trailing whitespace.
25
are all and additional 8-bits wide leading to a sparse upper tree.
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
26
27
Updates to a CHKMap are done preferentially via the apply_delta method, to
28
allow optimisation of the update operation; but individual map/unmap calls are
29
possible and supported. All changes via map/unmap are buffered in memory until
30
the _save method is called to force serialisation of the tree. apply_delta
31
performs a _save implicitly.
32
33
TODO:
34
-----
35
36
Densely packed upper nodes.
37
38
"""
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
39
3735.2.31 by Robert Collins
CHKMap.iter_changes
40
import heapq
3735.2.62 by Robert Collins
Create a rudimentary CHK page cache.
41
3735.9.18 by John Arbash Meinel
Make the versionedfile import lazy.
42
from bzrlib import lazy_import
43
lazy_import.lazy_import(globals(), """
44
from bzrlib import versionedfile
45
""")
3735.2.62 by Robert Collins
Create a rudimentary CHK page cache.
46
from bzrlib.lru_cache import LRUCache
47
48
# approx 2MB
49
_PAGE_CACHE_SIZE = 2*1024*1024 / 4*1024
50
_page_cache = LRUCache(_PAGE_CACHE_SIZE)
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
51
52
53
class CHKMap(object):
54
    """A persistent map from string to string backed by a CHK store."""
55
56
    def __init__(self, store, root_key):
57
        """Create a CHKMap object.
58
59
        :param store: The store the CHKMap is stored in.
60
        :param root_key: The root key of the map. None to create an empty
61
            CHKMap.
62
        """
63
        self._store = store
64
        if root_key is None:
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
65
            self._root_node = LeafNode()
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
66
        else:
3735.2.41 by Robert Collins
Make the parent_id_basename index be updated during CHKInventory.apply_delta.
67
            self._root_node = self._node_key(root_key)
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
68
69
    def apply_delta(self, delta):
70
        """Apply a delta to the map.
71
72
        :param delta: An iterable of old_key, new_key, new_value tuples.
73
            If new_key is not None, then new_key->new_value is inserted
74
            into the map; if old_key is not None, then the old mapping
75
            of old_key is removed.
76
        """
77
        for old, new, value in delta:
78
            if old is not None and old != new:
79
                # unmap
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
80
                self.unmap(old)
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
81
        for old, new, value in delta:
82
            if new is not None:
83
                # map
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
84
                self.map(new, value)
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
85
        return self._save()
86
87
    def _ensure_root(self):
88
        """Ensure that the root node is an object not a key."""
89
        if type(self._root_node) == tuple:
90
            # Demand-load the root
3735.2.31 by Robert Collins
CHKMap.iter_changes
91
            self._root_node = self._get_node(self._root_node)
92
93
    def _get_node(self, node):
94
        """Get a node.
95
96
        Node that this does not update the _items dict in objects containing a
97
        reference to this node. As such it does not prevent subsequent IO being
98
        performed.
3735.11.1 by John Arbash Meinel
Clean up some trailing whitespace.
99
3735.2.31 by Robert Collins
CHKMap.iter_changes
100
        :param node: A tuple key or node object.
101
        :return: A node object.
102
        """
103
        if type(node) == tuple:
104
            bytes = self._read_bytes(node)
105
            return _deserialise(bytes, node)
106
        else:
107
            return node
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
108
109
    def _read_bytes(self, key):
110
        stream = self._store.get_record_stream([key], 'unordered', True)
111
        return stream.next().get_bytes_as('fulltext')
112
3735.9.2 by John Arbash Meinel
Add a _dump_tree helper that assists in debugging what is going on.
113
    def _dump_tree(self):
114
        """Return the tree in a string representation."""
115
        self._ensure_root()
116
        res = self._dump_tree_node(self._root_node, prefix='', indent='')
3735.11.9 by John Arbash Meinel
Switch _dump_tree to returning trailing '\n' for nicer results
117
        res.append('') # Give a trailing '\n'
3735.9.4 by John Arbash Meinel
Some small cleanups, and fix _dump_tree to handle in-progress nodes.
118
        return '\n'.join(res)
3735.9.2 by John Arbash Meinel
Add a _dump_tree helper that assists in debugging what is going on.
119
120
    def _dump_tree_node(self, node, prefix, indent):
121
        """For this node and all children, generate a string representation."""
122
        result = []
3735.9.4 by John Arbash Meinel
Some small cleanups, and fix _dump_tree to handle in-progress nodes.
123
        node_key = node.key()
124
        if node_key is not None:
125
            node_key = node_key[0]
126
        result.append('%s%r %s %s' % (indent, prefix, node.__class__.__name__,
127
                                        node_key))
3735.9.2 by John Arbash Meinel
Add a _dump_tree helper that assists in debugging what is going on.
128
        if isinstance(node, InternalNode):
129
            # Trigger all child nodes to get loaded
130
            list(node._iter_nodes(self._store))
3735.9.4 by John Arbash Meinel
Some small cleanups, and fix _dump_tree to handle in-progress nodes.
131
            for prefix, sub in sorted(node._items.iteritems()):
3735.9.2 by John Arbash Meinel
Add a _dump_tree helper that assists in debugging what is going on.
132
                result.extend(self._dump_tree_node(sub, prefix, indent + '  '))
133
        else:
3735.9.4 by John Arbash Meinel
Some small cleanups, and fix _dump_tree to handle in-progress nodes.
134
            for key, value in sorted(node._items.iteritems()):
135
                result.append('      %r %r' % (key, value))
3735.9.2 by John Arbash Meinel
Add a _dump_tree helper that assists in debugging what is going on.
136
        return result
137
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
138
    @classmethod
3735.2.43 by Robert Collins
Teach CHKMap how to iter items in 2-tuple keyspaces.
139
    def from_dict(klass, store, initial_value, maximum_size=0, key_width=1):
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
140
        """Create a CHKMap in store with initial_value as the content.
3735.11.1 by John Arbash Meinel
Clean up some trailing whitespace.
141
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
142
        :param store: The store to record initial_value in, a VersionedFiles
143
            object with 1-tuple keys supporting CHK key generation.
144
        :param initial_value: A dict to store in store. Its keys and values
145
            must be bytestrings.
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
146
        :param maximum_size: The maximum_size rule to apply to nodes. This
147
            determines the size at which no new data is added to a single node.
3735.2.43 by Robert Collins
Teach CHKMap how to iter items in 2-tuple keyspaces.
148
        :param key_width: The number of elements in each key_tuple being stored
149
            in this map.
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
150
        :return: The root chk of te resulting CHKMap.
151
        """
152
        result = CHKMap(store, None)
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
153
        result._root_node.set_maximum_size(maximum_size)
3735.2.43 by Robert Collins
Teach CHKMap how to iter items in 2-tuple keyspaces.
154
        result._root_node._key_width = key_width
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
155
        delta = []
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
156
        for key, value in initial_value.items():
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
157
            delta.append((None, key, value))
158
        result.apply_delta(delta)
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
159
        return result._save()
160
3735.2.30 by Robert Collins
Start iter_changes between CHKMap instances.
161
    def iter_changes(self, basis):
162
        """Iterate over the changes between basis and self.
163
164
        :return: An iterator of tuples: (key, old_value, new_value). Old_value
165
            is None for keys only in self; new_value is None for keys only in
166
            basis.
167
        """
3735.2.31 by Robert Collins
CHKMap.iter_changes
168
        # Overview:
169
        # Read both trees in lexographic, highest-first order.
170
        # Any identical nodes we skip
171
        # Any unique prefixes we output immediately.
172
        # values in a leaf node are treated as single-value nodes in the tree
173
        # which allows them to be not-special-cased. We know to output them
174
        # because their value is a string, not a key(tuple) or node.
175
        #
176
        # corner cases to beware of when considering this function:
177
        # *) common references are at different heights.
178
        #    consider two trees:
179
        #    {'a': LeafNode={'aaa':'foo', 'aab':'bar'}, 'b': LeafNode={'b'}}
180
        #    {'a': InternalNode={'aa':LeafNode={'aaa':'foo', 'aab':'bar'}, 'ab':LeafNode={'ab':'bar'}}
181
        #     'b': LeafNode={'b'}}
182
        #    the node with aaa/aab will only be encountered in the second tree
183
        #    after reading the 'a' subtree, but it is encountered in the first
184
        #    tree immediately. Variations on this may have read internal nodes like this.
185
        #    we want to cut the entire pending subtree when we realise we have a common node.
3735.11.1 by John Arbash Meinel
Clean up some trailing whitespace.
186
        #    For this we use a list of keys - the path to a node - and check the entire path is
3735.2.31 by Robert Collins
CHKMap.iter_changes
187
        #    clean as we process each item.
188
        if self._node_key(self._root_node) == self._node_key(basis._root_node):
189
            return
190
        self._ensure_root()
191
        basis._ensure_root()
192
        excluded_keys = set()
193
        self_node = self._root_node
194
        basis_node = basis._root_node
195
        # A heap, each element is prefix, node(tuple/NodeObject/string),
196
        # key_path (a list of tuples, tail-sharing down the tree.)
197
        self_pending = []
198
        basis_pending = []
199
        def process_node(prefix, node, path, a_map, pending):
200
            # take a node and expand it
201
            node = a_map._get_node(node)
202
            if type(node) == LeafNode:
203
                path = (node._key, path)
204
                for key, value in node._items.items():
205
                    heapq.heappush(pending, ('\x00'.join(key), value, path))
206
            else:
207
                # type(node) == InternalNode
208
                path = (node._key, path)
209
                for prefix, child in node._items.items():
210
                    heapq.heappush(pending, (prefix, child, path))
211
        process_node(None, self_node, None, self, self_pending)
212
        process_node(None, basis_node, None, basis, basis_pending)
213
        self_seen = set()
214
        basis_seen = set()
215
        excluded_keys = set()
216
        def check_excluded(key_path):
217
            # Note that this is N^2, it depends on us trimming trees
218
            # aggressively to not become slow.
219
            # A better implementation would probably have a reverse map
3735.11.1 by John Arbash Meinel
Clean up some trailing whitespace.
220
            # back to the children of a node, and jump straight to it when
3735.2.31 by Robert Collins
CHKMap.iter_changes
221
            # a common node is detected, the proceed to remove the already
222
            # pending children. bzrlib.graph has a searcher module with a
223
            # similar problem.
224
            while key_path is not None:
225
                key, key_path = key_path
226
                if key in excluded_keys:
227
                    return True
228
            return False
229
3735.2.32 by Robert Collins
Activate test for common node skipping. - 50 times performance improvement.
230
        loop_counter = 0
3735.2.31 by Robert Collins
CHKMap.iter_changes
231
        while self_pending or basis_pending:
3735.2.32 by Robert Collins
Activate test for common node skipping. - 50 times performance improvement.
232
            loop_counter += 1
3735.2.31 by Robert Collins
CHKMap.iter_changes
233
            if not self_pending:
234
                # self is exhausted: output remainder of basis
235
                for prefix, node, path in basis_pending:
236
                    if check_excluded(path):
237
                        continue
238
                    node = basis._get_node(node)
239
                    if type(node) == str:
240
                        # a value
241
                        yield (tuple(prefix.split('\x00')), node, None)
242
                    else:
243
                        # subtree - fastpath the entire thing.
244
                        for key, value in node.iteritems(basis._store):
245
                            yield (key, value, None)
246
                return
247
            elif not basis_pending:
248
                # basis is exhausted: output remainder of self.
249
                for prefix, node, path in self_pending:
250
                    if check_excluded(path):
251
                        continue
252
                    node = self._get_node(node)
253
                    if type(node) == str:
254
                        # a value
255
                        yield (tuple(prefix.split('\x00')), None, node)
256
                    else:
257
                        # subtree - fastpath the entire thing.
258
                        for key, value in node.iteritems(self._store):
259
                            yield (key, None, value)
260
                return
261
            else:
262
                # XXX: future optimisation - yield the smaller items
263
                # immediately rather than pushing everything on/off the
264
                # heaps. Applies to both internal nodes and leafnodes.
265
                if self_pending[0][0] < basis_pending[0][0]:
266
                    # expand self
267
                    prefix, node, path = heapq.heappop(self_pending)
268
                    if check_excluded(path):
269
                        continue
270
                    if type(node) == str:
271
                        # a value
272
                        yield (tuple(prefix.split('\x00')), None, node)
273
                    else:
274
                        process_node(prefix, node, path, self, self_pending)
275
                        continue
276
                elif self_pending[0][0] > basis_pending[0][0]:
277
                    # expand basis
278
                    prefix, node, path = heapq.heappop(basis_pending)
279
                    if check_excluded(path):
280
                        continue
281
                    if type(node) == str:
282
                        # a value
283
                        yield (tuple(prefix.split('\x00')), node, None)
284
                    else:
285
                        process_node(prefix, node, path, basis, basis_pending)
286
                        continue
287
                else:
288
                    # common prefix: possibly expand both
289
                    if type(self_pending[0][1]) != str:
290
                        # process next self
291
                        read_self = True
292
                    else:
293
                        read_self = False
294
                    if type(basis_pending[0][1]) != str:
295
                        # process next basis
296
                        read_basis = True
297
                    else:
298
                        read_basis = False
299
                    if not read_self and not read_basis:
300
                        # compare a common value
301
                        self_details = heapq.heappop(self_pending)
302
                        basis_details = heapq.heappop(basis_pending)
303
                        if self_details[1] != basis_details[1]:
304
                            yield (tuple(self_details[0].split('\x00')),
305
                                basis_details[1], self_details[1])
306
                        continue
3735.2.32 by Robert Collins
Activate test for common node skipping. - 50 times performance improvement.
307
                    # At least one side wasn't a string.
308
                    if (self._node_key(self_pending[0][1]) ==
309
                        self._node_key(basis_pending[0][1])):
310
                        # Identical pointers, skip (and don't bother adding to
311
                        # excluded, it won't turn up again.
312
                        heapq.heappop(self_pending)
313
                        heapq.heappop(basis_pending)
314
                        continue
315
                    # Now we need to expand this node before we can continue
3735.2.31 by Robert Collins
CHKMap.iter_changes
316
                    if read_self:
317
                        prefix, node, path = heapq.heappop(self_pending)
318
                        if check_excluded(path):
319
                            continue
320
                        process_node(prefix, node, path, self, self_pending)
321
                    if read_basis:
322
                        prefix, node, path = heapq.heappop(basis_pending)
323
                        if check_excluded(path):
324
                            continue
325
                        process_node(prefix, node, path, basis, basis_pending)
3735.2.32 by Robert Collins
Activate test for common node skipping. - 50 times performance improvement.
326
        # print loop_counter
3735.2.30 by Robert Collins
Start iter_changes between CHKMap instances.
327
3735.2.9 by Robert Collins
Get a working chk_map using inventory implementation bootstrapped.
328
    def iteritems(self, key_filter=None):
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
329
        """Iterate over the entire CHKMap's contents."""
330
        self._ensure_root()
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
331
        return self._root_node.iteritems(self._store, key_filter=key_filter)
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
332
3735.2.12 by Robert Collins
Implement commit-via-deltas for split inventory repositories.
333
    def key(self):
334
        """Return the key for this map."""
335
        if isinstance(self._root_node, tuple):
336
            return self._root_node
337
        else:
338
            return self._root_node._key
339
3735.2.17 by Robert Collins
Cache node length to avoid full iteration on __len__ calls.
340
    def __len__(self):
341
        self._ensure_root()
342
        return len(self._root_node)
343
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
344
    def map(self, key, value):
345
        """Map a key tuple to value."""
346
        # Need a root object.
347
        self._ensure_root()
348
        prefix, node_details = self._root_node.map(self._store, key, value)
349
        if len(node_details) == 1:
350
            self._root_node = node_details[0][1]
351
        else:
3735.9.5 by John Arbash Meinel
Don't allow an InternalNode to add a key that doesn't fit.
352
            self._root_node = InternalNode(prefix)
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
353
            self._root_node.set_maximum_size(node_details[0][1].maximum_size)
354
            self._root_node._key_width = node_details[0][1]._key_width
355
            for split, node in node_details:
356
                self._root_node.add_node(split, node)
357
3735.2.31 by Robert Collins
CHKMap.iter_changes
358
    def _node_key(self, node):
359
        """Get the key for a node whether its a tuple o r node."""
360
        if type(node) == tuple:
361
            return node
362
        else:
363
            return node._key
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
364
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
365
    def unmap(self, key):
366
        """remove key from the map."""
367
        self._ensure_root()
3735.11.3 by John Arbash Meinel
At the end of unmap() see if children can be packed into a single Leaf.
368
        unmapped = self._root_node.unmap(self._store, key)
369
        self._root_node = unmapped
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
370
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
371
    def _save(self):
372
        """Save the map completely.
373
374
        :return: The key of the root node.
375
        """
376
        if type(self._root_node) == tuple:
377
            # Already saved.
378
            return self._root_node
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
379
        keys = list(self._root_node.serialise(self._store))
380
        return keys[-1]
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
381
382
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
383
class Node(object):
384
    """Base class defining the protocol for CHK Map nodes."""
385
386
    def __init__(self, key_width=1):
387
        """Create a node.
388
389
        :param key_width: The width of keys for this node.
390
        """
391
        self._key = None
392
        # Current number of elements
393
        self._len = 0
394
        self._maximum_size = 0
395
        self._key_width = 1
396
        # current size in bytes
397
        self._size = 0
398
        # The pointers/values this node has - meaning defined by child classes.
399
        self._items = {}
400
3735.9.4 by John Arbash Meinel
Some small cleanups, and fix _dump_tree to handle in-progress nodes.
401
    def __repr__(self):
402
        items_str = sorted(self._items)
403
        if len(items_str) > 20:
404
            items_str = items_str[16] + '...]'
405
        return '%s(key:%s len:%s size:%s max:%s items:%s)' % (
406
            self.__class__.__name__, self._key, self._len, self._size,
407
            self._maximum_size, items_str)
408
3735.2.38 by Robert Collins
Sufficient fixes to allow bzr-search to index a dev3 format repository.
409
    def key(self):
410
        return self._key
411
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
412
    def __len__(self):
413
        return self._len
414
415
    @property
416
    def maximum_size(self):
417
        """What is the upper limit for adding references to a node."""
418
        return self._maximum_size
419
420
    def set_maximum_size(self, new_size):
421
        """Set the size threshold for nodes.
422
423
        :param new_size: The size at which no data is added to a node. 0 for
424
            unlimited.
425
        """
426
        self._maximum_size = new_size
427
428
429
class LeafNode(Node):
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
430
    """A node containing actual key:value pairs.
3735.11.1 by John Arbash Meinel
Clean up some trailing whitespace.
431
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
432
    :ivar _items: A dict of key->value items. The key is in tuple form.
433
    """
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
434
435
    def __init__(self):
436
        Node.__init__(self)
437
        # The size of a leaf node with default values and no children.
438
        self._size = 12
439
440
    def _current_size(self):
441
        """Answer the current serialised size of this node."""
442
        return (self._size + len(str(self._len)) + len(str(self._key_width)) +
443
            len(str(self._maximum_size)))
444
445
    @classmethod
446
    def deserialise(klass, bytes, key):
447
        """Deserialise bytes, with key key, into a LeafNode.
448
449
        :param bytes: The bytes of the node.
450
        :param key: The key that the serialised node has.
451
        """
452
        result = LeafNode()
453
        lines = bytes.splitlines()
454
        items = {}
455
        if lines[0] != 'chkleaf:':
456
            raise ValueError("not a serialised leaf node: %r" % bytes)
457
        maximum_size = int(lines[1])
458
        width = int(lines[2])
459
        length = int(lines[3])
460
        for line in lines[4:]:
3735.2.25 by Robert Collins
CHKInventory core tests passing.
461
            elements = line.split('\x00', width)
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
462
            items[tuple(elements[:-1])] = elements[-1]
463
        if len(items) != length:
464
            raise AssertionError("item count mismatch")
465
        result._items = items
466
        result._len = length
467
        result._maximum_size = maximum_size
468
        result._key = key
469
        result._key_width = width
470
        result._size = len(bytes)
471
        return result
472
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
473
    def iteritems(self, store, key_filter=None):
3735.2.43 by Robert Collins
Teach CHKMap how to iter items in 2-tuple keyspaces.
474
        """Iterate over items in the node.
475
476
        :param key_filter: A filter to apply to the node. It should be a
477
            list/set/dict or similar repeatedly iterable container.
478
        """
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
479
        if key_filter is not None:
3735.2.43 by Robert Collins
Teach CHKMap how to iter items in 2-tuple keyspaces.
480
            # Adjust the filter - short elements go to a prefix filter. Would this
481
            # be cleaner explicitly? That would be no harder for InternalNode..
482
            # XXX: perhaps defaultdict? Profiling<rinse and repeat>
483
            filters = {}
484
            for key in key_filter:
485
                length_filter = filters.setdefault(len(key), set())
486
                length_filter.add(key)
487
            filters = filters.items()
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
488
            for item in self._items.iteritems():
3735.2.43 by Robert Collins
Teach CHKMap how to iter items in 2-tuple keyspaces.
489
                for length, length_filter in filters:
490
                    if item[0][:length] in length_filter:
491
                        yield item
492
                        break
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
493
        else:
494
            for item in self._items.iteritems():
495
                yield item
496
3735.9.4 by John Arbash Meinel
Some small cleanups, and fix _dump_tree to handle in-progress nodes.
497
    def _key_value_len(self, key, value):
498
        # TODO: Should probably be done without actually joining the key, but
499
        #       then that can be done via the C extension
500
        return 2 + len('\x00'.join(key)) + len(value)
501
3735.11.13 by John Arbash Meinel
Refactor the LeafNode.map() code so we can do _check_remap more cheaply.
502
    def _map_no_split(self, key, value):
503
        """Map a key to a value.
504
505
        This assumes either the key does not already exist, or you have already
506
        removed its size and length from self.
507
508
        :return: True if adding this node should cause us to split.
509
        """
510
        self._items[key] = value
511
        self._size += self._key_value_len(key, value)
512
        self._len += 1
513
        if (self._len > 1
514
            and self._maximum_size
515
            and self._current_size() > self._maximum_size):
516
            return True
517
        return False
518
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
519
    def map(self, store, key, value):
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
520
        """Map key to value."""
521
        if key in self._items:
3735.9.4 by John Arbash Meinel
Some small cleanups, and fix _dump_tree to handle in-progress nodes.
522
            self._size -= self._key_value_len(key, self._items[key])
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
523
            self._len -= 1
524
        self._key = None
3735.11.13 by John Arbash Meinel
Refactor the LeafNode.map() code so we can do _check_remap more cheaply.
525
        if self._map_no_split(key, value):
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
526
            common_prefix = self.unique_serialised_prefix()
527
            split_at = len(common_prefix) + 1
528
            result = {}
529
            for key, value in self._items.iteritems():
530
                serialised_key = self._serialised_key(key)
531
                prefix = serialised_key[:split_at]
3735.11.7 by John Arbash Meinel
Turns out that the LeafNode split code needs to be aware of having a key
532
                # TODO: Generally only 1 key can be exactly the right length,
533
                #       which means we can only have 1 key in the node pointed
534
                #       at by the 'prefix\0' key. We might want to consider
535
                #       folding it into the containing InternalNode rather than
536
                #       having a fixed length-1 node.
3735.11.8 by John Arbash Meinel
more comment
537
                #       Note this is probably not true for hash keys, as they
538
                #       may get a '\00' node anywhere, but won't have keys of
539
                #       different lengths.
3735.11.7 by John Arbash Meinel
Turns out that the LeafNode split code needs to be aware of having a key
540
                if len(prefix) < split_at:
541
                    prefix += '\x00'*(split_at - len(prefix))
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
542
                if prefix not in result:
543
                    node = LeafNode()
544
                    node.set_maximum_size(self._maximum_size)
545
                    node._key_width = self._key_width
546
                    result[prefix] = node
547
                else:
548
                    node = result[prefix]
549
                node.map(store, key, value)
550
            return common_prefix, result.items()
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
551
        else:
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
552
            return self.unique_serialised_prefix(), [("", self)]
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
553
554
    def serialise(self, store):
555
        """Serialise the tree to store.
556
557
        :param store: A VersionedFiles honouring the CHK extensions.
558
        :return: An iterable of the keys inserted by this operation.
559
        """
560
        lines = ["chkleaf:\n"]
561
        lines.append("%d\n" % self._maximum_size)
562
        lines.append("%d\n" % self._key_width)
563
        lines.append("%d\n" % self._len)
564
        for key, value in sorted(self._items.items()):
565
            lines.append("%s\x00%s\n" % ('\x00'.join(key), value))
566
        sha1, _, _ = store.add_lines((None,), (), lines)
567
        self._key = ("sha1:" + sha1,)
3735.2.63 by Robert Collins
Divert writes into the CHK page cache as well.
568
        _page_cache.add(self._key, ''.join(lines))
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
569
        return [self._key]
570
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
571
    def _serialised_key(self, key):
572
        """Return the serialised key for key in this node."""
573
        return '\x00'.join(key)
574
3735.2.26 by Robert Collins
CHKInventory migrated to new CHKMap code.
575
    def refs(self):
576
        """Return the references to other CHK's held by this node."""
577
        return []
578
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
579
    def unique_serialised_prefix(self):
580
        """Return the unique key prefix for this node.
581
582
        :return: A bytestring of the longest serialised key prefix that is
583
            unique within this node.
584
        """
585
        # may want to cache this eventually :- but wait for enough
586
        # functionality to profile.
587
        keys = list(self._items.keys())
588
        if not keys:
589
            return ""
590
        current_prefix = self._serialised_key(keys.pop(-1))
591
        while current_prefix and keys:
592
            next_key = self._serialised_key(keys.pop(-1))
593
            for pos, (left, right) in enumerate(zip(current_prefix, next_key)):
594
                if left != right:
595
                    pos -= 1
596
                    break
597
            current_prefix = current_prefix[:pos + 1]
598
        return current_prefix
599
600
    def unmap(self, store, key):
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
601
        """Unmap key from the node."""
602
        self._size -= 2 + len('\x00'.join(key)) + len(self._items[key])
603
        self._len -= 1
604
        del self._items[key]
605
        self._key = None
606
        return self
607
608
609
class InternalNode(Node):
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
610
    """A node that contains references to other nodes.
3735.11.1 by John Arbash Meinel
Clean up some trailing whitespace.
611
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
612
    An InternalNode is responsible for mapping serialised key prefixes to child
613
    nodes. It is greedy - it will defer splitting itself as long as possible.
614
    """
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
615
3735.9.5 by John Arbash Meinel
Don't allow an InternalNode to add a key that doesn't fit.
616
    def __init__(self, prefix=''):
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
617
        Node.__init__(self)
618
        # The size of an internalnode with default values and no children.
619
        # self._size = 12
620
        # How many octets key prefixes within this node are.
621
        self._node_width = 0
3735.9.5 by John Arbash Meinel
Don't allow an InternalNode to add a key that doesn't fit.
622
        self._prefix = prefix
623
624
    def __repr__(self):
625
        items_str = sorted(self._items)
626
        if len(items_str) > 20:
627
            items_str = items_str[16] + '...]'
628
        return '%s(key:%s len:%s size:%s max:%s prefix:%s items:%s)' % (
629
            self.__class__.__name__, self._key, self._len, self._size,
630
            self._maximum_size, self._prefix, items_str)
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
631
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
632
    def add_node(self, prefix, node):
633
        """Add a child node with prefix prefix, and node node.
634
635
        :param prefix: The serialised key prefix for node.
636
        :param node: The node being added.
637
        """
3735.9.5 by John Arbash Meinel
Don't allow an InternalNode to add a key that doesn't fit.
638
        assert self._prefix is not None
639
        assert prefix.startswith(self._prefix)
640
        assert len(prefix) == len(self._prefix) + 1
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
641
        self._len += len(node)
642
        if not len(self._items):
643
            self._node_width = len(prefix)
3735.9.11 by John Arbash Meinel
Handle when an InternalNode decides it needs to split.
644
        assert self._node_width == len(self._prefix) + 1
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
645
        self._items[prefix] = node
646
        self._key = None
647
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
648
    def _current_size(self):
649
        """Answer the current serialised size of this node."""
650
        return (self._size + len(str(self._len)) + len(str(self._key_width)) +
651
            len(str(self._maximum_size)))
652
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
653
    @classmethod
654
    def deserialise(klass, bytes, key):
3735.2.25 by Robert Collins
CHKInventory core tests passing.
655
        """Deserialise bytes to an InternalNode, with key key.
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
656
657
        :param bytes: The bytes of the node.
658
        :param key: The key that the serialised node has.
659
        :return: An InternalNode instance.
660
        """
661
        result = InternalNode()
662
        lines = bytes.splitlines()
663
        items = {}
664
        if lines[0] != 'chknode:':
665
            raise ValueError("not a serialised internal node: %r" % bytes)
666
        maximum_size = int(lines[1])
667
        width = int(lines[2])
668
        length = int(lines[3])
669
        for line in lines[4:]:
670
            prefix, flat_key = line.rsplit('\x00', 1)
671
            items[prefix] = (flat_key,)
672
        result._items = items
673
        result._len = length
674
        result._maximum_size = maximum_size
675
        result._key = key
676
        result._key_width = width
677
        result._size = len(bytes)
678
        result._node_width = len(prefix)
3735.9.5 by John Arbash Meinel
Don't allow an InternalNode to add a key that doesn't fit.
679
        result._prefix = result.unique_serialised_prefix()
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
680
        return result
681
682
    def iteritems(self, store, key_filter=None):
683
        for node in self._iter_nodes(store, key_filter=key_filter):
684
            for item in node.iteritems(store, key_filter=key_filter):
685
                yield item
686
687
    def _iter_nodes(self, store, key_filter=None):
688
        """Iterate over node objects which match key_filter.
689
690
        :param store: A store to use for accessing content.
691
        :param key_filter: A key filter to filter nodes. Only nodes that might
692
            contain a key in key_filter will be returned.
693
        :return: An iterable of nodes.
694
        """
695
        nodes = []
3735.2.31 by Robert Collins
CHKMap.iter_changes
696
        keys = {}
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
697
        if key_filter is None:
3735.2.31 by Robert Collins
CHKMap.iter_changes
698
            for prefix, node in self._items.iteritems():
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
699
                if type(node) == tuple:
3735.2.31 by Robert Collins
CHKMap.iter_changes
700
                    keys[node] = prefix
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
701
                else:
702
                    nodes.append(node)
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
703
        else:
3735.2.43 by Robert Collins
Teach CHKMap how to iter items in 2-tuple keyspaces.
704
            # XXX defaultdict ?
705
            length_filters = {}
706
            for key in key_filter:
707
                serialised_key = self._serialised_prefix_filter(key)
708
                length_filter = length_filters.setdefault(len(serialised_key),
709
                    set())
710
                length_filter.add(serialised_key)
711
            length_filters = length_filters.items()
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
712
            for prefix, node in self._items.iteritems():
3735.2.43 by Robert Collins
Teach CHKMap how to iter items in 2-tuple keyspaces.
713
                for length, length_filter in length_filters:
714
                    if prefix[:length] in length_filter:
715
                        if type(node) == tuple:
716
                            keys[node] = prefix
717
                        else:
718
                            nodes.append(node)
719
                        break
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
720
        if keys:
3735.2.62 by Robert Collins
Create a rudimentary CHK page cache.
721
            # Look in the page cache for some more bytes
722
            found_keys = set()
723
            for key in keys:
724
                try:
725
                    bytes = _page_cache[key]
726
                except KeyError:
727
                    continue
728
                else:
729
                    node = _deserialise(bytes, key)
730
                    nodes.append(node)
731
                    self._items[keys[key]] = node
732
                    found_keys.add(key)
733
            for key in found_keys:
734
                del keys[key]
735
        if keys:
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
736
            # demand load some pages.
737
            stream = store.get_record_stream(keys, 'unordered', True)
738
            for record in stream:
3735.2.62 by Robert Collins
Create a rudimentary CHK page cache.
739
                bytes = record.get_bytes_as('fulltext')
740
                node = _deserialise(bytes, record.key)
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
741
                nodes.append(node)
3735.2.31 by Robert Collins
CHKMap.iter_changes
742
                self._items[keys[record.key]] = node
3735.2.62 by Robert Collins
Create a rudimentary CHK page cache.
743
                _page_cache.add(record.key, bytes)
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
744
        return nodes
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
745
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
746
    def map(self, store, key, value):
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
747
        """Map key to value."""
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
748
        if not len(self._items):
749
            raise AssertionError("cant map in an empty InternalNode.")
3735.9.5 by John Arbash Meinel
Don't allow an InternalNode to add a key that doesn't fit.
750
        serialised_key = self._serialised_key(key)
3735.9.11 by John Arbash Meinel
Handle when an InternalNode decides it needs to split.
751
        assert self._node_width == len(self._prefix) + 1
3735.9.5 by John Arbash Meinel
Don't allow an InternalNode to add a key that doesn't fit.
752
        if not serialised_key.startswith(self._prefix):
3735.9.11 by John Arbash Meinel
Handle when an InternalNode decides it needs to split.
753
            # This key doesn't fit in this index, so we need to split at the
754
            # point where it would fit.
755
            # XXX: Do we need the serialised_key in its maximum length?
3735.9.5 by John Arbash Meinel
Don't allow an InternalNode to add a key that doesn't fit.
756
            new_prefix = self.unique_serialised_prefix(serialised_key)
757
            new_parent = InternalNode(new_prefix)
758
            new_parent.set_maximum_size(self._maximum_size)
759
            new_parent._key_width = self._key_width
760
            new_parent.add_node(self._prefix[:len(new_prefix)+1], self)
3735.9.11 by John Arbash Meinel
Handle when an InternalNode decides it needs to split.
761
            assert new_parent._node_width == len(new_parent._prefix) + 1
3735.9.5 by John Arbash Meinel
Don't allow an InternalNode to add a key that doesn't fit.
762
            return new_parent.map(store, key, value)
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
763
        children = self._iter_nodes(store, key_filter=[key])
764
        if children:
765
            child = children[0]
3735.9.11 by John Arbash Meinel
Handle when an InternalNode decides it needs to split.
766
            # if isinstance(child, InternalNode):
767
            #     child_prefix = child._prefix
768
            #     child_ser_key = child._serialised_key(key)
769
            #     if not child_ser_key.startswith(child_prefix):
770
            #         import pdb; pdb.set_trace()
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
771
        else:
772
            # new child needed:
773
            child = self._new_child(serialised_key, LeafNode)
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
774
        old_len = len(child)
3735.11.11 by John Arbash Meinel
Add logic to map() so that it can also collapse when necessary.
775
        if isinstance(child, LeafNode):
776
            old_size = child._current_size()
777
        else:
778
            old_size = None
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
779
        prefix, node_details = child.map(store, key, value)
780
        if len(node_details) == 1:
3735.9.11 by John Arbash Meinel
Handle when an InternalNode decides it needs to split.
781
            # child may have shrunk, or might be a new node
782
            child = node_details[0][1]
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
783
            self._len = self._len - old_len + len(child)
784
            self._items[serialised_key] = child
3735.2.29 by Robert Collins
Untested code is broken code.
785
            self._key = None
3735.11.11 by John Arbash Meinel
Add logic to map() so that it can also collapse when necessary.
786
            new_node = self
787
            if (isinstance(child, LeafNode)
788
                and (old_size is None or child._current_size() < old_size)):
789
                # The old node was an InternalNode which means it has now
790
                # collapsed, so we need to check if it will chain to a collapse
791
                # at this level. Or the LeafNode has shrunk in size, so we need
792
                # to check that as well.
793
                new_node = self._check_remap(store)
794
            return new_node.unique_serialised_prefix(), [("", new_node)]
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
795
        # child has overflown - create a new intermediate node.
796
        # XXX: This is where we might want to try and expand our depth
797
        # to refer to more bytes of every child (which would give us
798
        # multiple pointers to child nodes, but less intermediate nodes)
3735.9.5 by John Arbash Meinel
Don't allow an InternalNode to add a key that doesn't fit.
799
        # TODO: Is this mapped as serialised_key or as prefix?
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
800
        child = self._new_child(serialised_key, InternalNode)
3735.9.5 by John Arbash Meinel
Don't allow an InternalNode to add a key that doesn't fit.
801
        child._prefix = prefix
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
802
        for split, node in node_details:
803
            child.add_node(split, node)
804
        self._len = self._len - old_len + len(child)
3735.2.29 by Robert Collins
Untested code is broken code.
805
        self._key = None
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
806
        return self.unique_serialised_prefix(), [("", self)]
807
808
    def _new_child(self, serialised_key, klass):
809
        """Create a new child node of type klass."""
810
        child = klass()
811
        child.set_maximum_size(self._maximum_size)
812
        child._key_width = self._key_width
813
        self._items[serialised_key] = child
814
        return child
815
816
    def serialise(self, store):
817
        """Serialise the node to store.
818
819
        :param store: A VersionedFiles honouring the CHK extensions.
820
        :return: An iterable of the keys inserted by this operation.
821
        """
822
        for node in self._items.itervalues():
823
            if type(node) == tuple:
824
                # Never deserialised.
825
                continue
826
            if node._key is not None:
827
                # Never altered
828
                continue
829
            for key in node.serialise(store):
830
                yield key
831
        lines = ["chknode:\n"]
832
        lines.append("%d\n" % self._maximum_size)
833
        lines.append("%d\n" % self._key_width)
834
        lines.append("%d\n" % self._len)
835
        for prefix, node in sorted(self._items.items()):
836
            if type(node) == tuple:
837
                key = node[0]
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
838
            else:
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
839
                key = node._key[0]
840
            lines.append("%s\x00%s\n" % (prefix, key))
841
        sha1, _, _ = store.add_lines((None,), (), lines)
842
        self._key = ("sha1:" + sha1,)
3735.2.63 by Robert Collins
Divert writes into the CHK page cache as well.
843
        _page_cache.add(self._key, ''.join(lines))
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
844
        yield self._key
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
845
846
    def _serialised_key(self, key):
847
        """Return the serialised key for key in this node."""
848
        return ('\x00'.join(key) + '\x00'*self._node_width)[:self._node_width]
849
3735.2.43 by Robert Collins
Teach CHKMap how to iter items in 2-tuple keyspaces.
850
    def _serialised_prefix_filter(self, key):
851
        """Serialise key for use as a prefix filter in iteritems."""
852
        if len(key) == self._key_width:
853
            return self._serialised_key(key)
854
        return '\x00'.join(key)[:self._node_width]
855
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
856
    def _split(self, offset):
857
        """Split this node into smaller nodes starting at offset.
858
859
        :param offset: The offset to start the new child nodes at.
860
        :return: An iterable of (prefix, node) tuples. prefix is a byte
861
            prefix for reaching node.
862
        """
863
        if offset >= self._node_width:
864
            for node in self._items.values():
865
                for result in node._split(offset):
866
                    yield result
867
            return
868
        for key, node in self._items.items():
869
            pass
870
3735.2.26 by Robert Collins
CHKInventory migrated to new CHKMap code.
871
    def refs(self):
872
        """Return the references to other CHK's held by this node."""
873
        if self._key is None:
874
            raise AssertionError("unserialised nodes have no refs.")
875
        refs = []
876
        for value in self._items.itervalues():
877
            if type(value) == tuple:
878
                refs.append(value)
879
            else:
880
                refs.append(value.key())
881
        return refs
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
882
3735.9.5 by John Arbash Meinel
Don't allow an InternalNode to add a key that doesn't fit.
883
    def unique_serialised_prefix(self, extra_key=None):
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
884
        """Return the unique key prefix for this node.
885
886
        :return: A bytestring of the longest serialised key prefix that is
887
            unique within this node.
888
        """
889
        # may want to cache this eventually :- but wait for enough
890
        # functionality to profile.
891
        keys = list(self._items.keys())
3735.9.5 by John Arbash Meinel
Don't allow an InternalNode to add a key that doesn't fit.
892
        if extra_key is not None:
893
            keys.append(extra_key)
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
894
        if not keys:
895
            return ""
896
        current_prefix = keys.pop(-1)
897
        while current_prefix and keys:
898
            next_key = keys.pop(-1)
899
            for pos, (left, right) in enumerate(zip(current_prefix, next_key)):
900
                if left != right:
901
                    pos -= 1
902
                    break
903
            current_prefix = current_prefix[:pos + 1]
904
        return current_prefix
905
906
    def unmap(self, store, key):
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
907
        """Remove key from this node and it's children."""
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
908
        if not len(self._items):
909
            raise AssertionError("cant unmap in an empty InternalNode.")
910
        children = self._iter_nodes(store, key_filter=[key])
911
        if children:
912
            child = children[0]
913
        else:
914
            raise KeyError(key)
915
        self._len -= 1
916
        unmapped = child.unmap(store, key)
3735.11.3 by John Arbash Meinel
At the end of unmap() see if children can be packed into a single Leaf.
917
        self._key = None
918
        serialised_key = self._serialised_key(key)
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
919
        if len(unmapped) == 0:
920
            # All child nodes are gone, remove the child:
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
921
            del self._items[serialised_key]
3735.11.3 by John Arbash Meinel
At the end of unmap() see if children can be packed into a single Leaf.
922
            unmapped = None
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
923
        else:
924
            # Stash the returned node
925
            self._items[serialised_key] = unmapped
3735.2.23 by Robert Collins
Test unmapping with one child left but multiple keys.
926
        if len(self._items) == 1:
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
927
            # this node is no longer needed:
928
            return self._items.values()[0]
3735.11.10 by John Arbash Meinel
Change how _check_remap works so it doesn't have to load all keys.
929
        if isinstance(unmapped, InternalNode):
930
            return self
931
        return self._check_remap(store)
932
933
    def _check_remap(self, store):
3735.11.11 by John Arbash Meinel
Add logic to map() so that it can also collapse when necessary.
934
        """Check if all keys contained by children fit in a single LeafNode.
935
936
        :param store: A store to use for reading more nodes
937
        :return: Either self, or a new LeafNode which should replace self.
938
        """
3735.11.10 by John Arbash Meinel
Change how _check_remap works so it doesn't have to load all keys.
939
        # Logic for how we determine when we need to rebuild
3735.11.3 by John Arbash Meinel
At the end of unmap() see if children can be packed into a single Leaf.
940
        # 1) Implicitly unmap() is removing a key which means that the child
941
        #    nodes are going to be shrinking by some extent.
942
        # 2) If all children are LeafNodes, it is possible that they could be
943
        #    combined into a single LeafNode, which can then completely replace
944
        #    this internal node with a single LeafNode
945
        # 3) If *one* child is an InternalNode, we assume it has already done
946
        #    all the work to determine that its children cannot collapse, and
947
        #    we can then assume that those nodes *plus* the current nodes don't
948
        #    have a chance of collapsing either.
949
        #    So a very cheap check is to just say if 'unmapped' is an
950
        #    InternalNode, we don't have to check further.
3735.11.10 by John Arbash Meinel
Change how _check_remap works so it doesn't have to load all keys.
951
3735.11.3 by John Arbash Meinel
At the end of unmap() see if children can be packed into a single Leaf.
952
        # TODO: Another alternative is to check the total size of all known
953
        #       LeafNodes. If there is some formula we can use to determine the
954
        #       final size without actually having to read in any more
955
        #       children, it would be nice to have. However, we have to be
956
        #       careful with stuff like nodes that pull out the common prefix
957
        #       of each key, as adding a new key can change the common prefix
958
        #       and cause size changes greater than the length of one key.
959
        #       So for now, we just add everything to a new Leaf until it
960
        #       splits, as we know that will give the right answer
961
        new_leaf = LeafNode()
962
        new_leaf.set_maximum_size(self._maximum_size)
963
        new_leaf._key_width = self._key_width
3735.11.10 by John Arbash Meinel
Change how _check_remap works so it doesn't have to load all keys.
964
        keys = {}
965
        # There is some overlap with _iter_nodes here, but not a lot, and it
966
        # allows us to do quick evaluation without paging everything in
967
        for prefix, node in self._items.iteritems():
968
            if type(node) == tuple:
969
                keys[node] = prefix
970
            else:
971
                if isinstance(node, InternalNode):
972
                    # Without looking at any leaf nodes, we are sure
973
                    return self
974
                for key, value in node._items.iteritems():
3735.11.13 by John Arbash Meinel
Refactor the LeafNode.map() code so we can do _check_remap more cheaply.
975
                    if new_leaf._map_no_split(key, value):
976
                        # Adding this key would cause a split, so we know we
977
                        # don't need to collapse
3735.11.10 by John Arbash Meinel
Change how _check_remap works so it doesn't have to load all keys.
978
                        return self
979
        # So far, everything fits. Page in the rest of the nodes, and see if it
980
        # holds true.
981
        if keys:
3735.11.12 by John Arbash Meinel
Add a TODO describing a possible optimization.
982
            # TODO: Consider looping over a limited set of keys (like 25 or so
983
            #       at a time). If we have to read more than 25 we almost
984
            #       certainly won't fit them all into a single new LeafNode, so
985
            #       reading the extra nodes is a waste.
986
            #       This will probably matter more with hash serialised keys,
987
            #       as we will get InternalNodes with more references.
988
            #       The other argument is that unmap() is uncommon, so we don't
989
            #       need to optimize it. But map() with a slightly shorter
990
            #       value may happen a lot.
3735.11.10 by John Arbash Meinel
Change how _check_remap works so it doesn't have to load all keys.
991
            stream = store.get_record_stream(keys, 'unordered', True)
992
            nodes = []
993
            # Fully consume the stream, even if we could determine that we
994
            # don't need to continue. We requested the bytes, we may as well
995
            # use them
996
            for record in stream:
997
                node = _deserialise(record.get_bytes_as('fulltext'), record.key)
998
                self._items[keys[record.key]] = node
999
                nodes.append(node)
1000
            for node in nodes:
1001
                if isinstance(node, InternalNode):
1002
                    # We know we won't fit
1003
                    return self
1004
                for key, value in node._items.iteritems():
3735.11.13 by John Arbash Meinel
Refactor the LeafNode.map() code so we can do _check_remap more cheaply.
1005
                    if new_leaf._map_no_split(key, value):
3735.11.10 by John Arbash Meinel
Change how _check_remap works so it doesn't have to load all keys.
1006
                        return self
1007
3735.11.3 by John Arbash Meinel
At the end of unmap() see if children can be packed into a single Leaf.
1008
        # We have gone to every child, and everything fits in a single leaf
1009
        # node, we no longer need this internal node
1010
        return new_leaf
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
1011
1012
3735.2.16 by Robert Collins
Untested extensions to support repodetails
1013
def _deserialise(bytes, key):
1014
    """Helper for repositorydetails - convert bytes to a node."""
3735.2.24 by Robert Collins
test_chk_map tests all passing.
1015
    if bytes.startswith("chkleaf:\n"):
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
1016
        return LeafNode.deserialise(bytes, key)
1017
    elif bytes.startswith("chknode:\n"):
1018
        return InternalNode.deserialise(bytes, key)
3735.2.16 by Robert Collins
Untested extensions to support repodetails
1019
    else:
1020
        raise AssertionError("Unknown node type.")
3735.9.1 by John Arbash Meinel
Start working on an iter_interesting_nodes, which can find nodes to transmit
1021
1022
3735.9.17 by John Arbash Meinel
Pass around a progress bar and switch to using an adapter.
1023
def _find_children_info(store, interesting_keys, uninteresting_keys,
3735.9.19 by John Arbash Meinel
Refactor iter_interesting a little bit.
1024
                        adapter, pb):
3735.9.7 by John Arbash Meinel
Cleanup pass.
1025
    """Read the associated records, and determine what is interesting."""
1026
    uninteresting_keys = set(uninteresting_keys)
1027
    chks_to_read = uninteresting_keys.union(interesting_keys)
1028
    next_uninteresting = set()
1029
    next_interesting = set()
1030
    uninteresting_items = set()
1031
    interesting_items = set()
1032
    interesting_records = []
3735.9.14 by John Arbash Meinel
Start using the iter_interesting_nodes.
1033
    # records_read = set()
3735.9.7 by John Arbash Meinel
Cleanup pass.
1034
    for record in store.get_record_stream(chks_to_read, 'unordered', True):
3735.9.14 by John Arbash Meinel
Start using the iter_interesting_nodes.
1035
        # records_read.add(record.key())
3735.9.17 by John Arbash Meinel
Pass around a progress bar and switch to using an adapter.
1036
        if pb is not None:
1037
            pb.tick()
1038
        if record.storage_kind != 'fulltext':
1039
            bytes = adapter.get_bytes(record,
1040
                        record.get_bytes_as(record.storage_kind))
1041
        else:
1042
            bytes = record.get_bytes_as('fulltext')
1043
        node = _deserialise(bytes, record.key)
3735.9.7 by John Arbash Meinel
Cleanup pass.
1044
        if record.key in uninteresting_keys:
1045
            if isinstance(node, InternalNode):
3735.9.14 by John Arbash Meinel
Start using the iter_interesting_nodes.
1046
                next_uninteresting.update(node.refs())
3735.9.7 by John Arbash Meinel
Cleanup pass.
1047
            else:
3735.9.14 by John Arbash Meinel
Start using the iter_interesting_nodes.
1048
                # We know we are at a LeafNode, so we can pass None for the
1049
                # store
1050
                uninteresting_items.update(node.iteritems(None))
3735.9.7 by John Arbash Meinel
Cleanup pass.
1051
        else:
1052
            interesting_records.append(record)
1053
            if isinstance(node, InternalNode):
3735.9.14 by John Arbash Meinel
Start using the iter_interesting_nodes.
1054
                next_interesting.update(node.refs())
3735.9.7 by John Arbash Meinel
Cleanup pass.
1055
            else:
3735.9.14 by John Arbash Meinel
Start using the iter_interesting_nodes.
1056
                interesting_items.update(node.iteritems(None))
1057
    # TODO: Filter out records that have already been read, as node splitting
1058
    #       can cause us to reference the same nodes via shorter and longer
1059
    #       paths
3735.9.7 by John Arbash Meinel
Cleanup pass.
1060
    return (next_uninteresting, uninteresting_items,
1061
            next_interesting, interesting_records, interesting_items)
1062
1063
3735.9.19 by John Arbash Meinel
Refactor iter_interesting a little bit.
1064
def _find_all_uninteresting(store, interesting_root_keys,
1065
                            uninteresting_root_keys, adapter, pb):
1066
    """Determine the full set of uninteresting keys."""
1067
    # What about duplicates between interesting_root_keys and
1068
    # uninteresting_root_keys?
1069
    if not uninteresting_root_keys:
1070
        # Shortcut case. We know there is nothing uninteresting to filter out
1071
        # So we just let the rest of the algorithm do the work
1072
        # We know there is nothing uninteresting, and we didn't have to read
1073
        # any interesting records yet.
1074
        return (set(), set(), set(interesting_root_keys), [], set())
3735.9.7 by John Arbash Meinel
Cleanup pass.
1075
    all_uninteresting_chks = set(uninteresting_root_keys)
1076
    all_uninteresting_items = set()
1077
1078
    # First step, find the direct children of both the interesting and
1079
    # uninteresting set
1080
    (uninteresting_keys, uninteresting_items,
1081
     interesting_keys, interesting_records,
1082
     interesting_items) = _find_children_info(store, interesting_root_keys,
3735.9.17 by John Arbash Meinel
Pass around a progress bar and switch to using an adapter.
1083
                                              uninteresting_root_keys,
1084
                                              adapter=adapter, pb=pb)
3735.9.7 by John Arbash Meinel
Cleanup pass.
1085
    all_uninteresting_chks.update(uninteresting_keys)
1086
    all_uninteresting_items.update(uninteresting_items)
1087
    del uninteresting_items
1088
    # Note: Exact matches between interesting and uninteresting do not need
1089
    #       to be search further. Non-exact matches need to be searched in case
1090
    #       there is a future exact-match
1091
    uninteresting_keys.difference_update(interesting_keys)
1092
3735.9.6 by John Arbash Meinel
Add a first pass to the interesting search.
1093
    # Second, find the full set of uninteresting bits reachable by the
3735.9.1 by John Arbash Meinel
Start working on an iter_interesting_nodes, which can find nodes to transmit
1094
    # uninteresting roots
3735.9.7 by John Arbash Meinel
Cleanup pass.
1095
    chks_to_read = uninteresting_keys
3735.9.1 by John Arbash Meinel
Start working on an iter_interesting_nodes, which can find nodes to transmit
1096
    while chks_to_read:
1097
        next_chks = set()
3735.9.17 by John Arbash Meinel
Pass around a progress bar and switch to using an adapter.
1098
        for record in store.get_record_stream(chks_to_read, 'unordered', False):
3735.9.1 by John Arbash Meinel
Start working on an iter_interesting_nodes, which can find nodes to transmit
1099
            # TODO: Handle 'absent'
3735.9.17 by John Arbash Meinel
Pass around a progress bar and switch to using an adapter.
1100
            if pb is not None:
1101
                pb.tick()
1102
            if record.storage_kind != 'fulltext':
1103
                bytes = adapter.get_bytes(record,
1104
                            record.get_bytes_as(record.storage_kind))
1105
            else:
1106
                bytes = record.get_bytes_as('fulltext')
1107
            node = _deserialise(bytes, record.key)
3735.9.1 by John Arbash Meinel
Start working on an iter_interesting_nodes, which can find nodes to transmit
1108
            if isinstance(node, InternalNode):
1109
                # uninteresting_prefix_chks.update(node._items.iteritems())
1110
                chks = node._items.values()
1111
                # TODO: We remove the entries that are already in
1112
                #       uninteresting_chks ?
1113
                next_chks.update(chks)
3735.9.7 by John Arbash Meinel
Cleanup pass.
1114
                all_uninteresting_chks.update(chks)
3735.9.1 by John Arbash Meinel
Start working on an iter_interesting_nodes, which can find nodes to transmit
1115
            else:
3735.9.7 by John Arbash Meinel
Cleanup pass.
1116
                all_uninteresting_items.update(node._items.iteritems())
3735.9.1 by John Arbash Meinel
Start working on an iter_interesting_nodes, which can find nodes to transmit
1117
        chks_to_read = next_chks
3735.9.19 by John Arbash Meinel
Refactor iter_interesting a little bit.
1118
    return (all_uninteresting_chks, all_uninteresting_items,
1119
            interesting_keys, interesting_records, interesting_items)
1120
1121
1122
def iter_interesting_nodes(store, interesting_root_keys,
1123
                           uninteresting_root_keys, pb=None):
1124
    """Given root keys, find interesting nodes.
1125
1126
    Evaluate nodes referenced by interesting_root_keys. Ones that are also
1127
    referenced from uninteresting_root_keys are not considered interesting.
1128
1129
    :param interesting_root_keys: keys which should be part of the
1130
        "interesting" nodes (which will be yielded)
1131
    :param uninteresting_root_keys: keys which should be filtered out of the
1132
        result set.
1133
    :return: Yield
1134
        (interesting records, interesting chk's, interesting key:values)
1135
    """
1136
    # TODO: consider that it may be more memory efficient to use the 20-byte
1137
    #       sha1 string, rather than tuples of hexidecimal sha1 strings.
1138
1139
    # A way to adapt from the compressed texts back into fulltexts
1140
    # In a way, this seems like a layering inversion to have CHKMap know the
1141
    # details of versionedfile
1142
    adapter_class = versionedfile.adapter_registry.get(
1143
        ('knit-ft-gz', 'fulltext'))
1144
    adapter = adapter_class(store)
1145
1146
    (all_uninteresting_chks, all_uninteresting_items, interesting_keys,
1147
     interesting_records, interesting_items) = _find_all_uninteresting(store,
1148
        interesting_root_keys, uninteresting_root_keys, adapter, pb)
3735.9.7 by John Arbash Meinel
Cleanup pass.
1149
1150
    # Now that we know everything uninteresting, we can yield information from
1151
    # our first request
1152
    interesting_items.difference_update(all_uninteresting_items)
1153
    records = dict((record.key, record) for record in interesting_records
1154
                    if record.key not in all_uninteresting_chks)
3735.9.19 by John Arbash Meinel
Refactor iter_interesting a little bit.
1155
    if records or interesting_items:
1156
        yield records, interesting_items
3735.9.7 by John Arbash Meinel
Cleanup pass.
1157
    interesting_keys.difference_update(all_uninteresting_chks)
1158
1159
    chks_to_read = interesting_keys
3735.9.1 by John Arbash Meinel
Start working on an iter_interesting_nodes, which can find nodes to transmit
1160
    while chks_to_read:
1161
        next_chks = set()
3735.9.17 by John Arbash Meinel
Pass around a progress bar and switch to using an adapter.
1162
        for record in store.get_record_stream(chks_to_read, 'unordered', False):
1163
            if pb is not None:
1164
                pb.tick()
3735.9.7 by John Arbash Meinel
Cleanup pass.
1165
            # TODO: Handle 'absent'?
3735.9.17 by John Arbash Meinel
Pass around a progress bar and switch to using an adapter.
1166
            if record.storage_kind != 'fulltext':
1167
                bytes = adapter.get_bytes(record,
1168
                            record.get_bytes_as(record.storage_kind))
1169
            else:
1170
                bytes = record.get_bytes_as('fulltext')
1171
            node = _deserialise(bytes, record.key)
3735.9.1 by John Arbash Meinel
Start working on an iter_interesting_nodes, which can find nodes to transmit
1172
            if isinstance(node, InternalNode):
3735.9.15 by John Arbash Meinel
Found a bug in iter_interesting_nodes and its test suite.
1173
                chks = set(node.refs())
1174
                chks.difference_update(all_uninteresting_chks)
1175
                # Is set() and .difference_update better than:
1176
                # chks = [chk for chk in node.refs()
1177
                #              if chk not in all_uninteresting_chks]
3735.9.1 by John Arbash Meinel
Start working on an iter_interesting_nodes, which can find nodes to transmit
1178
                next_chks.update(chks)
1179
                # These are now uninteresting everywhere else
3735.9.7 by John Arbash Meinel
Cleanup pass.
1180
                all_uninteresting_chks.update(chks)
3735.9.19 by John Arbash Meinel
Refactor iter_interesting a little bit.
1181
                interesting_items = []
3735.9.1 by John Arbash Meinel
Start working on an iter_interesting_nodes, which can find nodes to transmit
1182
            else:
3735.9.19 by John Arbash Meinel
Refactor iter_interesting a little bit.
1183
                interesting_items = [item for item in node._items.iteritems()
1184
                                     if item not in all_uninteresting_items]
3735.9.15 by John Arbash Meinel
Found a bug in iter_interesting_nodes and its test suite.
1185
                # TODO: Do we need to filter out items that we have already
1186
                #       seen on other pages? We don't really want to buffer the
1187
                #       whole thing, but it does mean that callers need to
1188
                #       understand they may get duplicate values.
3735.9.7 by John Arbash Meinel
Cleanup pass.
1189
                # all_uninteresting_items.update(interesting_items)
3735.9.19 by John Arbash Meinel
Refactor iter_interesting a little bit.
1190
            yield {record.key: record}, interesting_items
3735.9.1 by John Arbash Meinel
Start working on an iter_interesting_nodes, which can find nodes to transmit
1191
        chks_to_read = next_chks