/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/bzr/btree_index.py

  • Committer: Jelmer Vernooij
  • Date: 2019-09-01 15:33:59 UTC
  • mto: This revision was merged to the branch mainline in revision 7404.
  • Revision ID: jelmer@jelmer.uk-20190901153359-9gl0ai0x5wuiv444
Rename init-repo to init-shared-repo.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2008, 2009, 2010 Canonical Ltd
 
1
# Copyright (C) 2008-2011 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
17
17
 
18
18
"""B+Tree indices"""
19
19
 
20
 
import cStringIO
21
 
from bisect import bisect_right
 
20
from __future__ import absolute_import, division
 
21
 
 
22
from ..lazy_import import lazy_import
 
23
lazy_import(globals(), """
 
24
import bisect
22
25
import math
23
26
import tempfile
24
27
import zlib
 
28
""")
25
29
 
26
 
from bzrlib import (
 
30
from .. import (
27
31
    chunk_writer,
28
32
    debug,
29
 
    errors,
30
33
    fifo_cache,
31
 
    index,
32
34
    lru_cache,
33
35
    osutils,
34
36
    static_tuple,
35
37
    trace,
36
 
    )
37
 
from bzrlib.index import _OPTION_NODE_REFS, _OPTION_KEY_ELEMENTS, _OPTION_LEN
38
 
from bzrlib.transport import get_transport
39
 
 
40
 
 
41
 
_BTSIGNATURE = "B+Tree Graph Index 2\n"
42
 
_OPTION_ROW_LENGTHS = "row_lengths="
43
 
_LEAF_FLAG = "type=leaf\n"
44
 
_INTERNAL_FLAG = "type=internal\n"
45
 
_INTERNAL_OFFSET = "offset="
 
38
    transport,
 
39
    )
 
40
from . import (
 
41
    index,
 
42
    )
 
43
from .index import _OPTION_NODE_REFS, _OPTION_KEY_ELEMENTS, _OPTION_LEN
 
44
from ..sixish import (
 
45
    BytesIO,
 
46
    map,
 
47
    range,
 
48
    viewitems,
 
49
    viewvalues,
 
50
    )
 
51
 
 
52
 
 
53
_BTSIGNATURE = b"B+Tree Graph Index 2\n"
 
54
_OPTION_ROW_LENGTHS = b"row_lengths="
 
55
_LEAF_FLAG = b"type=leaf\n"
 
56
_INTERNAL_FLAG = b"type=internal\n"
 
57
_INTERNAL_OFFSET = b"offset="
46
58
 
47
59
_RESERVED_HEADER_BYTES = 120
48
60
_PAGE_SIZE = 4096
62
74
    def __init__(self):
63
75
        """Create a _BuilderRow."""
64
76
        self.nodes = 0
65
 
        self.spool = None# tempfile.TemporaryFile(prefix='bzr-index-row-')
 
77
        self.spool = None  # tempfile.TemporaryFile(prefix='bzr-index-row-')
66
78
        self.writer = None
67
79
 
68
80
    def finish_node(self, pad=True):
69
81
        byte_lines, _, padding = self.writer.finish()
70
82
        if self.nodes == 0:
71
 
            self.spool = cStringIO.StringIO()
 
83
            self.spool = BytesIO()
72
84
            # padded note:
73
 
            self.spool.write("\x00" * _RESERVED_HEADER_BYTES)
 
85
            self.spool.write(b"\x00" * _RESERVED_HEADER_BYTES)
74
86
        elif self.nodes == 1:
75
87
            # We got bigger than 1 node, switch to a temp file
76
88
            spool = tempfile.TemporaryFile(prefix='bzr-index-row-')
137
149
            of nodes that BTreeBuilder will hold in memory.
138
150
        """
139
151
        index.GraphIndexBuilder.__init__(self, reference_lists=reference_lists,
140
 
            key_elements=key_elements)
 
152
                                         key_elements=key_elements)
141
153
        self._spill_at = spill_at
142
154
        self._backing_indices = []
143
155
        # A map of {key: (node_refs, value)}
158
170
        :param references: An iterable of iterables of keys. Each is a
159
171
            reference to another key.
160
172
        :param value: The value to associate with the key. It may be any
161
 
            bytes as long as it does not contain \0 or \n.
 
173
            bytes as long as it does not contain \\0 or \\n.
162
174
        """
163
175
        # Ensure that 'key' is a StaticTuple
164
176
        key = static_tuple.StaticTuple.from_sequence(key).intern()
165
177
        # we don't care about absent_references
166
178
        node_refs, _ = self._check_key_ref_value(key, references, value)
167
179
        if key in self._nodes:
168
 
            raise errors.BadIndexDuplicateKey(key, self)
 
180
            raise index.BadIndexDuplicateKey(key, self)
169
181
        self._nodes[key] = static_tuple.StaticTuple(node_refs, value)
170
182
        if self._nodes_by_key is not None and self._key_length > 1:
171
183
            self._update_nodes_by_key(key, value, node_refs)
193
205
            new_backing_file, size = self._spill_mem_keys_without_combining()
194
206
        # Note: The transport here isn't strictly needed, because we will use
195
207
        #       direct access to the new_backing._file object
196
 
        new_backing = BTreeGraphIndex(get_transport('.'), '<temp>', size)
 
208
        new_backing = BTreeGraphIndex(transport.get_transport_from_path('.'),
 
209
                                      '<temp>', size)
197
210
        # GC will clean up the file
198
211
        new_backing._file = new_backing_file
199
212
        if self._combine_backing_indices:
256
269
        current_values = []
257
270
        for iterator in iterators_to_combine:
258
271
            try:
