perobs 3.0.1 → 4.3.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.
Files changed (75) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +19 -18
  3. data/lib/perobs.rb +2 -0
  4. data/lib/perobs/Array.rb +68 -21
  5. data/lib/perobs/BTree.rb +110 -54
  6. data/lib/perobs/BTreeBlob.rb +14 -13
  7. data/lib/perobs/BTreeDB.rb +11 -10
  8. data/lib/perobs/BTreeNode.rb +551 -197
  9. data/lib/perobs/BTreeNodeCache.rb +10 -8
  10. data/lib/perobs/BTreeNodeLink.rb +11 -1
  11. data/lib/perobs/BigArray.rb +285 -0
  12. data/lib/perobs/BigArrayNode.rb +1002 -0
  13. data/lib/perobs/BigHash.rb +246 -0
  14. data/lib/perobs/BigTree.rb +197 -0
  15. data/lib/perobs/BigTreeNode.rb +873 -0
  16. data/lib/perobs/Cache.rb +47 -22
  17. data/lib/perobs/ClassMap.rb +2 -2
  18. data/lib/perobs/ConsoleProgressMeter.rb +61 -0
  19. data/lib/perobs/DataBase.rb +4 -3
  20. data/lib/perobs/DynamoDB.rb +62 -20
  21. data/lib/perobs/EquiBlobsFile.rb +174 -59
  22. data/lib/perobs/FNV_Hash_1a_64.rb +54 -0
  23. data/lib/perobs/FlatFile.rb +536 -242
  24. data/lib/perobs/FlatFileBlobHeader.rb +120 -84
  25. data/lib/perobs/FlatFileDB.rb +58 -27
  26. data/lib/perobs/FuzzyStringMatcher.rb +175 -0
  27. data/lib/perobs/Hash.rb +129 -35
  28. data/lib/perobs/IDList.rb +144 -0
  29. data/lib/perobs/IDListPage.rb +107 -0
  30. data/lib/perobs/IDListPageFile.rb +180 -0
  31. data/lib/perobs/IDListPageRecord.rb +142 -0
  32. data/lib/perobs/LockFile.rb +3 -0
  33. data/lib/perobs/Object.rb +28 -20
  34. data/lib/perobs/ObjectBase.rb +53 -10
  35. data/lib/perobs/PersistentObjectCache.rb +142 -0
  36. data/lib/perobs/PersistentObjectCacheLine.rb +99 -0
  37. data/lib/perobs/ProgressMeter.rb +97 -0
  38. data/lib/perobs/SpaceManager.rb +273 -0
  39. data/lib/perobs/SpaceTree.rb +63 -47
  40. data/lib/perobs/SpaceTreeNode.rb +134 -115
  41. data/lib/perobs/SpaceTreeNodeLink.rb +1 -1
  42. data/lib/perobs/StackFile.rb +1 -1
  43. data/lib/perobs/Store.rb +180 -70
  44. data/lib/perobs/version.rb +1 -1
  45. data/perobs.gemspec +4 -4
  46. data/test/Array_spec.rb +48 -39
  47. data/test/BTreeDB_spec.rb +2 -2
  48. data/test/BTree_spec.rb +50 -1
  49. data/test/BigArray_spec.rb +261 -0
  50. data/test/BigHash_spec.rb +152 -0
  51. data/test/BigTreeNode_spec.rb +153 -0
  52. data/test/BigTree_spec.rb +259 -0
  53. data/test/EquiBlobsFile_spec.rb +105 -5
  54. data/test/FNV_Hash_1a_64_spec.rb +59 -0
  55. data/test/FlatFileDB_spec.rb +199 -15
  56. data/test/FuzzyStringMatcher_spec.rb +261 -0
  57. data/test/Hash_spec.rb +27 -16
  58. data/test/IDList_spec.rb +77 -0
  59. data/test/LegacyDBs/LegacyDB.rb +155 -0
  60. data/test/LegacyDBs/version_3/class_map.json +1 -0
  61. data/test/LegacyDBs/version_3/config.json +1 -0
  62. data/test/LegacyDBs/version_3/database.blobs +0 -0
  63. data/test/LegacyDBs/version_3/database_spaces.blobs +0 -0
  64. data/test/LegacyDBs/version_3/index.blobs +0 -0
  65. data/test/LegacyDBs/version_3/version +1 -0
  66. data/test/LockFile_spec.rb +9 -6
  67. data/test/Object_spec.rb +5 -5
  68. data/test/SpaceManager_spec.rb +176 -0
  69. data/test/SpaceTree_spec.rb +27 -9
  70. data/test/Store_spec.rb +353 -206
  71. data/test/perobs_spec.rb +7 -3
  72. data/test/spec_helper.rb +9 -4
  73. metadata +59 -16
  74. data/lib/perobs/SpaceTreeNodeCache.rb +0 -76
  75. data/lib/perobs/TreeDB.rb +0 -277
@@ -2,7 +2,7 @@
2
2
  #
3
3
  # = BTreeBlob.rb -- Persistent Ruby Object Store
4
4
  #
5
- # Copyright (c) 2015, 2016 by Chris Schlaeger <chris@taskjuggler.org>
5
+ # Copyright (c) 2015, 2016, 2019 by Chris Schlaeger <chris@taskjuggler.org>
6
6
  #
7
7
  # MIT License
8
8
  #
@@ -33,7 +33,7 @@ require 'perobs/RobustFile'
33
33
  module PEROBS
34
34
 
35
35
  # This class manages the usage of the data blobs in the corresponding
36
- # HashedBlobsDB object.
36
+ # BTreeDB object.
37
37
  class BTreeBlob
38
38
 
39
39
  # Magic number used for index files.
@@ -65,7 +65,7 @@ module PEROBS
65
65
  end
66
66
 
67
67
  # Write the given bytes with the given ID into the DB.
68
- # @param id [Fixnum or Bignum] ID
68
+ # @param id [Integer] ID
69
69
  # @param raw [String] sequence of bytes
70
70
  def write_object(id, raw)
71
71
  if @entries.length > @btreedb.max_blob_size
@@ -87,7 +87,7 @@ module PEROBS
87
87
  end
