/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_serializer_pyx.pyx

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2009-07-20 08:56:45 UTC
  • mfrom: (4526.9.23 apply-inventory-delta)
  • Revision ID: pqm@pqm.ubuntu.com-20090720085645-54mtgybxua0yx6hw
(robertc) Add checks for inventory deltas which try to ensure that
        deltas that are not an exact fit are not applied. (Robert
        Collins, bug 397705, bug 367633)

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