/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 bzrlib/btree_index.py

  • Committer: John Ferlito
  • Date: 2009-09-02 04:31:45 UTC
  • mto: (4665.7.1 serve-init)
  • mto: This revision was merged to the branch mainline in revision 4913.
  • Revision ID: johnf@inodes.org-20090902043145-gxdsfw03ilcwbyn5
Add a debian init script for bzr --serve

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2008-2011 Canonical Ltd
 
1
# Copyright (C) 2008 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
 
from io import BytesIO
21
 
 
22
 
from ..lazy_import import lazy_import
23
 
lazy_import(globals(), """
24
 
import bisect
 
20
from bisect import bisect_right
25
21
import math
26
22
import tempfile
27
23
import zlib
28
 
""")
29
24
 
30
 
from .. import (
 
25
from bzrlib import (
31
26
    chunk_writer,
32
27
    debug,
 
28
    errors,
33
29
    fifo_cache,
 
30
    index,
34
31
    lru_cache,
35
32
    osutils,
36
 
    static_tuple,
37
33
    trace,
38
 
    transport,
39
 
    )
40
 
from . import (
41
 
    index,
42
 
    )
43
 
from .index import _OPTION_NODE_REFS, _OPTION_KEY_ELEMENTS, _OPTION_LEN
44
 
 
45
 
 
46
 
_BTSIGNATURE = b"B+Tree Graph Index 2\n"
47
 
_OPTION_ROW_LENGTHS = b"row_lengths="
48
 
_LEAF_FLAG = b"type=leaf\n"
49
 
_INTERNAL_FLAG = b"type=internal\n"
50
 
_INTERNAL_OFFSET = b"offset="
 
34
    )
 
35
from bzrlib.index import _OPTION_NODE_REFS, _OPTION_KEY_ELEMENTS, _OPTION_LEN
 
36
from bzrlib.transport import get_transport
 
37
 
 
38
 
 
39
_BTSIGNATURE = "B+Tree Graph Index 2\n"
 
40
_OPTION_ROW_LENGTHS = "row_lengths="
 
41
_LEAF_FLAG = "type=leaf\n"
 
42
_INTERNAL_FLAG = "type=internal\n"
 
43
_INTERNAL_OFFSET = "offset="
51
44
 
52
45
_RESERVED_HEADER_BYTES = 120
53
46
_PAGE_SIZE = 4096
67
60
    def __init__(self):
68
61
        """Create a _BuilderRow."""
69
62
        self.nodes = 0
70
 
        self.spool = None  # tempfile.TemporaryFile(prefix='bzr-index-row-')
 
63
        self.spool = tempfile.TemporaryFile()
71
64
        self.writer = None
72
65
 
73
66
    def finish_node(self, pad=True):
74
67
        byte_lines, _, padding = self.writer.finish()
75
68
        if self.nodes == 0:
76
 
            self.spool = BytesIO()
77
69
            # padded note:
78
 
            self.spool.write(b"\x00" * _RESERVED_HEADER_BYTES)
79
 
        elif self.nodes == 1:
80
 
            # We got bigger than 1 node, switch to a temp file
81
 
            spool = tempfile.TemporaryFile(prefix='bzr-index-row-')
82
 
            spool.write(self.spool.getvalue())
83
 
            self.spool = spool
 
70
            self.spool.write("\x00" * _RESERVED_HEADER_BYTES)
84
71
        skipped_bytes = 0
85
72
        if not pad and padding:
86
73
            del byte_lines[-1]
142
129
            of nodes that BTreeBuilder will hold in memory.
143
130
        """
144
131
        index.GraphIndexBuilder.__init__(self, reference_lists=reference_lists,
145
 
                                         key_elements=key_elements)
 
132
            key_elements=key_elements)
146
133
        self._spill_at = spill_at
147
134
        self._backing_indices = []
148
135
        # A map of {key: (node_refs, value)}
163
150
        :param references: An iterable of iterables of keys. Each is a
164
151
            reference to another key.
165
152
        :param value: The value to associate with the key. It may be any
166
 
            bytes as long as it does not contain \\0 or \\n.
 
153
            bytes as long as it does not contain \0 or \n.
167
154
        """
168
 
        # Ensure that 'key' is a StaticTuple
169
 
        key = static_tuple.StaticTuple.from_sequence(key).intern()
170
155
        # we don't care about absent_references
171
156
        node_refs, _ = self._check_key_ref_value(key, references, value)
172
157
        if key in self._nodes:
173
 
            raise index.BadIndexDuplicateKey(key, self)
174
 
        self._nodes[key] = static_tuple.StaticTuple(node_refs, value)
 
158
            raise errors.BadIndexDuplicateKey(key, self)
 
159
        self._nodes[key] = (node_refs, value)
 
160
        self._keys.add(key)
175
161
        if self._nodes_by_key is not None and self._key_length > 1:
176
162
            self._update_nodes_by_key(key, value, node_refs)
177
 
        if len(self._nodes) < self._spill_at:
 
163
        if len(self._keys) < self._spill_at:
178
164
            return
179
165
        self._spill_mem_keys_to_disk()
180
166
 
196
182
             backing_pos) = self._spill_mem_keys_and_combine()
197
183
        else:
198
184
            new_backing_file, size = self._spill_mem_keys_without_combining()
 
185
        dir_path, base_name = osutils.split(new_backing_file.name)
199
186
        # Note: The transport here isn't strictly needed, because we will use
200
187
        #       direct access to the new_backing._file object
201
 
        new_backing = BTreeGraphIndex(transport.get_transport_from_path('.'),
202
 
                                      '<temp>', size)
 
188
        new_backing = BTreeGraphIndex(get_transport(dir_path),
 
189
                                      base_name, size)
203
190
        # GC will clean up the file
204
191
        new_backing._file = new_backing_file
205
192
        if self._combine_backing_indices:
210
197
                self._backing_indices[backing_pos] = None
211
198
        else:
212
199
            self._backing_indices.append(new_backing)
 
200
        self._keys = set()
213
201
        self._nodes = {}
214
202
        self._nodes_by_key = None
215
203
 
262
250
        current_values = []
263
251
        for iterator in iterators_to_combine:
264
252
            try:
265
 
                current_values.append(next(iterator))
 
253
                current_values.append(iterator.next())
266
254
            except StopIteration:
267
255
                current_values.append(None)
268
256
        last = None