88
88
 
89
89
  # Read the entry for the given ID and return it as bytes.
90
- # @param id [Fixnum or Bignum] ID
90
+ # @param id [Integer] ID
91
91
  # @return [String] sequence of bytes or nil if ID is unknown
92
92
  def read_object(id)
93
93
  return nil unless (index_entry = find(id))
@@ -95,7 +95,7 @@ module PEROBS
95
95
  end
96
96
 
97
97
  # Find the data for the object with given id.
98
- # @param id [Fixnum or Bignum] Object ID
98
+ # @param id [Integer] Object ID
99
99
  # @return [Array] Returns an Array that represents the index entry for the
100
100
  # given object.
101
101
  def find(id)
@@ -109,7 +109,7 @@ module PEROBS
109
109
  end
110
110
 
111
111
  # Set a mark on the entry with the given ID.
112
- # @param id [Fixnum or Bignum] ID of the entry
112
+ # @param id [Integer] ID of the entry
113
113
  def mark(id)
114
114
  found = false
115
115
  @entries.each do |entry|
@@ -129,7 +129,7 @@ module PEROBS
129
129
  end
130
130
 
131
131
  # Check if the entry for a given ID is marked.
132
- # @param id [Fixnum or Bignum] ID of the entry
132
+ # @param id [Integer] ID of the entry
133
133
  # @param ignore_errors [Boolean] If set to true no errors will be raised
134
134
  # for non-existing objects.
135
135
  # @return [TrueClass or FalseClass] true if marked, false otherwise
@@ -144,11 +144,12 @@ module PEROBS
144
144
 
145
145
  # Remove all entries from the index that have not been marked.
146
146
  # @return [Array] List of deleted object IDs.
147
- def delete_unmarked_entries
147
+ def delete_unmarked_entries(&block)
148
148
  deleted_ids = []
149
149
  # First remove the entry from the hash table.
150
150
  @entries_by_id.delete_if do |id, e|
151
151
  if e[MARKED] == 0
152
+ yield(id) if block_given?
152
153
  deleted_ids << id
153
154
  true
154
155
  else
@@ -205,8 +206,8 @@ module PEROBS
205
206
 
206
207
  # Write a string of bytes into the file at the given address.
207
208
  # @param raw [String] bytes to write
208
- # @param address [Fixnum] offset in the file
209
- # @return [Fixnum] number of bytes written
209
+ # @param address [Integer] offset in the file
210
+ # @return [Integer] number of bytes written
210
211
  def write_to_blobs_file(raw, address)
211
212
  begin
212
213
  File.write(@blobs_file_name, raw, address)
@@ -236,9 +237,9 @@ module PEROBS
236
237
 
237
238
  # Reserve the bytes needed for the specified number of bytes with the
238
239
  # given ID.
239
- # @param id [Fixnum or Bignum] ID of the entry
240
- # @param bytes [Fixnum] number of bytes for this entry
241
- # @return [Fixnum] the start address of the reserved blob
240
+ # @param id [Integer] ID of the entry
241
+ # @param bytes [Integer] number of bytes for this entry
242
+ # @return [Integer] the start address of the reserved blob
242
243
  def reserve_bytes(id, bytes, crc32)
243
244
  # index of first blob after the last seen entry
244
245
  end_of_last_entry = 0
@@ -2,7 +2,8 @@
2
2
  #
3
3
  # = BTreeDB.rb -- Persistent Ruby Object Store
4
4
  #
5
- # Copyright (c) 2015, 2016 by Chris Schlaeger <chris@taskjuggler.org>
5
+ # Copyright (c) 2015, 2016, 2018, 2019
6
+ # by Chris Schlaeger <chris@taskjuggler.org>
6
7
  #
7
8
  # MIT License
8
9
  #
@@ -58,7 +59,7 @@ module PEROBS
58
59
  # nodes. The insert/find/delete time grows
59
60
  # linearly with the size.
60
61
  def initialize(db_name, options = {})
61
- super(options[:serializer] || :json)
62
+ super(options)
62
63
 
63
64
  @db_dir = db_name
64
65
  # Create the database directory if it doesn't exist yet.
@@ -103,7 +104,7 @@ module PEROBS
103
104
  end
104
105
 
105
106
  # Return true if the object with given ID exists
106
- # @param id [Fixnum or Bignum]
107
+ # @param id [Integer]
107
108
  def include?(id)
108
109
  !(blob = find_blob(id)).nil? && !blob.find(id).nil?
109
110
  end
@@ -143,7 +144,7 @@ module PEROBS
143
144
  end
144
145
 
145
146
  # Load the given object from the filesystem.
146
- # @param id [Fixnum or Bignum] object ID
147
+ # @param id [Integer] object ID
147
148
  # @return [Hash] Object as defined by PEROBS::ObjectBase or nil if ID does
148
149
  # not exist
149
150
  def get_object(id)
@@ -159,20 +160,20 @@ module PEROBS
159
160
  # Permanently delete all objects that have not been marked. Those are
160
161
  # orphaned and are no longer referenced by any actively used object.
161
162
  # @return [Array] List of IDs that have been removed from the DB.
162
- def delete_unmarked_objects
163
+ def delete_unmarked_objects(&block)
163
164
  deleted_ids = []
164
- each_blob { |blob| deleted_ids += blob.delete_unmarked_entries }
165
+ each_blob { |blob| deleted_ids += blob.delete_unmarked_entries(&block) }
165
166
  deleted_ids
166
167
  end
167
168
 
168
169
  # Mark an object.
169
- # @param id [Fixnum or Bignum] ID of the object to mark
170
+ # @param id [Integer] ID of the object to mark
170
171
  def mark(id)
171
172
  (blob = find_blob(id)) && blob.mark(id)
172
173
  end
173
174
 
174
175
  # Check if the object is marked.
175
- # @param id [Fixnum or Bignum] ID of the object to check
176
+ # @param id [Integer] ID of the object to check
176
177
  # @param ignore_errors [Boolean] If set to true no errors will be raised
177
178
  # for non-existing objects.
178
179
  def is_marked?(id, ignore_errors = false)
