perobs 4.5.0 → 4.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,5 @@
1
- # encoding: UTF-8
2
- #
1
+ # frozen_string_literal: true
2
+
3
3
  # = BigTreeNode.rb -- Persistent Ruby Object Store
4
4
  #
5
5
  # Copyright (c) 2016, 2017 by Chris Schlaeger <chris@taskjuggler.org>
@@ -25,11 +25,10 @@
25
25
  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26
26
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27
27
 
28
- require 'perobs/Object'
29
- require 'perobs/Array'
28
+ require_relative 'Object'
29
+ require_relative 'Array'
30
30
 
31
31
  module PEROBS
32
-
33
32
  # The BigTreeNode class provides the BTree nodes for the BigTree objects.
34
33
  # A node can either be a branch node or a leaf node. Branch nodes don't
35
34
  # store values, only references to child nodes. Leaf nodes don't have child
@@ -38,9 +37,8 @@ module PEROBS
38
37
  # associated with a value or determines the lower key boundary for the
39
38
  # following child node.
40
39
  class BigTreeNode < PEROBS::Object
41
-
42
40
  attr_persist :tree, :parent, :keys, :values, :children,
43
- :prev_sibling, :next_sibling
41
+ :prev_sibling, :next_sibling
44
42
 
45
43
  # Internal constructor. Use Store.new(BigTreeNode, ...) instead.
46
44
  # @param p [Handle]
@@ -94,23 +92,19 @@ module PEROBS
94
92
  node = myself
95
93
 
96
94
  # Traverse the tree to find the right node to add or replace the value.
97
- while node do
95
+ while node
98
96
  # All nodes that we find on the way that are full will be split into
99
97
  # two half-full nodes.
100
- if node.keys.size >= @tree.node_size
101
- node = node.split_node
102
- end
98
+ node = node.split_node if node.keys.size >= @tree.node_size
103
99
 
104
100
  # Once we have reached a leaf node we can insert or replace the value.
105
- if node.is_leaf?
106
- return node.insert_element(key, value)
107
- else
108
- # Descend into the right child node to add the value to.
109
- node = node.children[node.search_key_index(key)]
110
- end
101
+ return node.insert_element(key, value) if node.is_leaf?
102
+
103
+ # Descend into the right child node to add the value to.
104
+ node = node.children[node.search_key_index(key)]
111
105
  end
112
106
 
113
- PEROBS.log.fatal "Could not find proper node to insert into"
107
+ PEROBS.log.fatal 'Could not find proper node to insert into'
114
108
  end
115
109
 
116
110
  # Return the value that matches the given key or return nil if they key is
@@ -120,7 +114,7 @@ module PEROBS
120
114
  def get(key)
121
115
  node = self
122
116
 
123
- while node do
117
+ while node
124
118
  # Find index of the entry that best fits the key.
125
119
  i = node.search_key_index(key)
126
120
  if node.is_leaf?
@@ -133,8 +127,8 @@ module PEROBS
133
127
  node = node.children[i]
134
128
  end
135
129
 
136
- PEROBS.log.fatal "Could not find proper node to get from while " +
137
- "looking for key #{key}"
130
+ PEROBS.log.fatal 'Could not find proper node to get from while ' \
131
+ "looking for key #{key}"
138
132
  end
139
133
 
140
134
  # Return the node chain from the root to the leaf node storing the
@@ -143,9 +137,9 @@ module PEROBS
143
137
  # @return [Array of BigTreeNode] node list (may be empty)
144
138
  def node_chain(key)
145
139
  node = myself
146
- list = [ node ]
140
+ list = [node]
147
141
 
148
- while node do
142
+ while node
149
143
  # Find index of the entry that best fits the key.
150
144
  i = node.search_key_index(key)
151
145
  if node.is_leaf?
@@ -169,7 +163,7 @@ module PEROBS
169
163
  def has_key?(key)
170
164
  node = self
171
165
 
172
- while node do
166
+ while node
173
167
  # Find index of the entry that best fits the key.
174
168
  i = node.search_key_index(key)
175
169
  if node.is_leaf?
@@ -182,8 +176,8 @@ module PEROBS
182
176
  node = node.children[i]
183
177
  end
