/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,)
568
        return [self._key]
569
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
570
    def _serialised_key(self, key):
571
        """Return the serialised key for key in this node."""
572
        return '\x00'.join(key)
573
3735.2.26 by Robert Collins
CHKInventory migrated to new CHKMap code.
574
    def refs(self):
575
        """Return the references to other CHK's held by this node."""
576
        return []
577
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
578
    def unique_serialised_prefix(self):
579
        """Return the unique key prefix for this node.
580
581
        :return: A bytestring of the longest serialised key prefix that is
582
            unique within this node.
583
        """
584
        # may want to cache this eventually :- but wait for enough
585
        # functionality to profile.
586
        keys = list(self._items.keys())
587
        if not keys:
588
            return ""
589
        current_prefix = self._serialised_key(keys.pop(-1))
590
        while current_prefix and keys:
591
            next_key = self._serialised_key(keys.pop(-1))
592
            for pos, (left, right) in enumerate(zip(current_prefix, next_key)):
593
                if left != right:
594
                    pos -= 1
595
                    break
596
            current_prefix = current_prefix[:pos + 1]
597
        return current_prefix
598
599
    def unmap(self, store, key):
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
600
        """Unmap key from the node."""
601
        self._size -= 2 + len('\x00'.join(key)) + len(self._items[key])
602
        self._len -= 1
603
        del self._items[key]
604
        self._key = None
605
        return self
606
607
608
class InternalNode(Node):
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
609
    """A node that contains references to other nodes.
3735.11.1 by John Arbash Meinel
Clean up some trailing whitespace.
610
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
611
    An InternalNode is responsible for mapping serialised key prefixes to child
612
    nodes. It is greedy - it will defer splitting itself as long as possible.
613
    """
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
614
3735.9.5 by John Arbash Meinel
Don't allow an InternalNode to add a key that doesn't fit.
615
    def __init__(self, prefix=''):
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
616
        Node.__init__(self)
617
        # The size of an internalnode with default values and no children.
618
        # self._size = 12
619
        # How many octets key prefixes within this node are.
620
        self._node_width = 0
3735.9.5 by John Arbash Meinel
Don't allow an InternalNode to add a key that doesn't fit.
621
        self._prefix = prefix
622
623
    def __repr__(self):
624
        items_str = sorted(self._items)
625
        if len(items_str) > 20:
626
            items_str = items_str[16] + '...]'
627
        return '%s(key:%s len:%s size:%s max:%s prefix:%s items:%s)' % (
628
            self.__class__.__name__, self._key, self._len, self._size,
629
            self._maximum_size, self._prefix, items_str)
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
630
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
631
    def add_node(self, prefix, node):
632
        """Add a child node with prefix prefix, and node node.
633
634
        :param prefix: The serialised key prefix for node.
635
        :param node: The node being added.
636
        """
3735.9.5 by John Arbash Meinel
Don't allow an InternalNode to add a key that doesn't fit.
637
        assert self._prefix is not None
638
        assert prefix.startswith(self._prefix)
639
        assert len(prefix) == len(self._prefix) + 1
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
640
        self._len += len(node)
641
        if not len(self._items):
642
            self._node_width = len(prefix)
3735.9.11 by John Arbash Meinel
Handle when an InternalNode decides it needs to split.
643
        assert self._node_width == len(self._prefix) + 1
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
644
        self._items[prefix] = node
645
        self._key = None
646
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
647
    def _current_size(self):
648
        """Answer the current serialised size of this node."""
649
        return (self._size + len(str(self._len)) + len(str(self._key_width)) +
650
            len(str(self._maximum_size)))
651
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
652
    @classmethod
653
    def deserialise(klass, bytes, key):
3735.2.25 by Robert Collins
CHKInventory core tests passing.
654
        """Deserialise bytes to an InternalNode, with key key.
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
655
656
        :param bytes: The bytes of the node.
657
        :param key: The key that the serialised node has.
658
        :return: An InternalNode instance.
659
        """
660
        result = InternalNode()
661
        lines = bytes.splitlines()
662
        items = {}
663
        if lines[0] != 'chknode:':
664
            raise ValueError("not a serialised internal node: %r" % bytes)
665
        maximum_size = int(lines[1])
666
        width = int(lines[2])
667
        length = int(lines[3])
668
        for line in lines[4:]:
669
            prefix, flat_key = line.rsplit('\x00', 1)
670
            items[prefix] = (flat_key,)