@@ -189,7 +190,7 @@ module PEROBS
189
190
  end
190
191
 
191
192
  # Check if the stored object is syntactically correct.
192
- # @param id [Fixnum/Bignum] Object ID
193
+ # @param id [Integer] Object ID
193
194
  # @param repair [TrueClass/FalseClass] True if an repair attempt should be
194
195
  # made.
195
196
  # @return [TrueClass/FalseClass] True if the object is OK, otherwise
@@ -208,7 +209,7 @@ module PEROBS
208
209
  # Store the given serialized object into the cluster files. This method is
209
210
  # for internal use only!
210
211
  # @param raw [String] Serialized Object as defined by PEROBS::ObjectBase
211
- # @param id [Fixnum or Bignum] Object ID
212
+ # @param id [Integer] Object ID
212
213
  def put_raw_object(raw, id)
213
214
  find_blob(id, true).write_object(id, raw)
214
215
  end
@@ -39,8 +39,10 @@ module PEROBS
39
39
  # mark a node as leaf or branch node.
40
40
  class BTreeNode
41
41
 
42
- attr_reader :node_address, :parent, :is_leaf, :keys, :values, :children
43
- attr_accessor :dirty
42
+ Stats = Struct.new(:branch_depth, :nodes_count, :leave_nodes, :leaves)
43
+
44
+ attr_reader :node_address, :parent, :is_leaf, :next_sibling, :prev_sibling,
45
+ :keys, :values, :children
44
46
 
45
47
  # Create a new BTreeNode object for the given tree with the given parent
46
48
  # or recreate the node with the given node_address from the backing store.
@@ -49,58 +51,162 @@ module PEROBS
49
51
  # restore the node.
50
52
  # @param tree [BTree] The tree this node is part of
51
53
  # @param parent [BTreeNode] reference to parent node
54
+ # @param prev_sibling [BTreeNode] reference to previous sibling node
55
+ # @param next_sibling [BTreeNode] reference to next sibling node
52
56
  # @param node_address [Integer] the address of the node to read from the
53
57
  # backing store
54
58
  # @param is_leaf [Boolean] true if the node should be a leaf node, false
55
59
  # if not
56
- def initialize(tree, parent = nil, node_address = nil, is_leaf = true)
60
+ def initialize(tree, node_address = nil, parent = nil, is_leaf = true,
61
+ prev_sibling = nil, next_sibling = nil,
62
+ keys = nil, values = nil, children = nil)
57
63
  @tree = tree
58
- @parent = nil
59
64
  if node_address == 0
60
65
  PEROBS.log.fatal "Node address may not be 0"
61
66
  end
62
67
  @node_address = node_address
63
- @keys = []
68
+ @parent = link(parent)
69
+ @prev_sibling = link(prev_sibling)
70
+ @next_sibling = link(next_sibling)
71
+ @keys = keys || []
64
72
  if (@is_leaf = is_leaf)
65
- @values = []
73
+ @values = values || []
74
+ @children = nil
66
75
  else
67
- @children = []
76
+ @children = children || []
77
+ @values = nil
68
78
  end
79
+ end
69
80
 
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
81
+ # Create a new SpaceTreeNode. This method should be used for the creation
82
+ # of new nodes instead of calling the constructor directly.
83
+ # @param tree [BTree] The tree the new node should belong to
84
+ # @param parent [BTreeNode] The parent node
85
+ # @param is_leaf [Boolean] True if the node has no children, false
86
+ # otherwise
87
+ # @param prev_sibling [BTreeNode] reference to previous sibling node
88
+ # @param next_sibling [BTreeNode] reference to next sibling node
89
+ def BTreeNode::create(tree, parent = nil, is_leaf = true,
90
+ prev_sibling = nil, next_sibling = nil)
91
+ unless parent.nil? || parent.is_a?(BTreeNode) ||
92
+ parent.is_a?(BTreeNodeLink)
93
+ PEROBS.log.fatal "Parent node must be a BTreeNode but is of class " +
94
+ "#{parent.class}"
95
+ end
74
96
 
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
97
+ address = tree.nodes.free_address
98
+ node = BTreeNode.new(tree, address, parent, is_leaf, prev_sibling,
99
+ next_sibling)
100
+ # This is a new node. Make sure the data is written to the file.
101
+ tree.node_cache.insert(node)
102
+
103
+ # Insert the newly created node into the existing node chain.
104
+ if (node.prev_sibling = prev_sibling)
105
+ node.prev_sibling.next_sibling = BTreeNodeLink.new(tree, node)
106
+ end
107
+ if (node.next_sibling = next_sibling)
108
+ node.next_sibling.prev_sibling = BTreeNodeLink.new(tree, node)
109
+ end
110
+
111
+ BTreeNodeLink.new(tree, node)
112
+ end
113
+
114
+ # Restore a node from the backing store at the given address and tree.
115
+ # @param tree [BTree] The tree the node belongs to
116
+ # @param address [Integer] The address in the blob file.
117
+ def BTreeNode::load(tree, address, unused = nil)
118
+ unless address.is_a?(Integer)
119
+ PEROBS.log.fatal "address is not Integer: #{address.class}"
120
+ end
121
+
122
+ unless (bytes = tree.nodes.retrieve_blob(address))
123
+ PEROBS.log.fatal "SpaceTree node at address #{address} " +
124
+ "does not exist"
125
+ end
126
+
127
+ unless Zlib::crc32(bytes) != 0
128
+ PEROBS.log.fatal "Checksum failure in BTreeNode entry @#{address}"
129
+ end
130
+ ary = bytes.unpack(BTreeNode::node_bytes_format(tree))
131
+ # Read is_leaf
132
+ if ary[0] != 0 && ary[0] != 1
133
+ PEROBS.log.fatal "First byte of a BTreeNode entry must be 0 or 1"
134
+ end
135
+ is_leaf = ary[0] == 0 ? false : true
136
+ # This is the number of keys this node has.
137
+ key_count = ary[1]
138
+ data_count = ary[2]
139
+ # Read the parent node address
140
+ parent = ary[3] == 0 ? nil : BTreeNodeLink.new(tree, ary[3])
141
+ prev_sibling = ary[4] == 0 ? nil : BTreeNodeLink.new(tree, ary[4])
142
+ next_sibling = ary[5] == 0 ? nil : BTreeNodeLink.new(tree, ary[5])
143
+ # Read the keys
144
+ keys = ary[6, key_count]
145
+
146
+ children = nil
147
+ values = nil
148
+ if is_leaf
149
+ # Read the values
150
+ values = ary[6 + tree.order, data_count]
81
151
  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}"
