perobs 2.5.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/perobs/Array.rb +1 -1
- data/lib/perobs/BTree.rb +233 -0
- data/lib/perobs/BTreeDB.rb +2 -0
- data/lib/perobs/BTreeNode.rb +706 -0
- data/lib/perobs/BTreeNodeCache.rb +107 -0
- data/lib/perobs/BTreeNodeLink.rb +141 -0
- data/lib/perobs/EquiBlobsFile.rb +570 -0
- data/lib/perobs/FlatFile.rb +179 -78
- data/lib/perobs/FlatFileBlobHeader.rb +92 -17
- data/lib/perobs/FlatFileDB.rb +16 -7
- data/lib/perobs/LockFile.rb +181 -0
- data/lib/perobs/Object.rb +2 -1
- data/lib/perobs/SpaceTree.rb +181 -0
- data/lib/perobs/SpaceTreeNode.rb +672 -0
- data/lib/perobs/SpaceTreeNodeCache.rb +76 -0
- data/lib/perobs/SpaceTreeNodeLink.rb +103 -0
- data/lib/perobs/Store.rb +27 -13
- data/lib/perobs/version.rb +1 -1
- data/test/BTree_spec.rb +128 -0
- data/test/EquiBlobsFile_spec.rb +199 -0
- data/test/FlatFileDB_spec.rb +63 -9
- data/test/LockFile_spec.rb +133 -0
- data/test/SpaceTree_spec.rb +245 -0
- data/test/Store_spec.rb +3 -0
- data/test/spec_helper.rb +13 -0
- metadata +21 -13
- data/lib/perobs/FixedSizeBlobFile.rb +0 -193
- data/lib/perobs/FreeSpaceManager.rb +0 -204
- data/lib/perobs/IndexTree.rb +0 -145
- data/lib/perobs/IndexTreeNode.rb +0 -316
- data/test/FixedSizeBlobFile_spec.rb +0 -91
- data/test/FreeSpaceManager_spec.rb +0 -91
- data/test/IndexTree_spec.rb +0 -118
@@ -0,0 +1,706 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# = BTreeNode.rb -- Persistent Ruby Object Store
|
4
|
+
#
|
5
|
+
# Copyright (c) 2016, 2017 by Chris Schlaeger <chris@taskjuggler.org>
|
6
|
+
#
|
7
|
+
# MIT License
|
8
|
+
#
|
9
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
10
|
+
# a copy of this software and associated documentation files (the
|
11
|
+
# "Software"), to deal in the Software without restriction, including
|
12
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
13
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
14
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
15
|
+
# the following conditions:
|
16
|
+
#
|
17
|
+
# The above copyright notice and this permission notice shall be
|
18
|
+
# included in all copies or substantial portions of the Software.
|
19
|
+
#
|
20
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
21
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
22
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
23
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
24
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
25
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
26
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
27
|
+
|
28
|
+
require 'zlib'
|
29
|
+
|
30
|
+
require 'perobs/BTree'
|
31
|
+
require 'perobs/BTreeNodeLink'
|
32
|
+
|
33
|
+
module PEROBS
|
34
|
+
|
35
|
+
# The BTreeNode class manages more or less standard BTree nodes. All nodes
|
36
|
+
# contain BTree.order number of keys. Leaf node contain BTree.order number
|
37
|
+
# of values and no child references. Branch nodes only contain BTree.order +
|
38
|
+
# 1 number of child references but no values. The is_leaf flag is used to
|
39
|
+
# mark a node as leaf or branch node.
|
40
|
+
class BTreeNode
|
41
|
+
|
42
|
+
attr_reader :node_address, :parent, :is_leaf, :keys, :values, :children
|
43
|
+
attr_accessor :dirty
|
44
|
+
|
45
|
+
# Create a new BTreeNode object for the given tree with the given parent
|
46
|
+
# or recreate the node with the given node_address from the backing store.
|
47
|
+
# If node_address is nil a new node will be created. If not, node_address
|
48
|
+
# must be an existing address that can be found in the backing store to
|
49
|
+
# restore the node.
|
50
|
+
# @param tree [BTree] The tree this node is part of
|
51
|
+
# @param parent [BTreeNode] reference to parent node
|
52
|
+
# @param node_address [Integer] the address of the node to read from the
|
53
|
+
# backing store
|
54
|
+
# @param is_leaf [Boolean] true if the node should be a leaf node, false
|
55
|
+
# if not
|
56
|
+
def initialize(tree, parent = nil, node_address = nil, is_leaf = true)
|
57
|
+
@tree = tree
|
58
|
+
@parent = nil
|
59
|
+
if node_address == 0
|
60
|
+
PEROBS.log.fatal "Node address may not be 0"
|
61
|
+
end
|
62
|
+
@node_address = node_address
|
63
|
+
@keys = []
|
64
|
+
if (@is_leaf = is_leaf)
|
65
|
+
@values = []
|
66
|
+
else
|
67
|
+
@children = []
|
68
|
+
end
|
69
|
+
|
70
|
+
if node_address
|
71
|
+
unless node_address.is_a?(Integer)
|
72
|
+
PEROBS.log.fatal "node_address is not Integer: #{node_address.class}"
|
73
|
+
end
|
74
|
+
|
75
|
+
# This must be an existing node. Try to read it and fill the instance
|
76
|
+
# variables.
|
77
|
+
unless read_node
|
78
|
+
PEROBS.log.fatal "SpaceTree node at address #{node_address} " +
|
79
|
+
"does not exist"
|
80
|
+
end
|
81
|
+
else
|
82
|
+
unless parent.nil? || parent.is_a?(BTreeNode) ||
|
83
|
+
parent.is_a?(BTreeNodeLink)
|
84
|
+
PEROBS.log.fatal "Parent node must be a BTreeNode but is of class " +
|
85
|
+
"#{parent.class}"
|
86
|
+
end
|
87
|
+
|
88
|
+
# This is a new node. Make sure the data is written to the file.
|
89
|
+
@node_address = @tree.nodes.free_address
|
90
|
+
self.parent = parent
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def BTreeNode::node_bytes(order)
|
95
|
+
1 + # is_leaf
|
96
|
+
2 + # actual key count
|
97
|
+
2 + # actual value or children count (aka data count)
|
98
|
+
8 + # parent address
|
99
|
+
8 * order + # keys
|
100
|
+
8 * (order + 1) + # values or child addresses
|
101
|
+
4 # CRC32 checksum
|
102
|
+
end
|
103
|
+
|
104
|
+
# Insert or replace the given value by using the key as unique address.
|
105
|
+
# @param key [Integer] Unique key to retrieve the value
|
106
|
+
# @param value [Integer] value to insert
|
107
|
+
def insert(key, value)
|
108
|
+
node = self
|
109
|
+
|
110
|
+
# Traverse the tree to find the right node to add or replace the value.
|
111
|
+
while node do
|
112
|
+
# All nodes that we find on the way that are full will be split into
|
113
|
+
# two half-full nodes.
|
114
|
+
if node.keys.size >= @tree.order
|
115
|
+
node = node.split_node
|
116
|
+
end
|
117
|
+
|
118
|
+
# Once we have reached a leaf node we can insert or replace the value.
|
119
|
+
if node.is_leaf
|
120
|
+
node.insert_element(key, value)
|
121
|
+
return
|
122
|
+
else
|
123
|
+
# Descend into the right child node to add the value to.
|
124
|
+
node = node.children[node.search_key_index(key)]
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
PEROBS.log.fatal 'Could not find proper node to add to'
|
129
|
+
end
|
130
|
+
|
131
|
+
# Return the value that matches the given key or return nil if they key is
|
132
|
+
# unknown.
|
133
|
+
# @param key [Integer] key to search for
|
134
|
+
# @return [Integer or nil] value that matches the key
|
135
|
+
def get(key)
|
136
|
+
node = self
|
137
|
+
|
138
|
+
while node do
|
139
|
+
# Find index of the entry that best fits the key.
|
140
|
+
i = node.search_key_index(key)
|
141
|
+
if node.is_leaf
|
142
|
+
# This is a leaf node. Check if there is an exact match for the
|
143
|
+
# given key and return the corresponding value or nil.
|
144
|
+
return node.keys[i] == key ? node.values[i] : nil
|
145
|
+
end
|
146
|
+
|
147
|
+
# Descend into the right child node to continue the search.
|
148
|
+
node = node.children[i]
|
149
|
+
end
|
150
|
+
|
151
|
+
PEROBS.log.fatal "Could not find proper node to get from while " +
|
152
|
+
"looking for key #{key}"
|
153
|
+
end
|
154
|
+
|
155
|
+
# Return the value that matches the given key and remove the value from
|
156
|
+
# the tree. Return nil if the key is unknown.
|
157
|
+
# @param key [Integer] key to search for
|
158
|
+
# @return [Integer or nil] value that matches the key
|
159
|
+
def remove(key)
|
160
|
+
node = self
|
161
|
+
|
162
|
+
while node do
|
163
|
+
# Find index of the entry that best fits the key.
|
164
|
+
i = node.search_key_index(key)
|
165
|
+
if node.is_leaf
|
166
|
+
# This is a leaf node. Check if there is an exact match for the
|
167
|
+
# given key and return the corresponding value or nil.
|
168
|
+
if node.keys[i] == key
|
169
|
+
return node.remove_element(i)
|
170
|
+
else
|
171
|
+
return nil
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# Descend into the right child node to continue the search.
|
176
|
+
node = node.children[i]
|
177
|
+
end
|
178
|
+
|
179
|
+
PEROBS.log.fatal 'Could not find proper node to remove from'
|
180
|
+
end
|
181
|
+
|
182
|
+
# Split the current node into two nodes. The upper half of the elements
|
183
|
+
# will be moved into a newly created node. This node will retain the lower
|
184
|
+
# half.
|
185
|
+
# @return [BTreeNodeLink] common parent of the two nodes
|
186
|
+
def split_node
|
187
|
+
unless @parent
|
188
|
+
# The node is the root node. We need to create a parent node first.
|
189
|
+
self.parent = @tree.new_node(nil, nil, false)
|
190
|
+
@parent.set_child(0, self)
|
191
|
+
@tree.set_root(@parent)
|
192
|
+
end
|
193
|
+
|
194
|
+
# Create the new sibling that will take the 2nd half of the
|
195
|
+
# node content.
|
196
|
+
sibling = @tree.new_node(@parent, nil, @is_leaf)
|
197
|
+
# Determine the index of the middle element that gets moved to the
|
198
|
+
# parent. The order must be an uneven number, so adding 1 will get us
|
199
|
+
# the middle element.
|
200
|
+
mid = @tree.order / 2 + 1
|
201
|
+
# Insert the middle element key into the parent node
|
202
|
+
@parent.insert_element(@keys[mid], sibling)
|
203
|
+
copy_elements(mid + (@is_leaf ? 0 : 1), sibling)
|
204
|
+
trim(mid)
|
205
|
+
|
206
|
+
@parent
|
207
|
+
end
|
208
|
+
|
209
|
+
def merge_node(upper_sibling, parent_index)
|
210
|
+
if upper_sibling == self
|
211
|
+
PEROBS.log.fatal "Cannot merge node @#{@node_address} with self"
|
212
|
+
end
|
213
|
+
unless upper_sibling.is_leaf
|
214
|
+
insert_element(@parent.keys[parent_index], upper_sibling.children[0])
|
215
|
+
end
|
216
|
+
upper_sibling.copy_elements(0, self, @keys.size, upper_sibling.keys.size)
|
217
|
+
@tree.delete_node(upper_sibling.node_address)
|
218
|
+
|
219
|
+
@parent.remove_element(parent_index)
|
220
|
+
end
|
221
|
+
|
222
|
+
# Insert the given value or child into the current node using the key as
|
223
|
+
# index.
|
224
|
+
# @param key [Integer] key to address the value or child
|
225
|
+
# @param value_or_child [Integer or BTreeNode] value or BTreeNode
|
226
|
+
# reference
|
227
|
+
def insert_element(key, value_or_child)
|
228
|
+
if @keys.size >= @tree.order
|
229
|
+
PEROBS.log.fatal "Cannot insert into a full BTreeNode"
|
230
|
+
end
|
231
|
+
|
232
|
+
mark_as_modified
|
233
|
+
i = search_key_index(key)
|
234
|
+
if @keys[i] == key
|
235
|
+
# Overwrite existing entries
|
236
|
+
@keys[i] = key
|
237
|
+
if is_leaf
|
238
|
+
@values[i] = value_or_child
|
239
|
+
else
|
240
|
+
@children[i + 1] = BTreeNodeLink.new(@tree, value_or_child)
|
241
|
+
end
|
242
|
+
else
|
243
|
+
# Create a new entry
|
244
|
+
@keys.insert(i, key)
|
245
|
+
if is_leaf
|
246
|
+
@values.insert(i, value_or_child)
|
247
|
+
else
|
248
|
+
@children.insert(i + 1, BTreeNodeLink.new(@tree, value_or_child))
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
# Remove the element at the given index.
|
254
|
+
def remove_element(index)
|
255
|
+
# We need this key to find the link in the parent node.
|
256
|
+
first_key = @keys[0]
|
257
|
+
removed_value = nil
|
258
|
+
|
259
|
+
mark_as_modified
|
260
|
+
# Delete the key at the specified index.
|
261
|
+
unless @keys.delete_at(index)
|
262
|
+
PEROBS.log.fatal "Could not remove element #{index} from BTreeNode " +
|
263
|
+
"@#{@node_address}"
|
264
|
+
end
|
265
|
+
if @is_leaf
|
266
|
+
# For leaf nodes, also delete the corresponding value.
|
267
|
+
removed_value = @values.delete_at(index)
|
268
|
+
else
|
269
|
+
# The corresponding child has can be found at 1 index higher.
|
270
|
+
@children.delete_at(index + 1)
|
271
|
+
end
|
272
|
+
|
273
|
+
# Find the lower and upper siblings and the index of the key for this
|
274
|
+
# node in the parent node.
|
275
|
+
lower_sibling, upper_sibling, parent_index =
|
276
|
+
find_closest_siblings(first_key)
|
277
|
+
|
278
|
+
if lower_sibling &&
|
279
|
+
lower_sibling.keys.size + @keys.size < @tree.order
|
280
|
+
lower_sibling.merge_node(self, parent_index - 1)
|
281
|
+
elsif upper_sibling &&
|
282
|
+
@keys.size + upper_sibling.keys.size < @tree.order
|
283
|
+
merge_node(upper_sibling, parent_index)
|
284
|
+
end
|
285
|
+
|
286
|
+
# The merge has potentially invalidated this node. After this method has
|
287
|
+
# been called this copy of the node should no longer be used.
|
288
|
+
removed_value
|
289
|
+
end
|
290
|
+
|
291
|
+
def copy_elements(src_idx, dest_node, dst_idx = 0, count = nil)
|
292
|
+
unless count
|
293
|
+
count = @tree.order - src_idx
|
294
|
+
end
|
295
|
+
if dst_idx + count > @tree.order
|
296
|
+
PEROBS.log.fatal "Destination too small for copy operation"
|
297
|
+
end
|
298
|
+
if dest_node.is_leaf != @is_leaf
|
299
|
+
PEROBS.log.fatal "Source #{@is_leaf} and destination " +
|
300
|
+
"#{dest_node.is_leaf} node must be of same kind"
|
301
|
+
end
|
302
|
+
|
303
|
+
dest_node.keys[dst_idx, count] = @keys[src_idx, count]
|
304
|
+
dest_node.dirty = true
|
305
|
+
if @is_leaf
|
306
|
+
# For leaves we copy the keys and corresponding values.
|
307
|
+
dest_node.values[dst_idx, count] = @values[src_idx, count]
|
308
|
+
else
|
309
|
+
# For branch nodes we copy all but the first specified key (that
|
310
|
+
# one moved up to the parent) and all the children to the right of the
|
311
|
+
# moved-up key.
|
312
|
+
(count + 1).times do |i|
|
313
|
+
dest_node.set_child(dst_idx + i, @children[src_idx + i])
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
def parent=(p)
|
319
|
+
@parent = p ? BTreeNodeLink.new(@tree, p) : nil
|
320
|
+
mark_as_modified
|
321
|
+
end
|
322
|
+
|
323
|
+
def set_child(index, child)
|
324
|
+
if child
|
325
|
+
@children[index] = BTreeNodeLink.new(@tree, child)
|
326
|
+
@children[index].parent = self
|
327
|
+
else
|
328
|
+
@children[index] = nil
|
329
|
+
end
|
330
|
+
mark_as_modified
|
331
|
+
end
|
332
|
+
|
333
|
+
def trim(idx)
|
334
|
+
mark_as_modified
|
335
|
+
@keys = @keys[0..idx - 1]
|
336
|
+
if @is_leaf
|
337
|
+
@values = @values[0..idx - 1]
|
338
|
+
else
|
339
|
+
@children = @children[0..idx]
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
# Search the keys of the node that fits the given key. The result is
|
344
|
+
# either the index of an exact match or the index of the position where
|
345
|
+
# the given key would have to be inserted.
|
346
|
+
# @param key [Integer] key to search for
|
347
|
+
# @return [Integer] Index of the matching key or the insert position.
|
348
|
+
def search_key_index(key)
|
349
|
+
# Handle special case for empty keys list.
|
350
|
+
return 0 if @keys.empty?
|
351
|
+
|
352
|
+
# Keys are unique and always sorted. Use a binary search to find the
|
353
|
+
# index that fits the given key.
|
354
|
+
li = pi = 0
|
355
|
+
ui = @keys.size - 1
|
356
|
+
while li <= ui
|
357
|
+
# The pivot element is always in the middle between the lower and upper
|
358
|
+
# index.
|
359
|
+
pi = li + (ui - li) / 2
|
360
|
+
|
361
|
+
if key < @keys[pi]
|
362
|
+
# The pivot element is smaller than the key. Set the upper index to
|
363
|
+
# the pivot index.
|
364
|
+
ui = pi - 1
|
365
|
+
elsif key > @keys[pi]
|
366
|
+
# The pivot element is larger than the key. Set the lower index to
|
367
|
+
# the pivot index.
|
368
|
+
li = pi + 1
|
369
|
+
else
|
370
|
+
# We've found an exact match. For leaf nodes return the found index.
|
371
|
+
# For branch nodes we have to add one to the index since the larger
|
372
|
+
# child is the right one.
|
373
|
+
return @is_leaf ? pi : pi + 1
|
374
|
+
end
|
375
|
+
end
|
376
|
+
# No exact match was found. For the insert operaton we need to return
|
377
|
+
# the index of the first key that is larger than the given key.
|
378
|
+
@keys[pi] < key ? pi + 1 : pi
|
379
|
+
end
|
380
|
+
|
381
|
+
# Iterate over all the key/value pairs in this node and all sub-nodes.
|
382
|
+
# @yield [key, value]
|
383
|
+
def each
|
384
|
+
traverse do |node, position, stack|
|
385
|
+
if node.is_leaf && position < node.keys.size
|
386
|
+
yield(node.keys[position], node.values[position])
|
387
|
+
end
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
# This is a generic tree iterator. It yields before it descends into the
|
392
|
+
# child node and after (which is identical to before the next child
|
393
|
+
# descend). It yields the node, the position and the stack of parent
|
394
|
+
# nodes.
|
395
|
+
# @yield [node, position, stack]
|
396
|
+
def traverse
|
397
|
+
# We use a non-recursive implementation to traverse the tree. This stack
|
398
|
+
# keeps track of all the known still to be checked nodes.
|
399
|
+
stack = [ [ self, 0 ] ]
|
400
|
+
|
401
|
+
while !stack.empty?
|
402
|
+
node, position = stack.pop
|
403
|
+
|
404
|
+
# Call the payload method. The position marks where we are in the node
|
405
|
+
# with respect to the traversal. 0 means we've just entered the node
|
406
|
+
# for the first time and are about to descent to the first child.
|
407
|
+
# Position 1 is after the 1st child has been processed and before the
|
408
|
+
# 2nd child is being processed. If we have N children, the last
|
409
|
+
# position is N after we have processed the last child and are about
|
410
|
+
# to return to the parent node.
|
411
|
+
yield(node, position, stack)
|
412
|
+
|
413
|
+
if position <= @tree.order
|
414
|
+
# Push the next position for this node onto the stack.
|
415
|
+
stack.push([ node, position + 1 ])
|
416
|
+
|
417
|
+
if !node.is_leaf && node.children[position]
|
418
|
+
# If we have a child node for this position, push the linked node
|
419
|
+
# and the starting position onto the stack.
|
420
|
+
stack.push([ node.children[position], 0 ])
|
421
|
+
end
|
422
|
+
end
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
# Check consistency of the node and all subsequent nodes. In case an error
|
427
|
+
# is found, a message is logged and false is returned.
|
428
|
+
# @yield [key, value]
|
429
|
+
# @return [Boolean] true if tree has no errors
|
430
|
+
def check
|
431
|
+
traverse do |node, position, stack|
|
432
|
+
if position == 0
|
433
|
+
if node.parent && node.keys.size < 1
|
434
|
+
node.error "BTreeNode must have at least one entry"
|
435
|
+
return false
|
436
|
+
end
|
437
|
+
if node.keys.size > @tree.order
|
438
|
+
node.error "BTreeNode must not have more then #{@tree.order} " +
|
439
|
+
"keys, but has #{node.keys.size} keys"
|
440
|
+
end
|
441
|
+
|
442
|
+
last_key = nil
|
443
|
+
node.keys.each do |key|
|
444
|
+
if last_key && key < last_key
|
445
|
+
node.error "Keys are not increasing monotoneously: " +
|
446
|
+
"#{node.keys.inspect}"
|
447
|
+
return false
|
448
|
+
end
|
449
|
+
end
|
450
|
+
|
451
|
+
if node.is_leaf
|
452
|
+
unless node.keys.size == node.values.size
|
453
|
+
node.error "Key count (#{node.keys.size}) and value " +
|
454
|
+
"count (#{node.values.size}) don't match"
|
455
|
+
return false
|
456
|
+
end
|
457
|
+
else
|
458
|
+
unless node.keys.size == node.children.size - 1
|
459
|
+
node.error "Key count (#{node.keys.size}) must be one " +
|
460
|
+
"less than children count (#{node.children.size})"
|
461
|
+
return false
|
462
|
+
end
|
463
|
+
node.children.each_with_index do |child, i|
|
464
|
+
unless child.is_a?(BTreeNodeLink)
|
465
|
+
node.error "Child #{i} is of class #{child.class} " +
|
466
|
+
"instead of BTreeNodeLink"
|
467
|
+
return false
|
468
|
+
end
|
469
|
+
unless child.parent.is_a?(BTreeNodeLink)
|
470
|
+
node.error "Parent reference of child #{i} is of class " +
|
471
|
+
"#{child.class} instead of BTreeNodeLink"
|
472
|
+
return false
|
473
|
+
end
|
474
|
+
if child.node_address == node.node_address
|
475
|
+
node.error "Child #{i} points to self"
|
476
|
+
return false
|
477
|
+
end
|
478
|
+
if stack.include?(child)
|
479
|
+
node.error "Child #{i} points to ancester node"
|
480
|
+
return false
|
481
|
+
end
|
482
|
+
unless child.parent == node
|
483
|
+
node.error "Child #{i} does not have parent pointing " +
|
484
|
+
"to this node"
|
485
|
+
return false
|
486
|
+
end
|
487
|
+
end
|
488
|
+
end
|
489
|
+
elsif position <= node.keys.size
|
490
|
+
# These checks are done after we have completed the respective child
|
491
|
+
# node with index 'position - 1'.
|
492
|
+
index = position - 1
|
493
|
+
if !node.is_leaf
|
494
|
+
unless node.children[index].keys.last < node.keys[index]
|
495
|
+
node.error "Child #{node.children[index].node_address} " +
|
496
|
+
"has too large key #{node.children[index].keys.last}. " +
|
497
|
+
"Must be smaller than #{node.keys[index]}."
|
498
|
+
return false
|
499
|
+
end
|
500
|
+
unless node.children[position].keys.first >=
|
501
|
+
node.keys[index]
|
502
|
+
node.error "Child #{node.children[position].node_address} " +
|
503
|
+
"has too small key #{node.children[position].keys.first}. " +
|
504
|
+
"Must be larger than or equal to #{node.keys[index]}."
|
505
|
+
return false
|
506
|
+
end
|
507
|
+
else
|
508
|
+
if block_given?
|
509
|
+
# If a block was given, call this block with the key and value.
|
510
|
+
return false unless yield(node.keys[index], node.values[index])
|
511
|
+
end
|
512
|
+
end
|
513
|
+
end
|
514
|
+
end
|
515
|
+
|
516
|
+
true
|
517
|
+
end
|
518
|
+
|
519
|
+
def is_top?
|
520
|
+
@parent.nil? || @parent.parent.nil? || @parent.parent.parent.nil?
|
521
|
+
end
|
522
|
+
|
523
|
+
def to_s
|
524
|
+
str = ''
|
525
|
+
|
526
|
+
traverse do |node, position, stack|
|
527
|
+
if position == 0
|
528
|
+
begin
|
529
|
+
str += "#{node.parent ? node.parent.tree_prefix + ' +' : 'o'}" +
|
530
|
+
"#{node.tree_branch_mark}-" +
|
531
|
+
"#{node.keys.first.nil? ? '--' : 'v-'}#{node.tree_summary}\n"
|
532
|
+
rescue
|
533
|
+
str += "@@@@@@@@@@\n"
|
534
|
+
end
|
535
|
+
else
|
536
|
+
begin
|
537
|
+
if node.is_leaf
|
538
|
+
if node.keys[position - 1]
|
539
|
+
str += "#{node.tree_prefix} |" +
|
540
|
+
"[#{node.keys[position - 1]}, " +
|
541
|
+
"#{node.values[position - 1]}]\n"
|
542
|
+
end
|
543
|
+
else
|
544
|
+
if node.keys[position - 1]
|
545
|
+
str += "#{node.tree_prefix} #{node.keys[position - 1]}\n"
|
546
|
+
end
|
547
|
+
end
|
548
|
+
rescue
|
549
|
+
str += "@@@@@@@@@@\n"
|
550
|
+
end
|
551
|
+
end
|
552
|
+
end
|
553
|
+
|
554
|
+
str
|
555
|
+
end
|
556
|
+
|
557
|
+
def tree_prefix
|
558
|
+
node = self
|
559
|
+
str = ''
|
560
|
+
|
561
|
+
while node
|
562
|
+
is_last_child = false
|
563
|
+
if node.parent
|
564
|
+
is_last_child = node.parent.children.last == node
|
565
|
+
else
|
566
|
+
# Don't add lines for the top-level.
|
567
|
+
break
|
568
|
+
end
|
569
|
+
|
570
|
+
str = (is_last_child ? ' ' : ' |') + str
|
571
|
+
node = node.parent
|
572
|
+
end
|
573
|
+
|
574
|
+
str
|
575
|
+
end
|
576
|
+
|
577
|
+
def tree_branch_mark
|
578
|
+
return '' unless @parent
|
579
|
+
'-'
|
580
|
+
end
|
581
|
+
|
582
|
+
def tree_summary
|
583
|
+
s = " @#{@node_address}"
|
584
|
+
if @parent
|
585
|
+
begin
|
586
|
+
s += " ^#{@parent.node_address}"
|
587
|
+
rescue
|
588
|
+
s += ' ^@'
|
589
|
+
end
|
590
|
+
end
|
591
|
+
|
592
|
+
s
|
593
|
+
end
|
594
|
+
|
595
|
+
def error(msg)
|
596
|
+
PEROBS.log.error "Error in BTreeNode @#{@node_address}: #{msg}\n" +
|
597
|
+
@tree.to_s
|
598
|
+
end
|
599
|
+
|
600
|
+
def write_node
|
601
|
+
return unless @dirty
|
602
|
+
|
603
|
+
ary = [
|
604
|
+
@is_leaf ? 1 : 0,
|
605
|
+
@keys.size,
|
606
|
+
@is_leaf ? @values.size : @children.size,
|
607
|
+
@parent ? @parent.node_address : 0
|
608
|
+
] + @keys + ::Array.new(@tree.order - @keys.size, 0)
|
609
|
+
|
610
|
+
if @is_leaf
|
611
|
+
ary += @values + ::Array.new(@tree.order + 1 - @values.size, 0)
|
612
|
+
else
|
613
|
+
if @children.size != @keys.size + 1
|
614
|
+
PEROBS.log.fatal "write_node: Children count #{@children.size} " +
|
615
|
+
"is not #{@keys.size + 1}"
|
616
|
+
end
|
617
|
+
@children.each do |child|
|
618
|
+
PEROBS.log.fatal "write_node: Child must not be nil" unless child
|
619
|
+
end
|
620
|
+
ary += @children.map{ |c| c.node_address } +
|
621
|
+
::Array.new(@tree.order + 1 - @children.size, 0)
|
622
|
+
end
|
623
|
+
bytes = ary.pack(node_bytes_format)
|
624
|
+
bytes += [ Zlib::crc32(bytes) ].pack('L')
|
625
|
+
@tree.nodes.store_blob(@node_address, bytes)
|
626
|
+
@dirty = false
|
627
|
+
end
|
628
|
+
|
629
|
+
def mark_as_modified
|
630
|
+
@tree.mark_node_as_modified(self)
|
631
|
+
@dirty = true
|
632
|
+
end
|
633
|
+
|
634
|
+
private
|
635
|
+
|
636
|
+
def read_node
|
637
|
+
return false unless (bytes = @tree.nodes.retrieve_blob(@node_address))
|
638
|
+
|
639
|
+
unless Zlib::crc32(bytes) != 0
|
640
|
+
PEROBS.log.error "Checksum failure in BTreeNode entry @#{node_address}"
|
641
|
+
return false
|
642
|
+
end
|
643
|
+
ary = bytes.unpack(node_bytes_format)
|
644
|
+
# Read is_leaf
|
645
|
+
if ary[0] != 0 && ary[0] != 1
|
646
|
+
PEROBS.log.error "First byte of a BTreeNode entry must be 0 or 1"
|
647
|
+
return false
|
648
|
+
end
|
649
|
+
@is_leaf = ary[0] == 0 ? false : true
|
650
|
+
# This is the number of keys this node has.
|
651
|
+
key_count = ary[1]
|
652
|
+
data_count = ary[2]
|
653
|
+
# Read the parent node address
|
654
|
+
@parent = ary[3] == 0 ? nil : BTreeNodeLink.new(@tree, ary[3])
|
655
|
+
# Read the keys
|
656
|
+
@keys = ary[4, key_count]
|
657
|
+
|
658
|
+
if @is_leaf
|
659
|
+
# Read the values
|
660
|
+
@values = ary[4 + @tree.order, data_count]
|
661
|
+
else
|
662
|
+
# Read the child addresses
|
663
|
+
@children = []
|
664
|
+
data_count.times do |i|
|
665
|
+
address = ary[4 + @tree.order + i]
|
666
|
+
unless address > 0
|
667
|
+
PEROBS.log.fatal "Child address must not be 0"
|
668
|
+
end
|
669
|
+
@children << BTreeNodeLink.new(@tree, address)
|
670
|
+
end
|
671
|
+
end
|
672
|
+
|
673
|
+
@dirty = false
|
674
|
+
|
675
|
+
true
|
676
|
+
end
|
677
|
+
|
678
|
+
def node_bytes_format
|
679
|
+
# This does not include the 4 bytes for the CRC32 checksum
|
680
|
+
"CSSQQ#{@tree.order}Q#{@tree.order + 1}"
|
681
|
+
end
|
682
|
+
|
683
|
+
def find_closest_siblings(key)
|
684
|
+
# The root node has no siblings.
|
685
|
+
return [ nil, nil, nil ] unless @parent
|
686
|
+
|
687
|
+
parent_index = @parent.search_key_index(key)
|
688
|
+
unless @parent.children[parent_index] == self
|
689
|
+
PEROBS.log.fatal "Failed to find self in parent"
|
690
|
+
end
|
691
|
+
# The child that corresponds to the key at parent_index has an index of
|
692
|
+
# parent_index + 1! The lower_sibling has an child index of
|
693
|
+
# parent_index and the upper sibling has a child index of parent_index +
|
694
|
+
# 2.
|
695
|
+
lower_sibling = parent_index < 1 ?
|
696
|
+
nil : @parent.children[parent_index - 1]
|
697
|
+
upper_sibling = parent_index >= (@parent.children.size - 1) ?
|
698
|
+
nil : @parent.children[parent_index + 1]
|
699
|
+
|
700
|
+
[ lower_sibling, upper_sibling, parent_index ]
|
701
|
+
end
|
702
|
+
|
703
|
+
end
|
704
|
+
|
705
|
+
end
|
706
|
+
|