~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/index.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2009-08-24 21:25:26 UTC
  • mfrom: (4641.1.1 integration)
  • Revision ID: pqm@pqm.ubuntu.com-20090824212526-5j41jw5zsciji66e
(robertc) Back out draft documentation change landed as 'selftest
        improvements'. (Robert Collins)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007 Canonical Ltd
 
1
# Copyright (C) 2007, 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
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
"""Indexing facilities."""
18
18
 
27
27
from bisect import bisect_right
28
28
from cStringIO import StringIO
29
29
import re
 
30
import sys
30
31
 
31
32
from bzrlib.lazy_import import lazy_import
32
33
lazy_import(globals(), """
38
39
from bzrlib import (
39
40
    debug,
40
41
    errors,
41
 
    symbol_versioning,
42
42
    )
43
43
 
44
44
_HEADER_READV = (0, 200)
52
52
_newline_null_re = re.compile('[\n\0]')
53
53
 
54
54
 
 
55
def _has_key_from_parent_map(self, key):
 
56
    """Check if this index has one key.
 
57
 
 
58
    If it's possible to check for multiple keys at once through
 
59
    calling get_parent_map that should be faster.
 
60
    """
 
61
    return (key in self.get_parent_map([key]))
 
62
 
 
63
 
 
64
def _missing_keys_from_parent_map(self, keys):
 
65
    return set(keys) - set(self.get_parent_map(keys))
 
66
 
 
67
 
55
68
class GraphIndexBuilder(object):
56
69
    """A builder that can build a GraphIndex.
57
 
    
 
70
 
58
71
    The resulting graph has the structure:
59
 
    
 
72
 
60
73
    _SIGNATURE OPTIONS NODES NEWLINE
61
74
    _SIGNATURE     := 'Bazaar Graph Index 1' NEWLINE
62
75
    OPTIONS        := 'node_ref_lists=' DIGITS NEWLINE
84
97
        self._nodes = {}
85
98
        self._nodes_by_key = None
86
99
        self._key_length = key_elements
 
100
        self._optimize_for_size = False
 
101
        self._combine_backing_indices = True
87
102
 
88
103
    def _check_key(self, key):
89
104
        """Raise BadIndexKey if key is not a valid key for this index."""
95
110
            if not element or _whitespace_re.search(element) is not None:
96
111
                raise errors.BadIndexKey(element)
97
112
 
 
113
    def _external_references(self):
 
114
        """Return references that are not present in this index.
 
115
        """
 
116
        keys = set()
 
117
        refs = set()
 
118
        # TODO: JAM 2008-11-21 This makes an assumption about how the reference
 
119
        #       lists are used. It is currently correct for pack-0.92 through
 
120
        #       1.9, which use the node references (3rd column) second
 
121
        #       reference list as the compression parent. Perhaps this should
 
122
        #       be moved into something higher up the stack, since it
 
123
        #       makes assumptions about how the index is used.
 
124
        if self.reference_lists > 1:
 
125
            for node in self.iter_all_entries():
 
126
                keys.add(node[1])
 
127
                refs.update(node[3][1])
 
128
            return refs - keys
 
129
        else:
 
130
            # If reference_lists == 0 there can be no external references, and
 
131
            # if reference_lists == 1, then there isn't a place to store the
 
132
            # compression parent
 
133
            return set()
 
134
 
98
135
    def _get_nodes_by_key(self):
99
136
        if self._nodes_by_key is None:
100
137
            nodes_by_key = {}
209
246
        # one to pad all the data with reference-length and determine entry
210
247
        # addresses.
211
248
        # One to serialise.
212
 
        
 
249
 
213
250
        # forward sorted by key. In future we may consider topological sorting,
214
251
        # at the cost of table scans for direct lookup, or a second index for
215
252
        # direct lookup
278
315
                (len(result.getvalue()), expected_bytes))
279
316
        return result
280
317
 
 
318
    def set_optimize(self, for_size=None, combine_backing_indices=None):
 
319
        """Change how the builder tries to optimize the result.
 
320
 
 
321
        :param for_size: Tell the builder to try and make the index as small as
 
322
            possible.
 
323
        :param combine_backing_indices: If the builder spills to disk to save
 
324
            memory, should the on-disk indices be combined. Set to True if you
 
325
            are going to be probing the index, but to False if you are not. (If
 
326
            you are not querying, then the time spent combining is wasted.)
 
327
        :return: None
 
328
        """
 