184
178
 
185
- PEROBS.log.fatal "Could not find proper node to get from while " +
186
- "looking for key #{key}"
179
+ PEROBS.log.fatal 'Could not find proper node to get from while ' \
180
+ "looking for key #{key}"
187
181
  end
188
182
 
189
183
  # Return the value that matches the given key and remove the value from
@@ -193,18 +187,16 @@ module PEROBS
193
187
  def remove(key)
194
188
  node = self
195
189
 
196
- while node do
190
+ while node
197
191
  # Find index of the entry that best fits the key.
198
192
  i = node.search_key_index(key)
199
193
  if node.is_leaf?
200
194
  # This is a leaf node. Check if there is an exact match for the
201
195
  # given key and return the corresponding value or nil.
202
- if node.keys[i] == key
203
- @tree.entry_counter -= 1
204
- return node.remove_element(i)
205
- else
206
- return nil
207
- end
196
+ return nil if node.keys[i] != key
197
+
198
+ @tree.entry_counter -= 1
199
+ return node.remove_element(i)
208
200
  end
209
201
 
210
202
  # Descend into the right child node to continue the search.
@@ -217,10 +209,9 @@ module PEROBS
217
209
  # Iterate over all the key/value pairs in this node and all sub-nodes.
218
210
  # @yield [key, value]
219
211
  def each
220
- traverse do |node, position, stack|
221
- if node.is_leaf? && position < node.keys.size
212
+ traverse do |node, position, _|
213
+ node.is_leaf? && position < node.keys.size &&
222
214
  yield(node.keys[position], node.values[position])
223
- end
224
215
  end
225
216
  end
226
217
 
@@ -252,7 +243,7 @@ module PEROBS
252
243
  branch_depth = nil
253
244
 
254
245
  traverse do |node, position, stack|
255
- if position == 0
246
+ if position.zero?
256
247
  if node.parent
257
248
  # After a split the nodes will only have half the maximum keys.
258
249
  # For branch nodes one of the split nodes will have even 1 key
@@ -264,16 +255,16 @@ module PEROBS
264
255
  end
265
256
 
266
257
  if node.keys.size > @tree.node_size
267
- node.error "BigTree node must not have more then " +
268
- "#{@tree.node_size} keys, but has #{node.keys.size} keys"
258
+ node.error 'BigTree node must not have more then ' \
259
+ "#{@tree.node_size} keys, but has #{node.keys.size} keys"
269
260
  return false
270
261
  end
271
262
 
272
263
  last_key = nil
273
264
  node.keys.each do |key|
274
265
  if last_key && key < last_key
275
- node.error "Keys are not increasing monotoneously: " +
276
- "#{node.keys.inspect}"
266
+ node.error 'Keys are not increasing monotoneously: ' \
267
+ "#{node.keys.inspect}"
277
268
  return false
278
269
  end
279
270
  last_key = key
@@ -282,7 +273,7 @@ module PEROBS
282
273
  if node.is_leaf?
283
274
  if branch_depth
284
275
  unless branch_depth == stack.size
285
- node.error "All leaf nodes must have same distance from root"
276
+ node.error 'All leaf nodes must have same distance from root'
286
277
  return false
287
278
  end
288
279
  else
@@ -290,54 +281,54 @@ module PEROBS
290
281
  end
291
282
  if node.prev_sibling.nil?
292
283
  if @tree.first_leaf != node
293
- node.error "Leaf node #{node._id} has no previous sibling " +
294
- "but is not the first leaf of the tree"
284
+ node.error "Leaf node #{node._id} has no previous sibling " \
285
+ 'but is not the first leaf of the tree'
295
286
  return false
296
287
  end
297
288
  elsif node.prev_sibling.next_sibling != node
298
- node.error "next_sibling of previous sibling does not point to " +
299
- "this node"
289
+ node.error 'next_sibling of previous sibling does not point to ' \
290
+ 'this node'
300
291
  return false
301
292
  end
302
293
  if node.next_sibling.nil?
303
294
  if @tree.last_leaf != node
304
- node.error "Leaf node #{node._id} has no next sibling " +
305
- "but is not the last leaf of the tree"
295
+ node.error "Leaf node #{node._id} has no next sibling " \
296
+ 'but is not the last leaf of the tree'
306
297
  return false
