~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/_btree_serializer_pyx.pyx

  • Committer: Robert Collins
  • Date: 2007-07-15 15:40:37 UTC
  • mto: (2592.3.33 repository)
  • mto: This revision was merged to the branch mainline in revision 2624.
  • Revision ID: robertc@robertcollins.net-20070715154037-3ar8g89decddc9su
Make GraphIndex accept nodes as key, value, references, so that the method
signature is closer to what a simple key->value index delivers. Also
change the behaviour when the reference list count is zero to accept
key, value as nodes, and emit key, value to make it identical in that case
to a simple key->value index. This may not be a good idea, but for now it
seems ok.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2008 Canonical Ltd
2
 
#
3
 
# This program is free software; you can redistribute it and/or modify
4
 
# it under the terms of the GNU General Public License as published by
5
 
# the Free Software Foundation; either version 2 of the License, or
6
 
# (at your option) any later version.
7
 
#
8
 
# This program is distributed in the hope that it will be useful,
9
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
 
# GNU General Public License for more details.
12
 
#
13
 
# You should have received a copy of the GNU General Public License
14
 
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
 
#
17
 
 
18
 
"""Pyrex extensions to btree node parsing."""
19
 
 
20
 
#python2.4 support
21
 
cdef extern from "python-compat.h":
22
 
    pass
23
 
 
24
 
cdef extern from "stdlib.h":
25
 
    ctypedef unsigned size_t
26
 
 
27
 
cdef extern from "Python.h":
28
 
    ctypedef int Py_ssize_t # Required for older pyrex versions
29
 
    ctypedef struct PyObject:
30
 
        pass
31
 
    int PyList_Append(object lst, object item) except -1
32
 
 
33
 
    char *PyString_AsString(object p) except NULL
34
 
    object PyString_FromStringAndSize(char *, Py_ssize_t)
35
 
    PyObject *PyString_FromStringAndSize_ptr "PyString_FromStringAndSize" (char *, Py_ssize_t)
36
 
    int PyString_CheckExact(object s)
37
 
    int PyString_CheckExact_ptr "PyString_CheckExact" (PyObject *)
38
 
    Py_ssize_t PyString_Size(object p)
39
 
    Py_ssize_t PyString_GET_SIZE_ptr "PyString_GET_SIZE" (PyObject *)
40
 
    char * PyString_AS_STRING_ptr "PyString_AS_STRING" (PyObject *)
41
 
    int PyString_AsStringAndSize_ptr(PyObject *, char **buf, Py_ssize_t *len)
42
 
    void PyString_InternInPlace(PyObject **)
43
 
    int PyTuple_CheckExact(object t)
44
 
    Py_ssize_t PyTuple_GET_SIZE(object t)
45
 
    PyObject *PyTuple_GET_ITEM_ptr_object "PyTuple_GET_ITEM" (object tpl, int index)
46
 
    void Py_DECREF_ptr "Py_DECREF" (PyObject *)
47
 
 
48
 
cdef extern from "string.h":
49
 
    void *memcpy(void *dest, void *src, size_t n)
50
 
    void *memchr(void *s, int c, size_t n)
51
 
    # GNU extension
52
 
    # void *memrchr(void *s, int c, size_t n)
53
 
    int strncmp(char *s1, char *s2, size_t n)
54
 
 
55
 
 
56
 
# TODO: Find some way to import this from _dirstate_helpers
57
 
cdef void* _my_memrchr(void *s, int c, size_t n):
58
 
    # memrchr seems to be a GNU extension, so we have to implement it ourselves
59
 
    # It is not present in any win32 standard library
60
 
    cdef char *pos
61
 
    cdef char *start
62
 
 
63
 
    start = <char*>s
64
 
    pos = start + n - 1
65
 
    while pos >= start:
66
 
        if pos[0] == c:
67
 
            return <void*>pos
68
 
        pos = pos - 1
69
 
    return NULL
