perobs 4.0.0 → 4.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/perobs.rb +1 -0
- data/lib/perobs/Array.rb +66 -19
- data/lib/perobs/BTree.rb +83 -12
- data/lib/perobs/BTreeBlob.rb +1 -1
- data/lib/perobs/BTreeDB.rb +2 -2
- data/lib/perobs/BTreeNode.rb +365 -85
- data/lib/perobs/BigArray.rb +267 -0
- data/lib/perobs/BigArrayNode.rb +998 -0
- data/lib/perobs/BigHash.rb +262 -0
- data/lib/perobs/BigTree.rb +184 -0
- data/lib/perobs/BigTreeNode.rb +873 -0
- data/lib/perobs/ConsoleProgressMeter.rb +61 -0
- data/lib/perobs/DataBase.rb +4 -3
- data/lib/perobs/DynamoDB.rb +57 -15
- data/lib/perobs/EquiBlobsFile.rb +143 -51
- data/lib/perobs/FNV_Hash_1a_64.rb +54 -0
- data/lib/perobs/FlatFile.rb +363 -203
- data/lib/perobs/FlatFileBlobHeader.rb +98 -54
- data/lib/perobs/FlatFileDB.rb +42 -20
- data/lib/perobs/Hash.rb +58 -13
- 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/Object.rb +18 -15
- data/lib/perobs/ObjectBase.rb +38 -4
- data/lib/perobs/PersistentObjectCache.rb +53 -67
- data/lib/perobs/PersistentObjectCacheLine.rb +24 -12
- data/lib/perobs/ProgressMeter.rb +97 -0
- data/lib/perobs/SpaceTree.rb +21 -12
- data/lib/perobs/SpaceTreeNode.rb +53 -61
- data/lib/perobs/Store.rb +71 -32
- data/lib/perobs/version.rb +1 -1
- data/perobs.gemspec +4 -4
- data/test/Array_spec.rb +15 -6
- data/test/BTree_spec.rb +5 -2
- data/test/BigArray_spec.rb +214 -0
- data/test/BigHash_spec.rb +144 -0
- data/test/BigTreeNode_spec.rb +153 -0
- data/test/BigTree_spec.rb +259 -0
- data/test/EquiBlobsFile_spec.rb +105 -1
- data/test/FNV_Hash_1a_64_spec.rb +59 -0
- data/test/FlatFileDB_spec.rb +63 -14
- data/test/Hash_spec.rb +1 -2
- data/test/IDList_spec.rb +77 -0
- data/test/LegacyDBs/LegacyDB.rb +151 -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/SpaceTree_spec.rb +4 -1
- data/test/Store_spec.rb +290 -199
- data/test/spec_helper.rb +9 -4
- metadata +47 -10
- data/lib/perobs/TreeDB.rb +0 -277
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4da7810046e0984b00152ab2205ebb2e2d364b94
|
4
|
+
data.tar.gz: 74c48af35b674713b222d0dff1b7f0c3ef070b45
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5746476c388361dee6ac06094e7545e86ecb0569368c089e21429e4e6c3dbf92d8db5e7854249d7214c162f15a629306092dd0ca8bfbceb85eda7c08df95cf90
|
7
|
+
data.tar.gz: a495a6127f26bdc7326f1bd7ea3671ff9add499d0b455a9334c884c23a2960ee963eedb8d2bcb5c3f257af9dde4895bebd458a96e7c079dbd0752028e9200614
|
data/lib/perobs.rb
CHANGED
data/lib/perobs/Array.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
#
|
3
3
|
# = Array.rb -- Persistent Ruby Object Store
|
4
4
|
#
|
5
|
-
# Copyright (c) 2015, 2016 by Chris Schlaeger <chris@taskjuggler.org>
|
5
|
+
# Copyright (c) 2015, 2016, 2017 by Chris Schlaeger <chris@taskjuggler.org>
|
6
6
|
#
|
7
7
|
# MIT License
|
8
8
|
#
|
@@ -44,19 +44,27 @@ module PEROBS
|
|
44
44
|
|
45
45
|
attr_reader :data
|
46
46
|
|
47
|
-
# These methods do not mutate the Array
|
48
|
-
# operations.
|
47
|
+
# These methods do not mutate the Array but create a new PEROBS::Array
|
48
|
+
# object. They only perform read operations.
|
49
49
|
([
|
50
|
-
:&,
|
51
|
-
:
|
50
|
+
:|, :&, :+, :-, :collect, :compact, :drop, :drop_while,
|
51
|
+
:flatten, :map, :reject, :reverse, :rotate, :select, :shuffle, :slice,
|
52
|
+
:sort, :take, :take_while, :uniq, :values_at
|
53
|
+
] + Enumerable.instance_methods).uniq.each do |method_sym|
|
54
|
+
define_method(method_sym) do |*args, &block|
|
55
|
+
@store.cache.cache_read(self)
|
56
|
+
@store.new(PEROBS::Array, @data.send(method_sym, *args, &block))
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# These methods do not mutate the Array and only perform read operations.
|
61
|
+
# They do not return basic objects types.
|
62
|
+
([
|
63
|
+
:==, :[], :<=>, :at, :bsearch, :bsearch_index, :count, :cycle,
|
52
64
|
:each, :each_index, :empty?, :eql?, :fetch, :find_index, :first,
|
53
|
-
:
|
54
|
-
:
|
55
|
-
:
|
56
|
-
:repeated_permutation, :reverse, :reverse_each, :rindex, :rotate,
|
57
|
-
:sample, :select, :shelljoin, :shuffle, :size, :slice, :sort, :take,
|
58
|
-
:take_while, :to_a, :to_ary, :to_s, :transpose, :uniq, :values_at, :zip,
|
59
|
-
:|
|
65
|
+
:frozen?, :include?, :index, :join, :last, :length, :pack,
|
66
|
+
:pretty_print, :pretty_print_cycle, :reverse_each, :rindex, :sample,
|
67
|
+
:size, :to_a, :to_ary, :to_s
|
60
68
|
] + Enumerable.instance_methods).uniq.each do |method_sym|
|
61
69
|
define_method(method_sym) do |*args, &block|
|
62
70
|
@store.cache.cache_read(self)
|
@@ -64,12 +72,23 @@ module PEROBS
|
|
64
72
|
end
|
65
73
|
end
|
66
74
|
|
75
|
+
# These methods mutate the Array and return self.
|
76
|
+
[
|
77
|
+
:<<, :clear, :collect!, :compact!, :concat,
|
78
|
+
:fill, :flatten!, :insert, :keep_if, :map!, :push,
|
79
|
+
:reject!, :replace, :select!, :reverse!, :rotate!, :shuffle!,
|
80
|
+
:slice!, :sort!, :sort_by!, :uniq!
|
81
|
+
].each do |method_sym|
|
82
|
+
define_method(method_sym) do |*args, &block|
|
83
|
+
@store.cache.cache_write(self)
|
84
|
+
@data.send(method_sym, *args, &block)
|
85
|
+
myself
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
67
89
|
# These methods mutate the Array.
|
68
90
|
[
|
69
|
-
|
70
|
-
:delete_if, :fill, :flatten!, :insert, :keep_if, :map!, :pop, :push,
|
71
|
-
:reject!, :replace, :select!, :reverse!, :rotate!, :shift, :shuffle!,
|
72
|
-
:slice!, :sort!, :sort_by!, :uniq!, :unshift
|
91
|
+
:delete, :delete_at, :delete_if, :shift, :pop
|
73
92
|
].each do |method_sym|
|
74
93
|
define_method(method_sym) do |*args, &block|
|
75
94
|
@store.cache.cache_write(self)
|
@@ -81,17 +100,45 @@ module PEROBS
|
|
81
100
|
# PEROBS users should never call this method or equivalents of derived
|
82
101
|
# methods directly.
|
83
102
|
# @param p [PEROBS::Handle] PEROBS handle
|
84
|
-
# @param
|
103
|
+
# @param arg1 [Integer or Array] The requested size of the Array or an
|
104
|
+
# Array to initialize
|
85
105
|
# @param default [Any] The default value that is returned when no value is
|
86
106
|
# stored for a specific key.
|
87
|
-
def initialize(p,
|
107
|
+
def initialize(p, arg1 = 0, default = nil, &block)
|
88
108
|
super(p)
|
89
|
-
|
109
|
+
if arg1.is_a?(::Array)
|
110
|
+
arg1.each { |v| _check_assignment_value(v) }
|
111
|
+
@data = arg1.dup
|
112
|
+
elsif block_given?
|
113
|
+
@data = ::Array.new(arg1) do
|
114
|
+
_check_assignment_value(yield)
|
115
|
+
end
|
116
|
+
else
|
117
|
+
@data = ::Array.new(arg1, _check_assignment_value(default))
|
118
|
+
end
|
90
119
|
|
91
120
|
# Ensure that the newly created object will be pushed into the database.
|
92
121
|
@store.cache.cache_write(self)
|
93
122
|
end
|
94
123
|
|
124
|
+
# Proxy for the assignment method.
|
125
|
+
def []=(*args)
|
126
|
+
if (args.length == 2)
|
127
|
+
_check_assignment_value(args[1])
|
128
|
+
else
|
129
|
+
_check_assignment_value(args[2])
|
130
|
+
end
|
131
|
+
@store.cache.cache_write(self)
|
132
|
+
@data.[]=(*args)
|
133
|
+
end
|
134
|
+
|
135
|
+
# Proxy for the unshift method.
|
136
|
+
def unshift(val)
|
137
|
+
_check_assignment_value(val)
|
138
|
+
@store.cache.cache_write(self)
|
139
|
+
@data.unshift(val)
|
140
|
+
end
|
141
|
+
|
95
142
|
# Return a list of all object IDs of all persistend objects that this Array
|
96
143
|
# is referencing.
|
97
144
|
# @return [Array of Integer] IDs of referenced objects
|
data/lib/perobs/BTree.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
#
|
3
3
|
# = BTreeNode.rb -- Persistent Ruby Object Store
|
4
4
|
#
|
5
|
-
# Copyright (c) 2016, 2017 by Chris Schlaeger <chris@taskjuggler.org>
|
5
|
+
# Copyright (c) 2016, 2017, 2018 by Chris Schlaeger <chris@taskjuggler.org>
|
6
6
|
#
|
7
7
|
# MIT License
|
8
8
|
#
|
@@ -32,7 +32,7 @@ require 'perobs/BTreeNode'
|
|
32
32
|
|
33
33
|
module PEROBS
|
34
34
|
|
35
|
-
# This BTree class is very similar to a classic
|
35
|
+
# This BTree class is very similar to a classic B+Tree implementation. It
|
36
36
|
# manages a tree that is always balanced. The BTree is stored in the
|
37
37
|
# specified directory and partially kept in memory to speed up operations.
|
38
38
|
# The order of the tree specifies how many keys each node will be able to
|
@@ -40,16 +40,19 @@ module PEROBS
|
|
40
40
|
# have N + 1 references to child nodes instead.
|
41
41
|
class BTree
|
42
42
|
|
43
|
-
attr_reader :order, :nodes, :node_cache
|
43
|
+
attr_reader :order, :nodes, :node_cache, :first_leaf, :last_leaf, :size
|
44
44
|
|
45
45
|
# Create a new BTree object.
|
46
46
|
# @param dir [String] Directory to store the tree file
|
47
47
|
# @param name [String] Base name of the BTree related files in 'dir'
|
48
48
|
# @param order [Integer] The maximum number of keys per node. This number
|
49
49
|
# must be odd and larger than 2 and smaller than 2**16 - 1.
|
50
|
-
|
50
|
+
# @param progressmeter [ProgressMeter] reference to a ProgressMeter object
|
51
|
+
def initialize(dir, name, order, progressmeter)
|
51
52
|
@dir = dir
|
52
53
|
@name = name
|
54
|
+
@progressmeter = progressmeter
|
55
|
+
|
53
56
|
unless order > 2
|
54
57
|
PEROBS.log.fatal "BTree order must be larger than 2, not #{order}"
|
55
58
|
end
|
@@ -62,9 +65,14 @@ module PEROBS
|
|
62
65
|
@order = order
|
63
66
|
|
64
67
|
# This EquiBlobsFile contains the nodes of the BTree.
|
65
|
-
@nodes = EquiBlobsFile.new(@dir, @name,
|
68
|
+
@nodes = EquiBlobsFile.new(@dir, @name, @progressmeter,
|
66
69
|
BTreeNode::node_bytes(@order))
|
67
|
-
@
|
70
|
+
@nodes.register_custom_data('first_leaf')
|
71
|
+
@nodes.register_custom_data('last_leaf')
|
72
|
+
@nodes.register_custom_data('btree_size')
|
73
|
+
@node_cache = PersistentObjectCache.new(16384, 5000, BTreeNode, self)
|
74
|
+
@root = @first_leaf = @last_leaf = nil
|
75
|
+
@size = 0
|
68
76
|
|
69
77
|
# This BTree implementation uses a write cache to improve write
|
70
78
|
# performance of multiple successive read/write operations. This also
|
@@ -89,23 +97,46 @@ module PEROBS
|
|
89
97
|
|
90
98
|
@node_cache.clear
|
91
99
|
@nodes.open
|
92
|
-
|
93
|
-
|
94
|
-
|
100
|
+
|
101
|
+
if @nodes.total_entries == 0
|
102
|
+
# We've created a new nodes file
|
103
|
+
node = BTreeNode::create(self)
|
104
|
+
else
|
105
|
+
# We are loading an existing tree.
|
106
|
+
node = BTreeNode::load_and_link(self, @nodes.first_entry)
|
107
|
+
@first_leaf = BTreeNode::load_and_link(
|
108
|
+
self, @nodes.get_custom_data('first_leaf'))
|
109
|
+
@last_leaf = BTreeNode::load_and_link(
|
110
|
+
self, @nodes.get_custom_data('last_leaf'))
|
111
|
+
end
|
95
112
|
set_root(node)
|
113
|
+
|
114
|
+
# Get the total number of entries that are stored in the tree.
|
115
|
+
@size = @nodes.get_custom_data('btree_size')
|
96
116
|
end
|
97
117
|
|
98
118
|
# Close the tree file.
|
99
119
|
def close
|
120
|
+
|
121
|
+
def val_perc(value, total)
|
122
|
+
"#{value} (#{(value.to_f / total*100.0).to_i}%)"
|
123
|
+
end
|
124
|
+
|
100
125
|
sync
|
101
126
|
@nodes.close
|
102
127
|
@root = nil
|
103
128
|
end
|
104
129
|
|
130
|
+
# @return true if file is currently open
|
131
|
+
def is_open?
|
132
|
+
!@root.nil?
|
133
|
+
end
|
134
|
+
|
105
135
|
# Clear all pools and forget any registered spaces.
|
106
136
|
def clear
|
107
137
|
@node_cache.clear
|
108
138
|
@nodes.clear
|
139
|
+
@size = 0
|
109
140
|
set_root(BTreeNode::create(self))
|
110
141
|
end
|
111
142
|
|
@@ -114,6 +145,7 @@ module PEROBS
|
|
114
145
|
# all stored data from the BTree.
|
115
146
|
def erase
|
116
147
|
@nodes.erase
|
148
|
+
@size = 0
|
117
149
|
@root = nil
|
118
150
|
@dirty_flag.forced_unlock
|
119
151
|
end
|
@@ -121,6 +153,7 @@ module PEROBS
|
|
121
153
|
# Flush all pending modifications into the tree file.
|
122
154
|
def sync
|
123
155
|
@node_cache.flush(true)
|
156
|
+
@nodes.set_custom_data('btree_size', @size)
|
124
157
|
@nodes.sync
|
125
158
|
@dirty_flag.unlock if @dirty_flag.is_locked?
|
126
159
|
end
|
@@ -128,21 +161,54 @@ module PEROBS
|
|
128
161
|
# Check if the tree file contains any errors.
|
129
162
|
# @return [Boolean] true if no erros were found, false otherwise
|
130
163
|
def check(&block)
|
131
|
-
|
164
|
+
sync
|
165
|
+
return false unless @nodes.check
|
166
|
+
|
167
|
+
entries = 0
|
168
|
+
res = true
|
169
|
+
@progressmeter.start('Checking index structure', @size) do |pm|
|
170
|
+
res = @root.check do |k, v|
|
171
|
+
pm.update(entries += 1)
|
172
|
+
block_given? ? yield(k, v) : true
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
unless entries == @size
|
177
|
+
PEROBS.log.error "The BTree size (#{@size}) and the number of " +
|
178
|
+
"found entries (#{entries}) don't match"
|
179
|
+
return false
|
180
|
+
end
|
181
|
+
|
182
|
+
res
|
132
183
|
end
|
133
184
|
|
134
185
|
# Register a new node as root node of the tree.
|
186
|
+
# @param node [BTreeNode]
|
135
187
|
def set_root(node)
|
136
188
|
@root = node
|
137
189
|
@nodes.first_entry = node.node_address
|
138
190
|
end
|
139
191
|
|
192
|
+
# Set the address of the first leaf node.
|
193
|
+
# @param node [BTreeNode]
|
194
|
+
def set_first_leaf(node)
|
195
|
+
@first_leaf = node
|
196
|
+
@nodes.set_custom_data('first_leaf', node.node_address)
|
197
|
+
end
|
198
|
+
|
199
|
+
# Set the address of the last leaf node.
|
200
|
+
# @param node [BTreeNode]
|
201
|
+
def set_last_leaf(node)
|
202
|
+
@last_leaf = node
|
203
|
+
@nodes.set_custom_data('last_leaf', node.node_address)
|
204
|
+
end
|
205
|
+
|
140
206
|
# Insert a new value into the tree using the key as a unique index. If the
|
141
207
|
# key already exists the old value will be overwritten.
|
142
208
|
# @param key [Integer] Unique key
|
143
209
|
# @param value [Integer] value
|
144
210
|
def insert(key, value)
|
145
|
-
@root.insert(key, value)
|
211
|
+
@size += 1 if @root.insert(key, value)
|
146
212
|
@node_cache.flush
|
147
213
|
end
|
148
214
|
|
@@ -157,7 +223,7 @@ module PEROBS
|
|
157
223
|
# Find and remove the value associated with the given key. If no entry was
|
158
224
|
# found, return nil, otherwise the found value.
|
159
225
|
def remove(key)
|
160
|
-
removed_value = @root.remove(key)
|
226
|
+
@size -= 1 unless (removed_value = @root.remove(key)).nil?
|
161
227
|
|
162
228
|
# Check if the root node only contains one child link after the delete
|
163
229
|
# operation. Then we can delete that node and pull the tree one level
|
@@ -187,6 +253,11 @@ module PEROBS
|
|
187
253
|
@nodes.delete_blob(address)
|
188
254
|
end
|
189
255
|
|
256
|
+
# @return [Integer] The number of entries stored in the tree.
|
257
|
+
def entries_count
|
258
|
+
@size
|
259
|
+
end
|
260
|
+
|
190
261
|
# @return [String] Human reable form of the tree.
|
191
262
|
def to_s
|
192
263
|
@root.to_s
|
data/lib/perobs/BTreeBlob.rb
CHANGED
data/lib/perobs/BTreeDB.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
#
|
3
3
|
# = BTreeDB.rb -- Persistent Ruby Object Store
|
4
4
|
#
|
5
|
-
# Copyright (c) 2015, 2016 by Chris Schlaeger <chris@taskjuggler.org>
|
5
|
+
# Copyright (c) 2015, 2016, 2018 by Chris Schlaeger <chris@taskjuggler.org>
|
6
6
|
#
|
7
7
|
# MIT License
|
8
8
|
#
|
@@ -58,7 +58,7 @@ module PEROBS
|
|
58
58
|
# nodes. The insert/find/delete time grows
|
59
59
|
# linearly with the size.
|
60
60
|
def initialize(db_name, options = {})
|
61
|
-
super(options
|
61
|
+
super(options)
|
62
62
|
|
63
63
|
@db_dir = db_name
|
64
64
|
# Create the database directory if it doesn't exist yet.
|
data/lib/perobs/BTreeNode.rb
CHANGED
@@ -39,7 +39,8 @@ module PEROBS
|
|
39
39
|
# mark a node as leaf or branch node.
|
40
40
|
class BTreeNode
|
41
41
|
|
42
|
-
attr_reader :node_address, :parent, :is_leaf, :
|
42
|
+
attr_reader :node_address, :parent, :is_leaf, :next_sibling, :prev_sibling,
|
43
|
+
:keys, :values, :children
|
43
44
|
|
44
45
|
# Create a new BTreeNode object for the given tree with the given parent
|
45
46
|
# or recreate the node with the given node_address from the backing store.
|
@@ -48,18 +49,23 @@ module PEROBS
|
|
48
49
|
# restore the node.
|
49
50
|
# @param tree [BTree] The tree this node is part of
|
50
51
|
# @param parent [BTreeNode] reference to parent node
|
52
|
+
# @param prev_sibling [BTreeNode] reference to previous sibling node
|
53
|
+
# @param next_sibling [BTreeNode] reference to next sibling node
|
51
54
|
# @param node_address [Integer] the address of the node to read from the
|
52
55
|
# backing store
|
53
56
|
# @param is_leaf [Boolean] true if the node should be a leaf node, false
|
54
57
|
# if not
|
55
58
|
def initialize(tree, node_address = nil, parent = nil, is_leaf = true,
|
59
|
+
prev_sibling = nil, next_sibling = nil,
|
56
60
|
keys = [], values = [], children = [])
|
57
61
|
@tree = tree
|
58
62
|
if node_address == 0
|
59
63
|
PEROBS.log.fatal "Node address may not be 0"
|
60
64
|
end
|
61
65
|
@node_address = node_address
|
62
|
-
@parent =
|
66
|
+
@parent = link(parent)
|
67
|
+
@prev_sibling = link(prev_sibling)
|
68
|
+
@next_sibling = link(next_sibling)
|
63
69
|
@keys = keys
|
64
70
|
if (@is_leaf = is_leaf)
|
65
71
|
@values = values
|
@@ -68,18 +74,6 @@ module PEROBS
|
|
68
74
|
@children = children
|
69
75
|
@values = []
|
70
76
|
end
|
71
|
-
|
72
|
-
ObjectSpace.define_finalizer(
|
73
|
-
self, BTreeNode._finalize(@tree, @node_address, object_id))
|
74
|
-
@tree.node_cache.insert(self, false)
|
75
|
-
end
|
76
|
-
|
77
|
-
# This method generates the destructor for the objects of this class. It
|
78
|
-
# is done this way to prevent the Proc object hanging on to a reference to
|
79
|
-
# self which would prevent the object from being collected. This internal
|
80
|
-
# method is not intended for users to call.
|
81
|
-
def BTreeNode::_finalize(tree, node_address, ruby_object_id)
|
82
|
-
proc { tree.node_cache._collect(node_address, ruby_object_id) }
|
83
77
|
end
|
84
78
|
|
85
79
|
# Create a new SpaceTreeNode. This method should be used for the creation
|
@@ -88,7 +82,10 @@ module PEROBS
|
|
88
82
|
# @param parent [BTreeNode] The parent node
|
89
83
|
# @param is_leaf [Boolean] True if the node has no children, false
|
90
84
|
# otherwise
|
91
|
-
|
85
|
+
# @param prev_sibling [BTreeNode] reference to previous sibling node
|
86
|
+
# @param next_sibling [BTreeNode] reference to next sibling node
|
87
|
+
def BTreeNode::create(tree, parent = nil, is_leaf = true,
|
88
|
+
prev_sibling = nil, next_sibling = nil)
|
92
89
|
unless parent.nil? || parent.is_a?(BTreeNode) ||
|
93
90
|
parent.is_a?(BTreeNodeLink)
|
94
91
|
PEROBS.log.fatal "Parent node must be a BTreeNode but is of class " +
|
@@ -96,17 +93,26 @@ module PEROBS
|
|
96
93
|
end
|
97
94
|
|
98
95
|
address = tree.nodes.free_address
|
99
|
-
node = BTreeNode.new(tree, address, parent, is_leaf
|
96
|
+
node = BTreeNode.new(tree, address, parent, is_leaf, prev_sibling,
|
97
|
+
next_sibling)
|
100
98
|
# This is a new node. Make sure the data is written to the file.
|
101
99
|
tree.node_cache.insert(node)
|
102
100
|
|
103
|
-
node
|
101
|
+
# Insert the newly created node into the existing node chain.
|
102
|
+
if (node.prev_sibling = prev_sibling)
|
103
|
+
node.prev_sibling.next_sibling = BTreeNodeLink.new(tree, node)
|
104
|
+
end
|
105
|
+
if (node.next_sibling = next_sibling)
|
106
|
+
node.next_sibling.prev_sibling = BTreeNodeLink.new(tree, node)
|
107
|
+
end
|
108
|
+
|
109
|
+
BTreeNodeLink.new(tree, node)
|
104
110
|
end
|
105
111
|
|
106
112
|
# Restore a node from the backing store at the given address and tree.
|
107
113
|
# @param tree [BTree] The tree the node belongs to
|
108
|
-
# @param
|
109
|
-
def BTreeNode::load(tree, address)
|
114
|
+
# @param address [Integer] The address in the blob file.
|
115
|
+
def BTreeNode::load(tree, address, unused = nil)
|
110
116
|
unless address.is_a?(Integer)
|
111
117
|
PEROBS.log.fatal "address is not Integer: #{address.class}"
|
112
118
|
end
|
@@ -130,19 +136,21 @@ module PEROBS
|
|
130
136
|
data_count = ary[2]
|
131
137
|
# Read the parent node address
|
132
138
|
parent = ary[3] == 0 ? nil : BTreeNodeLink.new(tree, ary[3])
|
139
|
+
prev_sibling = ary[4] == 0 ? nil : BTreeNodeLink.new(tree, ary[4])
|
140
|
+
next_sibling = ary[5] == 0 ? nil : BTreeNodeLink.new(tree, ary[5])
|
133
141
|
# Read the keys
|
134
|
-
keys = ary[
|
142
|
+
keys = ary[6, key_count]
|
135
143
|
|
136
144
|
children = nil
|
137
145
|
values = nil
|
138
146
|
if is_leaf
|
139
147
|
# Read the values
|
140
|
-
values = ary[
|
148
|
+
values = ary[6 + tree.order, data_count]
|
141
149
|
else
|
142
150
|
# Read the child addresses
|
143
151
|
children = []
|
144
152
|
data_count.times do |i|
|
145
|
-
child_address = ary[
|
153
|
+
child_address = ary[6 + tree.order + i]
|
146
154
|
unless child_address > 0
|
147
155
|
PEROBS.log.fatal "Child address must be larger than 0"
|
148
156
|
end
|
@@ -150,17 +158,28 @@ module PEROBS
|
|
150
158
|
end
|
151
159
|
end
|
152
160
|
|
153
|
-
node = BTreeNode.new(tree, address, parent, is_leaf,
|
161
|
+
node = BTreeNode.new(tree, address, parent, is_leaf,
|
162
|
+
prev_sibling, next_sibling, keys, values,
|
154
163
|
children)
|
155
164
|
tree.node_cache.insert(node, false)
|
156
165
|
|
157
166
|
node
|
158
167
|
end
|
159
168
|
|
169
|
+
# This is a wrapper around BTreeNode::load() that returns a BTreeNodeLink
|
170
|
+
# instead of the actual node.
|
171
|
+
# @param tree [BTree] The tree the node belongs to
|
172
|
+
# @param address [Integer] The address in the blob file.
|
173
|
+
# @return [BTreeNodeLink] Link to loaded noded
|
174
|
+
def BTreeNode::load_and_link(tree, address)
|
175
|
+
BTreeNodeLink.new(tree, BTreeNode::load(tree, address))
|
176
|
+
end
|
177
|
+
|
178
|
+
|
160
179
|
# @return [String] The format used for String.pack.
|
161
180
|
def BTreeNode::node_bytes_format(tree)
|
162
181
|
# This does not include the 4 bytes for the CRC32 checksum
|
163
|
-
"
|
182
|
+
"CSSQQQQ#{tree.order}Q#{tree.order + 1}"
|
164
183
|
end
|
165
184
|
|
166
185
|
# @return [Integer] The number of bytes needed to store a node.
|
@@ -169,6 +188,8 @@ module PEROBS
|
|
169
188
|
2 + # actual key count
|
170
189
|
2 + # actual value or children count (aka data count)
|
171
190
|
8 + # parent address
|
191
|
+
8 + # previous sibling address
|
192
|
+
8 + # next sibling address
|
172
193
|
8 * order + # keys
|
173
194
|
8 * (order + 1) + # values or child addresses
|
174
195
|
4 # CRC32 checksum
|
@@ -200,8 +221,7 @@ module PEROBS
|
|
200
221
|
|
201
222
|
# Once we have reached a leaf node we can insert or replace the value.
|
202
223
|
if node.is_leaf
|
203
|
-
node.insert_element(key, value)
|
204
|
-
return
|
224
|
+
return node.insert_element(key, value)
|
205
225
|
else
|
206
226
|
# Descend into the right child node to add the value to.
|
207
227
|
node = node.children[node.search_key_index(key)]
|
@@ -269,18 +289,19 @@ module PEROBS
|
|
269
289
|
def split_node
|
270
290
|
unless @parent
|
271
291
|
# The node is the root node. We need to create a parent node first.
|
272
|
-
self.parent = BTreeNode::create(@tree, nil, false)
|
292
|
+
self.parent = link(BTreeNode::create(@tree, nil, false))
|
273
293
|
@parent.set_child(0, self)
|
274
294
|
@tree.set_root(@parent)
|
275
295
|
end
|
276
296
|
|
277
297
|
# Create the new sibling that will take the 2nd half of the
|
278
298
|
# node content.
|
279
|
-
sibling = BTreeNode::create(@tree, @parent, @is_leaf)
|
299
|
+
sibling = BTreeNode::create(@tree, @parent, @is_leaf, link(self),
|
300
|
+
@next_sibling)
|
280
301
|
# Determine the index of the middle element that gets moved to the
|
281
302
|
# parent. The order must be an uneven number, so adding 1 will get us
|
282
303
|
# the middle element.
|
283
|
-
mid = @tree.order / 2
|
304
|
+
mid = @tree.order / 2
|
284
305
|
# Insert the middle element key into the parent node
|
285
306
|
@parent.insert_element(@keys[mid], sibling)
|
286
307
|
copy_elements(mid + (@is_leaf ? 0 : 1), sibling)
|
@@ -297,6 +318,9 @@ module PEROBS
|
|
297
318
|
insert_element(@parent.keys[parent_index], upper_sibling.children[0])
|
298
319
|
end
|
299
320
|
upper_sibling.copy_elements(0, self, @keys.size, upper_sibling.keys.size)
|
321
|
+
if (@next_sibling = link(upper_sibling.next_sibling))
|
322
|
+
@next_sibling.prev_sibling = link(self)
|
323
|
+
end
|
300
324
|
@tree.delete_node(upper_sibling.node_address)
|
301
325
|
|
302
326
|
@parent.remove_element(parent_index)
|
@@ -307,6 +331,7 @@ module PEROBS
|
|
307
331
|
# @param key [Integer] key to address the value or child
|
308
332
|
# @param value_or_child [Integer or BTreeNode] value or BTreeNode
|
309
333
|
# reference
|
334
|
+
# @return true for insert, false for overwrite
|
310
335
|
def insert_element(key, value_or_child)
|
311
336
|
if @keys.size >= @tree.order
|
312
337
|
PEROBS.log.fatal "Cannot insert into a full BTreeNode"
|
@@ -319,51 +344,48 @@ module PEROBS
|
|
319
344
|
if is_leaf
|
320
345
|
@values[i] = value_or_child
|
321
346
|
else
|
322
|
-
@children[i + 1] =
|
347
|
+
@children[i + 1] = link(value_or_child)
|
323
348
|
end
|
349
|
+
@tree.node_cache.insert(self)
|
350
|
+
|
351
|
+
return false
|
324
352
|
else
|
325
353
|
# Create a new entry
|
326
354
|
@keys.insert(i, key)
|
327
355
|
if is_leaf
|
328
356
|
@values.insert(i, value_or_child)
|
329
357
|
else
|
330
|
-
@children.insert(i + 1,
|
358
|
+
@children.insert(i + 1, link(value_or_child))
|
331
359
|
end
|
360
|
+
@tree.node_cache.insert(self)
|
361
|
+
|
362
|
+
return true
|
332
363
|
end
|
333
|
-
@tree.node_cache.insert(self)
|
334
364
|
end
|
335
365
|
|
336
366
|
# Remove the element at the given index.
|
337
367
|
def remove_element(index)
|
338
|
-
# We need this key to find the link in the parent node.
|
339
|
-
first_key = @keys[0]
|
340
|
-
removed_value = nil
|
341
|
-
|
342
368
|
# Delete the key at the specified index.
|
343
|
-
unless @keys.delete_at(index)
|
344
|
-
PEROBS.log.fatal "Could not remove element #{index} from
|
369
|
+
unless (key = @keys.delete_at(index))
|
370
|
+
PEROBS.log.fatal "Could not remove element #{index} from BigTreeNode " +
|
345
371
|
"@#{@node_address}"
|
346
372
|
end
|
347
|
-
if
|
348
|
-
# For leaf nodes, also delete the corresponding value.
|
349
|
-
removed_value = @values.delete_at(index)
|
350
|
-
else
|
351
|
-
# The corresponding child has can be found at 1 index higher.
|
352
|
-
@children.delete_at(index + 1)
|
353
|
-
end
|
354
|
-
@tree.node_cache.insert(self)
|
373
|
+
update_branch_key(key) if index == 0
|
355
374
|
|
356
|
-
#
|
357
|
-
|
358
|
-
|
359
|
-
find_closest_siblings(first_key)
|
375
|
+
# Delete the corresponding value.
|
376
|
+
removed_value = @values.delete_at(index)
|
377
|
+
@tree.node_cache.insert(self)
|
360
378
|
|
361
|
-
if
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
379
|
+
if @keys.length < min_keys
|
380
|
+
if @prev_sibling && @prev_sibling.parent == @parent
|
381
|
+
borrow_from_previous_sibling(@prev_sibling) ||
|
382
|
+
@prev_sibling.merge_with_leaf_node(self)
|
383
|
+
elsif @next_sibling && @next_sibling.parent == @parent
|
384
|
+
borrow_from_next_sibling(@next_sibling) ||
|
385
|
+
merge_with_leaf_node(@next_sibling)
|
386
|
+
elsif @parent
|
387
|
+
PEROBS.log.fatal "Cannot not find adjecent leaf siblings"
|
388
|
+
end
|
367
389
|
end
|
368
390
|
|
369
391
|
# The merge has potentially invalidated this node. After this method has
|
@@ -371,7 +393,88 @@ module PEROBS
|
|
371
393
|
removed_value
|
372
394
|
end
|
373
395
|
|
396
|
+
def remove_child(node)
|
397
|
+
unless (index = search_node_index(node))
|
398
|
+
PEROBS.log.fatal "Cannot remove child #{node.node_address} " +
|
399
|
+
"from node #{@node_address}"
|
400
|
+
end
|
401
|
+
|
402
|
+
@tree.node_cache.insert(self)
|
403
|
+
if index == 0
|
404
|
+
# Removing the first child is a bit more complicated as the
|
405
|
+
# corresponding branch key is in a parent node.
|
406
|
+
key = @keys.shift
|
407
|
+
update_branch_key(key)
|
408
|
+
else
|
409
|
+
# For all other children we can just remove the corresponding key.
|
410
|
+
@keys.delete_at(index - 1)
|
411
|
+
end
|
412
|
+
|
413
|
+
# Remove the child node link.
|
414
|
+
child = @children.delete_at(index)
|
415
|
+
# Unlink the neighbouring siblings from the child
|
416
|
+
child.prev_sibling.next_sibling = child.next_sibling if child.prev_sibling
|
417
|
+
child.next_sibling.prev_sibling = child.prev_sibling if child.next_sibling
|
418
|
+
|
419
|
+
if @keys.length < min_keys
|
420
|
+
# The node has become too small. Try borrowing a node from an adjecent
|
421
|
+
# sibling or merge with an adjecent node.
|
422
|
+
if @prev_sibling && @prev_sibling.parent == @parent
|
423
|
+
borrow_from_previous_sibling(@prev_sibling) ||
|
424
|
+
@prev_sibling.merge_with_branch_node(self)
|
425
|
+
elsif @next_sibling && @next_sibling.parent == @parent
|
426
|
+
borrow_from_next_sibling(@next_sibling) ||
|
427
|
+
merge_with_branch_node(@next_sibling)
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
if @parent.nil? && @children.length == 1
|
432
|
+
# If the node just below the root only has one child it will become
|
433
|
+
# the new root node.
|
434
|
+
new_root = @children.first
|
435
|
+
new_root.parent = nil
|
436
|
+
@tree.set_root(new_root)
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
def merge_with_leaf_node(node)
|
441
|
+
if @keys.length + node.keys.length > @tree.order
|
442
|
+
PEROBS.log.fatal "Leaf nodes are too big to merge"
|
443
|
+
end
|
444
|
+
|
445
|
+
@keys += node.keys
|
446
|
+
@values += node.values
|
447
|
+
@tree.node_cache.insert(self)
|
448
|
+
|
449
|
+
node.parent.remove_child(node)
|
450
|
+
end
|
451
|
+
|
452
|
+
def merge_with_branch_node(node)
|
453
|
+
if @keys.length + 1 + node.keys.length > @tree.order
|
454
|
+
PEROBS.log.fatal "Branch nodes are too big to merge"
|
455
|
+
end
|
456
|
+
|
457
|
+
index = @parent.search_node_index(node) - 1
|
458
|
+
@keys << @parent.keys[index]
|
459
|
+
@keys += node.keys
|
460
|
+
node.children.each { |c| c.parent = link(self) }
|
461
|
+
@children += node.children
|
462
|
+
@tree.node_cache.insert(self)
|
463
|
+
|
464
|
+
node.parent.remove_child(node)
|
465
|
+
end
|
466
|
+
|
467
|
+
def search_node_index(node)
|
468
|
+
index = search_key_index(node.keys.first)
|
469
|
+
unless @children[index] == node
|
470
|
+
raise RuntimeError, "Child at index #{index} is not the requested node"
|
471
|
+
end
|
472
|
+
|
473
|
+
index
|
474
|
+
end
|
475
|
+
|
374
476
|
def copy_elements(src_idx, dest_node, dst_idx = 0, count = nil)
|
477
|
+
dest_node = dest_node.get_node
|
375
478
|
unless count
|
376
479
|
count = @tree.order - src_idx
|
377
480
|
end
|
@@ -399,18 +502,47 @@ module PEROBS
|
|
399
502
|
end
|
400
503
|
|
401
504
|
def parent=(p)
|
402
|
-
@parent = p
|
505
|
+
@parent = p
|
506
|
+
@tree.node_cache.insert(self)
|
507
|
+
|
508
|
+
p
|
509
|
+
end
|
510
|
+
|
511
|
+
def prev_sibling=(node)
|
512
|
+
@prev_sibling = node
|
513
|
+
if node.nil? && @is_leaf
|
514
|
+
# If this node is a leaf node without a previous sibling we need to
|
515
|
+
# register it as the first leaf node.
|
516
|
+
@tree.set_first_leaf(BTreeNodeLink.new(@tree, self))
|
517
|
+
end
|
518
|
+
|
403
519
|
@tree.node_cache.insert(self)
|
520
|
+
|
521
|
+
node
|
522
|
+
end
|
523
|
+
|
524
|
+
def next_sibling=(node)
|
525
|
+
@next_sibling = node
|
526
|
+
@tree.node_cache.insert(self)
|
527
|
+
if node.nil? && @is_leaf
|
528
|
+
# If this node is a leaf node without a next sibling we need to
|
529
|
+
# register it as the last leaf node.
|
530
|
+
@tree.set_last_leaf(BTreeNodeLink.new(@tree, self))
|
531
|
+
end
|
532
|
+
|
533
|
+
node
|
404
534
|
end
|
405
535
|
|
406
536
|
def set_child(index, child)
|
407
537
|
if child
|
408
|
-
@children[index] =
|
409
|
-
@children[index].parent = self
|
538
|
+
@children[index] = link(child)
|
539
|
+
@children[index].parent = link(self)
|
410
540
|
else
|
411
541
|
@children[index] = nil
|
412
542
|
end
|
413
543
|
@tree.node_cache.insert(self)
|
544
|
+
|
545
|
+
child
|
414
546
|
end
|
415
547
|
|
416
548
|
def trim(idx)
|
@@ -511,12 +643,20 @@ module PEROBS
|
|
511
643
|
# @yield [key, value]
|
512
644
|
# @return [Boolean] true if tree has no errors
|
513
645
|
def check
|
646
|
+
branch_depth = nil
|
647
|
+
|
514
648
|
traverse do |node, position, stack|
|
515
649
|
if position == 0
|
516
|
-
if node.parent
|
517
|
-
|
518
|
-
|
650
|
+
if node.parent
|
651
|
+
# After a split the nodes will only have half the maximum keys.
|
652
|
+
# For branch nodes one of the split nodes will have even 1 key
|
653
|
+
# less as this will become the branch key in a parent node.
|
654
|
+
if node.keys.size < min_keys - (node.is_leaf ? 0 : 1)
|
655
|
+
node.error "BTreeNode #{node.node_address} has too few keys"
|
656
|
+
return false
|
657
|
+
end
|
519
658
|
end
|
659
|
+
|
520
660
|
if node.keys.size > @tree.order
|
521
661
|
node.error "BTreeNode must not have more then #{@tree.order} " +
|
522
662
|
"keys, but has #{node.keys.size} keys"
|
@@ -529,16 +669,43 @@ module PEROBS
|
|
529
669
|
"#{node.keys.inspect}"
|
530
670
|
return false
|
531
671
|
end
|
672
|
+
last_key = key
|
532
673
|
end
|
533
674
|
|
534
675
|
if node.is_leaf
|
676
|
+
if branch_depth
|
677
|
+
unless branch_depth == stack.size
|
678
|
+
node.error "All leaf nodes must have same distance from root "
|
679
|
+
return false
|
680
|
+
end
|
681
|
+
else
|
682
|
+
branch_depth = stack.size
|
683
|
+
end
|
684
|
+
if node.prev_sibling.nil? && @tree.first_leaf != node
|
685
|
+
node.error "Leaf node #{node.node_address} has no previous " +
|
686
|
+
"sibling but is not the first leaf of the tree"
|
687
|
+
return false
|
688
|
+
end
|
689
|
+
if node.next_sibling.nil? && @tree.last_leaf != node
|
690
|
+
node.error "Leaf node #{node.node_address} has no next sibling " +
|
691
|
+
"but is not the last leaf of the tree"
|
692
|
+
return false
|
693
|
+
end
|
535
694
|
unless node.keys.size == node.values.size
|
536
695
|
node.error "Key count (#{node.keys.size}) and value " +
|
537
696
|
"count (#{node.values.size}) don't match"
|
538
697
|
return false
|
539
698
|
end
|
699
|
+
unless node.children.empty?
|
700
|
+
node.error "@children must be nil for a leaf node"
|
701
|
+
return false
|
702
|
+
end
|
540
703
|
else
|
541
|
-
unless node.
|
704
|
+
unless node.values.empty?
|
705
|
+
node.error "@values must be nil for a branch node"
|
706
|
+
return false
|
707
|
+
end
|
708
|
+
unless node.children.size == node.keys.size + 1
|
542
709
|
node.error "Key count (#{node.keys.size}) must be one " +
|
543
710
|
"less than children count (#{node.children.size})"
|
544
711
|
return false
|
@@ -551,10 +718,10 @@ module PEROBS
|
|
551
718
|
end
|
552
719
|
unless child.parent.is_a?(BTreeNodeLink)
|
553
720
|
node.error "Parent reference of child #{i} is of class " +
|
554
|
-
"#{child.class} instead of BTreeNodeLink"
|
721
|
+
"#{child.parent.class} instead of BTreeNodeLink"
|
555
722
|
return false
|
556
723
|
end
|
557
|
-
if child
|
724
|
+
if child == node
|
558
725
|
node.error "Child #{i} points to self"
|
559
726
|
return false
|
560
727
|
end
|
@@ -567,6 +734,22 @@ module PEROBS
|
|
567
734
|
"to this node"
|
568
735
|
return false
|
569
736
|
end
|
737
|
+
if i > 0
|
738
|
+
unless node.children[i - 1].next_sibling == child
|
739
|
+
node.error "next_sibling of node " +
|
740
|
+
"#{node.children[i - 1].node_address} " +
|
741
|
+
"must point to node #{child.node_address}"
|
742
|
+
return false
|
743
|
+
end
|
744
|
+
end
|
745
|
+
if i < node.children.length - 1
|
746
|
+
unless child == node.children[i + 1].prev_sibling
|
747
|
+
node.error "prev_sibling of node " +
|
748
|
+
"#{node.children[i + 1].node_address} " +
|
749
|
+
"must point to node #{child.node_address}"
|
750
|
+
return false
|
751
|
+
end
|
752
|
+
end
|
570
753
|
end
|
571
754
|
end
|
572
755
|
elsif position <= node.keys.size
|
@@ -580,8 +763,7 @@ module PEROBS
|
|
580
763
|
"Must be smaller than #{node.keys[index]}."
|
581
764
|
return false
|
582
765
|
end
|
583
|
-
unless node.children[position].keys.first >=
|
584
|
-
node.keys[index]
|
766
|
+
unless node.children[position].keys.first >= node.keys[index]
|
585
767
|
node.error "Child #{node.children[position].node_address} " +
|
586
768
|
"has too small key #{node.children[position].keys.first}. " +
|
587
769
|
"Must be larger than or equal to #{node.keys[index]}."
|
@@ -671,13 +853,26 @@ module PEROBS
|
|
671
853
|
s += ' ^@'
|
672
854
|
end
|
673
855
|
end
|
856
|
+
if @prev_sibling
|
857
|
+
begin
|
858
|
+
s += " <#{@prev_sibling.node_address}"
|
859
|
+
rescue
|
860
|
+
s += ' <@'
|
861
|
+
end
|
862
|
+
end
|
863
|
+
if @next_sibling
|
864
|
+
begin
|
865
|
+
s += " >#{@next_sibling.node_address}"
|
866
|
+
rescue
|
867
|
+
s += ' >@'
|
868
|
+
end
|
869
|
+
end
|
674
870
|
|
675
871
|
s
|
676
872
|
end
|
677
873
|
|
678
874
|
def error(msg)
|
679
|
-
PEROBS.log.error "Error in BTreeNode @#{@node_address}: #{msg}
|
680
|
-
@tree.to_s
|
875
|
+
PEROBS.log.error "Error in BTreeNode @#{@node_address}: #{msg}"
|
681
876
|
end
|
682
877
|
|
683
878
|
def write_node
|
@@ -685,7 +880,9 @@ module PEROBS
|
|
685
880
|
@is_leaf ? 1 : 0,
|
686
881
|
@keys.size,
|
687
882
|
@is_leaf ? @values.size : @children.size,
|
688
|
-
@parent ? @parent.node_address : 0
|
883
|
+
@parent ? @parent.node_address : 0,
|
884
|
+
@prev_sibling ? @prev_sibling.node_address : 0,
|
885
|
+
@next_sibling ? @next_sibling.node_address : 0
|
689
886
|
] + @keys + ::Array.new(@tree.order - @keys.size, 0)
|
690
887
|
|
691
888
|
if @is_leaf
|
@@ -708,24 +905,107 @@ module PEROBS
|
|
708
905
|
|
709
906
|
private
|
710
907
|
|
711
|
-
def
|
712
|
-
|
713
|
-
|
908
|
+
def min_keys
|
909
|
+
@tree.order / 2
|
910
|
+
end
|
911
|
+
|
912
|
+
def link(node)
|
913
|
+
return nil if node.nil?
|
914
|
+
|
915
|
+
if node.is_a?(BTreeNodeLink)
|
916
|
+
return node
|
917
|
+
elsif node.is_a?(BTreeNode) || node.is_a?(Integer)
|
918
|
+
return BTreeNodeLink.new(@tree, node)
|
919
|
+
else
|
920
|
+
PEROBS.log.fatal "Node link must be a BTreeNode, not a #{node.class}"
|
921
|
+
end
|
922
|
+
end
|
923
|
+
|
924
|
+
# Try to borrow an element from the preceding sibling.
|
925
|
+
# @return [True or False] True if an element was borrowed, false
|
926
|
+
# otherwise.
|
927
|
+
def borrow_from_previous_sibling(prev_node)
|
928
|
+
if prev_node.keys.length - 1 > min_keys
|
929
|
+
index = @parent.search_node_index(self) - 1
|
930
|
+
|
931
|
+
@tree.node_cache.insert(self)
|
932
|
+
@tree.node_cache.insert(prev_node.get_node)
|
933
|
+
@tree.node_cache.insert(@parent.get_node)
|
934
|
+
if @is_leaf
|
935
|
+
# Move the last key of the previous node to the front of this node
|
936
|
+
@keys.unshift(prev_node.keys.pop)
|
937
|
+
# Register the new lead key of this node with its parent
|
938
|
+
@parent.keys[index] = @keys.first
|
939
|
+
# Move the last value of the previous node to the front of this node
|
940
|
+
@values.unshift(prev_node.values.pop)
|
941
|
+
else
|
942
|
+
# For branch nodes the branch key will be the borrowed key.
|
943
|
+
@keys.unshift(@parent.keys[index])
|
944
|
+
# And the last key of the previous key will become the new branch
|
945
|
+
# key for this node.
|
946
|
+
@parent.keys[index] = prev_node.keys.pop
|
947
|
+
# Move the last child of the previous node to the front of this node
|
948
|
+
@children.unshift(node = prev_node.children.pop)
|
949
|
+
node.parent = link(self)
|
950
|
+
end
|
951
|
+
|
952
|
+
return true
|
953
|
+
end
|
954
|
+
|
955
|
+
false
|
956
|
+
end
|
957
|
+
|
958
|
+
# Try to borrow an element from the next sibling.
|
959
|
+
# @return [True or False] True if an element was borrowed, false
|
960
|
+
# otherwise.
|
961
|
+
def borrow_from_next_sibling(next_node)
|
962
|
+
if next_node.keys.length - 1 > min_keys
|
963
|
+
# The next sibling now has a new lead key that requires the branch key
|
964
|
+
# to be updated in the parent node.
|
965
|
+
index = next_node.parent.search_node_index(next_node) - 1
|
966
|
+
|
967
|
+
@tree.node_cache.insert(self)
|
968
|
+
@tree.node_cache.insert(next_node.get_node)
|
969
|
+
@tree.node_cache.insert(next_node.parent.get_node)
|
970
|
+
if @is_leaf
|
971
|
+
# Move the first key of the next node to the end of the this node
|
972
|
+
@keys << next_node.keys.shift
|
973
|
+
# Register the new lead key of next_node with its parent
|
974
|
+
next_node.parent.keys[index] = next_node.keys.first
|
975
|
+
# Move the first value of the next node to the end of this node
|
976
|
+
@values << next_node.values.shift
|
977
|
+
else
|
978
|
+
# For branch nodes we need to get the lead key from the parent of
|
979
|
+
# next_node.
|
980
|
+
@keys << next_node.parent.keys[index]
|
981
|
+
# The old lead key of next_node becomes the branch key in the parent
|
982
|
+
# of next_node. And the keys of next_node are shifted.
|
983
|
+
next_node.parent.keys[index] = next_node.keys.shift
|
984
|
+
# Move the first child of the next node to the end of this node
|
985
|
+
@children << (node = next_node.children.shift)
|
986
|
+
node.parent = link(self)
|
987
|
+
end
|
988
|
+
|
989
|
+
return true
|
990
|
+
end
|
991
|
+
|
992
|
+
false
|
993
|
+
end
|
994
|
+
|
995
|
+
def update_branch_key(old_key)
|
996
|
+
new_key = @keys.first
|
997
|
+
return unless (node = @parent)
|
714
998
|
|
715
|
-
|
716
|
-
|
717
|
-
|
999
|
+
while node
|
1000
|
+
if (index = node.keys.index(old_key))
|
1001
|
+
node.keys[index] = new_key
|
1002
|
+
@tree.node_cache.insert(node.get_node)
|
1003
|
+
return
|
1004
|
+
end
|
1005
|
+
node = node.parent
|
718
1006
|
end
|
719
|
-
# The child that corresponds to the key at parent_index has an index of
|
720
|
-
# parent_index + 1! The lower_sibling has an child index of
|
721
|
-
# parent_index and the upper sibling has a child index of parent_index +
|
722
|
-
# 2.
|
723
|
-
lower_sibling = parent_index < 1 ?
|
724
|
-
nil : @parent.children[parent_index - 1]
|
725
|
-
upper_sibling = parent_index >= (@parent.children.size - 1) ?
|
726
|
-
nil : @parent.children[parent_index + 1]
|
727
1007
|
|
728
|
-
|
1008
|
+
# The smallest element has no branch key.
|
729
1009
|
end
|
730
1010
|
|
731
1011
|
end
|