307
298
  end
308
299
  elsif node.next_sibling.prev_sibling != node
309
- node.error "previous_sibling of next sibling does not point to " +
310
- "this node"
300
+ node.error 'previous_sibling of next sibling does not point to ' \
301
+ 'this node'
311
302
  return false
312
303
  end
313
304
  unless node.keys.size == node.values.size
314
- node.error "Key count (#{node.keys.size}) and value " +
315
- "count (#{node.values.size}) don't match"
316
- return false
305
+ node.error "Key count (#{node.keys.size}) and value " \
306
+ "count (#{node.values.size}) don't match"
307
+ return false
317
308
  end
318
309
  if node.children
319
- node.error "children must be nil for a leaf node"
310
+ node.error 'children must be nil for a leaf node'
320
311
  return false
321
312
  end
322
313
  else
323
314
  if node.values
324
- node.error "values must be nil for a branch node"
315
+ node.error 'values must be nil for a branch node'
325
316
  return false
326
317
  end
327
318
  unless node.children.size == node.keys.size + 1
328
- node.error "Key count (#{node.keys.size}) must be one " +
329
- "less than children count (#{node.children.size})"
330
- return false
319
+ node.error "Key count (#{node.keys.size}) must be one " \
320
+ "less than children count (#{node.children.size})"
321
+ return false
331
322
  end
332
323
  node.children.each_with_index do |child, i|
333
324
  unless child.is_a?(BigTreeNode)
334
- node.error "Child #{i} is of class #{child.class} " +
335
- "instead of BigTreeNode"
325
+ node.error "Child #{i} is of class #{child.class} " \
326
+ 'instead of BigTreeNode'
336
327
  return false
337
328
  end
338
329
  unless child.parent.is_a?(BigTreeNode)
339
- node.error "Parent reference of child #{i} is of class " +
340
- "#{child.class} instead of BigTreeNode"
330
+ node.error "Parent reference of child #{i} is of class " \
331
+ "#{child.class} instead of BigTreeNode"
341
332
  return false
342
333
  end
343
334
  if child == node
@@ -349,25 +340,22 @@ module PEROBS
349
340
  return false
350
341
  end
351
342
  unless child.parent == node
352
- node.error "Child #{i} does not have parent pointing " +
353
- "to this node"
343
+ node.error "Child #{i} does not have parent pointing " \
344
+ 'to this node'
354
345
  return false
355
346
  end
356
- if i > 0
357
- unless node.children[i - 1].next_sibling == child
358
- node.error "next_sibling of node " +
359
- "#{node.children[i - 1]._id} " +
360
- "must point to node #{child._id}"
361
- return false
362
- end
347
+ if i.positive? && node.children[i - 1].next_sibling != child
348
+ node.error 'next_sibling of node ' \
349
+ "#{node.children[i - 1]._id} " \
350
+ "must point to node #{child._id}"
351
+ return false
363
352
  end
364
- if i < node.children.length - 1
365
- unless child == node.children[i + 1].prev_sibling
366
- node.error "prev_sibling of node " +
367
- "#{node.children[i + 1]._id} " +
368
- "must point to node #{child._id}"
369
- return false
370
- end
353
+ if i < node.children.length - 1 &&
354
+ child != node.children[i + 1].prev_sibling
355
+ node.error 'prev_sibling of node ' \
356
+ "#{node.children[i + 1]._id} " \
357
+ "must point to node #{child._id}"
358
+ return false
371
359
  end
372
360
  end
373
361
  end
@@ -382,15 +370,15 @@ module PEROBS
382
370
  end
383
371
  else
384
372
  unless node.children[index].keys.last < node.keys[index]
385
- node.error "Child #{node.children[index]._id} " +
386
- "has too large key #{node.children[index].keys.last}. " +
387
- "Must be smaller than #{node.keys[index]}."
373
+ node.error "Child #{node.children[index]._id} " \
374
+ "has too large key #{node.children[index].keys.last}. " \
375
+ "Must be smaller than #{node.keys[index]}."
388
376
  return false
389
377
  end
390
378
  unless node.children[position].keys.first >= node.keys[index]