70
 
 
71
 
# TODO: Import this from _dirstate_helpers when it is merged
72
 
cdef object safe_string_from_size(char *s, Py_ssize_t size):
73
 
    if size < 0:
74
 
        raise AssertionError(
75
 
            'tried to create a string with an invalid size: %d @0x%x'
76
 
            % (size, <int>s))
77
 
    return PyString_FromStringAndSize(s, size)
78
 
 
79
 
 
80
 
cdef object safe_interned_string_from_size(char *s, Py_ssize_t size):
81
 
    cdef PyObject *py_str
82
 
    if size < 0:
83
 
        raise AssertionError(
84
 
            'tried to create a string with an invalid size: %d @0x%x'
85
 
            % (size, <int>s))
86
 
    py_str = PyString_FromStringAndSize_ptr(s, size)
87
 
    PyString_InternInPlace(&py_str)
88
 
    result = <object>py_str
89
 
    # Casting a PyObject* to an <object> triggers an INCREF from Pyrex, so we
90
 
    # DECREF it to avoid geting immortal strings
91
 
    Py_DECREF_ptr(py_str)
92
 
    return result
93
 
 
94
 
 
95
 
cdef class BTreeLeafParser:
96
 
    """Parse the leaf nodes of a BTree index.
97
 
 
98
 
    :ivar bytes: The PyString object containing the uncompressed text for the
99
 
        node.
100
 
    :ivar key_length: An integer describing how many pieces the keys have for
101
 
        this index.
102
 
    :ivar ref_list_length: An integer describing how many references this index
103
 
        contains.
104
 
    :ivar keys: A PyList of keys found in this node.
105
 
 
106
 
    :ivar _cur_str: A pointer to the start of the next line to parse
107
 
    :ivar _end_str: A pointer to the end of bytes
108
 
    :ivar _start: Pointer to the location within the current line while
109
 
        parsing.
110
 
    :ivar _header_found: True when we have parsed the header for this node
111
 
    """
112
 
 
113
 
    cdef object bytes
114
 
    cdef int key_length
115
 
    cdef int ref_list_length
116
 
    cdef object keys
117
 
 
118
 
    cdef char * _cur_str
119
 
    cdef char * _end_str
120
 
    # The current start point for parsing
121
 
    cdef char * _start
122
 
 
123
 
    cdef int _header_found
124
 
 
125
 
    def __init__(self, bytes, key_length, ref_list_length):
126
 
        self.bytes = bytes
127
 
        self.key_length = key_length
128
 
        self.ref_list_length = ref_list_length
129
 
        self.keys = []
130
 
        self._cur_str = NULL
131
 
        self._end_str = NULL
132
 
        self._header_found = 0
133
 
 
134
 
    cdef extract_key(self, char * last):
135
 
        """Extract a key.
136
 
 
137
 
        :param last: points at the byte after the last byte permitted for the
138
 
            key.
139
 
        """
140
 
        cdef char *temp_ptr
141
 
        cdef int loop_counter
142
 
        # keys are tuples
143
 
        loop_counter = 0
144
 
        key_segments = []
145
 
        while loop_counter < self.key_length:
146
 
            loop_counter = loop_counter + 1
147
 
            # grab a key segment
148
 
            temp_ptr = <char*>memchr(self._start, c'\0', last - self._start)
149
 
            if temp_ptr == NULL:
150
 
                if loop_counter == self.key_length:
151
 
                    # capture to last
152
 
                    temp_ptr = last
153
 
                else:
154
 
                    # Invalid line
155
 
                    failure_string = ("invalid key, wanted segment from " +
156
 
                        repr(safe_string_from_size(self._start,
157
 
                                                   last - self._start)))
158
 
                    raise AssertionError(failure_string)
159
 
            # capture the key string
160
 
            # TODO: Consider using PyIntern_FromString, the only caveat is that
161
 
            # it assumes a NULL-terminated string, so we have to check if