259
 
                current_values.append(iterator.next())
 
272
                current_values.append(next(iterator))
260
273
            except StopIteration:
261
274
                current_values.append(None)
262
275
        last = None
263
276
        while True:
264
277
            # Decorate candidates with the value to allow 2.4's min to be used.
265
278
            candidates = [(item[1][1], item) for item
266
 
                in enumerate(current_values) if item[1] is not None]
 
279
                          in enumerate(current_values) if item[1] is not None]
267
280
            if not len(candidates):
268
281
                return
269
282
            selected = min(candidates)
270
283
            # undecorate back to (pos, node)
271
284
            selected = selected[1]
272
285
            if last == selected[1][1]:
273
 
                raise errors.BadIndexDuplicateKey(last, self)
 
286
                raise index.BadIndexDuplicateKey(last, self)
274
287
            last = selected[1][1]
275
288
            # Yield, with self as the index
276
289
            yield (self,) + selected[1][1:]
277
290
            pos = selected[0]
278
291
            try:
279
 
                current_values[pos] = iterators_to_combine[pos].next()
 
292
                current_values[pos] = next(iterators_to_combine[pos])
280
293
            except StopIteration:
281
294
                current_values[pos] = None
282
295
 
289
302
            flag when writing out. This is used by the _spill_mem_keys_to_disk
290
303
            functionality.
291
304
        """
 
305
        new_leaf = False
292
306
        if rows[-1].writer is None:
293
307
            # opening a new leaf chunk;
 
308
            new_leaf = True
294
309
            for pos, internal_row in enumerate(rows[:-1]):
295
310
                # flesh out any internal nodes that are needed to
296
311
                # preserve the height of the tree
297
312
                if internal_row.writer is None:
298
313
                    length = _PAGE_SIZE
299
314
                    if internal_row.nodes == 0:
300
 
                        length -= _RESERVED_HEADER_BYTES # padded
 
315
                        length -= _RESERVED_HEADER_BYTES  # padded
301
316
                    if allow_optimize:
302
317
                        optimize_for_size = self._optimize_for_size
303
318
                    else:
304
319
                        optimize_for_size = False
305
 
                    internal_row.writer = chunk_writer.ChunkWriter(length, 0,
306
 
                        optimize_for_size=optimize_for_size)
 
320
                    internal_row.writer = chunk_writer.ChunkWriter(
 
321
                        length, 0, optimize_for_size=optimize_for_size)
307
322
                    internal_row.writer.write(_INTERNAL_FLAG)
308
 
                    internal_row.writer.write(_INTERNAL_OFFSET +
309
 
                        str(rows[pos + 1].nodes) + "\n")
 
323
                    internal_row.writer.write(_INTERNAL_OFFSET
 
324
                                              + b"%d\n" % rows[pos + 1].nodes)
310
325
            # add a new leaf
311
326
            length = _PAGE_SIZE
312
327
            if rows[-1].nodes == 0:
313
 
                length -= _RESERVED_HEADER_BYTES # padded
314
 
            rows[-1].writer = chunk_writer.ChunkWriter(length,
315
 
                optimize_for_size=self._optimize_for_size)
 
328
                length -= _RESERVED_HEADER_BYTES  # padded
 
329
            rows[-1].writer = chunk_writer.ChunkWriter(
 
330
                length, optimize_for_size=self._optimize_for_size)
316
331
            rows[-1].writer.write(_LEAF_FLAG)
317
332
        if rows[-1].writer.write(line):
 
333
            # if we failed to write, despite having an empty page to write to,
 
334
            # then line is too big. raising the error avoids infinite recursion
 
335
            # searching for a suitably large page that will not be found.
 
336
            if new_leaf:
 
337
                raise index.BadIndexKey(string_key)
318
338
            # this key did not fit in the node:
319
339
            rows[-1].finish_node()
320
 
            key_line = string_key + "\n"
 
340
            key_line = string_key + b"\n"
321
341
            new_row = True
322
342
            for row in reversed(rows[:-1]):
323
343
                # Mark the start of the next node in the node above. If it
344
364
                    reserved_bytes,
345
365
                    optimize_for_size=self._optimize_for_size)
346
366
                new_row.writer.write(_INTERNAL_FLAG)
347
 
                new_row.writer.write(_INTERNAL_OFFSET +
348
 
                    str(rows[1].nodes - 1) + "\n")
 
367
                new_row.writer.write(_INTERNAL_OFFSET
 
368
                                     + b"%d\n" % (rows[1].nodes - 1))
349
369
                new_row.writer.write(key_line)
350
 
            self._add_key(string_key, line, rows, allow_optimize=allow_optimize)
 
370
            self._add_key(string_key, line, rows,
 
371
                          allow_optimize=allow_optimize)
351
372
 
352
373
    def _write_nodes(self, node_iterator, allow_optimize=True):
353
374
        """Write node_iterator out as a B+Tree.
379
400
                # First key triggers the first row
380
401
                rows.append(_LeafBuilderRow())
381
402
            key_count += 1
382
 
            string_key, line = _btree_serializer._flatten_node(node,
383
 
                                    self.reference_lists)
384
 
            self._add_key(string_key, line, rows, allow_optimize=allow_optimize)
 
403
            string_key, line = _btree_serializer._flatten_node(
 
404
                node, self.reference_lists)
 
405
            self._add_key(string_key, line, rows,
 
406
                          allow_optimize=allow_optimize)
385
407
        for row in reversed(rows):
386
 
            pad = (type(row) != _LeafBuilderRow)
 
408
            pad = (not isinstance(row, _LeafBuilderRow))
387
409
            row.finish_node(pad=pad)