671
        result._items = items
672
        result._len = length
673
        result._maximum_size = maximum_size
674
        result._key = key
675
        result._key_width = width
676
        result._size = len(bytes)
677
        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.
678
        result._prefix = result.unique_serialised_prefix()
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
679
        return result
680
681
    def iteritems(self, store, key_filter=None):
682
        for node in self._iter_nodes(store, key_filter=key_filter):
683
            for item in node.iteritems(store, key_filter=key_filter):
684
                yield item
685
686
    def _iter_nodes(self, store, key_filter=None):
687
        """Iterate over node objects which match key_filter.
688
689
        :param store: A store to use for accessing content.
690
        :param key_filter: A key filter to filter nodes. Only nodes that might
691
            contain a key in key_filter will be returned.
692
        :return: An iterable of nodes.
693
        """
694
        nodes = []
3735.2.31 by Robert Collins
CHKMap.iter_changes
695
        keys = {}
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
696
        if key_filter is None:
3735.2.31 by Robert Collins
CHKMap.iter_changes
697
            for prefix, node in self._items.iteritems():
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
698
                if type(node) == tuple:
3735.2.31 by Robert Collins
CHKMap.iter_changes
699
                    keys[node] = prefix
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
700
                else:
701
                    nodes.append(node)
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
702
        else:
3735.2.43 by Robert Collins
Teach CHKMap how to iter items in 2-tuple keyspaces.
703
            # XXX defaultdict ?
704
            length_filters = {}
705
            for key in key_filter:
706
                serialised_key = self._serialised_prefix_filter(key)
707
                length_filter = length_filters.setdefault(len(serialised_key),
708
                    set())
709
                length_filter.add(serialised_key)
710
            length_filters = length_filters.items()
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
711
            for prefix, node in self._items.iteritems():
3735.2.43 by Robert Collins
Teach CHKMap how to iter items in 2-tuple keyspaces.
712
                for length, length_filter in length_filters:
713
                    if prefix[:length] in length_filter:
714
                        if type(node) == tuple:
715
                            keys[node] = prefix
716
                        else:
717
                            nodes.append(node)
718
                        break
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
719
        if keys:
3735.2.62 by Robert Collins
Create a rudimentary CHK page cache.
720
            # Look in the page cache for some more bytes
721
            found_keys = set()
722
            for key in keys:
723
                try:
724
                    bytes = _page_cache[key]
725
                except KeyError:
726
                    continue
727
                else:
728
                    node = _deserialise(bytes, key)
729
                    nodes.append(node)
730
                    self._items[keys[key]] = node
731
                    found_keys.add(key)
732
            for key in found_keys:
733
                del keys[key]
734
        if keys:
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
735
            # demand load some pages.
736
            stream = store.get_record_stream(keys, 'unordered', True)
737
            for record in stream:
3735.2.62 by Robert Collins
Create a rudimentary CHK page cache.
738
                bytes = record.get_bytes_as('fulltext')
739
                node = _deserialise(bytes, record.key)
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
740
                nodes.append(node)
3735.2.31 by Robert Collins
CHKMap.iter_changes
741
                self._items[keys[record.key]] = node
3735.2.62 by Robert Collins
Create a rudimentary CHK page cache.
742
                _page_cache.add(record.key, bytes)
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
743
        return nodes
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
744
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
745
    def map(self, store, key, value):
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
746
        """Map key to value."""
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
747
        if not len(self._items):
748
            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.
749
        serialised_key = self._serialised_key(key)
3735.9.11 by John Arbash Meinel
Handle when an InternalNode decides it needs to split.
750
        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.
751
        if not serialised_key.startswith(self._prefix):
3735.9.11 by John Arbash Meinel
Handle when an InternalNode decides it needs to split.
752
            # This key doesn't fit in this index, so we need to split at the
753
            # point where it would fit.
754
            # 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.
755
            new_prefix = self.unique_serialised_prefix(serialised_key)
756
            new_parent = InternalNode(new_prefix)
757
            new_parent.set_maximum_size(self._maximum_size)
758
            new_parent._key_width = self._key_width
759
            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.
760
            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.
761
            return new_parent.map(store, key, value)
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
762
        children = self._iter_nodes(store, key_filter=[key])
763
        if children:
764
            child = children[0]
3735.9.11 by John Arbash Meinel
Handle when an InternalNode decides it needs to split.
765
            # if isinstance(child, InternalNode):