269
257
        while True:
270
258
            # Decorate candidates with the value to allow 2.4's min to be used.
271
259
            candidates = [(item[1][1], item) for item
272
 
                          in enumerate(current_values) if item[1] is not None]
 
260
                in enumerate(current_values) if item[1] is not None]
273
261
            if not len(candidates):
274
262
                return
275
263
            selected = min(candidates)
276
264
            # undecorate back to (pos, node)
277
265
            selected = selected[1]
278
266
            if last == selected[1][1]:
279
 
                raise index.BadIndexDuplicateKey(last, self)
 
267
                raise errors.BadIndexDuplicateKey(last, self)
280
268
            last = selected[1][1]
281
269
            # Yield, with self as the index
282
270
            yield (self,) + selected[1][1:]
283
271
            pos = selected[0]
284
272
            try:
285
 
                current_values[pos] = next(iterators_to_combine[pos])
 
273
                current_values[pos] = iterators_to_combine[pos].next()
286
274
            except StopIteration:
287
275
                current_values[pos] = None
288
276
 
295
283
            flag when writing out. This is used by the _spill_mem_keys_to_disk
296
284
            functionality.
297
285
        """
298
 
        new_leaf = False
299
286
        if rows[-1].writer is None:
300
287
            # opening a new leaf chunk;
301
 
            new_leaf = True
302
288
            for pos, internal_row in enumerate(rows[:-1]):
303
289
                # flesh out any internal nodes that are needed to
304
290
                # preserve the height of the tree
305
291
                if internal_row.writer is None:
306
292
                    length = _PAGE_SIZE
307
293
                    if internal_row.nodes == 0:
308
 
                        length -= _RESERVED_HEADER_BYTES  # padded
 
294
                        length -= _RESERVED_HEADER_BYTES # padded
309
295
                    if allow_optimize:
310
296
                        optimize_for_size = self._optimize_for_size
311
297
                    else:
312
298
                        optimize_for_size = False
313
 
                    internal_row.writer = chunk_writer.ChunkWriter(
314
 
                        length, 0, optimize_for_size=optimize_for_size)
 
299
                    internal_row.writer = chunk_writer.ChunkWriter(length, 0,
 
300
                        optimize_for_size=optimize_for_size)
315
301
                    internal_row.writer.write(_INTERNAL_FLAG)
316
 
                    internal_row.writer.write(_INTERNAL_OFFSET
317
 
                                              + b"%d\n" % rows[pos + 1].nodes)
 
302
                    internal_row.writer.write(_INTERNAL_OFFSET +
 
303
                        str(rows[pos + 1].nodes) + "\n")
318
304
            # add a new leaf
319
305
            length = _PAGE_SIZE
320
306
            if rows[-1].nodes == 0:
321
 
                length -= _RESERVED_HEADER_BYTES  # padded
322
 
            rows[-1].writer = chunk_writer.ChunkWriter(
323
 
                length, optimize_for_size=self._optimize_for_size)
 
307
                length -= _RESERVED_HEADER_BYTES # padded
 
308
            rows[-1].writer = chunk_writer.ChunkWriter(length,
 
309
                optimize_for_size=self._optimize_for_size)
324
310
            rows[-1].writer.write(_LEAF_FLAG)
325
311
        if rows[-1].writer.write(line):
326
 
            # if we failed to write, despite having an empty page to write to,
327
 
            # then line is too big. raising the error avoids infinite recursion
328
 
            # searching for a suitably large page that will not be found.
329
 
            if new_leaf:
330
 
                raise index.BadIndexKey(string_key)
331
312
            # this key did not fit in the node:
332
313
            rows[-1].finish_node()
333
 
            key_line = string_key + b"\n"
 
314
            key_line = string_key + "\n"
334
315
            new_row = True
335
316
            for row in reversed(rows[:-1]):
336
317
                # Mark the start of the next node in the node above. If it
357
338
                    reserved_bytes,
358
339
                    optimize_for_size=self._optimize_for_size)
359
340
                new_row.writer.write(_INTERNAL_FLAG)
360
 
                new_row.writer.write(_INTERNAL_OFFSET
361
 
                                     + b"%d\n" % (rows[1].nodes - 1))
 
341
                new_row.writer.write(_INTERNAL_OFFSET +
 
342
                    str(rows[1].nodes - 1) + "\n")
362
343
                new_row.writer.write(key_line)
363
 
            self._add_key(string_key, line, rows,
364
 
                          allow_optimize=allow_optimize)
 
344
            self._add_key(string_key, line, rows, allow_optimize=allow_optimize)
365
345
 
366
346
    def _write_nodes(self, node_iterator, allow_optimize=True):
367
347
        """Write node_iterator out as a B+Tree.
393
373
                # First key triggers the first row
394
374
                rows.append(_LeafBuilderRow())
395
375
            key_count += 1
396
 
            string_key, line = _btree_serializer._flatten_node(
397
 
                node, self.reference_lists)
398
 
            self._add_key(string_key, line, rows,
399
 
                          allow_optimize=allow_optimize)
 
376
            string_key, line = _btree_serializer._flatten_node(node,
 
377
                                    self.reference_lists)
 
378
            self._add_key(string_key, line, rows, allow_optimize=allow_optimize)
400
379
        for row in reversed(rows):
401
 
            pad = (not isinstance(row, _LeafBuilderRow))
 
380
            pad = (type(row) != _LeafBuilderRow)
402
381
            row.finish_node(pad=pad)
 
382
        result = tempfile.NamedTemporaryFile(prefix='bzr-index-')
403
383
        lines = [_BTSIGNATURE]
404
 
        lines.append(b'%s%d\n' % (_OPTION_NODE_REFS, self.reference_lists))
405
 
        lines.append(b'%s%d\n' % (_OPTION_KEY_ELEMENTS, self._key_length))
406
 
        lines.append(b'%s%d\n' % (_OPTION_LEN, key_count))
 
384
        lines.append(_OPTION_NODE_REFS + str(self.reference_lists) + '\n')
 
385
        lines.append(_OPTION_KEY_ELEMENTS + str(self._key_length) + '\n')
 
386
        lines.append(_OPTION_LEN + str(key_count) + '\n')
407
387
        row_lengths = [row.nodes for row in rows]
408
 
        lines.append(_OPTION_ROW_LENGTHS + ','.join(
409
 
            map(str, row_lengths)).encode('ascii') + b'\n')