162
 
            # temp_ptr[0] == c'\0' or some other char.
163
 
            key_element = safe_interned_string_from_size(self._start,
164
 
                                                         temp_ptr - self._start)
165
 
            # advance our pointer
166
 
            self._start = temp_ptr + 1
167
 
            PyList_Append(key_segments, key_element)
168
 
        return tuple(key_segments)
169
 
 
170
 
    cdef int process_line(self) except -1:
171
 
        """Process a line in the bytes."""
172
 
        cdef char *last
173
 
        cdef char *temp_ptr
174
 
        cdef char *ref_ptr
175
 
        cdef char *next_start
176
 
        cdef int loop_counter
177
 
 
178
 
        self._start = self._cur_str
179
 
        # Find the next newline
180
 
        last = <char*>memchr(self._start, c'\n', self._end_str - self._start)
181
 
        if last == NULL:
182
 
            # Process until the end of the file
183
 
            last = self._end_str
184
 
            self._cur_str = self._end_str
185
 
        else:
186
 
            # And the next string is right after it
187
 
            self._cur_str = last + 1
188
 
            # The last character is right before the '\n'
189
 
            last = last
190
 
 
191
 
        if last == self._start:
192
 
            # parsed it all.
193
 
            return 0
194
 
        if last < self._start:
195
 
            # Unexpected error condition - fail
196
 
            return -1
197
 
        if 0 == self._header_found:
198
 
            # The first line in a leaf node is the header "type=leaf\n"
199
 
            if strncmp("type=leaf", self._start, last - self._start) == 0:
200
 
                self._header_found = 1
201
 
                return 0
202
 
            else:
203
 
                raise AssertionError('Node did not start with "type=leaf": %r'
204
 
                    % (safe_string_from_size(self._start, last - self._start)))
205
 
                return -1
206
 
 
207
 
        key = self.extract_key(last)
208
 
        # find the value area
209
 
        temp_ptr = <char*>_my_memrchr(self._start, c'\0', last - self._start)
210
 
        if temp_ptr == NULL:
211
 
            # Invalid line
212
 
            return -1
213
 
        else:
214
 
            # capture the value string
215
 
            value = safe_string_from_size(temp_ptr + 1, last - temp_ptr - 1)
216
 
            # shrink the references end point
217
 
            last = temp_ptr
218
 
        if self.ref_list_length:
219
 
            ref_lists = []
220
 
            loop_counter = 0
221
 
            while loop_counter < self.ref_list_length:
222
 
                ref_list = []
223
 
                # extract a reference list
224
 
                loop_counter = loop_counter + 1
225
 
                if last < self._start:
226
 
                    return -1
227
 
                # find the next reference list end point:
228
 
                temp_ptr = <char*>memchr(self._start, c'\t', last - self._start)
229
 
                if temp_ptr == NULL:
230
 
                    # Only valid for the last list
231
 
                    if loop_counter != self.ref_list_length:
232
 
                        # Invalid line
233
 
                        return -1
234
 
                        raise AssertionError("invalid key")
235
 
                    else:
236
 
                        # scan to the end of the ref list area
237
 
                        ref_ptr = last
238
 
                        next_start = last
239
 
                else:
240
 
                    # scan to the end of this ref list
241
 
                    ref_ptr = temp_ptr
242
 
                    next_start = temp_ptr + 1
243
 
                # Now, there may be multiple keys in the ref list.
244
 
                while self._start < ref_ptr:
245
 
                    # loop finding keys and extracting them
246
 
                    temp_ptr = <char*>memchr(self._start, c'\r',
247
 
                                             ref_ptr - self._start)
248
 
                    if temp_ptr == NULL:
249
 
                        # key runs to the end
250
 
                        temp_ptr = ref_ptr
251
 
                    PyList_Append(ref_list, self.extract_key(temp_ptr))
252
 
                PyList_Append(ref_lists, tuple(ref_list))