388
410
        lines = [_BTSIGNATURE]
389
 
        lines.append(_OPTION_NODE_REFS + str(self.reference_lists) + '\n')
390
 
        lines.append(_OPTION_KEY_ELEMENTS + str(self._key_length) + '\n')
391
 
        lines.append(_OPTION_LEN + str(key_count) + '\n')
 
411
        lines.append(b'%s%d\n' % (_OPTION_NODE_REFS, self.reference_lists))
 
412
        lines.append(b'%s%d\n' % (_OPTION_KEY_ELEMENTS, self._key_length))
 
413
        lines.append(b'%s%d\n' % (_OPTION_LEN, key_count))
392
414
        row_lengths = [row.nodes for row in rows]
393
 
        lines.append(_OPTION_ROW_LENGTHS + ','.join(map(str, row_lengths)) + '\n')
 
415
        lines.append(_OPTION_ROW_LENGTHS + ','.join(
 
416
            map(str, row_lengths)).encode('ascii') + b'\n')
394
417
        if row_lengths and row_lengths[-1] > 1:
395
418
            result = tempfile.NamedTemporaryFile(prefix='bzr-index-')
396
419
        else:
397
 
            result = cStringIO.StringIO()
 
420
            result = BytesIO()
398
421
        result.writelines(lines)
399
422
        position = sum(map(len, lines))
400
 
        root_row = True
401
423
        if position > _RESERVED_HEADER_BYTES:
402
424
            raise AssertionError("Could not fit the header in the"
403
425
                                 " reserved space: %d > %d"
404
426
                                 % (position, _RESERVED_HEADER_BYTES))
405
427
        # write the rows out:
406
428
        for row in rows:
407
 
            reserved = _RESERVED_HEADER_BYTES # reserved space for first node
 
429
            reserved = _RESERVED_HEADER_BYTES  # reserved space for first node
408
430
            row.spool.flush()
409
431
            row.spool.seek(0)
410
432
            # copy nodes to the finalised file.
412
434
            node = row.spool.read(_PAGE_SIZE)
413
435
            result.write(node[reserved:])
414
436
            if len(node) == _PAGE_SIZE:
415
 
                result.write("\x00" * (reserved - position))
416
 
            position = 0 # Only the root row actually has an offset
 
437
                result.write(b"\x00" * (reserved - position))
 
438
            position = 0  # Only the root row actually has an offset
417
439
            copied_len = osutils.pumpfile(row.spool, result)
418
440
            if copied_len != (row.nodes - 1) * _PAGE_SIZE:
419
 
                if type(row) != _LeafBuilderRow:
 
441
                if not isinstance(row, _LeafBuilderRow):
420
442
                    raise AssertionError("Incorrect amount of data copied"
421
 
                        " expected: %d, got: %d"
422
 
                        % ((row.nodes - 1) * _PAGE_SIZE,
423
 
                           copied_len))
 
443
                                         " expected: %d, got: %d"
 
444
                                         % ((row.nodes - 1) * _PAGE_SIZE,
 
445
                                            copied_len))
424
446
        result.flush()
425
447
        size = result.tell()
426
448
        result.seek(0)
442
464
            efficient order for the index (in this case dictionary hash order).
443
465
        """
444
466
        if 'evil' in debug.debug_flags:
445
 
            trace.mutter_callsite(3,
446
 
                "iter_all_entries scales with size of history.")
 
467
            trace.mutter_callsite(
 
468
                3, "iter_all_entries scales with size of history.")
447
469
        # Doing serial rather than ordered would be faster; but this shouldn't
448
470
        # be getting called routinely anyway.
449
471
        iterators = [self._iter_mem_nodes()]
458
480
        """Iterate over keys within the index.
459
481
 
460
482
        :param keys: An iterable providing the keys to be retrieved.
461
 
        :return: An iterable of (index, key, value, reference_lists). There is no
462
 
            defined order for the result iteration - it will be in the most
 
483
        :return: An iterable of (index, key, value, reference_lists). There is
 
484
            no defined order for the result iteration - it will be in the most
463
485
            efficient order for the index (keys iteration order in this case).
464
486
        """
465
487
        keys = set(keys)
483
505
        # Find things that are in backing indices that have not been handled
484
506
        # yet.
485
507
        if not self._backing_indices:
486
 
            return # We won't find anything there either
 
508
            return  # We won't find anything there either
487
509
        # Remove all of the keys that we found locally
488
510
        keys.difference_update(local_keys)
489
511
        for backing in self._backing_indices:
512
534
            will be returned, and every match that is in the index will be
513
535
            returned.
514
536
        """
515
 
        # XXX: To much duplication with the GraphIndex class; consider finding
516
 
        # a good place to pull out the actual common logic.
517
537
        keys = set(keys)
518
538
        if not keys:
519
539
            return
524
544
                yield (self,) + node[1:]
525
545
        if self._key_length == 1:
526
546
            for key in keys:
527
 
                # sanity check
528
 
                if key[0] is None:
529
 
                    raise errors.BadIndexKey(key)
530
 
                if len(key) != self._key_length:
531
 
                    raise errors.BadIndexKey(key)
 
547
                index._sanity_check_key(self, key)
532
548
                try:
533
549
                    node = self._nodes[key]
534
550
                except KeyError:
538
554
                else:
539
555
                    yield self, key, node[1]
540
556
            return
541
 
        for key in keys:
542
 
            # sanity check
543
 
            if key[0] is None:
544
 
                raise errors.BadIndexKey(key)
545
 
            if len(key) != self._key_length:
546
 
                raise errors.BadIndexKey(key)
547
 
            # find what it refers to:
548
 
            key_dict = self._get_nodes_by_key()
549
 
            elements = list(key)
550
 
            # find the subdict to return
551
 
            try:
552
 
                while len(elements) and elements[0] is not None:
553
 
                    key_dict = key_dict[elements[0]]
554
 
                    elements.pop(0)
555
 
            except KeyError:
556
 
                # a non-existant lookup.
557
 
                continue
558
 
            if len(elements):
559
 
                dicts = [key_dict]
560
 
                while dicts:
561
 
                    key_dict = dicts.pop(-1)
562
 
                    # can't be empty or would not exist
563
 
                    item, value = key_dict.iteritems().next()
564
 
                    if type(value) == dict:
565
 
                        # push keys
566
 
                        dicts.extend(key_dict.itervalues())
567
 
                    else:
568
 
                        # yield keys
569
 
                        for value in key_dict.itervalues():
570
 
                            yield (self, ) + tuple(value)
571
 
            else:
572
 
                yield (self, ) + key_dict
 
557
        nodes_by_key = self._get_nodes_by_key()
 
558
        for entry in index._iter_entries_prefix(self, nodes_by_key, keys):
 
559
            yield entry
573
560
 
574
561
    def _get_nodes_by_key(self):
575
562
        if self._nodes_by_key is None:
576
563
            nodes_by_key = {}
577
564
            if self.reference_lists:
578
 
                for key, (references, value) in self._nodes.iteritems():
 
565
                for key, (references, value) in viewitems(self._nodes):
579
566
                    key_dict = nodes_by_key
580
567
                    for subkey in key[:-1]:
581
568
                        key_dict = key_dict.setdefault(subkey, {})
582
569
                    key_dict[key[-1]] = key, value, references
583
570
            else:
584
 
                for key, (references, value) in self._nodes.iteritems():
 
571
                for key, (references, value) in viewitems(self._nodes):
585
572
                    key_dict = nodes_by_key
586
573
                    for subkey in key[:-1]:
587
574
                        key_dict = key_dict.setdefault(subkey, {})
594
581
 
595
582
        For InMemoryGraphIndex the estimate is exact.
596
583
        """
597
 
        return len(self._nodes) + sum(backing.key_count() for backing in
598
 
            self._backing_indices if backing is not None)
 
584
        return len(self._nodes) + sum(
 
585
            backing.key_count()
 
586
            for backing in self._backing_indices
 
587
            if backing is not None)
599
588
 
600
589
    def validate(self):
601
590
        """In memory index's have no known corruption at the moment."""
602
591
 
603
 
 
604
 
class _LeafNode(object):
 
592
    def __lt__(self, other):
 
593
        if isinstance(other, type(self)):
 
594
            return self._nodes < other._nodes
 
595
        # Always sort existing indexes before ones that are still being built.
 
596
        if isinstance(other, BTreeGraphIndex):
 
597
            return False
 
598
        raise TypeError
 
599
 
 
600
 
 
601
class _LeafNode(dict):
605
602
    """A leaf node for a serialised B+Tree index."""
606
603
 
607
 
    __slots__ = ('keys', 'min_key', 'max_key')
 
604
    __slots__ = ('min_key', 'max_key', '_keys')
608
605
 
609
606
    def __init__(self, bytes, key_length, ref_list_length):
610
607
        """Parse bytes to create a leaf node object."""
611
608
        # splitlines mangles the \r delimiters.. don't use it.
612
 
        key_list = _btree_serializer._parse_leaf_lines(bytes,
613
 
            key_length, ref_list_length)
 
609
        key_list = _btree_serializer._parse_leaf_lines(
 
610
            bytes, key_length, ref_list_length)
614
611
        if key_list:
615
612
            self.min_key = key_list[0][0]
616
613
            self.max_key = key_list[-1][0]
617
614
        else:
618
615
            self.min_key = self.max_key = None
619
 
        self.keys = dict(key_list)
 
616
        super(_LeafNode, self).__init__(key_list)
 
617
        self._keys = dict(self)
 
618
 
 
619
    def all_items(self):
 
620
        """Return a sorted list of (key, (value, refs)) items"""
 
621
        items = sorted(self.items())
 
622
        return items
 
623
 
 
624
    def all_keys(self):
 
625
        """Return a sorted list of all keys."""
 
626
        keys = sorted(self.keys())
 
627
        return keys
620
628
 
621
629
 
622
630
class _InternalNode(object):
627
635
    def __init__(self, bytes):
628
636
        """Parse bytes to create an internal node object."""
629
637
        # splitlines mangles the \r delimiters.. don't use it.
630
 
        self.keys = self._parse_lines(bytes.split('\n'))
 
638
        self.keys = self._parse_lines(bytes.split(b'\n'))
631
639
 
632
640
    def _parse_lines(self, lines):
633
641
        nodes = []
634
642
        self.offset = int(lines[1][7:])
635
643
        as_st = static_tuple.StaticTuple.from_sequence
636
644
        for line in lines[2:]:
637
 
            if line == '':
 
645
            if line == b'':
638
646
                break
639
 
            nodes.append(as_st(map(intern, line.split('\0'))).intern())
 
647
            # GZ 2017-05-24: Used to intern() each chunk of line as well, need
 
648
            # to recheck performance and perhaps adapt StaticTuple to adjust.
 
649
            nodes.append(as_st(line.split(b'\0')).intern())
640
650
        return nodes
641
651
 
642
652
 
671
681
        self._recommended_pages = self._compute_recommended_pages()
672
682
        self._root_node = None
673
683
        self._base_offset = offset
 
684
        self._leaf_factory = _LeafNode
674
685
        # Default max size is 100,000 leave values