410
 
        if row_lengths and row_lengths[-1] > 1:
411
 
            result = tempfile.NamedTemporaryFile(prefix='bzr-index-')
412
 
        else:
413
 
            result = BytesIO()
 
388
        lines.append(_OPTION_ROW_LENGTHS + ','.join(map(str, row_lengths)) + '\n')
414
389
        result.writelines(lines)
415
390
        position = sum(map(len, lines))
 
391
        root_row = True
416
392
        if position > _RESERVED_HEADER_BYTES:
417
393
            raise AssertionError("Could not fit the header in the"
418
394
                                 " reserved space: %d > %d"
419
395
                                 % (position, _RESERVED_HEADER_BYTES))
420
396
        # write the rows out:
421
397
        for row in rows:
422
 
            reserved = _RESERVED_HEADER_BYTES  # reserved space for first node
 
398
            reserved = _RESERVED_HEADER_BYTES # reserved space for first node
423
399
            row.spool.flush()
424
400
            row.spool.seek(0)
425
401
            # copy nodes to the finalised file.
426
402
            # Special case the first node as it may be prefixed
427
403
            node = row.spool.read(_PAGE_SIZE)
428
404
            result.write(node[reserved:])
429
 
            if len(node) == _PAGE_SIZE:
430
 
                result.write(b"\x00" * (reserved - position))
431
 
            position = 0  # Only the root row actually has an offset
 
405
            result.write("\x00" * (reserved - position))
 
406
            position = 0 # Only the root row actually has an offset
432
407
            copied_len = osutils.pumpfile(row.spool, result)
433
408
            if copied_len != (row.nodes - 1) * _PAGE_SIZE:
434
 
                if not isinstance(row, _LeafBuilderRow):
 
409
                if type(row) != _LeafBuilderRow:
435
410
                    raise AssertionError("Incorrect amount of data copied"
436
 
                                         " expected: %d, got: %d"
437
 
                                         % ((row.nodes - 1) * _PAGE_SIZE,
438
 
                                            copied_len))
 
411
                        " expected: %d, got: %d"
 
412
                        % ((row.nodes - 1) * _PAGE_SIZE,
 
413
                           copied_len))
439
414
        result.flush()
440
415
        size = result.tell()
441
416
        result.seek(0)
457
432
            efficient order for the index (in this case dictionary hash order).
458
433
        """
459
434
        if 'evil' in debug.debug_flags:
460
 
            trace.mutter_callsite(
461
 
                3, "iter_all_entries scales with size of history.")
 
435
            trace.mutter_callsite(3,
 
436
                "iter_all_entries scales with size of history.")
462
437
        # Doing serial rather than ordered would be faster; but this shouldn't
463
438
        # be getting called routinely anyway.
464
439
        iterators = [self._iter_mem_nodes()]
473
448
        """Iterate over keys within the index.
474
449
 
475
450
        :param keys: An iterable providing the keys to be retrieved.
476
 
        :return: An iterable of (index, key, value, reference_lists). There is
477
 
            no defined order for the result iteration - it will be in the most
 
451
        :return: An iterable of (index, key, value, reference_lists). There is no
 
452
            defined order for the result iteration - it will be in the most
478
453
            efficient order for the index (keys iteration order in this case).
479
454
        """
480
455
        keys = set(keys)
481
 
        # Note: We don't use keys.intersection() here. If you read the C api,
482
 
        #       set.intersection(other) special cases when other is a set and
483
 
        #       will iterate the smaller of the two and lookup in the other.
484
 
        #       It does *not* do this for any other type (even dict, unlike
485
 
        #       some other set functions.) Since we expect keys is generally <<
486
 
        #       self._nodes, it is faster to iterate over it in a list
487
 
        #       comprehension
488
 
        nodes = self._nodes
489
 
        local_keys = [key for key in keys if key in nodes]
 
456
        local_keys = keys.intersection(self._keys)
490
457
        if self.reference_lists:
491
458
            for key in local_keys:
492
 
                node = nodes[key]
 
459
                node = self._nodes[key]
493
460
                yield self, key, node[1], node[0]
494
461
        else:
495
462
            for key in local_keys:
496
 
                node = nodes[key]
 
463
                node = self._nodes[key]
497
464
                yield self, key, node[1]
498
465
        # Find things that are in backing indices that have not been handled
499
466
        # yet.
500
467
        if not self._backing_indices:
501
 
            return  # We won't find anything there either
 
468
            return # We won't find anything there either
502
469
        # Remove all of the keys that we found locally
503
470
        keys.difference_update(local_keys)
504
471
        for backing in self._backing_indices:
527
494
            will be returned, and every match that is in the index will be
528
495
            returned.
529
496
        """
 
497
        # XXX: To much duplication with the GraphIndex class; consider finding
 
498
        # a good place to pull out the actual common logic.
530
499
        keys = set(keys)
531
500
        if not keys:
532
501
            return
537
506
                yield (self,) + node[1:]
538
507
        if self._key_length == 1:
539
508
            for key in keys:
540
 
                index._sanity_check_key(self, key)
 
509
                # sanity check
 
510
                if key[0] is None:
 
511
                    raise errors.BadIndexKey(key)
 
512
                if len(key) != self._key_length:
 
513
                    raise errors.BadIndexKey(key)
541
514
                try:
542
515
                    node = self._nodes[key]
543
516
                except KeyError:
547
520
                else:
548
521
                    yield self, key, node[1]
549
522
            return
550
 
        nodes_by_key = self._get_nodes_by_key()
551
 
        for entry in index._iter_entries_prefix(self, nodes_by_key, keys):
552
 
            yield entry
 
523
        for key in keys:
 
524
            # sanity check
 
525
            if key[0] is None:
 
526
                raise errors.BadIndexKey(key)
 
527
            if len(key) != self._key_length:
 
528
                raise errors.BadIndexKey(key)
 
529
            # find what it refers to:
 
530
            key_dict = self._get_nodes_by_key()
 
531
            elements = list(key)
 
532
            # find the subdict to return
 
533
            try:
 
534
                while len(elements) and elements[0] is not None:
 
535
                    key_dict = key_dict[elements[0]]
 
536
                    elements.pop(0)
 
537
            except KeyError:
 
538
                # a non-existant lookup.
 
539
                continue
 
540
            if len(elements):
 
541
                dicts = [key_dict]
 
