perobs 2.5.0 → 3.0.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/Array.rb +1 -1
- data/lib/perobs/BTree.rb +233 -0
- data/lib/perobs/BTreeDB.rb +2 -0
- data/lib/perobs/BTreeNode.rb +706 -0
- data/lib/perobs/BTreeNodeCache.rb +107 -0
- data/lib/perobs/BTreeNodeLink.rb +141 -0
- data/lib/perobs/EquiBlobsFile.rb +570 -0
- data/lib/perobs/FlatFile.rb +179 -78
- data/lib/perobs/FlatFileBlobHeader.rb +92 -17
- data/lib/perobs/FlatFileDB.rb +16 -7
- data/lib/perobs/LockFile.rb +181 -0
- data/lib/perobs/Object.rb +2 -1
- data/lib/perobs/SpaceTree.rb +181 -0
- data/lib/perobs/SpaceTreeNode.rb +672 -0
- data/lib/perobs/SpaceTreeNodeCache.rb +76 -0
- data/lib/perobs/SpaceTreeNodeLink.rb +103 -0
- data/lib/perobs/Store.rb +27 -13
- data/lib/perobs/version.rb +1 -1
- data/test/BTree_spec.rb +128 -0
- data/test/EquiBlobsFile_spec.rb +199 -0
- data/test/FlatFileDB_spec.rb +63 -9
- data/test/LockFile_spec.rb +133 -0
- data/test/SpaceTree_spec.rb +245 -0
- data/test/Store_spec.rb +3 -0
- data/test/spec_helper.rb +13 -0
- metadata +21 -13
- data/lib/perobs/FixedSizeBlobFile.rb +0 -193
- data/lib/perobs/FreeSpaceManager.rb +0 -204
- data/lib/perobs/IndexTree.rb +0 -145
- data/lib/perobs/IndexTreeNode.rb +0 -316
- data/test/FixedSizeBlobFile_spec.rb +0 -91
- data/test/FreeSpaceManager_spec.rb +0 -91
- data/test/IndexTree_spec.rb +0 -118
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b157aa5361418e9985f54fd0b08d6158efb83137
|
4
|
+
data.tar.gz: 0cf5c58f8ceaaa4c463d122875b969479fd04501
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 82cb0f25912d1063dcba7a6012ca0cc80f1dfff72ac7500cf19d771745faf35f09759592176c3b0a25a9cd46bbdb88874013f58d9f2b669405624cf33f582867
|
7
|
+
data.tar.gz: 2d9690e7863f4a2b049090c1094b9e838f7412ca3b0e708f111a186daf56c1f0826aa66d1aadb8b56566b485b241df74f381e27d0c3fd0b043097528a594f469
|
data/lib/perobs/Array.rb
CHANGED
@@ -102,7 +102,7 @@ module PEROBS
|
|
102
102
|
end
|
103
103
|
|
104
104
|
# This method should only be used during store repair operations. It will
|
105
|
-
# delete all
|
105
|
+
# delete all references to the given object ID.
|
106
106
|
# @param id [Fixnum/Bignum] targeted object ID
|
107
107
|
def _delete_reference_to_id(id)
|
108
108
|
@data.delete_if do |v|
|
data/lib/perobs/BTree.rb
ADDED
@@ -0,0 +1,233 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# = BTreeNode.rb -- Persistent Ruby Object Store
|
4
|
+
#
|
5
|
+
# Copyright (c) 2016, 2017 by Chris Schlaeger <chris@taskjuggler.org>
|
6
|
+
#
|
7
|
+
# MIT License
|
8
|
+
#
|
9
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
10
|
+
# a copy of this software and associated documentation files (the
|
11
|
+
# "Software"), to deal in the Software without restriction, including
|
12
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
13
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
14
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
15
|
+
# the following conditions:
|
16
|
+
#
|
17
|
+
# The above copyright notice and this permission notice shall be
|
18
|
+
# included in all copies or substantial portions of the Software.
|
19
|
+
#
|
20
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
21
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
22
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
23
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
24
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
25
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
26
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
27
|
+
|
28
|
+
require 'perobs/LockFile'
|
29
|
+
require 'perobs/EquiBlobsFile'
|
30
|
+
require 'perobs/BTreeNodeCache'
|
31
|
+
require 'perobs/BTreeNode'
|
32
|
+
|
33
|
+
module PEROBS
|
34
|
+
|
35
|
+
# This BTree class is very similar to a classic BTree implementation. It
|
36
|
+
# manages a tree that is always balanced. The BTree is stored in the
|
37
|
+
# specified directory and partially kept in memory to speed up operations.
|
38
|
+
# The order of the tree specifies how many keys each node will be able to
|
39
|
+
# hold. Leaf nodes will have a value associated with each key. Branch nodes
|
40
|
+
# have N + 1 references to child nodes instead.
|
41
|
+
class BTree
|
42
|
+
|
43
|
+
attr_reader :order, :nodes
|
44
|
+
|
45
|
+
# Create a new BTree object.
|
46
|
+
# @param dir [String] Directory to store the tree file
|
47
|
+
# @param name [String] Base name of the BTree related files in 'dir'
|
48
|
+
# @param order [Integer] The maximum number of keys per node. This number
|
49
|
+
# must be odd and larger than 2 and smaller than 2**16 - 1.
|
50
|
+
def initialize(dir, name, order)
|
51
|
+
@dir = dir
|
52
|
+
@name = name
|
53
|
+
unless order > 2
|
54
|
+
PEROBS.log.fatal "BTree order must be larger than 2, not #{order}"
|
55
|
+
end
|
56
|
+
unless order % 2 == 1
|
57
|
+
PEROBS.log.fatal "BTree order must be an uneven number, not #{order}"
|
58
|
+
end
|
59
|
+
unless order < 2 ** 16 - 1
|
60
|
+
PEROBS.log.fatal "BTree order must be smaller than #{2**16 - 1}"
|
61
|
+
end
|
62
|
+
@order = order
|
63
|
+
|
64
|
+
# This EquiBlobsFile contains the nodes of the BTree.
|
65
|
+
@nodes = EquiBlobsFile.new(@dir, @name,
|
66
|
+
BTreeNode::node_bytes(@order))
|
67
|
+
@node_cache = BTreeNodeCache.new
|
68
|
+
|
69
|
+
# This BTree implementation uses a write cache to improve write
|
70
|
+
# performance of multiple successive read/write operations. This also
|
71
|
+
# means that data may not be written on the backing store until the
|
72
|
+
# sync() or close() methods have been called. A bug in the program or a
|
73
|
+
# premature program termination can lead to data loss. To detect such
|
74
|
+
# situations, we use a lock file whenever there are pending writes.
|
75
|
+
@is_dirty = false
|
76
|
+
@dirty_flag = LockFile.new(File.join(@dir, name + '.dirty'),
|
77
|
+
{ :timeout_secs => 0 })
|
78
|
+
end
|
79
|
+
|
80
|
+
# Open the tree file.
|
81
|
+
def open(file_must_exist = false)
|
82
|
+
if @dirty_flag.is_locked?
|
83
|
+
PEROBS.log.fatal "Index file #{@nodes.file_name} is already " +
|
84
|
+
"locked"
|
85
|
+
end
|
86
|
+
if file_must_exist && !@nodes.file_exist?
|
87
|
+
PEROBS.log.fatal "Index file #{@nodes.file_name} does not exist"
|
88
|
+
end
|
89
|
+
|
90
|
+
@node_cache.clear
|
91
|
+
@nodes.open
|
92
|
+
set_root(new_node(nil, @nodes.total_entries == 0 ?
|
93
|
+
nil : @nodes.first_entry))
|
94
|
+
end
|
95
|
+
|
96
|
+
# Close the tree file.
|
97
|
+
def close
|
98
|
+
sync
|
99
|
+
@nodes.close
|
100
|
+
@root = nil
|
101
|
+
end
|
102
|
+
|
103
|
+
# Clear all pools and forget any registered spaces.
|
104
|
+
def clear
|
105
|
+
@node_cache.clear
|
106
|
+
@nodes.clear
|
107
|
+
set_root(new_node(nil))
|
108
|
+
end
|
109
|
+
|
110
|
+
# Erase the backing store of the BTree. This method should only be called
|
111
|
+
# when not having the BTree open. And it obviously and permanently erases
|
112
|
+
# all stored data from the BTree.
|
113
|
+
def erase
|
114
|
+
@nodes.erase
|
115
|
+
@dirty_flag.forced_unlock
|
116
|
+
end
|
117
|
+
|
118
|
+
# Flush all pending modifications into the tree file.
|
119
|
+
def sync
|
120
|
+
@node_cache.flush(true)
|
121
|
+
@nodes.sync
|
122
|
+
@dirty_flag.unlock if @dirty_flag.is_locked?
|
123
|
+
end
|
124
|
+
|
125
|
+
# Check if the tree file contains any errors.
|
126
|
+
# @return [Boolean] true if no erros were found, false otherwise
|
127
|
+
def check(&block)
|
128
|
+
@root.check(&block)
|
129
|
+
end
|
130
|
+
|
131
|
+
# Register a new node as root node of the tree.
|
132
|
+
def set_root(node)
|
133
|
+
@root = node
|
134
|
+
@nodes.first_entry = node.node_address
|
135
|
+
@node_cache.set_root(node)
|
136
|
+
end
|
137
|
+
|
138
|
+
# Insert a new value into the tree using the key as a unique index. If the
|
139
|
+
# key already exists the old value will be overwritten.
|
140
|
+
# @param key [Integer] Unique key
|
141
|
+
# @param value [Integer] value
|
142
|
+
def insert(key, value)
|
143
|
+
@root.insert(key, value)
|
144
|
+
@node_cache.flush
|
145
|
+
end
|
146
|
+
|
147
|
+
# Retrieve the value associated with the given key. If no entry was found,
|
148
|
+
# return nil.
|
149
|
+
# @param key [Integer] Unique key
|
150
|
+
# @return [Integer or nil] found value or nil
|
151
|
+
def get(key)
|
152
|
+
@root.get(key)
|
153
|
+
end
|
154
|
+
|
155
|
+
# Find and remove the value associated with the given key. If no entry was
|
156
|
+
# found, return nil, otherwise the found value.
|
157
|
+
def remove(key)
|
158
|
+
removed_value = @root.remove(key)
|
159
|
+
|
160
|
+
# Check if the root node only contains one child link after the delete
|
161
|
+
# operation. Then we can delete that node and pull the tree one level
|
162
|
+
# up. This could happen for a sequence of nodes that all got merged to
|
163
|
+
# single child nodes.
|
164
|
+
while !@root.is_leaf && @root.children.size == 1
|
165
|
+
old_root = @root
|
166
|
+
set_root(@root.children.first)
|
167
|
+
@root.parent = nil
|
168
|
+
delete_node(old_root.node_address)
|
169
|
+
end
|
170
|
+
|
171
|
+
@node_cache.flush
|
172
|
+
removed_value
|
173
|
+
end
|
174
|
+
|
175
|
+
# Iterate over all key/value pairs that are stored in the tree.
|
176
|
+
# @yield [key, value]
|
177
|
+
def each(&block)
|
178
|
+
@root.each(&block)
|
179
|
+
end
|
180
|
+
|
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
|
+
# Delete the node at the given address in the BTree file.
|
193
|
+
# @param address [Integer] address in file
|
194
|
+
def delete_node(address)
|
195
|
+
@node_cache.delete(address)
|
196
|
+
@nodes.delete_blob(address)
|
197
|
+
end
|
198
|
+
|
199
|
+
# @return [String] Human reable form of the tree.
|
200
|
+
def to_s
|
201
|
+
@root.to_s
|
202
|
+
end
|
203
|
+
|
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
|
+
end
|
231
|
+
|
232
|
+
end
|
233
|
+
|
data/lib/perobs/BTreeDB.rb
CHANGED
@@ -182,8 +182,10 @@ module PEROBS
|
|
182
182
|
# Basic consistency check.
|
183
183
|
# @param repair [TrueClass/FalseClass] True if found errors should be
|
184
184
|
# repaired.
|
185
|
+
# @return number of errors found
|
185
186
|
def check_db(repair = false)
|
186
187
|
each_blob { |blob| blob.check(repair) }
|
188
|
+
0
|
187
189
|
end
|
188
190
|
|
189
191
|
# Check if the stored object is syntactically correct.
|