152
+ # Read the child addresses
153
+ children = []
154
+ data_count.times do |i|
155
+ child_address = ary[6 + tree.order + i]
156
+ unless child_address > 0
157
+ PEROBS.log.fatal "Child address must be larger than 0"
158
+ end
159
+ children << BTreeNodeLink.new(tree, child_address)
86
160
  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
161
  end
162
+
163
+ node = BTreeNode.new(tree, address, parent, is_leaf,
164
+ prev_sibling, next_sibling, keys, values,
165
+ children)
166
+ tree.node_cache.insert(node, false)
167
+
168
+ node
169
+ end
170
+
171
+ # This is a wrapper around BTreeNode::load() that returns a BTreeNodeLink
172
+ # instead of the actual node.
173
+ # @param tree [BTree] The tree the node belongs to
174
+ # @param address [Integer] The address in the blob file.
175
+ # @return [BTreeNodeLink] Link to loaded noded
176
+ def BTreeNode::load_and_link(tree, address)
177
+ BTreeNodeLink.new(tree, BTreeNode::load(tree, address))
178
+ end
179
+
180
+
181
+ # @return [String] The format used for String.pack.
182
+ def BTreeNode::node_bytes_format(tree)
183
+ # This does not include the 4 bytes for the CRC32 checksum
184
+ "CSSQQQQ#{tree.order}Q#{tree.order + 1}"
92
185
  end
93
186
 
187
+ # @return [Integer] The number of bytes needed to store a node.
94
188
  def BTreeNode::node_bytes(order)
95
189
  1 + # is_leaf
96
190
  2 + # actual key count
97
191
  2 + # actual value or children count (aka data count)
98
192
  8 + # parent address
193
+ 8 + # previous sibling address
194
+ 8 + # next sibling address
99
195
  8 * order + # keys
100
196
  8 * (order + 1) + # values or child addresses
101
197
  4 # CRC32 checksum
102
198
  end
103
199
 
200
+ # Save the node into the blob file.
201
+ def save
202
+ write_node
203
+ end
204
+
205
+ # The node address uniquely identifies a BTreeNode.
206
+ def uid
207
+ @node_address
208
+ end
209
+
104
210
  # Insert or replace the given value by using the key as unique address.
105
211
  # @param key [Integer] Unique key to retrieve the value
106
212
  # @param value [Integer] value to insert
@@ -117,11 +223,11 @@ module PEROBS
117
223
 
118
224
  # Once we have reached a leaf node we can insert or replace the value.
119
225
  if node.is_leaf
120
- node.insert_element(key, value)
121
- return
226
+ return node.insert_element(key, value)
122
227
  else
123
228
  # Descend into the right child node to add the value to.
124
229
  node = node.children[node.search_key_index(key)]
230
+ node = node.get_node if node
125
231
  end
126
232
  end
127
233
 
@@ -146,6 +252,62 @@ module PEROBS
146
252
 
147
253
  # Descend into the right child node to continue the search.
148
254
  node = node.children[i]
255
+ node = node.get_node if node
256
+ end
257
+
258
+ PEROBS.log.fatal "Could not find proper node to get from while " +
259
+ "looking for key #{key}"
260
+ end
261
+
262
+ # Return the key/value pair that matches the given key or the next larger
263
+ # key/value pair with a key that is at least as large as key +
264
+ # min_miss_increment.
265
+ # @param key [Integer] key to search for
266
+ # @param min_miss_increment [Integer] minimum required key increment in
267
+ # case an exact key match could not be found
268
+ # @return [Integer or nil] value that matches the key
269
+ def get_best_match(key, min_miss_increment)
270
+ node = self
271
+
272
+ while node do
273
+ # Find index of the entry that best fits the key.
274
+ i = node.search_key_index(key)
275
+ if node.is_leaf
276
+ # This is a leaf node. Check if there is an exact match for the
277
+ # given key.
278
+ if node.keys[i] == key
279
+ # Return the corresponding value/value pair.
280
+ return [ key, node.values[i] ]
281
+ else
282
+ # No exact key match. Now search the larger keys for the first
283
+ # that is at least key + min_miss_increment large.
284
+ keys = node.keys
285
+ keys_length = keys.length
286
+ while node
287
+ if i >= keys_length
288
+ # We've reached the end of a node. Continue search in next
289
+ # sibling.
290
+ return nil unless (node = node.next_sibling)
291
+ node = node.get_node
292
+ keys = node.keys
293
+ keys_length = keys.length
294
+ i = -1
295
+ elsif keys[i] >= key + min_miss_increment
296
+ # We've found a key that fits the critera. Return the
297
+ # corresponding key/value pair.
298
+ return [ keys[i], node.values[i] ]
299
+ end
300
+
301
+ i += 1
302
+ end
303
+
304
+ return nil
305
+ end
306
+ end
307
+
308
+ # Descend into the right child node to continue the search.
309
+ node = node.children[i]
310
+ node = node.get_node if node
149
311
  end
150
312
 
151
313
  PEROBS.log.fatal "Could not find proper node to get from while " +
@@ -174,6 +336,7 @@ module PEROBS
174
336
 
175
337
  # Descend into the right child node to continue the search.
176
338
  node = node.children[i]
339
+ node = node.get_node if node
177
340
  end
178
341
 
179
342
  PEROBS.log.fatal 'Could not find proper node to remove from'
@@ -186,18 +349,19 @@ module PEROBS
186
349
  def split_node
187
350
  unless @parent
188
351
  # The node is the root node. We need to create a parent node first.
189
- self.parent = @tree.new_node(nil, nil, false)
352
+ self.parent = link(BTreeNode::create(@tree, nil, false))
190
353
  @parent.set_child(0, self)