542
                while dicts:
 
543
                    key_dict = dicts.pop(-1)
 
544
                    # can't be empty or would not exist
 
545
                    item, value = key_dict.iteritems().next()
 
546
                    if type(value) == dict:
 
547
                        # push keys
 
548
                        dicts.extend(key_dict.itervalues())
 
549
                    else:
 
550
                        # yield keys
 
551
                        for value in key_dict.itervalues():
 
552
                            yield (self, ) + value
 
553
            else:
 
554
                yield (self, ) + key_dict
553
555
 
554
556
    def _get_nodes_by_key(self):
555
557
        if self._nodes_by_key is None:
556
558
            nodes_by_key = {}
557
559
            if self.reference_lists:
558
 
                for key, (references, value) in self._nodes.items():
 
560
                for key, (references, value) in self._nodes.iteritems():
559
561
                    key_dict = nodes_by_key
560
562
                    for subkey in key[:-1]:
561
563
                        key_dict = key_dict.setdefault(subkey, {})
562
564
                    key_dict[key[-1]] = key, value, references
563
565
            else:
564
 
                for key, (references, value) in self._nodes.items():
 
566
                for key, (references, value) in self._nodes.iteritems():
565
567
                    key_dict = nodes_by_key
566
568
                    for subkey in key[:-1]:
567
569
                        key_dict = key_dict.setdefault(subkey, {})
574
576
 
575
577
        For InMemoryGraphIndex the estimate is exact.
576
578
        """
577
 
        return len(self._nodes) + sum(
578
 
            backing.key_count()
579
 
            for backing in self._backing_indices
580
 
            if backing is not None)
 
579
        return len(self._keys) + sum(backing.key_count() for backing in
 
580
            self._backing_indices if backing is not None)
581
581
 
582
582
    def validate(self):
583
583
        """In memory index's have no known corruption at the moment."""
584
584
 
585
 
    def __lt__(self, other):
586
 
        if isinstance(other, type(self)):
587
 
            return self._nodes < other._nodes
588
 
        # Always sort existing indexes before ones that are still being built.
589
 
        if isinstance(other, BTreeGraphIndex):
590
 
            return False
591
 
        raise TypeError
592
 
 
593
 
 
594
 
class _LeafNode(dict):
 
585
 
 
586
class _LeafNode(object):
595
587
    """A leaf node for a serialised B+Tree index."""
596
588
 
597
 
    __slots__ = ('min_key', 'max_key', '_keys')
 
589
    __slots__ = ('keys', 'min_key', 'max_key')
598
590
 
599
591
    def __init__(self, bytes, key_length, ref_list_length):
600
592
        """Parse bytes to create a leaf node object."""
601
593
        # splitlines mangles the \r delimiters.. don't use it.
602
 
        key_list = _btree_serializer._parse_leaf_lines(
603
 
            bytes, key_length, ref_list_length)
 
594
        key_list = _btree_serializer._parse_leaf_lines(bytes,
 
595
            key_length, ref_list_length)
604
596
        if key_list:
605
597
            self.min_key = key_list[0][0]
606
598
            self.max_key = key_list[-1][0]
607
599
        else:
608
600
            self.min_key = self.max_key = None
609
 
        super(_LeafNode, self).__init__(key_list)
610
 
        self._keys = dict(self)
611
 
 
612
 
    def all_items(self):
613
 
        """Return a sorted list of (key, (value, refs)) items"""
614
 
        items = sorted(self.items())
615
 
        return items
616
 
 
617
 
    def all_keys(self):
618
 
        """Return a sorted list of all keys."""
619
 
        keys = sorted(self.keys())
620
 
        return keys
 
601
        self.keys = dict(key_list)
621
602
 
622
603
 
623
604
class _InternalNode(object):
628
609
    def __init__(self, bytes):
629
610
        """Parse bytes to create an internal node object."""
630
611
        # splitlines mangles the \r delimiters.. don't use it.
631
 
        self.keys = self._parse_lines(bytes.split(b'\n'))
 
612
        self.keys = self._parse_lines(bytes.split('\n'))
632
613
 
633
614
    def _parse_lines(self, lines):
634
615
        nodes = []
635
616
        self.offset = int(lines[1][7:])
636
 
        as_st = static_tuple.StaticTuple.from_sequence
637
617
        for line in lines[2:]:
638
 
            if line == b'':
 
618
            if line == '':
639
619
                break
640
 
            # GZ 2017-05-24: Used to intern() each chunk of line as well, need
641
 
            # to recheck performance and perhaps adapt StaticTuple to adjust.
642
 
            nodes.append(as_st(line.split(b'\0')).intern())
 
620
            nodes.append(tuple(map(intern, line.split('\0'))))
643
621
        return nodes
644
622
 
645
623
 
650
628
    memory except when very large walks are done.
651
629
    """
652
630
 
653
 
    def __init__(self, transport, name, size, unlimited_cache=False,
654
 
                 offset=0):
 
631
    def __init__(self, transport, name, size):
655
632
        """Create a B+Tree index object on the index name.
656
633
 
657
634
        :param transport: The transport to read data for the index from.
661
638
            the initial read (to read the root node header) can be done
662
639
            without over-reading even on empty indices, and on small indices
663
640
            allows single-IO to read the entire index.
664
 
        :param unlimited_cache: If set to True, then instead of using an
665
 
            LRUCache with size _NODE_CACHE_SIZE, we will use a dict and always
666
 
            cache all leaf nodes.
667
 
        :param offset: The start of the btree index data isn't byte 0 of the
668
 
            file. Instead it starts at some point later.