391
- node.error "Child #{node.children[position]._id} " +
392
- "has too small key #{node.children[position].keys.first}. " +
393
- "Must be larger than or equal to #{node.keys[index]}."
379
+ node.error "Child #{node.children[position]._id} " \
380
+ "has too small key #{node.children[position].keys.first}. " \
381
+ "Must be larger than or equal to #{node.keys[index]}."
394
382
  return false
395
383
  end
396
384
  end
@@ -404,12 +392,12 @@ module PEROBS
404
392
  def to_s
405
393
  str = ''
406
394
 
407
- traverse do |node, position, stack|
408
- if position == 0
395
+ traverse do |node, position, _|
396
+ if position.zero?
409
397
  begin
410
- str += "#{node.parent ? node.parent.tree_prefix + ' +' : 'o'}" +
411
- "#{node.tree_branch_mark}-" +
412
- "#{node.keys.first.nil? ? '--' : 'v-'}#{node.tree_summary}\n"
398
+ str += "#{node.parent ? node.parent.tree_prefix + ' +' : 'o'}" \
399
+ "#{node.tree_branch_mark}-" \
400
+ "#{node.keys.first.nil? ? '--' : 'v-'}#{node.tree_summary}\n"
413
401
  rescue => e
414
402
  str += "@@@@@@@@@@: #{e.message}\n"
415
403
  end
@@ -417,14 +405,12 @@ module PEROBS
417
405
  begin
418
406
  if node.is_leaf?
419
407
  if node.keys[position - 1]
420
- str += "#{node.tree_prefix} |" +
421
- "[#{node.keys[position - 1]}, " +
408
+ str += "#{node.tree_prefix} |" \
409
+ "[#{node.keys[position - 1]}, " \
422
410
  "#{node.values[position - 1]}]\n"
423
411
  end
424
- else
425
- if node.keys[position - 1]
426
- str += "#{node.tree_prefix} #{node.keys[position - 1]}\n"
427
- end
412
+ elsif node.keys[position - 1]
413
+ str += "#{node.tree_prefix} #{node.keys[position - 1]}\n"
428
414
  end
429
415
  rescue => e
430
416
  str += "@@@@@@@@@@: #{e.message}\n"
@@ -518,10 +504,9 @@ module PEROBS
518
504
  def remove_element(index)
519
505
  # Delete the key at the specified index.
520
506
  unless (key = @keys.delete_at(index))
521
- PEROBS.log.fatal "Could not remove element #{index} from BigTreeNode " +
522
- "@#{@_id}"
507
+ PEROBS.log.fatal "Could not remove element #{index} from BigTreeNode @#{@_id}"
523
508
  end
524
- update_branch_key(key) if index == 0
509
+ update_branch_key(key) if index.zero?
525
510
 
526
511
  # Delete the corresponding value.
527
512
  removed_value = @values.delete_at(index)
@@ -533,7 +518,7 @@ module PEROBS
533
518
  borrow_from_next_sibling(@next_sibling) ||
534
519
  merge_with_leaf_node(@next_sibling)
535
520
  elsif @parent
536
- PEROBS.log.fatal "Cannot not find adjecent leaf siblings"
521
+ PEROBS.log.fatal 'Cannot not find adjecent leaf siblings'
537
522
  end
538
523
  end
539
524
 
@@ -549,7 +534,7 @@ module PEROBS
549
534
  PEROBS.log.fatal "Cannot remove child #{node._id} from node #{@_id}"
550
535
  end
551
536
 
552
- if index == 0
537
+ if index.zero?
553
538
  # Removing the first child is a bit more complicated as the
554
539
  # corresponding branch key is in a parent node.
555
540
  key = @keys.shift
@@ -592,7 +577,7 @@ module PEROBS
592
577
 
593
578
  def merge_with_leaf_node(node)
594
579
  if @keys.length + node.keys.length > @tree.node_size
595
- PEROBS.log.fatal "Leaf nodes are too big to merge"
580
+ PEROBS.log.fatal 'Leaf nodes are too big to merge'
596
581
  end
597
582
 
598
583
  self.keys += node.keys
@@ -603,7 +588,7 @@ module PEROBS
603
588
 