675
 
        self._leaf_value_cache = None # lru_cache.LRUCache(100*1000)
 
686
        self._leaf_value_cache = None  # lru_cache.LRUCache(100*1000)
676
687
        if unlimited_cache:
677
688
            self._leaf_node_cache = {}
678
689
            self._internal_node_cache = {}
684
695
            self._internal_node_cache = fifo_cache.FIFOCache(100)
685
696
        self._key_count = None
686
697
        self._row_lengths = None
687
 
        self._row_offsets = None # Start of each row, [-1] is the end
 
698
        self._row_offsets = None  # Start of each row, [-1] is the end
 
699
 
 
700
    def __hash__(self):
 
701
        return id(self)
688
702
 
689
703
    def __eq__(self, other):
690
704
        """Equal when self and other were created with the same parameters."""
691
705
        return (
692
 
            type(self) == type(other) and
693
 
            self._transport == other._transport and
694
 
            self._name == other._name and
695
 
            self._size == other._size)
 
706
            isinstance(self, type(other))
 
707
            and self._transport == other._transport
 
708
            and self._name == other._name
 
709
            and self._size == other._size)
 
710
 
 
711
    def __lt__(self, other):
 
712
        if isinstance(other, type(self)):
 
713
            return ((self._name, self._size) < (other._name, other._size))
 
714
        # Always sort existing indexes before ones that are still being built.
 
715
        if isinstance(other, BTreeBuilder):
 
716
            return True
 
717
        raise TypeError
696
718
 
697
719
    def __ne__(self, other):
698
720
        return not self.__eq__(other)
713
735
        found = {}
714
736
        start_of_leaves = None
715
737
        for node_pos, node in self._read_nodes(sorted(nodes)):
716
 
            if node_pos == 0: # Special case
 
738
            if node_pos == 0:  # Special case
717
739
                self._root_node = node
718
740
            else:
719
741
                if start_of_leaves is None:
732
754
        pages fit in that length.
733
755
        """
734
756
        recommended_read = self._transport.recommended_page_size()
735
 
        recommended_pages = int(math.ceil(recommended_read /
736
 
                                          float(_PAGE_SIZE)))
 
757
        recommended_pages = int(math.ceil(recommended_read / _PAGE_SIZE))
737
758
        return recommended_pages
738
759
 
739
760
    def _compute_total_pages_in_index(self):
750
771
            return self._row_offsets[-1]
751
772
        # This is the number of pages as defined by the size of the index. They
752
773
        # should be indentical.
753
 
        total_pages = int(math.ceil(self._size / float(_PAGE_SIZE)))
 
774
        total_pages = int(math.ceil(self._size / _PAGE_SIZE))
754
775
        return total_pages
755
776
 
756
777
    def _expand_offsets(self, offsets):
787
808
        if total_pages - len(cached_offsets) <= self._recommended_pages:
788
809
            # Read whatever is left
789
810
            if cached_offsets:
790
 
                expanded = [x for x in xrange(total_pages)
791
 
                               if x not in cached_offsets]
 
811
                expanded = [x for x in range(total_pages)
 
812
                            if x not in cached_offsets]
792
813
            else:
793
 
                expanded = range(total_pages)
 
814
                expanded = list(range(total_pages))
794
815
            if 'index' in debug.debug_flags:
795
816
                trace.mutter('  reading all unread pages: %s', expanded)
796
817
            return expanded
845
866
                if first is None:
846
867
                    first, end = self._find_layer_first_and_end(pos)
847
868
                previous = pos - 1
848
 
                if (previous > 0
849
 
                    and previous not in cached_offsets
850
 
                    and previous not in final_offsets
851
 
                    and previous >= first):
 
869
                if (previous > 0 and
 
870
                    previous not in cached_offsets and
 
871
                    previous not in final_offsets and
 
872
                        previous >= first):
852
873
                    next_tips.add(previous)
853
874
                after = pos + 1
854
 
                if (after < total_pages
855
 
                    and after not in cached_offsets
856
 
                    and after not in final_offsets
857
 
                    and after < end):
 
875
                if (after < total_pages and
 
876
                    after not in cached_offsets and
 
877
                    after not in final_offsets and
 
878
                        after < end):
858
879
                    next_tips.add(after)
859
880
                # This would keep us from going bigger than
860
881
                # recommended_pages by only expanding the first offsets.
884
905
            self._get_root_node()
885
906
        if ref_list_num + 1 > self.node_ref_lists:
886
907
            raise ValueError('No ref list %d, index has %d ref lists'
887
 
                % (ref_list_num, self.node_ref_lists))
 
908
                             % (ref_list_num, self.node_ref_lists))
888
909
        keys = set()
889
910
        refs = set()
890
911
        for node in self.iter_all_entries():
909
930
 
910
931
    def _get_offsets_to_cached_pages(self):
911
932
        """Determine what nodes we already have cached."""
912
 
        cached_offsets = set(self._internal_node_cache.keys())
 
933
        cached_offsets = set(self._internal_node_cache)
 
934
        # cache may be dict or LRUCache, keys() is the common method
913
935
        cached_offsets.update(self._leaf_node_cache.keys())
914
936
        if self._root_node is not None:
915
937
            cached_offsets.add(0)
948
970
    def _cache_leaf_values(self, nodes):
949
971
        """Cache directly from key => value, skipping the btree."""
950
972
        if self._leaf_value_cache is not None:
951
 
            for node in nodes.itervalues():
952
 
                for key, value in node.keys.iteritems():
 
973
            for node in viewvalues(nodes):
 
974
                for key, value in node.all_items():
953
975
                    if key in self._leaf_value_cache:
954
976
                        # Don't add the rest of the keys, we've seen this node
955
977
                        # before.
965
987
    def iter_all_entries(self):
966
988
        """Iterate over all keys within the index.