669
641
        """
670
642
        self._transport = transport
671
643
        self._name = name
673
645
        self._file = None
674
646
        self._recommended_pages = self._compute_recommended_pages()
675
647
        self._root_node = None
676
 
        self._base_offset = offset
677
 
        self._leaf_factory = _LeafNode
678
648
        # Default max size is 100,000 leave values
679
 
        self._leaf_value_cache = None  # lru_cache.LRUCache(100*1000)
680
 
        if unlimited_cache:
681
 
            self._leaf_node_cache = {}
682
 
            self._internal_node_cache = {}
683
 
        else:
684
 
            self._leaf_node_cache = lru_cache.LRUCache(_NODE_CACHE_SIZE)
685
 
            # We use a FIFO here just to prevent possible blowout. However, a
686
 
            # 300k record btree has only 3k leaf nodes, and only 20 internal
687
 
            # nodes. A value of 100 scales to ~100*100*100 = 1M records.
688
 
            self._internal_node_cache = fifo_cache.FIFOCache(100)
 
649
        self._leaf_value_cache = None # lru_cache.LRUCache(100*1000)
 
650
        self._leaf_node_cache = lru_cache.LRUCache(_NODE_CACHE_SIZE)
 
651
        # We could limit this, but even a 300k record btree has only 3k leaf
 
652
        # nodes, and only 20 internal nodes. So the default of 100 nodes in an
 
653
        # LRU would mean we always cache everything anyway, no need to pay the
 
654
        # overhead of LRU
 
655
        self._internal_node_cache = fifo_cache.FIFOCache(100)
689
656
        self._key_count = None
690
657
        self._row_lengths = None
691
 
        self._row_offsets = None  # Start of each row, [-1] is the end
692
 
 
693
 
    def __hash__(self):
694
 
        return id(self)
 
658
        self._row_offsets = None # Start of each row, [-1] is the end
695
659
 
696
660
    def __eq__(self, other):
697
661
        """Equal when self and other were created with the same parameters."""
698
662
        return (
699
 
            isinstance(self, type(other))
700
 
            and self._transport == other._transport
701
 
            and self._name == other._name
702
 
            and self._size == other._size)
703
 
 
704
 
    def __lt__(self, other):
705
 
        if isinstance(other, type(self)):
706
 
            return ((self._name, self._size) < (other._name, other._size))
707
 
        # Always sort existing indexes before ones that are still being built.
708
 
        if isinstance(other, BTreeBuilder):
709
 
            return True
710
 
        raise TypeError
 
663
            type(self) == type(other) and
 
664
            self._transport == other._transport and
 
665
            self._name == other._name and
 
666
            self._size == other._size)
711
667
 
712
668
    def __ne__(self, other):
713
669
        return not self.__eq__(other)
728
684
        found = {}
729
685
        start_of_leaves = None
730
686
        for node_pos, node in self._read_nodes(sorted(nodes)):
731
 
            if node_pos == 0:  # Special case
 
687
            if node_pos == 0: # Special case
732
688
                self._root_node = node
733
689
            else:
734
690
                if start_of_leaves is None:
735
691
                    start_of_leaves = self._row_offsets[-2]
736
692
                if node_pos < start_of_leaves:
737
 
                    self._internal_node_cache[node_pos] = node
 
693
                    self._internal_node_cache.add(node_pos, node)
738
694
                else:
739
 
                    self._leaf_node_cache[node_pos] = node
 
695
                    self._leaf_node_cache.add(node_pos, node)
740
696
            found[node_pos] = node
741
697
        return found
742
698
 
747
703
        pages fit in that length.
748
704
        """
749
705
        recommended_read = self._transport.recommended_page_size()
750
 
        recommended_pages = int(math.ceil(recommended_read / _PAGE_SIZE))
 
706
        recommended_pages = int(math.ceil(recommended_read /
 
707
                                          float(_PAGE_SIZE)))
751
708
        return recommended_pages
752
709
 
753
710
    def _compute_total_pages_in_index(self):
764
721
            return self._row_offsets[-1]
765
722
        # This is the number of pages as defined by the size of the index. They
766
723
        # should be indentical.
767
 
        total_pages = int(math.ceil(self._size / _PAGE_SIZE))
 
724
        total_pages = int(math.ceil(self._size / float(_PAGE_SIZE)))
768
725
        return total_pages
769
726
 
770
727
    def _expand_offsets(self, offsets):
801
758
        if total_pages - len(cached_offsets) <= self._recommended_pages:
802
759
            # Read whatever is left
803
760
            if cached_offsets:
804
 
                expanded = [x for x in range(total_pages)
805
 
                            if x not in cached_offsets]
 
761
                expanded = [x for x in xrange(total_pages)
 
762
                               if x not in cached_offsets]
806
763
            else:
807
 
                expanded = list(range(total_pages))
 
764
                expanded = range(total_pages)
808
765
            if 'index' in debug.debug_flags:
809
766
                trace.mutter('  reading all unread pages: %s', expanded)
810
767
            return expanded
859
816
                if first is None:
860
817
                    first, end = self._find_layer_first_and_end(pos)
861
818
                previous = pos - 1
862
 
                if (previous > 0 and
863
 
                    previous not in cached_offsets and
864
 
                    previous not in final_offsets and
865
 
                        previous >= first):
 
819
                if (previous > 0
 
820
                    and previous not in cached_offsets
 
821
                    and previous not in final_offsets
 
822
                    and previous >= first):
866
823
                    next_tips.add(previous)
867
824
                after = pos + 1
868
 
                if (after < total_pages and
869
 
                    after not in cached_offsets and
870
 
                    after not in final_offsets and
871
 
                        after < end):
 
825
                if (after < total_pages
 
826
                    and after not in cached_offsets
 
827
                    and after not in final_offsets
 
828
                    and after < end):
872
829
                    next_tips.add(after)
873
830
                # This would keep us from going bigger than
874
831
                # recommended_pages by only expanding the first offsets.
880
837
            new_tips = next_tips
881
838
        return final_offsets
882
839
 
883
 
    def clear_cache(self):
884
 
        """Clear out any cached/memoized values.
885
 
 
886
 
        This can be called at any time, but generally it is used when we have
887
 
        extracted some information, but don't expect to be requesting any more
888
 
        from this index.
889
 
        """
890
 
        # Note that we don't touch self._root_node or self._internal_node_cache
891
 
        # We don't expect either of those to be big, and it can save
892
 
        # round-trips in the future. We may re-evaluate this if InternalNode
893
 
        # memory starts to be an issue.
894
 
        self._leaf_node_cache.clear()
895
 
 
896
840
    def external_references(self, ref_list_num):
897
841
        if self._root_node is None:
898
842
            self._get_root_node()
899
843
        if ref_list_num + 1 > self.node_ref_lists:
900
844
            raise ValueError('No ref list %d, index has %d ref lists'
901
 
                             % (ref_list_num, self.node_ref_lists))
 
845
                % (ref_list_num, self.node_ref_lists))
902
846
        keys = set()
903
847
        refs = set()
904
848
        for node in self.iter_all_entries():
923
867
 
924
868
    def _get_offsets_to_cached_pages(self):