253
 
                # prepare for the next reference list
254
 
                self._start = next_start
255
 
            ref_lists = tuple(ref_lists)
256
 
            node_value = (value, ref_lists)
257
 
        else:
258
 
            if last != self._start:
259
 
                # unexpected reference data present
260
 
                return -1
261
 
            node_value = (value, ())
262
 
        PyList_Append(self.keys, (key, node_value))
263
 
        return 0
264
 
 
265
 
    def parse(self):
266
 
        cdef Py_ssize_t byte_count
267
 
        if not PyString_CheckExact(self.bytes):
268
 
            raise AssertionError('self.bytes is not a string.')
269
 
        byte_count = PyString_Size(self.bytes)
270
 
        self._cur_str = PyString_AsString(self.bytes)
271
 
        # This points to the last character in the string
272
 
        self._end_str = self._cur_str + byte_count
273
 
        while self._cur_str < self._end_str:
274
 
            self.process_line()
275
 
        return self.keys
276
 
 
277
 
 
278
 
def _parse_leaf_lines(bytes, key_length, ref_list_length):
279
 
    parser = BTreeLeafParser(bytes, key_length, ref_list_length)
280
 
    return parser.parse()
281
 
 
282
 
 
283
 
def _flatten_node(node, reference_lists):
284
 
    """Convert a node into the serialized form.
285
 
 
286
 
    :param node: A tuple representing a node:
287
 
        (index, key_tuple, value, references)
288
 
    :param reference_lists: Does this index have reference lists?
289
 
    :return: (string_key, flattened)
290
 
        string_key  The serialized key for referencing this node
291
 
        flattened   A string with the serialized form for the contents
292
 
    """
293
 
    cdef int have_reference_lists
294
 
    cdef Py_ssize_t flat_len
295
 
    cdef Py_ssize_t key_len
296
 
    cdef Py_ssize_t node_len
297
 
    cdef PyObject * val
298
 
    cdef char * value
299
 
    cdef Py_ssize_t value_len
300
 
    cdef char * out
301
 
    cdef Py_ssize_t refs_len
302
 
    cdef Py_ssize_t next_len
303
 
    cdef int first_ref_list
304
 
    cdef int first_reference
305
 
    cdef int i
306
 
    cdef PyObject *ref_bit
307
 
    cdef Py_ssize_t ref_bit_len
308
 
 
309
 
    if not PyTuple_CheckExact(node):
310
 
        raise TypeError('We expected a tuple() for node not: %s'
311
 
            % type(node))
312
 
    node_len = PyTuple_GET_SIZE(node)
313
 
    have_reference_lists = reference_lists
314
 
    if have_reference_lists:
315
 
        if node_len != 4:
316
 
            raise ValueError('With ref_lists, we expected 4 entries not: %s'
317
 
                % len(node))
318
 
    elif node_len < 3:
319
 
        raise ValueError('Without ref_lists, we need at least 3 entries not: %s'
320
 
            % len(node))
321
 
    # I don't expect that we can do faster than string.join()
322
 
    string_key = '\0'.join(<object>PyTuple_GET_ITEM_ptr_object(node, 1))
323
 
 
324
 
    # TODO: instead of using string joins, precompute the final string length,
325
 
    #       and then malloc a single string and copy everything in.
326
 
 
327
 
    # TODO: We probably want to use PySequenceFast, because we have lists and
328
 
    #       tuples, but we aren't sure which we will get.
329
 
 
330
 
    # line := string_key NULL flat_refs NULL value LF
331
 
    # string_key := BYTES (NULL BYTES)*
332
 
    # flat_refs := ref_list (TAB ref_list)*
333
 
    # ref_list := ref (CR ref)*
334
 
    # ref := BYTES (NULL BYTES)*
335
 
    # value := BYTES
336
 
    refs_len = 0
337
 
    if have_reference_lists:
338
 
        # Figure out how many bytes it will take to store the references
