perobs 3.0.2 → 4.0.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 +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
|