967
989
 
968
 
        :return: An iterable of (index, key, value) or (index, key, value, reference_lists).
 
990
        :return: An iterable of (index, key, value) or
 
991
            (index, key, value, reference_lists).
969
992
            The former tuple is used when there are no reference lists in the
970
993
            index, making the API compatible with simple key:value index types.
971
994
            There is no defined order for the result iteration - it will be in
972
995
            the most efficient order for the index.
973
996
        """
974
997
        if 'evil' in debug.debug_flags:
975
 
            trace.mutter_callsite(3,
976
 
                "iter_all_entries scales with size of history.")
 
998
            trace.mutter_callsite(
 
999
                3, "iter_all_entries scales with size of history.")
977
1000
        if not self.key_count():
978
1001
            return
979
1002
        if self._row_offsets[-1] == 1:
980
1003
            # There is only the root node, and we read that via key_count()
981
1004
            if self.node_ref_lists:
982
 
                for key, (value, refs) in sorted(self._root_node.keys.items()):
 
1005
                for key, (value, refs) in self._root_node.all_items():
983
1006
                    yield (self, key, value, refs)
984
1007
            else:
985
 
                for key, (value, refs) in sorted(self._root_node.keys.items()):
 
1008
                for key, (value, refs) in self._root_node.all_items():
986
1009
                    yield (self, key, value)
987
1010
            return
988
1011
        start_of_leaves = self._row_offsets[-2]
989
1012
        end_of_leaves = self._row_offsets[-1]
990
 
        needed_offsets = range(start_of_leaves, end_of_leaves)
 
1013
        needed_offsets = list(range(start_of_leaves, end_of_leaves))
991
1014
        if needed_offsets == [0]:
992
1015
            # Special case when we only have a root node, as we have already
993
1016
            # read everything
998
1021
        # for spilling index builds to disk.
999
1022
        if self.node_ref_lists:
1000
1023
            for _, node in nodes:
1001
 
                for key, (value, refs) in sorted(node.keys.items()):
 
1024
                for key, (value, refs) in node.all_items():
1002
1025
                    yield (self, key, value, refs)
1003
1026
        else:
1004
1027
            for _, node in nodes:
1005
 
                for key, (value, refs) in sorted(node.keys.items()):
 
1028
                for key, (value, refs) in node.all_items():
1006
1029
                    yield (self, key, value)
1007
1030
 
1008
1031
    @staticmethod
1031
1054
        #       function, so there is even more to be gained.
1032
1055
        # iter_steps = len(in_keys) + len(fixed_keys)
1033
1056
        # bisect_steps = len(in_keys) * math.log(len(fixed_keys), 2)
1034
 
        if len(in_keys) == 1: # Bisect will always be faster for M = 1
1035
 
            return [(bisect_right(fixed_keys, in_keys[0]), in_keys)]
 
1057
        if len(in_keys) == 1:  # Bisect will always be faster for M = 1
 
1058
            return [(bisect.bisect_right(fixed_keys, in_keys[0]), in_keys)]
1036
1059
        # elif bisect_steps < iter_steps:
1037
1060
        #     offsets = {}
1038
1061
        #     for key in in_keys:
1041
1064
        #     return [(o, offsets[o]) for o in sorted(offsets)]
1042
1065
        in_keys_iter = iter(in_keys)
1043
1066
        fixed_keys_iter = enumerate(fixed_keys)
1044
 
        cur_in_key = in_keys_iter.next()
1045
 
        cur_fixed_offset, cur_fixed_key = fixed_keys_iter.next()
1046
 
 
1047
 
        class InputDone(Exception): pass
1048
 
        class FixedDone(Exception): pass
 
1067
        cur_in_key = next(in_keys_iter)
 
1068
        cur_fixed_offset, cur_fixed_key = next(fixed_keys_iter)
 
1069
 
 
1070
        class InputDone(Exception):
 
1071
            pass
 
1072
 
 
1073
        class FixedDone(Exception):
 
1074
            pass
1049
1075
 
1050
1076
        output = []
1051
1077
        cur_out = []
1064
1090
                    while cur_in_key < cur_fixed_key:
1065
1091
                        cur_keys.append(cur_in_key)
1066
1092
                        try:
1067
 
                            cur_in_key = in_keys_iter.next()
 
1093
                            cur_in_key = next(in_keys_iter)
1068
1094
                        except StopIteration:
1069
1095
                            raise InputDone
1070
1096
                    # At this point cur_in_key must be >= cur_fixed_key
1072
1098
                # the end
1073
1099
                while cur_in_key >= cur_fixed_key:
1074
1100
                    try:
1075
 
                        cur_fixed_offset, cur_fixed_key = fixed_keys_iter.next()
 
1101
                        cur_fixed_offset, cur_fixed_key = next(fixed_keys_iter)
1076
1102
                    except StopIteration:
1077
1103
                        raise FixedDone
1078
1104
        except InputDone:
1110
1136
                positions = self._multi_bisect_right(sub_keys, node.keys)
1111
1137
                node_offset = next_row_start + node.offset
1112
1138
                next_nodes_and_keys.extend([(node_offset + pos, s_keys)
1113
 
                                           for pos, s_keys in positions])
 
1139
                                            for pos, s_keys in positions])
1114
1140
            keys_at_index = next_nodes_and_keys
1115
1141
        # We should now be at the _LeafNodes
1116
1142
        node_indexes = [idx for idx, s_keys in keys_at_index]
1159
1185
                else:
1160
1186
                    needed_keys.append(key)
1161
1187
 
1162
 
        last_key = None
1163
1188
        needed_keys = keys
1164
1189
        if not needed_keys:
1165
1190
            return
1169
1194
                continue
1170
1195
            node = nodes[node_index]
1171
1196
            for next_sub_key in sub_keys:
1172
 
                if next_sub_key in node.keys:
1173
 
                    value, refs = node.keys[next_sub_key]
 
1197
                if next_sub_key in node:
 
1198
                    value, refs = node[next_sub_key]
1174
1199
                    if self.node_ref_lists:
1175
1200
                        yield (self, next_sub_key, value, refs)
1176
1201
                    else:
1208
1233
            return set()
1209
1234
        if ref_list_num >= self.node_ref_lists:
1210
1235
            raise ValueError('No ref list %d, index has %d ref lists'
1211
 
                % (ref_list_num, self.node_ref_lists))
 
1236
                             % (ref_list_num, self.node_ref_lists))
1212
1237
 
1213
1238
        # The main trick we are trying to accomplish is that when we find a
1214
1239
        # key listing its parents, we expect that the parent key is also likely
1244
1269
            # sub_keys is all of the keys we are looking for that should exist
1245
1270
            # on this page, if they aren't here, then they won't be found
1246
1271
            node = nodes[node_index]
1247
 
            node_keys = node.keys
1248
1272
            parents_to_check = set()
1249
1273
            for next_sub_key in sub_keys:
1250
 
                if next_sub_key not in node_keys:
 
1274
                if next_sub_key not in node:
1251
1275
                    # This one is just not present in the index at all
1252
1276
                    missing_keys.add(next_sub_key)
1253
1277
                else:
1254
 
                    value, refs = node_keys[next_sub_key]
 
1278
                    value, refs = node[next_sub_key]
1255
1279
                    parent_keys = refs[ref_list_num]
1256
1280
                    parent_map[next_sub_key] = parent_keys
1257
1281
                    parents_to_check.update(parent_keys)
1264
1288
            while parents_to_check:
1265
1289
                next_parents_to_check = set()
1266
1290
                for key in parents_to_check:
1267
 
                    if key in node_keys:
1268
 
                        value, refs = node_keys[key]
 
1291
                    if key in node:
 
1292
                        value, refs = node[key]
1269
1293
                        parent_keys = refs[ref_list_num]
1270
1294
                        parent_map[key] = parent_keys
1271
1295
                        next_parents_to_check.update(parent_keys)
1290
1314
                            # LeafNode
1291
1315
                            parents_not_on_page.add(key)
1292
1316
                        else:
1293
 
                            # assert key != node.min_key and key != node.max_key
 
1317
                            # assert (key != node.min_key and
 
1318
                            #         key != node.max_key)
1294
1319
                            # If it was going to be present, it would be on
1295
1320
                            # *this* page, so mark it missing.
1296
1321
                            missing_keys.add(key)
1333
1358
            self._get_root_node()
1334
1359
        # TODO: only access nodes that can satisfy the prefixes we are looking
1335
1360
        # for. For now, to meet API usage (as this function is not used by
1336
 
        # current bzrlib) just suck the entire index and iterate in memory.
 
1361
        # current breezy) just suck the entire index and iterate in memory.
1337
1362
        nodes = {}
1338
1363
        if self.node_ref_lists:
1339
1364
            if self._key_length == 1:
1365
1390
                    key_dict[key[-1]] = key_value
1366
1391
        if self._key_length == 1:
1367
1392
            for key in keys:
1368
 
                # sanity check
1369
 
                if key[0] is None:
1370
 
                    raise errors.BadIndexKey(key)
1371
 
                if len(key) != self._key_length:
1372
 
                    raise errors.BadIndexKey(key)
 
1393
                index._sanity_check_key(self, key)
1373
1394
                try:
1374
1395
                    if self.node_ref_lists:
1375
1396
                        value, node_refs = nodes[key]
1379
1400
                except KeyError:
1380
1401
                    pass
1381
1402
            return
1382
 
        for key in keys:
1383
 
            # sanity check
1384
 
            if key[0] is None:
1385
 
                raise errors.BadIndexKey(key)
1386
 
            if len(key) != self._key_length:
1387
 
                raise errors.BadIndexKey(key)
1388
 
            # find what it refers to:
1389
 
            key_dict = nodes_by_key
1390
 
            elements = list(key)
1391
 
            # find the subdict whose contents should be returned.
1392
 
            try:
1393
 
                while len(elements) and elements[0] is not None:
1394
 
                    key_dict = key_dict[elements[0]]
1395
 
                    elements.pop(0)
1396
 
            except KeyError:
1397
 
                # a non-existant lookup.
1398
 
                continue
1399
 
            if len(elements):
1400
 
                dicts = [key_dict]
1401
 
                while dicts:
1402
 
                    key_dict = dicts.pop(-1)
1403
 
                    # can't be empty or would not exist
1404
 
                    item, value = key_dict.iteritems().next()
1405
 
                    if type(value) == dict:
1406
 
                        # push keys
1407
 
                        dicts.extend(key_dict.itervalues())
1408
 
                    else:
1409
 
                        # yield keys
1410
 
                        for value in key_dict.itervalues():
1411
 
                            # each value is the key:value:node refs tuple
1412
 
                            # ready to yield.
1413
 
                            yield (self, ) + value
1414
 
            else:
1415
 
                # the last thing looked up was a terminal element
1416
 
                yield (self, ) + key_dict
 
1403
        for entry in index._iter_entries_prefix(self, nodes_by_key, keys):
 
1404
            yield entry
1417
1405
 
1418
1406
    def key_count(self):
1419
1407
        """Return an estimate of the number of keys in this index.