766
            #     child_prefix = child._prefix
767
            #     child_ser_key = child._serialised_key(key)
768
            #     if not child_ser_key.startswith(child_prefix):
769
            #         import pdb; pdb.set_trace()
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
770
        else:
771
            # new child needed:
772
            child = self._new_child(serialised_key, LeafNode)
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
773
        old_len = len(child)
3735.11.11 by John Arbash Meinel
Add logic to map() so that it can also collapse when necessary.
774
        if isinstance(child, LeafNode):
775
            old_size = child._current_size()
776
        else:
777
            old_size = None
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
778
        prefix, node_details = child.map(store, key, value)
779
        if len(node_details) == 1:
3735.9.11 by John Arbash Meinel
Handle when an InternalNode decides it needs to split.
780
            # child may have shrunk, or might be a new node
781
            child = node_details[0][1]
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
782
            self._len = self._len - old_len + len(child)
783
            self._items[serialised_key] = child
3735.2.29 by Robert Collins
Untested code is broken code.
784
            self._key = None
3735.11.11 by John Arbash Meinel
Add logic to map() so that it can also collapse when necessary.
785
            new_node = self
786
            if (isinstance(child, LeafNode)
787
                and (old_size is None or child._current_size() < old_size)):
788
                # The old node was an InternalNode which means it has now
789
                # collapsed, so we need to check if it will chain to a collapse
790
                # at this level. Or the LeafNode has shrunk in size, so we need
791
                # to check that as well.
792
                new_node = self._check_remap(store)
793
            return new_node.unique_serialised_prefix(), [("", new_node)]
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
794
        # child has overflown - create a new intermediate node.
795
        # XXX: This is where we might want to try and expand our depth
796
        # to refer to more bytes of every child (which would give us
797
        # 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.
798
        # 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.
799
        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.
800
        child._prefix = prefix
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
801
        for split, node in node_details:
802
            child.add_node(split, node)
803
        self._len = self._len - old_len + len(child)
3735.2.29 by Robert Collins
Untested code is broken code.
804
        self._key = None
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
805
        return self.unique_serialised_prefix(), [("", self)]
806
807
    def _new_child(self, serialised_key, klass):
808
        """Create a new child node of type klass."""
809
        child = klass()
810
        child.set_maximum_size(self._maximum_size)
811
        child._key_width = self._key_width
812
        self._items[serialised_key] = child
813
        return child
814
815
    def serialise(self, store):
816
        """Serialise the node to store.
817
818
        :param store: A VersionedFiles honouring the CHK extensions.
819
        :return: An iterable of the keys inserted by this operation.
820
        """
821
        for node in self._items.itervalues():
822
            if type(node) == tuple:
823
                # Never deserialised.
824
                continue
825
            if node._key is not None:
826
                # Never altered
827
                continue
828
            for key in node.serialise(store):
829
                yield key
830
        lines = ["chknode:\n"]
831
        lines.append("%d\n" % self._maximum_size)
832
        lines.append("%d\n" % self._key_width)
833
        lines.append("%d\n" % self._len)
834
        for prefix, node in sorted(self._items.items()):
835
            if type(node) == tuple:
836
                key = node[0]
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
837
            else:
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
838
                key = node._key[0]
839
            lines.append("%s\x00%s\n" % (prefix, key))
840
        sha1, _, _ = store.add_lines((None,), (), lines)
841
        self._key = ("sha1:" + sha1,)
842
        yield self._key
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
843
844
    def _serialised_key(self, key):
845
        """Return the serialised key for key in this node."""
846
        return ('\x00'.join(key) + '\x00'*self._node_width)[:self._node_width]
847
3735.2.43 by Robert Collins
Teach CHKMap how to iter items in 2-tuple keyspaces.
848
    def _serialised_prefix_filter(self, key):
849
        """Serialise key for use as a prefix filter in iteritems."""
850
        if len(key) == self._key_width:
851
            return self._serialised_key(key)
852
        return '\x00'.join(key)[:self._node_width]
853
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
854
    def _split(self, offset):
855
        """Split this node into smaller nodes starting at offset.
856
857
        :param offset: The offset to start the new child nodes at.
858
        :return: An iterable of (prefix, node) tuples. prefix is a byte
859
            prefix for reaching node.
860
        """
861
        if offset >= self._node_width:
862
            for node in self._items.values():
863
                for result in node._split(offset):
864
                    yield result