329
        # GraphIndexBuilder itself doesn't pay attention to the flag yet, but
 
330
        # other builders do.
 
331
        if for_size is not None:
 
332
            self._optimize_for_size = for_size
 
333
        if combine_backing_indices is not None:
 
334
            self._combine_backing_indices = combine_backing_indices
 
335
 
 
336
    def find_ancestry(self, keys, ref_list_num):
 
337
        """See CombinedGraphIndex.find_ancestry()"""
 
338
        pending = set(keys)
 
339
        parent_map = {}
 
340
        missing_keys = set()
 
341
        while pending:
 
342
            next_pending = set()
 
343
            for _, key, value, ref_lists in self.iter_entries(pending):
 
344
                parent_keys = ref_lists[ref_list_num]
 
345
                parent_map[key] = parent_keys
 
346
                next_pending.update([p for p in parent_keys if p not in
 
347
                                     parent_map])
 
348
                missing_keys.update(pending.difference(parent_map))
 
349
            pending = next_pending
 
350
        return parent_map, missing_keys
 
351
 
281
352
 
282
353
class GraphIndex(object):
283
354
    """An index for data with embedded graphs.
284
 
 
 
355
 
285
356
    The index maps keys to a list of key reference lists, and a value.
286
357
    Each node has the same number of key reference lists. Each key reference
287
358
    list can be empty or an arbitrary length. The value is an opaque NULL
288
 
    terminated string without any newlines. The storage of the index is 
 
359
    terminated string without any newlines. The storage of the index is
289
360
    hidden in the interface: keys and key references are always tuples of
290
361
    bytestrings, never the internal representation (e.g. dictionary offsets).
291
362
 
366
437
        self._keys_by_offset = {}
367
438
        # ready-to-return key:value or key:value, node_ref_lists
368
439
        self._nodes = {}
369
 
        self._nodes_by_key = {}
 
440
        self._nodes_by_key = None
370
441
        trailers = 0
371
442
        pos = stream.tell()
372
443
        lines = stream.read().split('\n')
381
452
            else:
382
453
                node_value = value
383
454
            self._nodes[key] = node_value
384
 
            if self._key_length > 1:
385
 
                # TODO: We may want to do this lazily, but if we are calling
386
 
                #       _buffer_all, we are likely to be doing
387
 
                #       iter_entries_prefix
388
 
                key_dict = self._nodes_by_key
389
 
                if self.node_ref_lists:
390
 
                    key_value = key, node_value[0], node_value[1]
391
 
                else:
392
 
                    key_value = key, node_value
393
 
                # For a key of (foo, bar, baz) create
394
 
                # _nodes_by_key[foo][bar][baz] = key_value
395
 
                for subkey in key[:-1]:
396
 
                    key_dict = key_dict.setdefault(subkey, {})
397
 
                key_dict[key[-1]] = key_value
398
455
        # cache the keys for quick set intersections
399
456
        self._keys = set(self._nodes)
400
457
        if trailers != 1:
401
458
            # there must be one line - the empty trailer line.
402
459
            raise errors.BadIndexData(self)
403
460
 
 
461
    def external_references(self, ref_list_num):
 
462
        """Return references that are not present in this index.
 
463
        """
 
464
        self._buffer_all()
 
465
        if ref_list_num + 1 > self.node_ref_lists:
 
466
            raise ValueError('No ref list %d, index has %d ref lists'
 
467
                % (ref_list_num, self.node_ref_lists))
 
468
        refs = set()
 
469
        for key, (value, ref_lists) in self._nodes.iteritems():
 
470
            ref_list = ref_lists[ref_list_num]
 
471
            refs.update(ref_list)
 
472
        return refs - self._keys
 
473
 
 
474
    def _get_nodes_by_key(self):
 
475
        if self._nodes_by_key is None:
 
476
            nodes_by_key = {}
 
477
            if self.node_ref_lists:
 
478
                for key, (value, references) in self._nodes.iteritems():
 
479
                    key_dict = nodes_by_key
 
480
                    for subkey in key[:-1]:
 
481
                        key_dict = key_dict.setdefault(subkey, {})
 
482
                    key_dict[key[-1]] = key, value, references
 
483
            else:
 
484
                for key, value in self._nodes.iteritems():
 
485
                    key_dict = nodes_by_key
 
486
                    for subkey in key[:-1]:
 
487
                        key_dict = key_dict.setdefault(subkey, {})
 
488
                    key_dict[key[-1]] = key, value
 
489
            self._nodes_by_key = nodes_by_key
 
490
        return self._nodes_by_key
 
491
 
404
492
    def iter_all_entries(self):
405
493
        """Iterate over all keys within the index.