191
354
  @tree.set_root(@parent)
192
355
  end
193
356
 
194
357
  # Create the new sibling that will take the 2nd half of the
195
358
  # node content.
196
- sibling = @tree.new_node(@parent, nil, @is_leaf)
359
+ sibling = BTreeNode::create(@tree, @parent, @is_leaf, link(self),
360
+ @next_sibling)
197
361
  # Determine the index of the middle element that gets moved to the
198
362
  # parent. The order must be an uneven number, so adding 1 will get us
199
363
  # the middle element.
200
- mid = @tree.order / 2 + 1
364
+ mid = @tree.order / 2
201
365
  # Insert the middle element key into the parent node
202
366
  @parent.insert_element(@keys[mid], sibling)
203
367
  copy_elements(mid + (@is_leaf ? 0 : 1), sibling)
@@ -206,30 +370,17 @@ module PEROBS
206
370
  @parent
207
371
  end
208
372
 
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
373
  # Insert the given value or child into the current node using the key as
223
374
  # index.
224
375
  # @param key [Integer] key to address the value or child
225
376
  # @param value_or_child [Integer or BTreeNode] value or BTreeNode
226
377
  # reference
378
+ # @return true for insert, false for overwrite
227
379
  def insert_element(key, value_or_child)
228
380
  if @keys.size >= @tree.order
229
381
  PEROBS.log.fatal "Cannot insert into a full BTreeNode"
230
382
  end
231
383
 
232
- mark_as_modified
233
384
  i = search_key_index(key)
234
385
  if @keys[i] == key
235
386
  # Overwrite existing entries
@@ -237,58 +388,132 @@ module PEROBS
237
388
  if is_leaf
238
389
  @values[i] = value_or_child
239
390
  else
240
- @children[i + 1] = BTreeNodeLink.new(@tree, value_or_child)
391
+ @children[i + 1] = link(value_or_child)
241
392
  end
393
+ @tree.node_cache.insert(self)
394
+
395
+ return false
242
396
  else
243
397
  # Create a new entry
244
398
  @keys.insert(i, key)
245
399
  if is_leaf
246
400
  @values.insert(i, value_or_child)
247
401
  else
248
- @children.insert(i + 1, BTreeNodeLink.new(@tree, value_or_child))
402
+ @children.insert(i + 1, link(value_or_child))
249
403
  end
404
+ @tree.node_cache.insert(self)
405
+
406
+ return true
250
407
  end
251
408
  end
252
409
 
253
410
  # Remove the element at the given index.
254
411
  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
412
  # Delete the key at the specified index.
261
- unless @keys.delete_at(index)
262
- PEROBS.log.fatal "Could not remove element #{index} from BTreeNode " +
413
+ unless (key = @keys.delete_at(index))
414
+ PEROBS.log.fatal "Could not remove element #{index} from BigTreeNode " +
263
415
  "@#{@node_address}"
264
416
  end
265
- if @is_leaf
266
- # For leaf nodes, also delete the corresponding value.
267
- removed_value = @values.delete_at(index)
417
+ update_branch_key(key) if index == 0
418
+
419
+ # Delete the corresponding value.
420
+ removed_value = @values.delete_at(index)
421
+ @tree.node_cache.insert(self)
422
+
423
+ if @keys.length < min_keys
424
+ if @prev_sibling && @prev_sibling.parent == @parent
425
+ borrow_from_previous_sibling(@prev_sibling) ||
426
+ @prev_sibling.merge_with_leaf_node(self)
427
+ elsif @next_sibling && @next_sibling.parent == @parent
428
+ borrow_from_next_sibling(@next_sibling) ||
429
+ merge_with_leaf_node(@next_sibling)
430
+ elsif @parent
431
+ PEROBS.log.fatal "Cannot not find adjecent leaf siblings"
432
+ end
433
+ end
434
+
435
+ # The merge has potentially invalidated this node. After this method has
436
+ # been called this copy of the node should no longer be used.
437
+ removed_value
438
+ end
439
+
440
+ def remove_child(node)
441
+ unless (index = search_node_index(node))
442
+ PEROBS.log.fatal "Cannot remove child #{node.node_address} " +
443
+ "from node #{@node_address}"
444
+ end
445
+
446
+ @tree.node_cache.insert(self)
447
+ if index == 0
448
+ # Removing the first child is a bit more complicated as the
449
+ # corresponding branch key is in a parent node.
450
+ key = @keys.shift
451
+ update_branch_key(key)
268
452
  else
269
- # The corresponding child has can be found at 1 index higher.
270
- @children.delete_at(index + 1)
453
+ # For all other children we can just remove the corresponding key.
454
+ @keys.delete_at(index - 1)
455
+ end
456
+
457
+ # Remove the child node link.
458
+ child = @children.delete_at(index)
459
+ # Unlink the neighbouring siblings from the child
460
+ child.prev_sibling.next_sibling = child.next_sibling if child.prev_sibling
461
+ child.next_sibling.prev_sibling = child.prev_sibling if child.next_sibling
462
+
463
+ if @keys.length < min_keys
464
+ # The node has become too small. Try borrowing a node from an adjecent
465
+ # sibling or merge with an adjecent node.
466
+ if @prev_sibling && @prev_sibling.parent == @parent
467
+ borrow_from_previous_sibling(@prev_sibling) ||
468
+ @prev_sibling.merge_with_branch_node(self)
469
+ elsif @next_sibling && @next_sibling.parent == @parent
470
+ borrow_from_next_sibling(@next_sibling) ||
471
+ merge_with_branch_node(@next_sibling)
472
+ end
271
473
  end
272
474
 
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)
475
+ # Delete the node from the cache and backing store.
476
+ @tree.delete_node(node.node_address)
477
+ end
277
478
 
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)
479
+ def merge_with_leaf_node(node)
480
+ if @keys.length + node.keys.length > @tree.order
481
+ PEROBS.log.fatal "Leaf nodes are too big to merge"
284
482
  end