865
            return
866
        for key, node in self._items.items():
867
            pass
868
3735.2.26 by Robert Collins
CHKInventory migrated to new CHKMap code.
869
    def refs(self):
870
        """Return the references to other CHK's held by this node."""
871
        if self._key is None:
872
            raise AssertionError("unserialised nodes have no refs.")
873
        refs = []
874
        for value in self._items.itervalues():
875
            if type(value) == tuple:
876
                refs.append(value)
877
            else:
878
                refs.append(value.key())
879
        return refs
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
880
3735.9.5 by John Arbash Meinel
Don't allow an InternalNode to add a key that doesn't fit.
881
    def unique_serialised_prefix(self, extra_key=None):
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
882
        """Return the unique key prefix for this node.
883
884
        :return: A bytestring of the longest serialised key prefix that is
885
            unique within this node.
886
        """
887
        # may want to cache this eventually :- but wait for enough
888
        # functionality to profile.
889
        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.
890
        if extra_key is not None:
891
            keys.append(extra_key)
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
892
        if not keys:
893
            return ""
894
        current_prefix = keys.pop(-1)
895
        while current_prefix and keys:
896
            next_key = keys.pop(-1)
897
            for pos, (left, right) in enumerate(zip(current_prefix, next_key)):
898
                if left != right:
899
                    pos -= 1
900
                    break
901
            current_prefix = current_prefix[:pos + 1]
902
        return current_prefix
903
904
    def unmap(self, store, key):
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
905
        """Remove key from this node and it's children."""
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
906
        if not len(self._items):
907
            raise AssertionError("cant unmap in an empty InternalNode.")
908
        children = self._iter_nodes(store, key_filter=[key])
909
        if children:
910
            child = children[0]
911
        else:
912
            raise KeyError(key)
913
        self._len -= 1
914
        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.
915
        self._key = None
916
        serialised_key = self._serialised_key(key)
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
917
        if len(unmapped) == 0:
918
            # All child nodes are gone, remove the child:
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
919
            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.
920
            unmapped = None
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
921
        else:
922
            # Stash the returned node
923
            self._items[serialised_key] = unmapped
3735.2.23 by Robert Collins
Test unmapping with one child left but multiple keys.
924
        if len(self._items) == 1:
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
925
            # this node is no longer needed:
926
            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.
927
        if isinstance(unmapped, InternalNode):
928
            return self
929
        return self._check_remap(store)
930
931
    def _check_remap(self, store):
3735.11.11 by John Arbash Meinel
Add logic to map() so that it can also collapse when necessary.
932
        """Check if all keys contained by children fit in a single LeafNode.
933
934
        :param store: A store to use for reading more nodes
935
        :return: Either self, or a new LeafNode which should replace self.
936
        """
3735.11.10 by John Arbash Meinel
Change how _check_remap works so it doesn't have to load all keys.
937
        # 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.
938
        # 1) Implicitly unmap() is removing a key which means that the child
939
        #    nodes are going to be shrinking by some extent.
940
        # 2) If all children are LeafNodes, it is possible that they could be
941
        #    combined into a single LeafNode, which can then completely replace
942
        #    this internal node with a single LeafNode
943
        # 3) If *one* child is an InternalNode, we assume it has already done
944
        #    all the work to determine that its children cannot collapse, and
945
        #    we can then assume that those nodes *plus* the current nodes don't
946
        #    have a chance of collapsing either.
947
        #    So a very cheap check is to just say if 'unmapped' is an
948
        #    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.
949
3735.11.3 by John Arbash Meinel
At the end of unmap() see if children can be packed into a single Leaf.
950
        # TODO: Another alternative is to check the total size of all known
951
        #       LeafNodes. If there is some formula we can use to determine the
952
        #       final size without actually having to read in any more
953
        #       children, it would be nice to have. However, we have to be
954
        #       careful with stuff like nodes that pull out the common prefix
955
        #       of each key, as adding a new key can change the common prefix
956
        #       and cause size changes greater than the length of one key.
957
        #       So for now, we just add everything to a new Leaf until it
958
        #       splits, as we know that will give the right answer
959
        new_leaf = LeafNode()
960
        new_leaf.set_maximum_size(self._maximum_size)
961
        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.
962
        keys = {}
963
        # There is some overlap with _iter_nodes here, but not a lot, and it
964
        # allows us to do quick evaluation without paging everything in
965
        for prefix, node in self._items.iteritems():