406
494
 
450
538
 
451
539
    def _resolve_references(self, references):
452
540
        """Return the resolved key references for references.
453
 
        
 
541
 
454
542
        References are resolved by looking up the location of the key in the
455
543
        _keys_by_offset map and substituting the key name, preserving ordering.
456
544
 
457
 
        :param references: An iterable of iterables of key locations. e.g. 
 
545
        :param references: An iterable of iterables of key locations. e.g.
458
546
            [[123, 456], [123]]
459
547
        :return: A tuple of tuples of keys.
460
548
        """
593
681
                else:
594
682
                    yield self, key, self._nodes[key]
595
683
            return
 
684
        nodes_by_key = self._get_nodes_by_key()
596
685
        for key in keys:
597
686
            # sanity check
598
687
            if key[0] is None:
600
689
            if len(key) != self._key_length:
601
690
                raise errors.BadIndexKey(key)
602
691
            # find what it refers to:
603
 
            key_dict = self._nodes_by_key
 
692
            key_dict = nodes_by_key
604
693
            elements = list(key)
605
694
            # find the subdict whose contents should be returned.
606
695
            try:
617
706
                    # can't be empty or would not exist
618
707
                    item, value = key_dict.iteritems().next()
619
708
                    if type(value) == dict:
620
 
                        # push keys 
 
709
                        # push keys
621
710
                        dicts.extend(key_dict.itervalues())
622
711
                    else:
623
712
                        # yield keys
629
718
                # the last thing looked up was a terminal element
630
719
                yield (self, ) + key_dict
631
720
 
 
721
    def _find_ancestors(self, keys, ref_list_num, parent_map, missing_keys):
 
722
        """See BTreeIndex._find_ancestors."""
 
723
        # The api can be implemented as a trivial overlay on top of
 
724
        # iter_entries, it is not an efficient implementation, but it at least
 
725
        # gets the job done.
 
726
        found_keys = set()
 
727
        search_keys = set()
 
728
        for index, key, value, refs in self.iter_entries(keys):
 
729
            parent_keys = refs[ref_list_num]
 
730
            found_keys.add(key)
 
731
            parent_map[key] = parent_keys
 
732
            search_keys.update(parent_keys)
 
733
        # Figure out what, if anything, was missing
 
734
        missing_keys.update(set(keys).difference(found_keys))
 
735
        search_keys = search_keys.difference(parent_map)
 
736
        return search_keys
 
737
 
632
738
    def key_count(self):
633
739
        """Return an estimate of the number of keys in this index.
634
 
        
 
740
 
635
741
        For GraphIndex the estimate is exact.
636
742
        """
637
743
        if self._key_count is None:
653
759
        # Possible improvements:
654
760
        #  - only bisect lookup each key once
655
761
        #  - sort the keys first, and use that to reduce the bisection window
656
 
        # ----- 
 
762
        # -----
657
763
        # this progresses in three parts:
658
764
        # read data
659
765
        # parse it
668
774
                # We have the key parsed.
669
775
                continue
670
776
            index = self._parsed_key_index(key)
