perobs 3.0.2 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +18 -17
- data/lib/perobs/BTree.rb +9 -44
- data/lib/perobs/BTreeNode.rb +116 -88
- data/lib/perobs/BTreeNodeCache.rb +10 -8
- data/lib/perobs/BTreeNodeLink.rb +1 -1
- data/lib/perobs/Cache.rb +14 -14
- data/lib/perobs/DynamoDB.rb +1 -1
- data/lib/perobs/EquiBlobsFile.rb +7 -2
- data/lib/perobs/FlatFile.rb +28 -49
- data/lib/perobs/FlatFileBlobHeader.rb +1 -19
- data/lib/perobs/FlatFileDB.rb +5 -0
- data/lib/perobs/LockFile.rb +3 -0
- data/lib/perobs/Object.rb +8 -3
- data/lib/perobs/ObjectBase.rb +6 -4
- data/lib/perobs/PersistentObjectCache.rb +153 -0
- data/lib/perobs/PersistentObjectCacheLine.rb +87 -0
- data/lib/perobs/SpaceTree.rb +5 -3
- data/lib/perobs/SpaceTreeNode.rb +15 -8
- data/lib/perobs/Store.rb +41 -13
- data/lib/perobs/version.rb +1 -1
- data/test/Array_spec.rb +38 -38
- data/test/BTree_spec.rb +45 -0
- data/test/EquiBlobsFile_spec.rb +0 -4
- data/test/FlatFileDB_spec.rb +1 -1
- data/test/Hash_spec.rb +14 -13
- data/test/Object_spec.rb +5 -5
- data/test/Store_spec.rb +62 -19
- data/test/perobs_spec.rb +7 -3
- metadata +4 -3
- data/lib/perobs/SpaceTreeNodeCache.rb +0 -149
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a988f009b1b97bc8da2b2bcfeb65399284a07032
|
4
|
+
data.tar.gz: 4346d5cdbec5b4154741f0a41d3f526985803912
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 639673c10faa5082c742258ee9848f5e951c6d1ec7fc9f03ec7066260ea5042b6c8b90020df6fa506b8be54cc5fd9793a0939f80ef1da726c27832c8a2eb8c85
|
7
|
+
data.tar.gz: 92e63145a1fc9b6e76dbdf6f4cdebfb5a8935bfcbd93a9634e22c12f874b945b656a17fe93aa03a7a3189a00371d2e2abd8d411862ad0ad51b0c364f7b7dc027
|
data/README.md
CHANGED
@@ -30,7 +30,7 @@ classes:
|
|
30
30
|
|
31
31
|
When you derive your own class from PEROBS::Object you need to
|
32
32
|
specify which instance variables should be persistent. By using
|
33
|
-
|
33
|
+
'attr_persist' you can provide a list of symbols that declare the instance
|
34
34
|
variables to persist. This will also create getter and setter methods
|
35
35
|
for these instance varables. You can set default values in the
|
36
36
|
constructor . The constructor of PEROBS::ObjectBase derived objects
|
@@ -61,13 +61,14 @@ almost every Ruby data type. YAML is much slower than JSON and Marshal
|
|
61
61
|
is not guaranteed to be compatible between Ruby versions.
|
62
62
|
|
63
63
|
Once you have created a store you can assign objects to it. All
|
64
|
-
persistent objects must be created with Store.new().
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
64
|
+
persistent objects must be created with Store.new(). The Store object
|
65
|
+
is available via the @store instance variable provided by the parent
|
66
|
+
class. This is necessary as you will only deal with proxy objects in
|
67
|
+
your code. Except for the member methods, you will never deal with
|
68
|
+
the objects directly. Instead Store.new() returns a POXReference
|
69
|
+
object that acts as a transparent proxy. This proxy is needed as your
|
70
|
+
code never knows if the actual object is really loaded into the memory
|
71
|
+
or not. PEROBS will handle this transparently for you.
|
71
72
|
|
72
73
|
A build-in cache keeps access latencies to recently used objects low
|
73
74
|
and lazily flushes modified objects into the persistend back-end when
|
@@ -92,19 +93,19 @@ require 'perobs'
|
|
92
93
|
|
93
94
|
class Person < PEROBS::Object
|
94
95
|
|
95
|
-
|
96
|
+
attr_persist :name, :mother, :father, :kids, :spouse, :status
|
96
97
|
|
97
98
|
def initialize(p, name)
|
98
99
|
super(p)
|
99
|
-
|
100
|
-
|
101
|
-
|
100
|
+
self.name = name
|
101
|
+
self.kids = @store.new(PEROBS::Array)
|
102
|
+
self.status = :single
|
102
103
|
end
|
103
104
|
|
104
105
|
def restore
|
105
106
|
# Use block version of attr_init() to avoid creating unneded
|
106
107
|
# objects. The block is only called when @father doesn't exist yet.
|
107
|
-
attr_init(:father) do { store.new(Person, 'Dad') }
|
108
|
+
attr_init(:father) do { @store.new(Person, 'Dad') }
|
108
109
|
end
|
109
110
|
|
110
111
|
def merry(spouse)
|
@@ -136,7 +137,7 @@ contains the 3 Person objects.
|
|
136
137
|
### Accessing persistent instance variables
|
137
138
|
|
138
139
|
All instance variables that should be persisted must be declared with
|
139
|
-
'
|
140
|
+
'attr_persist'. This will create the instance variable, a getter and setter
|
140
141
|
method for it. These getter and setter methods are the recommended way
|
141
142
|
to access instance variables both from ouside of the instances as well
|
142
143
|
as from within. To access the setter or getter method from within an
|
@@ -166,8 +167,8 @@ object to another object.
|
|
166
167
|
### Caveats and known issues
|
167
168
|
|
168
169
|
PEROBS is currently not thread-safe. You cannot simultaneously access
|
169
|
-
the database from multiple application.
|
170
|
-
|
170
|
+
the database from multiple application. The library uses locks to
|
171
|
+
ensure that only one Store object is accessing the database at a time.
|
171
172
|
|
172
173
|
## Installation
|
173
174
|
|
@@ -187,7 +188,7 @@ Or install it yourself as:
|
|
187
188
|
|
188
189
|
## Copyright and License
|
189
190
|
|
190
|
-
Copyright (c) 2015, 2016 by Chris Schlaeger <chris@taskjuggler.org>
|
191
|
+
Copyright (c) 2015, 2016, 2017 by Chris Schlaeger <chris@taskjuggler.org>
|
191
192
|
|
192
193
|
PEROBS and all accompanying files are licensed under this MIT License
|
193
194
|
|
data/lib/perobs/BTree.rb
CHANGED
@@ -27,7 +27,7 @@
|
|
27
27
|
|
28
28
|
require 'perobs/LockFile'
|
29
29
|
require 'perobs/EquiBlobsFile'
|
30
|
-
require 'perobs/
|
30
|
+
require 'perobs/PersistentObjectCache'
|
31
31
|
require 'perobs/BTreeNode'
|
32
32
|
|
33
33
|
module PEROBS
|
@@ -40,7 +40,7 @@ module PEROBS
|
|
40
40
|
# have N + 1 references to child nodes instead.
|
41
41
|
class BTree
|
42
42
|
|
43
|
-
attr_reader :order, :nodes
|
43
|
+
attr_reader :order, :nodes, :node_cache
|
44
44
|
|
45
45
|
# Create a new BTree object.
|
46
46
|
# @param dir [String] Directory to store the tree file
|
@@ -64,7 +64,7 @@ module PEROBS
|
|
64
64
|
# This EquiBlobsFile contains the nodes of the BTree.
|
65
65
|
@nodes = EquiBlobsFile.new(@dir, @name,
|
66
66
|
BTreeNode::node_bytes(@order))
|
67
|
-
@node_cache =
|
67
|
+
@node_cache = PersistentObjectCache.new(512, BTreeNode, self)
|
68
68
|
|
69
69
|
# This BTree implementation uses a write cache to improve write
|
70
70
|
# performance of multiple successive read/write operations. This also
|
@@ -89,8 +89,10 @@ module PEROBS
|
|
89
89
|
|
90
90
|
@node_cache.clear
|
91
91
|
@nodes.open
|
92
|
-
|
93
|
-
|
92
|
+
node = @nodes.total_entries == 0 ?
|
93
|
+
BTreeNode::create(self) :
|
94
|
+
BTreeNode::load(self, @nodes.first_entry)
|
95
|
+
set_root(node)
|
94
96
|
end
|
95
97
|
|
96
98
|
# Close the tree file.
|
@@ -104,7 +106,7 @@ module PEROBS
|
|
104
106
|
def clear
|
105
107
|
@node_cache.clear
|
106
108
|
@nodes.clear
|
107
|
-
set_root(
|
109
|
+
set_root(BTreeNode::create(self))
|
108
110
|
end
|
109
111
|
|
110
112
|
# Erase the backing store of the BTree. This method should only be called
|
@@ -112,6 +114,7 @@ module PEROBS
|
|
112
114
|
# all stored data from the BTree.
|
113
115
|
def erase
|
114
116
|
@nodes.erase
|
117
|
+
@root = nil
|
115
118
|
@dirty_flag.forced_unlock
|
116
119
|
end
|
117
120
|
|
@@ -132,7 +135,6 @@ module PEROBS
|
|
132
135
|
def set_root(node)
|
133
136
|
@root = node
|
134
137
|
@nodes.first_entry = node.node_address
|
135
|
-
@node_cache.set_root(node)
|
136
138
|
end
|
137
139
|
|
138
140
|
# Insert a new value into the tree using the key as a unique index. If the
|
@@ -178,17 +180,6 @@ module PEROBS
|
|
178
180
|
@root.each(&block)
|
179
181
|
end
|
180
182
|
|
181
|
-
# Mark the given node as being modified. This will cause the dirty_flag
|
182
|
-
# lock to be taken and the @is_dirty flag to be set.
|
183
|
-
# @param node [BTreeNode] node to mark
|
184
|
-
def mark_node_as_modified(node)
|
185
|
-
unless @is_dirty
|
186
|
-
@dirty_flag.lock
|
187
|
-
@is_dirty = true
|
188
|
-
end
|
189
|
-
@node_cache.mark_as_modified(node)
|
190
|
-
end
|
191
|
-
|
192
183
|
# Delete the node at the given address in the BTree file.
|
193
184
|
# @param address [Integer] address in file
|
194
185
|
def delete_node(address)
|
@@ -201,32 +192,6 @@ module PEROBS
|
|
201
192
|
@root.to_s
|
202
193
|
end
|
203
194
|
|
204
|
-
# Create a new BTreeNode. If the node_address is not nil, the node data is
|
205
|
-
# read from the backing store. The parent and is_leaf arguments are
|
206
|
-
# ignored in this case.
|
207
|
-
# @param parent [BTreeNode] parent node
|
208
|
-
# @param node_address [Integer or nil] address of the node to create
|
209
|
-
# @param is_leaf[Boolean] True if node is a leaf node, false otherweise
|
210
|
-
def new_node(parent, node_address = nil, is_leaf = true)
|
211
|
-
node = BTreeNode.new(self, parent, node_address, is_leaf)
|
212
|
-
@node_cache.insert(node)
|
213
|
-
|
214
|
-
node
|
215
|
-
end
|
216
|
-
|
217
|
-
# Return the BTreeNode that matches the given node address. If a blob
|
218
|
-
# address and size are given, a new node is created instead of being read
|
219
|
-
# from the file.
|
220
|
-
# @param node_address [Integer] Address of the node in the BTree file
|
221
|
-
# @return [BTreeNode]
|
222
|
-
def get_node(node_address)
|
223
|
-
if (node = @node_cache[node_address])
|
224
|
-
return node
|
225
|
-
end
|
226
|
-
|
227
|
-
new_node(nil, node_address)
|
228
|
-
end
|
229
|
-
|
230
195
|
end
|
231
196
|
|
232
197
|
end
|
data/lib/perobs/BTreeNode.rb
CHANGED
@@ -40,7 +40,6 @@ module PEROBS
|
|
40
40
|
class BTreeNode
|
41
41
|
|
42
42
|
attr_reader :node_address, :parent, :is_leaf, :keys, :values, :children
|
43
|
-
attr_accessor :dirty
|
44
43
|
|
45
44
|
# Create a new BTreeNode object for the given tree with the given parent
|
46
45
|
# or recreate the node with the given node_address from the backing store.
|
@@ -53,44 +52,118 @@ module PEROBS
|
|
53
52
|
# backing store
|
54
53
|
# @param is_leaf [Boolean] true if the node should be a leaf node, false
|
55
54
|
# if not
|
56
|
-
def initialize(tree,
|
55
|
+
def initialize(tree, node_address = nil, parent = nil, is_leaf = true,
|
56
|
+
keys = [], values = [], children = [])
|
57
57
|
@tree = tree
|
58
|
-
@parent = nil
|
59
58
|
if node_address == 0
|
60
59
|
PEROBS.log.fatal "Node address may not be 0"
|
61
60
|
end
|
62
61
|
@node_address = node_address
|
63
|
-
@
|
62
|
+
@parent = parent ? BTreeNodeLink.new(tree, parent) : nil
|
63
|
+
@keys = keys
|
64
64
|
if (@is_leaf = is_leaf)
|
65
|
-
@values =
|
66
|
-
else
|
65
|
+
@values = values
|
67
66
|
@children = []
|
67
|
+
else
|
68
|
+
@children = children
|
69
|
+
@values = []
|
68
70
|
end
|
69
71
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
72
|
+
ObjectSpace.define_finalizer(
|
73
|
+
self, BTreeNode._finalize(@tree, @node_address, object_id))
|
74
|
+
@tree.node_cache.insert(self, false)
|
75
|
+
end
|
74
76
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
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
|
+
end
|
84
|
+
|
85
|
+
# Create a new SpaceTreeNode. This method should be used for the creation
|
86
|
+
# of new nodes instead of calling the constructor directly.
|
87
|
+
# @param tree [BTree] The tree the new node should belong to
|
88
|
+
# @param parent [BTreeNode] The parent node
|
89
|
+
# @param is_leaf [Boolean] True if the node has no children, false
|
90
|
+
# otherwise
|
91
|
+
def BTreeNode::create(tree, parent = nil, is_leaf = true)
|
92
|
+
unless parent.nil? || parent.is_a?(BTreeNode) ||
|
93
|
+
parent.is_a?(BTreeNodeLink)
|
94
|
+
PEROBS.log.fatal "Parent node must be a BTreeNode but is of class " +
|
95
|
+
"#{parent.class}"
|
96
|
+
end
|
97
|
+
|
98
|
+
address = tree.nodes.free_address
|
99
|
+
node = BTreeNode.new(tree, address, parent, is_leaf)
|
100
|
+
# This is a new node. Make sure the data is written to the file.
|
101
|
+
tree.node_cache.insert(node)
|
102
|
+
|
103
|
+
node
|
104
|
+
end
|
105
|
+
|
106
|
+
# Restore a node from the backing store at the given address and tree.
|
107
|
+
# @param tree [BTree] The tree the node belongs to
|
108
|
+
# @param node_address [Integer] The address in the blob file.
|
109
|
+
def BTreeNode::load(tree, address)
|
110
|
+
unless address.is_a?(Integer)
|
111
|
+
PEROBS.log.fatal "address is not Integer: #{address.class}"
|
112
|
+
end
|
113
|
+
|
114
|
+
unless (bytes = tree.nodes.retrieve_blob(address))
|
115
|
+
PEROBS.log.fatal "SpaceTree node at address #{address} " +
|
116
|
+
"does not exist"
|
117
|
+
end
|
118
|
+
|
119
|
+
unless Zlib::crc32(bytes) != 0
|
120
|
+
PEROBS.log.fatal "Checksum failure in BTreeNode entry @#{address}"
|
121
|
+
end
|
122
|
+
ary = bytes.unpack(BTreeNode::node_bytes_format(tree))
|
123
|
+
# Read is_leaf
|
124
|
+
if ary[0] != 0 && ary[0] != 1
|
125
|
+
PEROBS.log.fatal "First byte of a BTreeNode entry must be 0 or 1"
|
126
|
+
end
|
127
|
+
is_leaf = ary[0] == 0 ? false : true
|
128
|
+
# This is the number of keys this node has.
|
129
|
+
key_count = ary[1]
|
130
|
+
data_count = ary[2]
|
131
|
+
# Read the parent node address
|
132
|
+
parent = ary[3] == 0 ? nil : BTreeNodeLink.new(tree, ary[3])
|
133
|
+
# Read the keys
|
134
|
+
keys = ary[4, key_count]
|
135
|
+
|
136
|
+
children = nil
|
137
|
+
values = nil
|
138
|
+
if is_leaf
|
139
|
+
# Read the values
|
140
|
+
values = ary[4 + tree.order, data_count]
|
81
141
|
else
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
142
|
+
# Read the child addresses
|
143
|
+
children = []
|
144
|
+
data_count.times do |i|
|
145
|
+
child_address = ary[4 + tree.order + i]
|
146
|
+
unless child_address > 0
|
147
|
+
PEROBS.log.fatal "Child address must be larger than 0"
|
148
|
+
end
|
149
|
+
children << BTreeNodeLink.new(tree, child_address)
|
86
150
|
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
151
|
end
|
152
|
+
|
153
|
+
node = BTreeNode.new(tree, address, parent, is_leaf, keys, values,
|
154
|
+
children)
|
155
|
+
tree.node_cache.insert(node, false)
|
156
|
+
|
157
|
+
node
|
158
|
+
end
|
159
|
+
|
160
|
+
# @return [String] The format used for String.pack.
|
161
|
+
def BTreeNode::node_bytes_format(tree)
|
162
|
+
# This does not include the 4 bytes for the CRC32 checksum
|
163
|
+
"CSSQQ#{tree.order}Q#{tree.order + 1}"
|
92
164
|
end
|
93
165
|
|
166
|
+
# @return [Integer] The number of bytes needed to store a node.
|
94
167
|
def BTreeNode::node_bytes(order)
|
95
168
|
1 + # is_leaf
|
96
169
|
2 + # actual key count
|
@@ -101,6 +174,16 @@ module PEROBS
|
|
101
174
|
4 # CRC32 checksum
|
102
175
|
end
|
103
176
|
|
177
|
+
# Save the node into the blob file.
|
178
|
+
def save
|
179
|
+
write_node
|
180
|
+
end
|
181
|
+
|
182
|
+
# The node address uniquely identifies a BTreeNode.
|
183
|
+
def uid
|
184
|
+
@node_address
|
185
|
+
end
|
186
|
+
|
104
187
|
# Insert or replace the given value by using the key as unique address.
|
105
188
|
# @param key [Integer] Unique key to retrieve the value
|
106
189
|
# @param value [Integer] value to insert
|
@@ -186,14 +269,14 @@ module PEROBS
|
|
186
269
|
def split_node
|
187
270
|
unless @parent
|
188
271
|
# The node is the root node. We need to create a parent node first.
|
189
|
-
self.parent = @tree
|
272
|
+
self.parent = BTreeNode::create(@tree, nil, false)
|
190
273
|
@parent.set_child(0, self)
|
191
274
|
@tree.set_root(@parent)
|
192
275
|
end
|
193
276
|
|
194
277
|
# Create the new sibling that will take the 2nd half of the
|
195
278
|
# node content.
|
196
|
-
sibling = @tree
|
279
|
+
sibling = BTreeNode::create(@tree, @parent, @is_leaf)
|
197
280
|
# Determine the index of the middle element that gets moved to the
|
198
281
|
# parent. The order must be an uneven number, so adding 1 will get us
|
199
282
|
# the middle element.
|
@@ -229,7 +312,6 @@ module PEROBS
|
|
229
312
|
PEROBS.log.fatal "Cannot insert into a full BTreeNode"
|
230
313
|
end
|
231
314
|
|
232
|
-
mark_as_modified
|
233
315
|
i = search_key_index(key)
|
234
316
|
if @keys[i] == key
|
235
317
|
# Overwrite existing entries
|
@@ -248,6 +330,7 @@ module PEROBS
|
|
248
330
|
@children.insert(i + 1, BTreeNodeLink.new(@tree, value_or_child))
|
249
331
|
end
|
250
332
|
end
|
333
|
+
@tree.node_cache.insert(self)
|
251
334
|
end
|
252
335
|
|
253
336
|
# Remove the element at the given index.
|
@@ -256,7 +339,6 @@ module PEROBS
|
|
256
339
|
first_key = @keys[0]
|
257
340
|
removed_value = nil
|
258
341
|
|
259
|
-
mark_as_modified
|
260
342
|
# Delete the key at the specified index.
|
261
343
|
unless @keys.delete_at(index)
|
262
344
|
PEROBS.log.fatal "Could not remove element #{index} from BTreeNode " +
|
@@ -269,6 +351,7 @@ module PEROBS
|
|
269
351
|
# The corresponding child has can be found at 1 index higher.
|
270
352
|
@children.delete_at(index + 1)
|
271
353
|
end
|
354
|
+
@tree.node_cache.insert(self)
|
272
355
|
|
273
356
|
# Find the lower and upper siblings and the index of the key for this
|
274
357
|
# node in the parent node.
|
@@ -301,7 +384,6 @@ module PEROBS
|
|
301
384
|
end
|
302
385
|
|
303
386
|
dest_node.keys[dst_idx, count] = @keys[src_idx, count]
|
304
|
-
dest_node.dirty = true
|
305
387
|
if @is_leaf
|
306
388
|
# For leaves we copy the keys and corresponding values.
|
307
389
|
dest_node.values[dst_idx, count] = @values[src_idx, count]
|
@@ -313,11 +395,12 @@ module PEROBS
|
|
313
395
|
dest_node.set_child(dst_idx + i, @children[src_idx + i])
|
314
396
|
end
|
315
397
|
end
|
398
|
+
@tree.node_cache.insert(dest_node)
|
316
399
|
end
|
317
400
|
|
318
401
|
def parent=(p)
|
319
402
|
@parent = p ? BTreeNodeLink.new(@tree, p) : nil
|
320
|
-
|
403
|
+
@tree.node_cache.insert(self)
|
321
404
|
end
|
322
405
|
|
323
406
|
def set_child(index, child)
|
@@ -327,17 +410,17 @@ module PEROBS
|
|
327
410
|
else
|
328
411
|
@children[index] = nil
|
329
412
|
end
|
330
|
-
|
413
|
+
@tree.node_cache.insert(self)
|
331
414
|
end
|
332
415
|
|
333
416
|
def trim(idx)
|
334
|
-
mark_as_modified
|
335
417
|
@keys = @keys[0..idx - 1]
|
336
418
|
if @is_leaf
|
337
419
|
@values = @values[0..idx - 1]
|
338
420
|
else
|
339
421
|
@children = @children[0..idx]
|
340
422
|
end
|
423
|
+
@tree.node_cache.insert(self)
|
341
424
|
end
|
342
425
|
|
343
426
|
# Search the keys of the node that fits the given key. The result is
|
@@ -598,8 +681,6 @@ module PEROBS
|
|
598
681
|
end
|
599
682
|
|
600
683
|
def write_node
|
601
|
-
return unless @dirty
|
602
|
-
|
603
684
|
ary = [
|
604
685
|
@is_leaf ? 1 : 0,
|
605
686
|
@keys.size,
|
@@ -620,66 +701,13 @@ module PEROBS
|
|
620
701
|
ary += @children.map{ |c| c.node_address } +
|
621
702
|
::Array.new(@tree.order + 1 - @children.size, 0)
|
622
703
|
end
|
623
|
-
bytes = ary.pack(node_bytes_format)
|
704
|
+
bytes = ary.pack(BTreeNode::node_bytes_format(@tree))
|
624
705
|
bytes += [ Zlib::crc32(bytes) ].pack('L')
|
625
706
|
@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
707
|
end
|
633
708
|
|
634
709
|
private
|
635
710
|
|
636
|
-
def read_node
|
637
|
-
return false unless (bytes = @tree.nodes.retrieve_blob(@node_address))
|
638
|
-
|
639
|
-
unless Zlib::crc32(bytes) != 0
|
640
|
-
PEROBS.log.error "Checksum failure in BTreeNode entry @#{node_address}"
|
641
|
-
return false
|
642
|
-
end
|
643
|
-
ary = bytes.unpack(node_bytes_format)
|
644
|
-
# Read is_leaf
|
645
|
-
if ary[0] != 0 && ary[0] != 1
|
646
|
-
PEROBS.log.error "First byte of a BTreeNode entry must be 0 or 1"
|
647
|
-
return false
|
648
|
-
end
|
649
|
-
@is_leaf = ary[0] == 0 ? false : true
|
650
|
-
# This is the number of keys this node has.
|
651
|
-
key_count = ary[1]
|
652
|
-
data_count = ary[2]
|
653
|
-
# Read the parent node address
|
654
|
-
@parent = ary[3] == 0 ? nil : BTreeNodeLink.new(@tree, ary[3])
|
655
|
-
# Read the keys
|
656
|
-
@keys = ary[4, key_count]
|
657
|
-
|
658
|
-
if @is_leaf
|
659
|
-
# Read the values
|
660
|
-
@values = ary[4 + @tree.order, data_count]
|
661
|
-
else
|
662
|
-
# Read the child addresses
|
663
|
-
@children = []
|
664
|
-
data_count.times do |i|
|
665
|
-
address = ary[4 + @tree.order + i]
|
666
|
-
unless address > 0
|
667
|
-
PEROBS.log.fatal "Child address must not be 0"
|
668
|
-
end
|
669
|
-
@children << BTreeNodeLink.new(@tree, address)
|
670
|
-
end
|
671
|
-
end
|
672
|
-
|
673
|
-
@dirty = false
|
674
|
-
|
675
|
-
true
|
676
|
-
end
|
677
|
-
|
678
|
-
def node_bytes_format
|
679
|
-
# This does not include the 4 bytes for the CRC32 checksum
|
680
|
-
"CSSQQ#{@tree.order}Q#{@tree.order + 1}"
|
681
|
-
end
|
682
|
-
|
683
711
|
def find_closest_siblings(key)
|
684
712
|
# The root node has no siblings.
|
685
713
|
return [ nil, nil, nil ] unless @parent
|