966
            if type(node) == tuple:
967
                keys[node] = prefix
968
            else:
969
                if isinstance(node, InternalNode):
970
                    # Without looking at any leaf nodes, we are sure
971
                    return self
972
                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.
973
                    if new_leaf._map_no_split(key, value):
974
                        # Adding this key would cause a split, so we know we
975
                        # 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.
976
                        return self
977
        # So far, everything fits. Page in the rest of the nodes, and see if it
978
        # holds true.
979
        if keys:
3735.11.12 by John Arbash Meinel
Add a TODO describing a possible optimization.
980
            # TODO: Consider looping over a limited set of keys (like 25 or so
981
            #       at a time). If we have to read more than 25 we almost
982
            #       certainly won't fit them all into a single new LeafNode, so
983
            #       reading the extra nodes is a waste.
984
            #       This will probably matter more with hash serialised keys,
985
            #       as we will get InternalNodes with more references.
986
            #       The other argument is that unmap() is uncommon, so we don't
987
            #       need to optimize it. But map() with a slightly shorter
988
            #       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.
989
            stream = store.get_record_stream(keys, 'unordered', True)
990
            nodes = []
991
            # Fully consume the stream, even if we could determine that we
992
            # don't need to continue. We requested the bytes, we may as well
993
            # use them
994
            for record in stream:
995
                node = _deserialise(record.get_bytes_as('fulltext'), record.key)
996
                self._items[keys[record.key]] = node
997
                nodes.append(node)
998
            for node in nodes:
999
                if isinstance(node, InternalNode):
1000
                    # We know we won't fit
1001
                    return self
1002
                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.
1003
                    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.
1004
                        return self
1005
3735.11.3 by John Arbash Meinel
At the end of unmap() see if children can be packed into a single Leaf.
1006
        # We have gone to every child, and everything fits in a single leaf
1007
        # node, we no longer need this internal node
1008
        return new_leaf
3735.2.18 by Robert Collins
Partial multi-layer chk dictionary trees.
1009
1010
3735.2.16 by Robert Collins
Untested extensions to support repodetails
1011
def _deserialise(bytes, key):
1012
    """Helper for repositorydetails - convert bytes to a node."""
3735.2.24 by Robert Collins
test_chk_map tests all passing.
1013
    if bytes.startswith("chkleaf:\n"):
3735.2.21 by Robert Collins
BROKEN: multi level CHKMap tries, unfinished, subsystem in flux.
1014
        return LeafNode.deserialise(bytes, key)
1015
    elif bytes.startswith("chknode:\n"):
1016
        return InternalNode.deserialise(bytes, key)
3735.2.16 by Robert Collins
Untested extensions to support repodetails
1017
    else:
1018
        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
1019
1020
3735.9.17 by John Arbash Meinel
Pass around a progress bar and switch to using an adapter.
1021
def _find_children_info(store, interesting_keys, uninteresting_keys,
3735.9.19 by John Arbash Meinel
Refactor iter_interesting a little bit.
1022
                        adapter, pb):
3735.9.7 by John Arbash Meinel
Cleanup pass.
1023
    """Read the associated records, and determine what is interesting."""
1024
    uninteresting_keys = set(uninteresting_keys)
1025
    chks_to_read = uninteresting_keys.union(interesting_keys)
1026
    next_uninteresting = set()
1027
    next_interesting = set()
1028
    uninteresting_items = set()
1029
    interesting_items = set()
1030
    interesting_records = []
3735.9.14 by John Arbash Meinel
Start using the iter_interesting_nodes.
1031
    # records_read = set()
3735.9.7 by John Arbash Meinel
Cleanup pass.
1032
    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.
1033
        # records_read.add(record.key())
3735.9.17 by John Arbash Meinel
Pass around a progress bar and switch to using an adapter.
1034
        if pb is not None:
1035
            pb.tick()
1036
        if record.storage_kind != 'fulltext':
1037
            bytes = adapter.get_bytes(record,
1038
                        record.get_bytes_as(record.storage_kind))
1039
        else:
1040
            bytes = record.get_bytes_as('fulltext')
1041
        node = _deserialise(bytes, record.key)
3735.9.7 by John Arbash Meinel
Cleanup pass.
1042
        if record.key in uninteresting_keys:
1043
            if isinstance(node, InternalNode):
3735.9.14 by John Arbash Meinel
Start using the iter_interesting_nodes.
1044
                next_uninteresting.update(node.refs())