671
 
            if (len(self._parsed_key_map) and 
 
777
            if (len(self._parsed_key_map) and
672
778
                self._parsed_key_map[index][0] <= key and
673
779
                (self._parsed_key_map[index][1] >= key or
674
780
                 # end of the file has been parsed
678
784
                continue
679
785
            # - if we have examined this part of the file already - yes
680
786
            index = self._parsed_byte_index(location)
681
 
            if (len(self._parsed_byte_map) and 
 
787
            if (len(self._parsed_byte_map) and
682
788
                self._parsed_byte_map[index][0] <= location and
683
789
                self._parsed_byte_map[index][1] > location):
684
790
                # the byte region has been parsed, so no read is needed.
939
1045
        # adjust offset and data to the parseable data.
940
1046
        trimmed_data = data[trim_start:trim_end]
941
1047
        if not (trimmed_data):
942
 
            raise AssertionError('read unneeded data [%d:%d] from [%d:%d]' 
 
1048
            raise AssertionError('read unneeded data [%d:%d] from [%d:%d]'
943
1049
                % (trim_start, trim_end, offset, offset + len(data)))
944
1050
        if trim_start:
945
1051
            offset += trim_start
973
1079
                raise errors.BadIndexData(self)
974
1080
            # keys are tuples. Each element is a string that may occur many
975
1081
            # times, so we intern them to save space. AB, RC, 200807
976
 
            key = tuple(intern(element) for element in elements[:self._key_length])
 
1082
            key = tuple([intern(element) for element in elements[:self._key_length]])
977
1083
            if first_key is None:
978
1084
                first_key = key
979
1085
            absent, references, value = elements[-3:]
1090
1196
 
1091
1197
class CombinedGraphIndex(object):
1092
1198
    """A GraphIndex made up from smaller GraphIndices.
1093
 
    
 
1199
 
1094
1200
    The backing indices must implement GraphIndex, and are presumed to be
1095
1201
    static data.
1096
1202
 
1101
1207
    in the index list.
1102
1208
    """
1103
1209
 
1104
 
    def __init__(self, indices):
 
1210
    def __init__(self, indices, reload_func=None):
1105
1211
        """Create a CombinedGraphIndex backed by indices.
1106
1212
 
1107
1213
        :param indices: An ordered list of indices to query for data.
 
1214
        :param reload_func: A function to call if we find we are missing an
 
1215
            index. Should have the form reload_func() => True/False to indicate
 
1216
            if reloading actually changed anything.
1108
1217
        """
1109
1218
        self._indices = indices
 
1219
        self._reload_func = reload_func
1110
1220
 
1111
1221
    def __repr__(self):
1112
1222
        return "%s(%s)" % (
1113
1223
                self.__class__.__name__,
1114
1224
                ', '.join(map(repr, self._indices)))
1115
1225
 
1116
 
    @symbol_versioning.deprecated_method(symbol_versioning.one_one)
1117
 
    def get_parents(self, revision_ids):
1118
 
        """See graph._StackedParentsProvider.get_parents.
1119
 
        
1120
 
        This implementation thunks the graph.Graph.get_parents api across to
1121
 
        GraphIndex.
1122
 
 
1123
 
        :param revision_ids: An iterable of graph keys for this graph.
1124
 
        :return: A list of parent details for each key in revision_ids.
1125
 
            Each parent details will be one of:
1126
 
             * None when the key was missing
1127
 
             * (NULL_REVISION,) when the key has no parents.
1128
 
             * (parent_key, parent_key...) otherwise.
1129
 
        """
1130
 
        parent_map = self.get_parent_map(revision_ids)
1131
 
        return [parent_map.get(r, None) for r in revision_ids]
1132
 
 
1133
1226
    def get_parent_map(self, keys):
1134
 
        """See graph._StackedParentsProvider.get_parent_map"""
 
1227
        """See graph.StackedParentsProvider.get_parent_map"""
1135
1228
        search_keys = set(keys)
1136
1229
        if NULL_REVISION in search_keys:
1137
1230
            search_keys.discard(NULL_REVISION)
1145
1238
            found_parents[key] = parents
1146
1239
        return found_parents
1147
1240
 
 
1241
    has_key = _has_key_from_parent_map
 
1242
 
1148
1243
    def insert_index(self, pos, index):
1149
1244
        """Insert a new index in the list of indices to query.
1150
1245
 
1164
1259
            the most efficient order for the index.
1165
1260
        """
1166
1261
        seen_keys = set()
1167
 
        for index in self._indices:
1168
 
            for node in index.iter_all_entries():
1169
 
                if node[1] not in seen_keys:
1170
 
                    yield node
1171
 
                    seen_keys.add(node[1])
 
1262
        while True:
 
1263
            try:
 
1264
                for index in self._indices:
 
1265
                    for node in index.iter_all_entries():
 
1266
                        if node[1] not in seen_keys:
 
1267
                            yield node
 
1268
                            seen_keys.add(node[1])
 
1269
                return
 
1270
            except errors.NoSuchFile:
 
1271
                self._reload_or_raise()
1172
1272
 
1173
1273
    def iter_entries(self, keys):
1174
1274
        """Iterate over keys within the index.
1182
1282
            efficient order for the index.
1183
1283
        """
1184
1284
        keys = set(keys)
1185
 
        for index in self._indices:
1186
 
            if not keys:
 
1285
        while True:
 
1286
            try:
 
1287
                for index in self._indices:
 
1288
                    if not keys:
 
1289
                        return
 
1290
                    for node in index.iter_entries(keys):
 
1291
                        keys.remove(node[1])
 
1292
                        yield node
1187
1293
                return
1188
 
            for node in index.iter_entries(keys):
1189
 
                keys.remove(node[1])
1190
 
                yield node
 
1294
            except errors.NoSuchFile:
 
1295
                self._reload_or_raise()
1191
1296
 
1192
1297
    def iter_entries_prefix(self, keys):
1193
1298
        """Iterate over keys within the index using prefix matching.
1213
1318
        if not keys:
1214
1319
            return
1215
1320
        seen_keys = set()
1216
 
        for index in self._indices:
1217
 
            for node in index.iter_entries_prefix(keys):
1218
 
                if node[1] in seen_keys:
1219
 
                    continue
1220
 
                seen_keys.add(node[1])
1221
 
                yield node
 
1321
        while True:
 
1322
            try:
 
1323
                for index in self._indices:
 
1324
                    for node in index.iter_entries_prefix(keys):
 
1325
                        if node[1] in seen_keys:
 
1326
                            continue
 
1327
                        seen_keys.add(node[1])
 
1328
                        yield node
 
1329
                return
 
1330
            except errors.NoSuchFile:
 
1331
                self._reload_or_raise()
 
1332
 
 
1333
    def find_ancestry(self, keys, ref_list_num):
 
1334
        """Find the complete ancestry for the given set of keys.
 
1335
 
 
1336
        Note that this is a whole-ancestry request, so it should be used
 
1337
        sparingly.
 
1338
 
 
1339
        :param keys: An iterable of keys to look for
 
1340
        :param ref_list_num: The reference list which references the parents
 
1341
            we care about.
 
1342
        :return: (parent_map, missing_keys)
 
1343
        """
 
1344
        missing_keys = set()
 
1345
        parent_map = {}
 
1346
        keys_to_lookup = set(keys)
 
1347
        generation = 0
 
1348
        while keys_to_lookup:
 
1349
            # keys that *all* indexes claim are missing, stop searching them
 
1350
            generation += 1
 
1351
            all_index_missing = None
 
1352
            # print 'gen\tidx\tsub\tn_keys\tn_pmap\tn_miss'
 
1353
            # print '%4d\t\t\t%4d\t%5d\t%5d' % (generation, len(keys_to_lookup),
 
1354
            #                                   len(parent_map),
 
1355
            #                                   len(missing_keys))
 
1356
            for index_idx, index in enumerate(self._indices):
 
1357
                # TODO: we should probably be doing something with
 
1358
                #       'missing_keys' since we've already determined that
 
1359
                #       those revisions have not been found anywhere
 
1360
                index_missing_keys = set()
 
1361
                # Find all of the ancestry we can from this index
 
1362
                # keep looking until the search_keys set is empty, which means
 
1363
                # things we didn't find should be in index_missing_keys
 
1364
                search_keys = keys_to_lookup
 
1365
                sub_generation = 0
 
1366
                # print '    \t%2d\t\t%4d\t%5d\t%5d' % (
 
1367
                #     index_idx, len(search_keys),
 
1368
                #     len(parent_map), len(index_missing_keys))
 
1369
                while search_keys:
 
1370
                    sub_generation += 1
 
1371
                    # TODO: ref_list_num should really be a parameter, since
 
1372
                    #       CombinedGraphIndex does not know what the ref lists
 
1373
                    #       mean.
 
1374
                    search_keys = index._find_ancestors(search_keys,
 
1375
                        ref_list_num, parent_map, index_missing_keys)
 
1376
                    # print '    \t  \t%2d\t%4d\t%5d\t%5d' % (
 
1377
                    #     sub_generation, len(search_keys),
 
1378
                    #     len(parent_map), len(index_missing_keys))
 
1379
                # Now set whatever was missing to be searched in the next index
 
1380
                keys_to_lookup = index_missing_keys
 
1381
                if all_index_missing is None:
 
1382
                    all_index_missing = set(index_missing_keys)
 
1383
                else:
 
1384
                    all_index_missing.intersection_update(index_missing_keys)
 
1385
                if not keys_to_lookup:
 
1386
                    break
 
1387
            if all_index_missing is None:
 
1388
                # There were no indexes, so all search keys are 'missing'
 
1389
                missing_keys.update(keys_to_lookup)
 
1390
                keys_to_lookup = None
 
1391
            else:
 
1392
                missing_keys.update(all_index_missing)
 
1393
                keys_to_lookup.difference_update(all_index_missing)
 
1394
        return parent_map, missing_keys
1222
1395
 
1223
1396
    def key_count(self):
1224
1397
        """Return an estimate of the number of keys in this index.
1225
 
        
 
1398
 
1226
1399
        For CombinedGraphIndex this is approximated by the sum of the keys of
1227
1400
        the child indices. As child indices may have duplicate keys this can
1228
1401
        have a maximum error of the number of child indices * largest number of
1229
1402
        keys in any index.
1230
1403
        """
1231
 
        return sum((index.key_count() for index in self._indices), 0)
 
1404
        while True:
 
1405
            try:
 
1406
                return sum((index.key_count() for index in self._indices), 0)
 
1407
            except errors.NoSuchFile:
 
1408
                self._reload_or_raise()
 
1409
 
 
1410
    missing_keys = _missing_keys_from_parent_map
 
1411
 
 
1412
    def _reload_or_raise(self):
 
1413
        """We just got a NoSuchFile exception.
 
1414
 
 
1415
        Try to reload the indices, if it fails, just raise the current
 
1416
        exception.
 
1417
        """
 
1418
        if self._reload_func is None:
 
1419
            raise
 
1420
        exc_type, exc_value, exc_traceback = sys.exc_info()
 
1421
        trace.mutter('Trying to reload after getting exception: %s',
 
1422
                     exc_value)
 
1423
        if not self._reload_func():
 
1424
            # We tried to reload, but nothing changed, so we fail anyway
 
1425
            trace.mutter('_reload_func indicated nothing has changed.'
 
1426
                         ' Raising original exception.')
 
1427
            raise exc_type, exc_value, exc_traceback
1232
1428
 
1233
1429
    def validate(self):
1234
1430
        """Validate that everything in the index can be accessed."""
1235
 
        for index in self._indices:
1236
 
            index.validate()
 
1431
        while True:
 
1432
            try:
 
1433
                for index in self._indices:
 
1434
                    index.validate()
 
1435
                return
 
1436
            except errors.NoSuchFile:
 
1437
                self._reload_or_raise()
1237
1438
 
1238
1439
 
1239
1440
class InMemoryGraphIndex(GraphIndexBuilder):
1326
1527
                    raise errors.BadIndexKey(key)
1327
1528
                node = self._nodes[key]
1328
1529
                if node[0]:
1329
 
                    continue 
 
1530
                    continue
1330
1531
                if self.reference_lists:
1331
1532
                    yield self, key, node[2], node[1]
1332
1533
                else:
1357
1558
                    # can't be empty or would not exist
1358
1559
                    item, value = key_dict.iteritems().next()
1359
1560
                    if type(value) == dict:
1360
 
                        # push keys 
 
1561
                        # push keys
1361
1562
                        dicts.extend(key_dict.itervalues())
1362
1563
                    else:
1363
1564
                        # yield keys
1368
1569
 
1369
1570
    def key_count(self):
1370
1571
        """Return an estimate of the number of keys in this index.
1371
 
        
 
1572
 
1372
1573
        For InMemoryGraphIndex the estimate is exact.
1373
1574
        """
1374
1575
        return len(self._keys)
1382
1583
 
1383
1584
    Queries against this will emit queries against the adapted Graph with the
1384
1585
    prefix added, queries for all items use iter_entries_prefix. The returned
1385
 
    nodes will have their keys and node references adjusted to remove the 
 
1586
    nodes will have their keys and node references adjusted to remove the
1386
1587
    prefix. Finally, an add_nodes_callback can be supplied - when called the
1387
1588
    nodes and references being added will have prefix prepended.
1388
1589
    """
1415
1616
                    adjusted_references))
1416
1617
        except ValueError:
1417
1618
            # XXX: TODO add an explicit interface for getting the reference list
1418
 
            # status, to handle this bit of user-friendliness in the API more 
 
1619
            # status, to handle this bit of user-friendliness in the API more
1419
1620
            # explicitly.
1420
1621
            for (key, value) in nodes:
1421
1622
                translated_nodes.append((self.prefix + key, value))
1493
1694
 
1494
1695
    def key_count(self):
1495
1696
        """Return an estimate of the number of keys in this index.
1496
 
        
 
1697
 
1497
1698
        For GraphIndexPrefixAdapter this is relatively expensive - key
1498
1699
        iteration with the prefix is done.
1499
1700
        """