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