3735.9.7 by John Arbash Meinel
Cleanup pass.
1045
            else:
3735.9.14 by John Arbash Meinel
Start using the iter_interesting_nodes.
1046
                # We know we are at a LeafNode, so we can pass None for the
1047
                # store
1048
                uninteresting_items.update(node.iteritems(None))
3735.9.7 by John Arbash Meinel
Cleanup pass.
1049
        else:
1050
            interesting_records.append(record)
1051
            if isinstance(node, InternalNode):
3735.9.14 by John Arbash Meinel
Start using the iter_interesting_nodes.
1052
                next_interesting.update(node.refs())
3735.9.7 by John Arbash Meinel
Cleanup pass.
1053
            else:
3735.9.14 by John Arbash Meinel
Start using the iter_interesting_nodes.
1054
                interesting_items.update(node.iteritems(None))
1055
    # TODO: Filter out records that have already been read, as node splitting
1056
    #       can cause us to reference the same nodes via shorter and longer
1057
    #       paths
3735.9.7 by John Arbash Meinel
Cleanup pass.
1058
    return (next_uninteresting, uninteresting_items,
1059
            next_interesting, interesting_records, interesting_items)
1060
1061
3735.9.19 by John Arbash Meinel
Refactor iter_interesting a little bit.
1062
def _find_all_uninteresting(store, interesting_root_keys,
1063
                            uninteresting_root_keys, adapter, pb):
1064
    """Determine the full set of uninteresting keys."""
1065
    # What about duplicates between interesting_root_keys and
1066
    # uninteresting_root_keys?
1067
    if not uninteresting_root_keys:
1068
        # Shortcut case. We know there is nothing uninteresting to filter out
1069
        # So we just let the rest of the algorithm do the work
1070
        # We know there is nothing uninteresting, and we didn't have to read
1071
        # any interesting records yet.
1072
        return (set(), set(), set(interesting_root_keys), [], set())
3735.9.7 by John Arbash Meinel
Cleanup pass.
1073
    all_uninteresting_chks = set(uninteresting_root_keys)
1074
    all_uninteresting_items = set()
1075
1076
    # First step, find the direct children of both the interesting and
1077
    # uninteresting set
1078
    (uninteresting_keys, uninteresting_items,
1079
     interesting_keys, interesting_records,
1080
     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.
1081
                                              uninteresting_root_keys,
1082
                                              adapter=adapter, pb=pb)
3735.9.7 by John Arbash Meinel
Cleanup pass.
1083
    all_uninteresting_chks.update(uninteresting_keys)
1084
    all_uninteresting_items.update(uninteresting_items)
1085
    del uninteresting_items
1086
    # Note: Exact matches between interesting and uninteresting do not need
1087
    #       to be search further. Non-exact matches need to be searched in case
1088
    #       there is a future exact-match
1089
    uninteresting_keys.difference_update(interesting_keys)
1090
3735.9.6 by John Arbash Meinel
Add a first pass to the interesting search.
1091
    # 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
1092
    # uninteresting roots
3735.9.7 by John Arbash Meinel
Cleanup pass.
1093
    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
1094
    while chks_to_read:
1095
        next_chks = set()
3735.9.17 by John Arbash Meinel
Pass around a progress bar and switch to using an adapter.
1096
        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
1097
            # TODO: Handle 'absent'
3735.9.17 by John Arbash Meinel
Pass around a progress bar and switch to using an adapter.
1098
            if pb is not None:
1099
                pb.tick()
1100
            if record.storage_kind != 'fulltext':
1101
                bytes = adapter.get_bytes(record,
1102
                            record.get_bytes_as(record.storage_kind))
1103
            else:
1104
                bytes = record.get_bytes_as('fulltext')
1105
            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
1106
            if isinstance(node, InternalNode):
1107
                # uninteresting_prefix_chks.update(node._items.iteritems())
1108
                chks = node._items.values()
1109
                # TODO: We remove the entries that are already in
1110
                #       uninteresting_chks ?
1111
                next_chks.update(chks)
3735.9.7 by John Arbash Meinel
Cleanup pass.
1112
                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
1113
            else:
3735.9.7 by John Arbash Meinel
Cleanup pass.
1114
                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
1115
        chks_to_read = next_chks
3735.9.19 by John Arbash Meinel
Refactor iter_interesting a little bit.
1116
    return (all_uninteresting_chks, all_uninteresting_items,
1117
            interesting_keys, interesting_records, interesting_items)