285
483
 
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
484
+ @keys += node.keys
485
+ @values += node.values
486
+ @tree.node_cache.insert(self)
487
+
488
+ node.parent.remove_child(node)
489
+ end
490
+
491
+ def merge_with_branch_node(node)
492
+ if @keys.length + 1 + node.keys.length > @tree.order
493
+ PEROBS.log.fatal "Branch nodes are too big to merge"
494
+ end
495
+
496
+ index = @parent.search_node_index(node) - 1
497
+ @keys << @parent.keys[index]
498
+ @keys += node.keys
499
+ node.children.each { |c| c.parent = link(self) }
500
+ @children += node.children
501
+ @tree.node_cache.insert(self)
502
+
503
+ node.parent.remove_child(node)
504
+ end
505
+
506
+ def search_node_index(node)
507
+ index = search_key_index(node.keys.first)
508
+ unless @children[index] == node
509
+ raise RuntimeError, "Child at index #{index} is not the requested node"
510
+ end
511
+
512
+ index
289
513
  end
290
514
 
291
515
  def copy_elements(src_idx, dest_node, dst_idx = 0, count = nil)
516
+ dest_node = dest_node.get_node
292
517
  unless count
293
518
  count = @tree.order - src_idx
294
519
  end
@@ -301,7 +526,6 @@ module PEROBS
301
526
  end
302
527
 
303
528
  dest_node.keys[dst_idx, count] = @keys[src_idx, count]
304
- dest_node.dirty = true
305
529
  if @is_leaf
306
530
  # For leaves we copy the keys and corresponding values.
307
531
  dest_node.values[dst_idx, count] = @values[src_idx, count]
@@ -313,31 +537,61 @@ module PEROBS
313
537
  dest_node.set_child(dst_idx + i, @children[src_idx + i])
314
538
  end
315
539
  end
540
+ @tree.node_cache.insert(dest_node)
316
541
  end
317
542
 
318
543
  def parent=(p)
319
- @parent = p ? BTreeNodeLink.new(@tree, p) : nil
320
- mark_as_modified
544
+ @parent = p
545
+ @tree.node_cache.insert(self)
546
+
547
+ p
548
+ end
549
+
550
+ def prev_sibling=(node)
551
+ @prev_sibling = node
552
+ if node.nil? && @is_leaf
553
+ # If this node is a leaf node without a previous sibling we need to
554
+ # register it as the first leaf node.
555
+ @tree.set_first_leaf(BTreeNodeLink.new(@tree, self))
556
+ end
557
+
558
+ @tree.node_cache.insert(self)
559
+
560
+ node
561
+ end
562
+
563
+ def next_sibling=(node)
564
+ @next_sibling = node
565
+ @tree.node_cache.insert(self)
566
+ if node.nil? && @is_leaf
567
+ # If this node is a leaf node without a next sibling we need to
568
+ # register it as the last leaf node.
569
+ @tree.set_last_leaf(BTreeNodeLink.new(@tree, self))
570
+ end
571
+
572
+ node
321
573
  end
322
574
 
323
575
  def set_child(index, child)
324
576
  if child
325
- @children[index] = BTreeNodeLink.new(@tree, child)
326
- @children[index].parent = self
577
+ @children[index] = link(child)
578
+ @children[index].parent = link(self)
327
579
  else
328
580
  @children[index] = nil
329
581
  end
330
- mark_as_modified
582
+ @tree.node_cache.insert(self)
583
+
584
+ child
331
585
  end
332
586
 
333
587
  def trim(idx)
334
- mark_as_modified
335
- @keys = @keys[0..idx - 1]
588
+ @keys.slice!(idx, @keys.length - idx)
336
589
  if @is_leaf
337
- @values = @values[0..idx - 1]
590
+ @values.slice!(idx, @values.length - idx)
338
591
  else
339
- @children = @children[0..idx]
592
+ @children.slice!(idx + 1, @children.length - idx - 1)
340
593
  end
594
+ @tree.node_cache.insert(self)
341
595
  end
342
596
 
343
597
  # Search the keys of the node that fits the given key. The result is
@@ -346,36 +600,8 @@ module PEROBS
346
600
  # @param key [Integer] key to search for
347
601
  # @return [Integer] Index of the matching key or the insert position.
348
602
  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
603
+ (@is_leaf ? @keys.bsearch_index { |x| x >= key } :
604
+ @keys.bsearch_index { |x| x > key }) || @keys.length
379
605
  end
380
606
 
381
607
  # Iterate over all the key/value pairs in this node and all sub-nodes.
@@ -426,17 +652,33 @@ module PEROBS
426
652
  # Check consistency of the node and all subsequent nodes. In case an error
427
653
  # is found, a message is logged and false is returned.
428
654
  # @yield [key, value]
429
- # @return [Boolean] true if tree has no errors
430
- def check
655
+ # @return [nil or Hash] nil in case of errors or a hash with some
656
+ # statistical information about the tree
657
+ def check(&block)
658
+ stats = Stats.new(nil, 0, 0, 0)
659
+
431
660
  traverse do |node, position, stack|
432
661
  if position == 0
433
- if node.parent && node.keys.size < 1
434
- node.error "BTreeNode must have at least one entry"
435
- return false
662
+ stats.nodes_count += 1
663
+ if node.parent
664
+ unless node.parent.is_a?(BTreeNodeLink)
665
+ node.error "parent is a #{node.parent.class} instead of a " +
666
+ "BTreeNodeLink"
667
+ return nil
668
+ end
669
+ # After a split the nodes will only have half the maximum keys.
670
+ # For branch nodes one of the split nodes will have even 1 key
671
+ # less as this will become the branch key in a parent node.
672
+ if node.keys.size < min_keys - (node.is_leaf ? 0 : 1)
673
+ node.error "BTreeNode #{node.node_address} has too few keys"
674
+ return nil
675
+ end
436
676
  end
677
+
437
678
  if node.keys.size > @tree.order
438
679
  node.error "BTreeNode must not have more then #{@tree.order} " +
439
680
  "keys, but has #{node.keys.size} keys"
681
+ return nil
440
682
  end
441
683
 
442
684
  last_key = nil
@@ -444,45 +686,101 @@ module PEROBS
444
686
  if last_key && key < last_key
