perobs 2.5.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+