1118
1119
1120
def iter_interesting_nodes(store, interesting_root_keys,
1121
                           uninteresting_root_keys, pb=None):
1122
    """Given root keys, find interesting nodes.
1123
1124
    Evaluate nodes referenced by interesting_root_keys. Ones that are also
1125
    referenced from uninteresting_root_keys are not considered interesting.
1126
1127
    :param interesting_root_keys: keys which should be part of the
1128
        "interesting" nodes (which will be yielded)
1129
    :param uninteresting_root_keys: keys which should be filtered out of the
1130
        result set.
1131
    :return: Yield
1132
        (interesting records, interesting chk's, interesting key:values)
1133
    """
1134
    # TODO: consider that it may be more memory efficient to use the 20-byte
1135
    #       sha1 string, rather than tuples of hexidecimal sha1 strings.
1136
1137
    # A way to adapt from the compressed texts back into fulltexts
1138
    # In a way, this seems like a layering inversion to have CHKMap know the
1139
    # details of versionedfile
1140
    adapter_class = versionedfile.adapter_registry.get(
1141
        ('knit-ft-gz', 'fulltext'))
1142
    adapter = adapter_class(store)
1143
1144
    (all_uninteresting_chks, all_uninteresting_items, interesting_keys,
1145
     interesting_records, interesting_items) = _find_all_uninteresting(store,
1146
        interesting_root_keys, uninteresting_root_keys, adapter, pb)
3735.9.7 by John Arbash Meinel
Cleanup pass.
1147
1148
    # Now that we know everything uninteresting, we can yield information from
1149
    # our first request
1150
    interesting_items.difference_update(all_uninteresting_items)
1151
    records = dict((record.key, record) for record in interesting_records
1152
                    if record.key not in all_uninteresting_chks)
3735.9.19 by John Arbash Meinel
Refactor iter_interesting a little bit.
1153
    if records or interesting_items:
1154
        yield records, interesting_items
3735.9.7 by John Arbash Meinel
Cleanup pass.
1155
    interesting_keys.difference_update(all_uninteresting_chks)
1156
1157
    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
1158
    while chks_to_read:
1159
        next_chks = set()
3735.9.17 by John Arbash Meinel
Pass around a progress bar and switch to using an adapter.
1160
        for record in store.get_record_stream(chks_to_read, 'unordered', False):
1161
            if pb is not None:
1162
                pb.tick()
3735.9.7 by John Arbash Meinel
Cleanup pass.
1163
            # TODO: Handle 'absent'?
3735.9.17 by John Arbash Meinel
Pass around a progress bar and switch to using an adapter.
1164
            if record.storage_kind != 'fulltext':
1165
                bytes = adapter.get_bytes(record,
1166
                            record.get_bytes_as(record.storage_kind))
1167
            else:
1168
                bytes = record.get_bytes_as('fulltext')
1169
            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
1170
            if isinstance(node, InternalNode):
3735.9.15 by John Arbash Meinel
Found a bug in iter_interesting_nodes and its test suite.
1171
                chks = set(node.refs())
1172
                chks.difference_update(all_uninteresting_chks)
1173
                # Is set() and .difference_update better than:
1174
                # chks = [chk for chk in node.refs()
1175
                #              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
1176
                next_chks.update(chks)
1177
                # These are now uninteresting everywhere else
3735.9.7 by John Arbash Meinel
Cleanup pass.
1178
                all_uninteresting_chks.update(chks)
3735.9.19 by John Arbash Meinel
Refactor iter_interesting a little bit.
1179
                interesting_items = []
3735.9.1 by John Arbash Meinel
Start working on an iter_interesting_nodes, which can find nodes to transmit
1180
            else:
3735.9.19 by John Arbash Meinel
Refactor iter_interesting a little bit.
1181
                interesting_items = [item for item in node._items.iteritems()
1182
                                     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.
1183
                # TODO: Do we need to filter out items that we have already
1184
                #       seen on other pages? We don't really want to buffer the
1185
                #       whole thing, but it does mean that callers need to
1186
                #       understand they may get duplicate values.
3735.9.7 by John Arbash Meinel
Cleanup pass.
1187
                # all_uninteresting_items.update(interesting_items)
3735.9.19 by John Arbash Meinel
Refactor iter_interesting a little bit.
1188
            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
1189
        chks_to_read = next_chks