perobs 3.0.1 → 4.3.0

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