445
687
  node.error "Keys are not increasing monotoneously: " +
446
688
  "#{node.keys.inspect}"
447
- return false
689
+ return nil
448
690
  end
691
+ last_key = key
449
692
  end
450
693
 
451
694
  if node.is_leaf
695
+ if stats.branch_depth
696
+ unless stats.branch_depth == node.tree_level
697
+ node.error "All leaf nodes must have same distance from root "
698
+ return nil
699
+ end
700
+ else
701
+ stats.branch_depth = node.tree_level
702
+ end
703
+ if node.prev_sibling && !node.prev_sibling.is_a?(BTreeNodeLink)
704
+ node.error "prev_sibling is a #{node.prev_sibling.class} " +
705
+ "instead of a BTreeNodeLink"
706
+ return nil
707
+ end
708
+ if node.next_sibling && !node.next_sibling.is_a?(BTreeNodeLink)
709
+ node.error "next_sibling is a #{node.next_sibling.class} " +
710
+ "instead of a BTreeNodeLink"
711
+ return nil
712
+ end
713
+ if node.prev_sibling.nil? && @tree.first_leaf != node
714
+ node.error "Leaf node #{node.node_address} has no previous " +
715
+ "sibling but is not the first leaf of the tree"
716
+ return nil
717
+ end
718
+ if node.next_sibling.nil? && @tree.last_leaf != node
719
+ node.error "Leaf node #{node.node_address} has no next sibling " +
720
+ "but is not the last leaf of the tree"
721
+ return nil
722
+ end
452
723
  unless node.keys.size == node.values.size
453
724
  node.error "Key count (#{node.keys.size}) and value " +
454
725
  "count (#{node.values.size}) don't match"
455
- return false
726
+ return nil
727
+ end
728
+ unless node.children.nil?
729
+ node.error "@children must be nil for a leaf node"
730
+ return nil
456
731
  end
732
+
733
+ stats.leave_nodes += 1
734
+ stats.leaves += node.keys.length
457
735
  else
458
- unless node.keys.size == node.children.size - 1
736
+ unless node.values.nil?
737
+ node.error "@values must be nil for a branch node"
738
+ return nil
739
+ end
740
+ unless node.children.size == node.keys.size + 1
459
741
  node.error "Key count (#{node.keys.size}) must be one " +
460
742
  "less than children count (#{node.children.size})"
461
- return false
743
+ return nil
462
744
  end
463
745
  node.children.each_with_index do |child, i|
464
746
  unless child.is_a?(BTreeNodeLink)
465
747
  node.error "Child #{i} is of class #{child.class} " +
466
748
  "instead of BTreeNodeLink"
467
- return false
749
+ return nil
468
750
  end
469
751
  unless child.parent.is_a?(BTreeNodeLink)
470
752
  node.error "Parent reference of child #{i} is of class " +
471
- "#{child.class} instead of BTreeNodeLink"
472
- return false
753
+ "#{child.parent.class} instead of BTreeNodeLink"
754
+ return nil
473
755
  end
474
- if child.node_address == node.node_address
756
+ if child == node
475
757
  node.error "Child #{i} points to self"
476
- return false
758
+ return nil
477
759
  end
478
760
  if stack.include?(child)
479
761
  node.error "Child #{i} points to ancester node"
480
- return false
762
+ return nil
481
763
  end
482
764
  unless child.parent == node
483
765
  node.error "Child #{i} does not have parent pointing " +
484
766
  "to this node"
485
- return false
767
+ return nil
768
+ end
769
+ if i > 0
770
+ unless node.children[i - 1].next_sibling == child
771
+ node.error "next_sibling of node " +
772
+ "#{node.children[i - 1].node_address} " +
773
+ "must point to node #{child.node_address}"
774
+ return nil
775
+ end
776
+ end
777
+ if i < node.children.length - 1
778
+ unless child == node.children[i + 1].prev_sibling
779
+ node.error "prev_sibling of node " +
780
+ "#{node.children[i + 1].node_address} " +
781
+ "must point to node #{child.node_address}"
782
+ return nil
783
+ end
486
784
  end
487
785
  end
488
786
  end
@@ -495,25 +793,26 @@ module PEROBS
495
793
  node.error "Child #{node.children[index].node_address} " +
496
794
  "has too large key #{node.children[index].keys.last}. " +
497
795
  "Must be smaller than #{node.keys[index]}."
498
- return false
796
+ return nil
499
797
  end
500
- unless node.children[position].keys.first >=
501
- node.keys[index]
798
+ unless node.children[position].keys.first >= node.keys[index]
502
799
  node.error "Child #{node.children[position].node_address} " +
503
800
  "has too small key #{node.children[position].keys.first}. " +
504
801
  "Must be larger than or equal to #{node.keys[index]}."
505
- return false
802
+ return nil
506
803
  end
507
804
  else
508
805
  if block_given?
509
806
  # If a block was given, call this block with the key and value.
510
- return false unless yield(node.keys[index], node.values[index])
807
+ unless yield(node.keys[index], node.values[index])
808
+ return nil
809
+ end
511
810
  end
512
811
  end
513
812
  end
514
813
  end
515
814
 
516
- true
815
+ stats
517
816
  end
518
817
 
519
818
  def is_top?
@@ -569,6 +868,7 @@ module PEROBS
569
868
 
570
869
  str = (is_last_child ? ' ' : ' |') + str
571
870
  node = node.parent
871
+ node = node.get_node if node
572
872
  end
573
873
 
574
874
  str
@@ -588,23 +888,47 @@ module PEROBS
588
888
  s += ' ^@'
589
889
  end
590
890
  end
891
+ if @prev_sibling
892
+ begin
893
+ s += " <#{@prev_sibling.node_address}"
894
+ rescue
895
+ s += ' <@'
896
+ end
897
+ end
898
+ if @next_sibling
899
+ begin
900
+ s += " >#{@next_sibling.node_address}"
901
+ rescue
902
+ s += ' >@'
903
+ end
904
+ end
591
905
 
592
906
  s
593
907
  end
594
908
 
