perobs 3.0.1 → 4.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +19 -18
- data/lib/perobs.rb +2 -0
- data/lib/perobs/Array.rb +68 -21
- data/lib/perobs/BTree.rb +110 -54
- data/lib/perobs/BTreeBlob.rb +14 -13
- data/lib/perobs/BTreeDB.rb +11 -10
- data/lib/perobs/BTreeNode.rb +551 -197
- data/lib/perobs/BTreeNodeCache.rb +10 -8
- data/lib/perobs/BTreeNodeLink.rb +11 -1
- data/lib/perobs/BigArray.rb +285 -0
- data/lib/perobs/BigArrayNode.rb +1002 -0
- data/lib/perobs/BigHash.rb +246 -0
- data/lib/perobs/BigTree.rb +197 -0
- data/lib/perobs/BigTreeNode.rb +873 -0
- data/lib/perobs/Cache.rb +47 -22
- data/lib/perobs/ClassMap.rb +2 -2
- data/lib/perobs/ConsoleProgressMeter.rb +61 -0
- data/lib/perobs/DataBase.rb +4 -3
- data/lib/perobs/DynamoDB.rb +62 -20
- data/lib/perobs/EquiBlobsFile.rb +174 -59
- data/lib/perobs/FNV_Hash_1a_64.rb +54 -0
- data/lib/perobs/FlatFile.rb +536 -242
- data/lib/perobs/FlatFileBlobHeader.rb +120 -84
- data/lib/perobs/FlatFileDB.rb +58 -27
- data/lib/perobs/FuzzyStringMatcher.rb +175 -0
- data/lib/perobs/Hash.rb +129 -35
- data/lib/perobs/IDList.rb +144 -0
- data/lib/perobs/IDListPage.rb +107 -0
- data/lib/perobs/IDListPageFile.rb +180 -0
- data/lib/perobs/IDListPageRecord.rb +142 -0
- data/lib/perobs/LockFile.rb +3 -0
- data/lib/perobs/Object.rb +28 -20
- data/lib/perobs/ObjectBase.rb +53 -10
- data/lib/perobs/PersistentObjectCache.rb +142 -0
- data/lib/perobs/PersistentObjectCacheLine.rb +99 -0
- data/lib/perobs/ProgressMeter.rb +97 -0
- data/lib/perobs/SpaceManager.rb +273 -0
- data/lib/perobs/SpaceTree.rb +63 -47
- data/lib/perobs/SpaceTreeNode.rb +134 -115
- data/lib/perobs/SpaceTreeNodeLink.rb +1 -1
- data/lib/perobs/StackFile.rb +1 -1
- data/lib/perobs/Store.rb +180 -70
- data/lib/perobs/version.rb +1 -1
- data/perobs.gemspec +4 -4
- data/test/Array_spec.rb +48 -39
- data/test/BTreeDB_spec.rb +2 -2
- data/test/BTree_spec.rb +50 -1
- data/test/BigArray_spec.rb +261 -0
- data/test/BigHash_spec.rb +152 -0
- data/test/BigTreeNode_spec.rb +153 -0
- data/test/BigTree_spec.rb +259 -0
- data/test/EquiBlobsFile_spec.rb +105 -5
- data/test/FNV_Hash_1a_64_spec.rb +59 -0
- data/test/FlatFileDB_spec.rb +199 -15
- data/test/FuzzyStringMatcher_spec.rb +261 -0
- data/test/Hash_spec.rb +27 -16
- data/test/IDList_spec.rb +77 -0
- data/test/LegacyDBs/LegacyDB.rb +155 -0
- data/test/LegacyDBs/version_3/class_map.json +1 -0
- data/test/LegacyDBs/version_3/config.json +1 -0
- data/test/LegacyDBs/version_3/database.blobs +0 -0
- data/test/LegacyDBs/version_3/database_spaces.blobs +0 -0
- data/test/LegacyDBs/version_3/index.blobs +0 -0
- data/test/LegacyDBs/version_3/version +1 -0
- data/test/LockFile_spec.rb +9 -6
- data/test/Object_spec.rb +5 -5
- data/test/SpaceManager_spec.rb +176 -0
- data/test/SpaceTree_spec.rb +27 -9
- data/test/Store_spec.rb +353 -206
- data/test/perobs_spec.rb +7 -3
- data/test/spec_helper.rb +9 -4
- metadata +59 -16
- data/lib/perobs/SpaceTreeNodeCache.rb +0 -76
- data/lib/perobs/TreeDB.rb +0 -277
data/lib/perobs/BTreeBlob.rb
CHANGED
@@ -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
|
-
#
|
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 [
|
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 [
|
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 [
|
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 [
|
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 [
|
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 [
|
209
|
-
# @return [
|
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 [
|
240
|
-
# @param bytes [
|
241
|
-
# @return [
|
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
|
data/lib/perobs/BTreeDB.rb
CHANGED
@@ -2,7 +2,8 @@
|
|
2
2
|
#
|
3
3
|
# = BTreeDB.rb -- Persistent Ruby Object Store
|
4
4
|
#
|
5
|
-
# Copyright (c) 2015, 2016
|
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
|
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 [
|
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 [
|
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 [
|
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 [
|
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 [
|
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 [
|
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
|
data/lib/perobs/BTreeNode.rb
CHANGED
@@ -39,8 +39,10 @@ module PEROBS
|
|
39
39
|
# mark a node as leaf or branch node.
|
40
40
|
class BTreeNode
|
41
41
|
|
42
|
-
|
43
|
-
|
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,
|
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
|
-
@
|
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
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
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
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
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
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
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
|
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
|
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
|
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] =
|
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,
|
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
|
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
|
266
|
-
|
267
|
-
|
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
|
-
#
|
270
|
-
@
|
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
|
-
#
|
274
|
-
|
275
|
-
|
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
|
-
|
279
|
-
|
280
|
-
|
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
|
-
|
287
|
-
|
288
|
-
|
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
|
320
|
-
|
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] =
|
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
|
-
|
582
|
+
@tree.node_cache.insert(self)
|
583
|
+
|
584
|
+
child
|
331
585
|
end
|
332
586
|
|
333
587
|
def trim(idx)
|
334
|
-
|
335
|
-
@keys = @keys[0..idx - 1]
|
588
|
+
@keys.slice!(idx, @keys.length - idx)
|
336
589
|
if @is_leaf
|
337
|
-
@values
|
590
|
+
@values.slice!(idx, @values.length - idx)
|
338
591
|
else
|
339
|
-
@children
|
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
|
-
|
350
|
-
|
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 [
|
430
|
-
|
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
|
-
|
434
|
-
|
435
|
-
|
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
|
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
|
-
|
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.
|
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
|
-
|
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
|
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
|
753
|
+
"#{child.parent.class} instead of BTreeNodeLink"
|
754
|
+
return nil
|
473
755
|
end
|
474
|
-
if child
|
756
|
+
if child == node
|
475
757
|
node.error "Child #{i} points to self"
|
476
|
-
return
|
758
|
+
return nil
|
477
759
|
end
|
478
760
|
if stack.include?(child)
|
479
761
|
node.error "Child #{i} points to ancester node"
|
480
|
-
return
|
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
|
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
|
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
|
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
|
-
|
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
|
-
|
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}
|
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
|
637
|
-
|
954
|
+
def min_keys
|
955
|
+
@tree.order / 2
|
956
|
+
end
|
638
957
|
|
639
|
-
|
640
|
-
|
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
|
659
|
-
|
660
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
998
|
+
return true
|
999
|
+
end
|
1000
|
+
|
1001
|
+
false
|
676
1002
|
end
|
677
1003
|
|
678
|
-
|
679
|
-
|
680
|
-
|
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
|
684
|
-
|
685
|
-
return
|
1041
|
+
def update_branch_key(old_key)
|
1042
|
+
new_key = @keys.first
|
1043
|
+
return unless (node = @parent)
|
686
1044
|
|
687
|
-
|
688
|
-
|
689
|
-
|
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
|
-
|
1054
|
+
# The smallest element has no branch key.
|
701
1055
|
end
|
702
1056
|
|
703
1057
|
end
|