925
869
        """Determine what nodes we already have cached."""
926
 
        cached_offsets = set(self._internal_node_cache)
927
 
        # cache may be dict or LRUCache, keys() is the common method
 
870
        cached_offsets = set(self._internal_node_cache.keys())
928
871
        cached_offsets.update(self._leaf_node_cache.keys())
929
872
        if self._root_node is not None:
930
873
            cached_offsets.add(0)
963
906
    def _cache_leaf_values(self, nodes):
964
907
        """Cache directly from key => value, skipping the btree."""
965
908
        if self._leaf_value_cache is not None:
966
 
            for node in nodes.values():
967
 
                for key, value in node.all_items():
 
909
            for node in nodes.itervalues():
 
910
                for key, value in node.keys.iteritems():
968
911
                    if key in self._leaf_value_cache:
969
912
                        # Don't add the rest of the keys, we've seen this node
970
913
                        # before.
980
923
    def iter_all_entries(self):
981
924
        """Iterate over all keys within the index.
982
925
 
983
 
        :return: An iterable of (index, key, value) or
984
 
            (index, key, value, reference_lists).
 
926
        :return: An iterable of (index, key, value) or (index, key, value, reference_lists).
985
927
            The former tuple is used when there are no reference lists in the
986
928
            index, making the API compatible with simple key:value index types.
987
929
            There is no defined order for the result iteration - it will be in
988
930
            the most efficient order for the index.
989
931
        """
990
932
        if 'evil' in debug.debug_flags:
991
 
            trace.mutter_callsite(
992
 
                3, "iter_all_entries scales with size of history.")
 
933
            trace.mutter_callsite(3,
 
934
                "iter_all_entries scales with size of history.")
993
935
        if not self.key_count():
994
936
            return
995
937
        if self._row_offsets[-1] == 1:
996
938
            # There is only the root node, and we read that via key_count()
997
939
            if self.node_ref_lists:
998
 
                for key, (value, refs) in self._root_node.all_items():
 
940
                for key, (value, refs) in sorted(self._root_node.keys.items()):
999
941
                    yield (self, key, value, refs)
1000
942
            else:
1001
 
                for key, (value, refs) in self._root_node.all_items():
 
943
                for key, (value, refs) in sorted(self._root_node.keys.items()):
1002
944
                    yield (self, key, value)
1003
945
            return
1004
946
        start_of_leaves = self._row_offsets[-2]
1005
947
        end_of_leaves = self._row_offsets[-1]
1006
 
        needed_offsets = list(range(start_of_leaves, end_of_leaves))
 
948
        needed_offsets = range(start_of_leaves, end_of_leaves)
1007
949
        if needed_offsets == [0]:
1008
950
            # Special case when we only have a root node, as we have already
1009
951
            # read everything
1014
956
        # for spilling index builds to disk.
1015
957
        if self.node_ref_lists:
1016
958
            for _, node in nodes:
1017
 
                for key, (value, refs) in node.all_items():
 
959
                for key, (value, refs) in sorted(node.keys.items()):
1018
960
                    yield (self, key, value, refs)
1019
961
        else:
1020
962
            for _, node in nodes:
1021
 
                for key, (value, refs) in node.all_items():
 
963
                for key, (value, refs) in sorted(node.keys.items()):
1022
964
                    yield (self, key, value)
1023
965
 
1024
966
    @staticmethod
1047
989
        #       function, so there is even more to be gained.
1048
990
        # iter_steps = len(in_keys) + len(fixed_keys)
1049
991
        # bisect_steps = len(in_keys) * math.log(len(fixed_keys), 2)
1050
 
        if len(in_keys) == 1:  # Bisect will always be faster for M = 1
1051
 
            return [(bisect.bisect_right(fixed_keys, in_keys[0]), in_keys)]
 
992
        if len(in_keys) == 1: # Bisect will always be faster for M = 1
 
993
            return [(bisect_right(fixed_keys, in_keys[0]), in_keys)]
1052
994
        # elif bisect_steps < iter_steps:
1053
995
        #     offsets = {}
1054
996
        #     for key in in_keys:
1057
999
        #     return [(o, offsets[o]) for o in sorted(offsets)]
1058
1000
        in_keys_iter = iter(in_keys)
1059
1001
        fixed_keys_iter = enumerate(fixed_keys)
1060
 
        cur_in_key = next(in_keys_iter)
1061
 
        cur_fixed_offset, cur_fixed_key = next(fixed_keys_iter)
1062
 
 
1063
 
        class InputDone(Exception):
1064
 
            pass
1065
 
 
1066
 
        class FixedDone(Exception):
1067
 
            pass
 
1002
        cur_in_key = in_keys_iter.next()
 
1003
        cur_fixed_offset, cur_fixed_key = fixed_keys_iter.next()
 
1004
 
 
1005
        class InputDone(Exception): pass
 
1006
        class FixedDone(Exception): pass
1068
1007
 
1069
1008
        output = []
1070
1009
        cur_out = []
1083
1022
                    while cur_in_key < cur_fixed_key:
1084
1023
                        cur_keys.append(cur_in_key)
1085
1024
                        try:
1086
 
                            cur_in_key = next(in_keys_iter)
 
1025
                            cur_in_key = in_keys_iter.next()
1087
1026
                        except StopIteration:
1088
1027
                            raise InputDone
1089
1028
                    # At this point cur_in_key must be >= cur_fixed_key
1091
1030
                # the end
1092
1031
                while cur_in_key >= cur_fixed_key:
1093
1032
                    try:
1094
 
                        cur_fixed_offset, cur_fixed_key = next(fixed_keys_iter)
 
1033
                        cur_fixed_offset, cur_fixed_key = fixed_keys_iter.next()
1095
1034
                    except StopIteration:
1096
1035
                        raise FixedDone
1097
1036
        except InputDone:
1129
1068
                positions = self._multi_bisect_right(sub_keys, node.keys)
1130
1069
                node_offset = next_row_start + node.offset
1131
1070
                next_nodes_and_keys.extend([(node_offset + pos, s_keys)
1132
 
                                            for pos, s_keys in positions])
 
1071
                                           for pos, s_keys in positions])
1133
1072
            keys_at_index = next_nodes_and_keys
1134
1073
        # We should now be at the _LeafNodes
1135
1074
        node_indexes = [idx for idx, s_keys in keys_at_index]
1178
1117
                else:
1179
1118
                    needed_keys.append(key)
1180
1119
 
 
1120
        last_key = None
1181
1121
        needed_keys = keys
1182
1122
        if not needed_keys:
1183
1123
            return
1187
1127
                continue
1188
1128
            node = nodes[node_index]
1189
1129
            for next_sub_key in sub_keys:
1190
 
                if next_sub_key in node:
1191
 
                    value, refs = node[next_sub_key]
 
1130
                if next_sub_key in node.keys:
 
1131
                    value, refs = node.keys[next_sub_key]
1192
1132
                    if self.node_ref_lists:
1193
1133
                        yield (self, next_sub_key, value, refs)
1194
1134
                    else:
1226
1166
            return set()
1227
1167
        if ref_list_num >= self.node_ref_lists:
1228
1168
            raise ValueError('No ref list %d, index has %d ref lists'
1229
 
                             % (ref_list_num, self.node_ref_lists))
 
1169
                % (ref_list_num, self.node_ref_lists))
1230
1170
 
1231
1171
        # The main trick we are trying to accomplish is that when we find a
1232
1172
        # key listing its parents, we expect that the parent key is also likely
1262
1202
            # sub_keys is all of the keys we are looking for that should exist
1263
1203
            # on this page, if they aren't here, then they won't be found
1264
1204
            node = nodes[node_index]
 
1205
            node_keys = node.keys
1265
1206
            parents_to_check = set()
1266
1207
            for next_sub_key in sub_keys:
1267
 
                if next_sub_key not in node:
 
1208
                if next_sub_key not in node_keys:
1268
1209
                    # This one is just not present in the index at all
1269
1210
                    missing_keys.add(next_sub_key)
1270
1211
                else:
1271
 
                    value, refs = node[next_sub_key]
 
1212
                    value, refs = node_keys[next_sub_key]
1272
1213
                    parent_keys = refs[ref_list_num]
1273
1214
                    parent_map[next_sub_key] = parent_keys
1274
1215
                    parents_to_check.update(parent_keys)
1281
1222
            while parents_to_check:
1282
1223
                next_parents_to_check = set()
1283
1224
                for key in parents_to_check:
1284
 
                    if key in node:
1285
 
                        value, refs = node[key]
 
1225
                    if key in node_keys:
 
1226
                        value, refs = node_keys[key]
1286
1227
                        parent_keys = refs[ref_list_num]
1287
1228
                        parent_map[key] = parent_keys
1288
1229
                        next_parents_to_check.update(parent_keys)
1307
1248
                            # LeafNode
1308
1249
                            parents_not_on_page.add(key)
1309
1250
                        else:
1310
 
                            # assert (key != node.min_key and
1311
 
                            #         key != node.max_key)
 
1251
                            # assert key != node.min_key and key != node.max_key
1312
1252
                            # If it was going to be present, it would be on
1313
1253
                            # *this* page, so mark it missing.
1314
1254
                            missing_keys.add(key)
1351
1291
            self._get_root_node()
1352
1292
        # TODO: only access nodes that can satisfy the prefixes we are looking
1353
1293
        # for. For now, to meet API usage (as this function is not used by
1354
 
        # current breezy) just suck the entire index and iterate in memory.
 
1294
        # current bzrlib) just suck the entire index and iterate in memory.
1355
1295
        nodes = {}
1356
1296
        if self.node_ref_lists:
1357
1297
            if self._key_length == 1:
1383
1323
                    key_dict[key[-1]] = key_value
1384
1324
        if self._key_length == 1:
1385
1325
            for key in keys:
1386
 
                index._sanity_check_key(self, key)
 
1326
                # sanity check
 
1327
                if key[0] is None:
 
1328
                    raise errors.BadIndexKey(key)
 
1329
                if len(key) != self._key_length:
 
1330
                    raise errors.BadIndexKey(key)
1387
1331
                try:
1388
1332
                    if self.node_ref_lists:
1389
1333
                        value, node_refs = nodes[key]
1393
1337
                except KeyError:
1394
1338
                    pass
1395
1339
            return
1396
 
        for entry in index._iter_entries_prefix(self, nodes_by_key, keys):
1397
 
            yield entry
 
1340
        for key in keys:
 
1341
            # sanity check
 
1342
            if key[0] is None:
 
1343
                raise errors.BadIndexKey(key)
 
1344
            if len(key) != self._key_length:
 
1345
                raise errors.BadIndexKey(key)
 
1346
            # find what it refers to:
 
1347
            key_dict = nodes_by_key
 
1348
            elements = list(key)
 
1349
            # find the subdict whose contents should be returned.
 
1350
            try:
 
1351
                while len(elements) and elements[0] is not None:
 
1352
                    key_dict = key_dict[elements[0]]
 
1353
                    elements.pop(0)
 
1354
            except KeyError:
 
1355
                # a non-existant lookup.
 
1356
                continue
 
1357
            if len(elements):
 
1358
                dicts = [key_dict]
 
1359
                while dicts:
 
1360
                    key_dict = dicts.pop(-1)
 
1361
                    # can't be empty or would not exist
 
1362
                    item, value = key_dict.iteritems().next()
 
1363
                    if type(value) == dict:
 
1364
                        # push keys
 
1365
                        dicts.extend(key_dict.itervalues())
 
1366
                    else:
 
1367
                        # yield keys
 
1368
                        for value in key_dict.itervalues():
 
1369
                            # each value is the key:value:node refs tuple
 
1370
                            # ready to yield.
 
1371
                            yield (self, ) + value
 
1372
            else:
 
1373
                # the last thing looked up was a terminal element
 
1374
                yield (self, ) + key_dict
1398
1375
 
1399
1376
    def key_count(self):
1400
1377
        """Return an estimate of the number of keys in this index.
1425
1402
        """
1426
1403
        signature = bytes[0:len(self._signature())]
1427
1404
        if not signature == self._signature():
1428
 
            raise index.BadIndexFormatSignature(self._name, BTreeGraphIndex)
 
1405
            raise errors.BadIndexFormatSignature(self._name, BTreeGraphIndex)
1429
1406
        lines = bytes[len(self._signature()):].splitlines()
1430
1407
        options_line = lines[0]
1431
1408
        if not options_line.startswith(_OPTION_NODE_REFS):
1432
 
            raise index.BadIndexOptions(self)
 
1409
            raise errors.BadIndexOptions(self)
1433
1410
        try:
1434
1411
            self.node_ref_lists = int(options_line[len(_OPTION_NODE_REFS):])
1435
1412
        except ValueError:
1436
 
            raise index.BadIndexOptions(self)
 
1413
            raise errors.BadIndexOptions(self)
1437
1414
        options_line = lines[1]
1438
1415
        if not options_line.startswith(_OPTION_KEY_ELEMENTS):
1439
 
            raise index.BadIndexOptions(self)
 
1416
            raise errors.BadIndexOptions(self)
1440
1417
        try:
1441
1418
            self._key_length = int(options_line[len(_OPTION_KEY_ELEMENTS):])
1442
1419
        except ValueError:
1443
 
            raise index.BadIndexOptions(self)
 
1420
            raise errors.BadIndexOptions(self)
1444
1421
        options_line = lines[2]
1445
1422
        if not options_line.startswith(_OPTION_LEN):
1446
 
            raise index.BadIndexOptions(self)
 
1423
            raise errors.BadIndexOptions(self)
1447
1424
        try:
1448
1425
            self._key_count = int(options_line[len(_OPTION_LEN):])
1449
1426
        except ValueError:
1450
 
            raise index.BadIndexOptions(self)
 
1427
            raise errors.BadIndexOptions(self)
1451
1428
        options_line = lines[3]
1452
1429
        if not options_line.startswith(_OPTION_ROW_LENGTHS):
1453
 
            raise index.BadIndexOptions(self)
 
1430
            raise errors.BadIndexOptions(self)
1454
1431
        try:
1455
 
            self._row_lengths = [int(length) for length in
1456
 
                                 options_line[len(_OPTION_ROW_LENGTHS):].split(
1457
 
                                     b',')
1458
 
                                 if length]
 
1432
            self._row_lengths = map(int, [length for length in
 
1433
                options_line[len(_OPTION_ROW_LENGTHS):].split(',')
 
1434
                if len(length)])
1459
1435
        except ValueError:
1460
 
            raise index.BadIndexOptions(self)
 
1436
            raise errors.BadIndexOptions(self)
1461
1437
        self._compute_row_offsets()
1462
1438
 
1463
1439
        # calculate the bytes we have processed
1480
1456
        # list of (offset, length) regions of the file that should, evenually
1481
1457
        # be read in to data_ranges, either from 'bytes' or from the transport
1482
1458
        ranges = []
1483
 
        base_offset = self._base_offset
1484
1459
        for index in nodes:
1485
 
            offset = (index * _PAGE_SIZE)
 
1460
            offset = index * _PAGE_SIZE
1486
1461
            size = _PAGE_SIZE
1487
1462
            if index == 0:
1488
1463
                # Root node - special case
1492
1467
                    # The only case where we don't know the size, is for very
1493
1468
                    # small indexes. So we read the whole thing
1494
1469
                    bytes = self._transport.get_bytes(self._name)
1495
 
                    num_bytes = len(bytes)
1496
 
                    self._size = num_bytes - base_offset
 
1470
                    self._size = len(bytes)
1497
1471
                    # the whole thing should be parsed out of 'bytes'
1498
 
                    ranges = [(start, min(_PAGE_SIZE, num_bytes - start))
1499
 
                              for start in range(
1500
 
                                  base_offset, num_bytes, _PAGE_SIZE)]
 
1472
                    ranges.append((0, len(bytes)))
1501
1473
                    break
1502
1474
            else:
1503
1475
                if offset > self._size:
1505
1477
                                         ' of the file %s > %s'
1506
1478
                                         % (offset, self._size))
1507
1479
                size = min(size, self._size - offset)
1508
 
            ranges.append((base_offset + offset, size))
 
1480
            ranges.append((offset, size))
1509
1481
        if not ranges:
1510
1482
            return
1511
1483
        elif bytes is not None:
1512
1484
            # already have the whole file
1513
 
            data_ranges = [(start, bytes[start:start + size])
1514
 
                           for start, size in ranges]
 
1485
            data_ranges = [(start, bytes[start:start+_PAGE_SIZE])
 
1486
                           for start in xrange(0, len(bytes), _PAGE_SIZE)]
1515
1487
        elif self._file is None:
1516
1488
            data_ranges = self._transport.readv(self._name, ranges)
1517
1489
        else:
1520
1492
                self._file.seek(offset)
1521
1493
                data_ranges.append((offset, self._file.read(size)))
1522
1494
        for offset, data in data_ranges:
1523
 
            offset -= base_offset
1524
1495
            if offset == 0:
1525
1496
                # extract the header
1526
1497
                offset, data = self._parse_header_from_bytes(data)
1528
1499
                    continue
1529
1500
            bytes = zlib.decompress(data)
1530
1501
            if bytes.startswith(_LEAF_FLAG):
1531
 
                node = self._leaf_factory(bytes, self._key_length,
1532
 
                                          self.node_ref_lists)
 
1502
                node = _LeafNode(bytes, self._key_length, self.node_ref_lists)
1533
1503
            elif bytes.startswith(_INTERNAL_FLAG):
1534
1504
                node = _InternalNode(bytes)
1535
1505
            else:
1536
1506
                raise AssertionError("Unknown node type for %r" % bytes)
1537
 
            yield offset // _PAGE_SIZE, node
 
1507
            yield offset / _PAGE_SIZE, node
1538
1508
 
1539
1509
    def _signature(self):
1540
1510
        """The file signature for this index type."""
1550
1520
            # We shouldn't be reading anything anyway
1551
1521
            start_node = 1
1552
1522
        node_end = self._row_offsets[-1]
1553
 
        for node in self._read_nodes(list(range(start_node, node_end))):
 
1523
        for node in self._read_nodes(range(start_node, node_end)):
1554
1524
            pass
1555
1525
 
1556
1526
 
1557
 
_gcchk_factory = _LeafNode
1558
 
 
1559
1527
try:
1560
 
    from . import _btree_serializer_pyx as _btree_serializer
1561
 
    _gcchk_factory = _btree_serializer._parse_into_chk
1562
 
except ImportError as e:
1563
 
    osutils.failed_to_load_extension(e)
1564
 
    from . import _btree_serializer_py as _btree_serializer
 
1528
    from bzrlib import _btree_serializer_pyx as _btree_serializer
 
1529
except ImportError:
 
1530
    from bzrlib import _btree_serializer_py as _btree_serializer