909
+ def tree_level
910
+ level = 1
911
+ node = self
912
+ while (node = node.parent)
913
+ level += 1
914
+ end
915
+
916
+ level
917
+ end
918
+
919
+
595
920
  def error(msg)
596
- PEROBS.log.error "Error in BTreeNode @#{@node_address}: #{msg}\n" +
597
- @tree.to_s
921
+ PEROBS.log.error "Error in BTreeNode @#{@node_address}: #{msg}"
598
922
  end
599
923
 
600
924
  def write_node
601
- return unless @dirty
602
-
603
925
  ary = [
604
926
  @is_leaf ? 1 : 0,
605
927
  @keys.size,
606
928
  @is_leaf ? @values.size : @children.size,
607
- @parent ? @parent.node_address : 0
929
+ @parent ? @parent.node_address : 0,
930
+ @prev_sibling ? @prev_sibling.node_address : 0,
931
+ @next_sibling ? @next_sibling.node_address : 0
608
932
  ] + @keys + ::Array.new(@tree.order - @keys.size, 0)
609
933
 
610
934
  if @is_leaf
@@ -620,84 +944,114 @@ module PEROBS
620
944
  ary += @children.map{ |c| c.node_address } +
621
945
  ::Array.new(@tree.order + 1 - @children.size, 0)
622
946
  end
623
- bytes = ary.pack(node_bytes_format)
947
+ bytes = ary.pack(BTreeNode::node_bytes_format(@tree))
624
948
  bytes += [ Zlib::crc32(bytes) ].pack('L')
625
949
  @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
950
  end
633
951
 
634
952
  private
635
953
 
636
- def read_node
637
- return false unless (bytes = @tree.nodes.retrieve_blob(@node_address))
954
+ def min_keys
955
+ @tree.order / 2
956
+ end
638
957
 
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]
958
+ def link(node)
959
+ return nil if node.nil?
657
960
 
658
- if @is_leaf
659
- # Read the values
660
- @values = ary[4 + @tree.order, data_count]
961
+ if node.is_a?(BTreeNodeLink)
962
+ return node
963
+ elsif node.is_a?(BTreeNode) || node.is_a?(Integer)
964
+ return BTreeNodeLink.new(@tree, node)
661
965
  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
966
+ PEROBS.log.fatal "Node link must be a BTreeNode, not a #{node.class}"
671
967
  end
968
+ end
672
969
 
673
- @dirty = false
970
+ # Try to borrow an element from the preceding sibling.
971
+ # @return [True or False] True if an element was borrowed, false
972
+ # otherwise.
973
+ def borrow_from_previous_sibling(prev_node)
974
+ if prev_node.keys.length - 1 > min_keys
975
+ index = @parent.search_node_index(self) - 1
976
+
977
+ @tree.node_cache.insert(self)
978
+ @tree.node_cache.insert(prev_node.get_node)
979
+ @tree.node_cache.insert(@parent.get_node)
980
+ if @is_leaf
981
+ # Move the last key of the previous node to the front of this node
982
+ @keys.unshift(prev_node.keys.pop)
983
+ # Register the new lead key of this node with its parent
984
+ @parent.keys[index] = @keys.first
985
+ # Move the last value of the previous node to the front of this node
986
+ @values.unshift(prev_node.values.pop)
987
+ else
988
+ # For branch nodes the branch key will be the borrowed key.
989
+ @keys.unshift(@parent.keys[index])
990
+ # And the last key of the previous key will become the new branch
991
+ # key for this node.
992
+ @parent.keys[index] = prev_node.keys.pop
993
+ # Move the last child of the previous node to the front of this node
994
+ @children.unshift(node = prev_node.children.pop)
995
+ node.parent = link(self)
996
+ end
674
997
 
675
- true
998
+ return true
999
+ end
1000
+
1001
+ false
676
1002
  end
677
1003
 
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}"
1004
+ # Try to borrow an element from the next sibling.
1005
+ # @return [True or False] True if an element was borrowed, false
1006
+ # otherwise.
1007
+ def borrow_from_next_sibling(next_node)
1008
+ if next_node.keys.length - 1 > min_keys
1009
+ # The next sibling now has a new lead key that requires the branch key
1010
+ # to be updated in the parent node.
1011
+ index = next_node.parent.search_node_index(next_node) - 1
1012
+
1013
+ @tree.node_cache.insert(self)
1014
+ @tree.node_cache.insert(next_node.get_node)
1015
+ @tree.node_cache.insert(next_node.parent.get_node)
1016
+ if @is_leaf
1017
+ # Move the first key of the next node to the end of the this node
1018
+ @keys << next_node.keys.shift
1019
+ # Register the new lead key of next_node with its parent
1020
+ next_node.parent.keys[index] = next_node.keys.first
1021
+ # Move the first value of the next node to the end of this node
1022
+ @values << next_node.values.shift
1023
+ else
1024
+ # For branch nodes we need to get the lead key from the parent of
1025
+ # next_node.
1026
+ @keys << next_node.parent.keys[index]
1027
+ # The old lead key of next_node becomes the branch key in the parent
1028
+ # of next_node. And the keys of next_node are shifted.
1029
+ next_node.parent.keys[index] = next_node.keys.shift
1030
+ # Move the first child of the next node to the end of this node
1031
+ @children << (node = next_node.children.shift)
1032
+ node.parent = link(self)
1033
+ end
1034
+
1035
+ return true
1036
+ end
1037
+
1038
+ false
681
1039
  end
682
1040
 
683
- def find_closest_siblings(key)
684
- # The root node has no siblings.
685
- return [ nil, nil, nil ] unless @parent
1041
+ def update_branch_key(old_key)
1042
+ new_key = @keys.first
1043
+ return unless (node = @parent)
686
1044
 
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"
1045
+ while node
1046
+ if (index = node.keys.index(old_key))
1047
+ node.keys[index] = new_key
1048
+ @tree.node_cache.insert(node.get_node)
1049
+ return
1050
+ end
1051
+ node = node.parent
690
1052
  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
1053
 
700
- [ lower_sibling, upper_sibling, parent_index ]
1054
+ # The smallest element has no branch key.
701
1055
  end
702
1056
 
703
1057
  end