1444
1432
        """
1445
1433
        signature = bytes[0:len(self._signature())]
1446
1434
        if not signature == self._signature():
1447
 
            raise errors.BadIndexFormatSignature(self._name, BTreeGraphIndex)
 
1435
            raise index.BadIndexFormatSignature(self._name, BTreeGraphIndex)
1448
1436
        lines = bytes[len(self._signature()):].splitlines()
1449
1437
        options_line = lines[0]
1450
1438
        if not options_line.startswith(_OPTION_NODE_REFS):
1451
 
            raise errors.BadIndexOptions(self)
 
1439
            raise index.BadIndexOptions(self)
1452
1440
        try:
1453
1441
            self.node_ref_lists = int(options_line[len(_OPTION_NODE_REFS):])
1454
1442
        except ValueError:
1455
 
            raise errors.BadIndexOptions(self)
 
1443
            raise index.BadIndexOptions(self)
1456
1444
        options_line = lines[1]
1457
1445
        if not options_line.startswith(_OPTION_KEY_ELEMENTS):
1458
 
            raise errors.BadIndexOptions(self)
 
1446
            raise index.BadIndexOptions(self)
1459
1447
        try:
1460
1448
            self._key_length = int(options_line[len(_OPTION_KEY_ELEMENTS):])
1461
1449
        except ValueError:
1462
 
            raise errors.BadIndexOptions(self)
 
1450
            raise index.BadIndexOptions(self)
1463
1451
        options_line = lines[2]
1464
1452
        if not options_line.startswith(_OPTION_LEN):
1465
 
            raise errors.BadIndexOptions(self)
 
1453
            raise index.BadIndexOptions(self)
1466
1454
        try:
1467
1455
            self._key_count = int(options_line[len(_OPTION_LEN):])
1468
1456
        except ValueError:
1469
 
            raise errors.BadIndexOptions(self)
 
1457
            raise index.BadIndexOptions(self)
1470
1458
        options_line = lines[3]
1471
1459
        if not options_line.startswith(_OPTION_ROW_LENGTHS):
1472
 
            raise errors.BadIndexOptions(self)
 
1460
            raise index.BadIndexOptions(self)
1473
1461
        try:
1474
 
            self._row_lengths = map(int, [length for length in
1475
 
                options_line[len(_OPTION_ROW_LENGTHS):].split(',')
1476
 
                if len(length)])
 
1462
            self._row_lengths = [int(length) for length in
 
1463
                                 options_line[len(_OPTION_ROW_LENGTHS):].split(
 
1464
                                     b',')
 
1465
                                 if length]
1477
1466
        except ValueError:
1478
 
            raise errors.BadIndexOptions(self)
 
1467
            raise index.BadIndexOptions(self)
1479
1468
        self._compute_row_offsets()
1480
1469
 
1481
1470
        # calculate the bytes we have processed
1514
1503
                    self._size = num_bytes - base_offset
1515
1504
                    # the whole thing should be parsed out of 'bytes'
1516
1505
                    ranges = [(start, min(_PAGE_SIZE, num_bytes - start))
1517
 
                        for start in xrange(base_offset, num_bytes, _PAGE_SIZE)]
 
1506
                              for start in range(
 
1507
                                  base_offset, num_bytes, _PAGE_SIZE)]
1518
1508
                    break
1519
1509
            else:
1520
1510
                if offset > self._size:
1527
1517
            return
1528
1518
        elif bytes is not None:
1529
1519
            # already have the whole file
1530
 
            data_ranges = [(start, bytes[start:start+size])
 
1520
            data_ranges = [(start, bytes[start:start + size])
1531
1521
                           for start, size in ranges]
1532
1522
        elif self._file is None:
1533
1523
            data_ranges = self._transport.readv(self._name, ranges)
1545
1535
                    continue
1546
1536
            bytes = zlib.decompress(data)
1547
1537
            if bytes.startswith(_LEAF_FLAG):
1548
 
                node = _LeafNode(bytes, self._key_length, self.node_ref_lists)
 
1538
                node = self._leaf_factory(bytes, self._key_length,
 
1539
                                          self.node_ref_lists)
1549
1540
            elif bytes.startswith(_INTERNAL_FLAG):
1550
1541
                node = _InternalNode(bytes)
1551
1542
            else:
1552
1543
                raise AssertionError("Unknown node type for %r" % bytes)
1553
 
            yield offset / _PAGE_SIZE, node
 
1544
            yield offset // _PAGE_SIZE, node
1554
1545
 
1555
1546
    def _signature(self):
1556
1547
        """The file signature for this index type."""
1566
1557
            # We shouldn't be reading anything anyway
1567
1558
            start_node = 1
1568
1559
        node_end = self._row_offsets[-1]
1569
 
        for node in self._read_nodes(range(start_node, node_end)):
 
1560
        for node in self._read_nodes(list(range(start_node, node_end))):
1570
1561
            pass
1571
1562
 
1572
1563
 
 
1564
_gcchk_factory = _LeafNode
 
1565
 
1573
1566
try:
1574
 
    from bzrlib import _btree_serializer_pyx as _btree_serializer
1575
 
except ImportError, e:
 
1567
    from . import _btree_serializer_pyx as _btree_serializer
 
1568
    _gcchk_factory = _btree_serializer._parse_into_chk
 
1569
except ImportError as e:
1576
1570
    osutils.failed_to_load_extension(e)
1577
 
    from bzrlib import _btree_serializer_py as _btree_serializer
 
1571
    from . import _btree_serializer_py as _btree_serializer