604
589
  def merge_with_branch_node(node)
605
590
  if @keys.length + 1 + node.keys.length > @tree.node_size
606
- PEROBS.log.fatal "Branch nodes are too big to merge"
591
+ PEROBS.log.fatal 'Branch nodes are too big to merge'
607
592
  end
608
593
 
609
594
  index = @parent.search_node_index(node) - 1
@@ -670,9 +655,9 @@ module PEROBS
670
655
  def traverse
671
656
  # We use a non-recursive implementation to traverse the tree. This stack
672
657
  # keeps track of all the known still to be checked nodes.
673
- stack = [ [ self, 0 ] ]
658
+ stack = [[self, 0]]
674
659
 
675
- while !stack.empty?
660
+ until stack.empty?
676
661
  node, position = stack.pop
677
662
 
678
663
  # Call the payload method. The position marks where we are in the node
@@ -684,15 +669,15 @@ module PEROBS
684
669
  # to return to the parent node.
685
670
  yield(node, position, stack)
686
671
 
687
- if position <= node.keys.size
688
- # Push the next position for this node onto the stack.
689
- stack.push([ node, position + 1 ])
672
+ next unless position <= node.keys.size
690
673
 
691
- if !node.is_leaf? && node.children[position]
692
- # If we have a child node for this position, push the linked node
693
- # and the starting position onto the stack.
694
- stack.push([ node.children[position], 0 ])
695
- end
674
+ # Push the next position for this node onto the stack.
675
+ stack.push([node, position + 1])
676
+
677
+ if !node.is_leaf? && node.children[position]
678
+ # If we have a child node for this position, push the linked node
679
+ # and the starting position onto the stack.
680
+ stack.push([node.children[position], 0])
696
681
  end
697
682
  end
698
683
  end
@@ -701,16 +686,12 @@ module PEROBS
701
686
  # @param stats [Stats] Data structure that stores the gathered data
702
687
  def statistics(stats)
703
688
  traverse do |node, position, stack|
704
- if position == 0
689
+ if position.zero?
705
690
  if node.is_leaf?
706
691
  stats.leaf_nodes += 1
707
692
  depth = stack.size + 1
708
- if stats.min_depth.nil? || stats.min_depth < depth
709
- stats.min_depth = depth
710
- end
711
- if stats.max_depth.nil? || stats.max_depth > depth
712
- stats.max_depth = depth
713
- end
693
+ stats.min_depth = depth if stats.min_depth.nil? || stats.min_depth < depth
694
+ stats.max_depth = depth if stats.max_depth.nil? || stats.max_depth > depth
714
695
  else
715
696
  stats.branch_nodes += 1
716
697
  end
@@ -743,6 +724,7 @@ module PEROBS
743
724
  # Branch node decoration for the inspection method.
744
725
  def tree_branch_mark
745
726
  return '' unless @parent
727
+
746
728
  '-'
747
729
  end
748
730
 
@@ -866,8 +848,5 @@ module PEROBS
866
848
 
867
849
  # The smallest element has no branch key.
868
850
  end
869
-
870
851
  end
871
-
872
852
  end
873
-
@@ -60,6 +60,12 @@ module PEROBS
60
60
  @by_id[id]
61
61
  end
62
62
 
63
+ # Get a list of all classes used in the Store.
64
+ # @return [Array] list of Ruby classes
65
+ def classes
66
+ @by_class.keys
67
+ end
68
+
63
69
  # Rename a set of classes to new names.
64
70
  # @param rename_map [Hash] Hash that maps old names to new names
65
71
  def rename(rename_map)
@@ -34,18 +34,23 @@ require 'fileutils'
34
34
 
35
35
  require 'perobs/Log'
36
36
  require 'perobs/ObjectBase'
37
+ require_relative 'ProgressMeter'
37
38
 
38
39
  module PEROBS
39
-
40
40
  # Base class for all storage back-ends.
41
41
  class DataBase
42
-
43
42
  # Create a new DataBase object. This method must be overwritten by the
44
43
  # deriving classes and then called via their constructor.
45
44
  def initialize(options)
46
45
  @serializer = options[:serializer] || :json
47
46
  @progressmeter = options[:progressmeter] || ProgressMeter.new
