perobs 2.5.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+