339
 
        ref_lists = <object>PyTuple_GET_ITEM_ptr_object(node, 3)
340
 
        next_len = len(ref_lists) # TODO: use a Py function
341
 
        if next_len > 0:
342
 
            # If there are no nodes, we don't need to do any work
343
 
            # Otherwise we will need (len - 1) '\t' characters to separate
344
 
            # the reference lists
345
 
            refs_len = refs_len + (next_len - 1)
346
 
            for ref_list in ref_lists:
347
 
                next_len = len(ref_list)
348
 
                if next_len > 0:
349
 
                    # We will need (len - 1) '\r' characters to separate the
350
 
                    # references
351
 
                    refs_len = refs_len + (next_len - 1)
352
 
                    for reference in ref_list:
353
 
                        if not PyTuple_CheckExact(reference):
354
 
                            raise TypeError(
355
 
                                'We expect references to be tuples not: %s'
356
 
                                % type(reference))
357
 
                        next_len = PyTuple_GET_SIZE(reference)
358
 
                        if next_len > 0:
359
 
                            # We will need (len - 1) '\x00' characters to
360
 
                            # separate the reference key
361
 
                            refs_len = refs_len + (next_len - 1)
362
 
                            for i from 0 <= i < next_len:
363
 
                                ref_bit = PyTuple_GET_ITEM_ptr_object(reference, i)
364
 
                                if not PyString_CheckExact_ptr(ref_bit):
365
 
                                    raise TypeError('We expect reference bits'
366
 
                                        ' to be strings not: %s'
367
 
                                        % type(<object>ref_bit))
368
 
                                refs_len = refs_len + PyString_GET_SIZE_ptr(ref_bit)
369
 
 
370
 
    # So we have the (key NULL refs NULL value LF)
371
 
    key_len = PyString_Size(string_key)
372
 
    val = PyTuple_GET_ITEM_ptr_object(node, 2)
373
 
    if not PyString_CheckExact_ptr(val):
374
 
        raise TypeError('Expected a plain str for value not: %s'
375
 
                        % type(<object>val))
376
 
    value = PyString_AS_STRING_ptr(val)
377
 
    value_len = PyString_GET_SIZE_ptr(val)
378
 
    flat_len = (key_len + 1 + refs_len + 1 + value_len + 1)
379
 
    line = PyString_FromStringAndSize(NULL, flat_len)
380
 
    # Get a pointer to the new buffer
381
 
    out = PyString_AsString(line)
382
 
    memcpy(out, PyString_AsString(string_key), key_len)
383
 
    out = out + key_len
384
 
    out[0] = c'\0'
385
 
    out = out + 1
386
 
    if refs_len > 0:
387
 
        first_ref_list = 1
388
 
        for ref_list in ref_lists:
389
 
            if first_ref_list == 0:
390
 
                out[0] = c'\t'
391
 
                out = out + 1
392
 
            first_ref_list = 0
393
 
            first_reference = 1
394
 
            for reference in ref_list:
395
 
                if first_reference == 0:
396
 
                    out[0] = c'\r'
397
 
                    out = out + 1
398
 
                first_reference = 0
399
 
                next_len = PyTuple_GET_SIZE(reference)
400
 
                for i from 0 <= i < next_len:
401
 
                    if i != 0:
402
 
                        out[0] = c'\x00'
403
 
                        out = out + 1
404
 
                    ref_bit = PyTuple_GET_ITEM_ptr_object(reference, i)
405
 
                    ref_bit_len = PyString_GET_SIZE_ptr(ref_bit)
406
 
                    memcpy(out, PyString_AS_STRING_ptr(ref_bit), ref_bit_len)
407
 
                    out = out + ref_bit_len
408
 
    out[0] = c'\0'
409
 
    out = out  + 1
410
 
    memcpy(out, value, value_len)
411
 
    out = out + value_len
412
 
    out[0] = c'\n'
413
 
    return string_key, line