48
47
  @config = {}
48
+ @class_map = nil
49
+ end
50
+
51
+ # Register the class map object needed to de-serialize objects.
52
+ def register_class_map(class_map)
53
+ @class_map = class_map
49
54
  end
50
55
 
51
56
  # A dummy open method. Deriving classes must overload them to insert their
@@ -81,19 +86,21 @@ module PEROBS
81
86
  # @param raw [String]
82
87
  # @return [Hash] Deserialized version
83
88
  def deserialize(raw)
84
- begin
85
- case @serializer
86
- when :marshal
87
- Marshal.load(raw)
88
- when :json
89
- JSON.parse(raw, :create_additions => true)
90
- when :yaml
89
+ case @serializer
90
+ when :marshal
91
+ Marshal.load(raw)
92
+ when :json
93
+ JSON.parse(raw, create_additions: true)
94
+ when :yaml
95
+ if RUBY_VERSION < '3.2'
91
96
  YAML.load(raw)
97
+ else
98
+ YAML.load(raw, permitted_classes: [Symbol, POReference] + @class_map.classes)
92
99
  end
93
- rescue => e
94
- PEROBS.log.fatal "Cannot de-serialize object with #{@serializer} " +
95
- "parser: " + e.message
96
100
  end
101
+ rescue => e
102
+ PEROBS.log.fatal "Cannot de-serialize object with #{@serializer} " +
103
+ "parser: " + e.message
97
104
  end
98
105
 
99
106
  # Check a config option and adjust it if needed.
@@ -127,7 +134,5 @@ module PEROBS
127
134
  end
128
135
  end
129
136
  end
130
-
131
137
  end
132
-
133
138
  end
data/lib/perobs/IDList.rb CHANGED
@@ -46,7 +46,7 @@ module PEROBS
46
46
  # that will be kept in memory. If the list is larger, values will
47
47
  # be cached in the specified file.
48
48
  # @param page_size [Integer] The number of values per page. The default
49
- # value is 32 which was found the best performing config in tests.
49
+ # value is 32 which was found the best performing config in tests.
50
50
  def initialize(dir, name, max_in_memory, page_size = 32)
51
51
  # The page_file manages the pages that store the values.
52
52
  @page_file = IDListPageFile.new(self, dir, name,
@@ -63,7 +63,7 @@ module PEROBS
63
63
  page = @page_records[index]
64
64
 
65
65
  # In case the page is already full we'll have to create a new page.
66
- # There is no guarantee that a split will yield an page with space as we
66
+ # There is no guarantee that a split will yield a page with space as we
67
67
  # split by ID range, not by distributing the values evenly across the
68
68
  # two pages.
69
69
  while page.is_full?
data/lib/perobs/Store.rb CHANGED
@@ -157,6 +157,7 @@ module PEROBS
157
157
  # Create a map that can translate classes to numerical IDs and vice
158
158
  # versa.
159
159
  @class_map = ClassMap.new(@db)
160
+ @db.register_class_map(@class_map)
160
161
 
161
162
  # List of PEROBS objects that are currently available as Ruby objects
162
163
  # hashed by their ID.
@@ -712,4 +713,3 @@ module PEROBS
712
713
  end
713
714
 
714
715
  end
715
-
@@ -1,4 +1,4 @@
1
1
  module PEROBS
2
2
  # The version number
3
- VERSION = "4.5.0"
3
+ VERSION = "4.6.0"
4
4
  end
data/perobs.gemspec CHANGED
@@ -16,9 +16,10 @@ GEM_SPEC = Gem::Specification.new do |spec|
16
16
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
17
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
18
  spec.require_paths = ["lib"]
19
- spec.required_ruby_version = '>=2.4'
19
+ spec.required_ruby_version = '>=2.5'
20
20
 
21
- spec.add_development_dependency 'bundler', '~> 2.3'
21
+ spec.add_development_dependency 'bundler', '~> 2.2'
22
22
  spec.add_development_dependency 'yard', '~>0.9.12'
23
23
  spec.add_development_dependency 'rake', '~> 13.0.3'
24
+ spec.add_development_dependency 'rspec', '~> 3.12'
24
25
  end