perobs 4.5.0 → 4.6.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.
@@ -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