perobs 4.2.0 → 4.5.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 +27 -16
- data/lib/perobs/BTree.rb +2 -2
- data/lib/perobs/BTreeNode.rb +46 -29
- data/lib/perobs/BigArrayNode.rb +11 -9
- data/lib/perobs/Cache.rb +32 -6
- data/lib/perobs/EquiBlobsFile.rb +2 -0
- data/lib/perobs/FlatFile.rb +40 -60
- data/lib/perobs/FuzzyStringMatcher.rb +32 -49
- data/lib/perobs/Hash.rb +68 -23
- data/lib/perobs/IDListPageFile.rb +2 -1
- data/lib/perobs/IDListPageRecord.rb +1 -1
- data/lib/perobs/Log.rb +5 -0
- data/lib/perobs/ObjectBase.rb +7 -0
- data/lib/perobs/SpaceTree.rb +1 -1
- data/lib/perobs/Store.rb +177 -125
- data/lib/perobs/version.rb +1 -1
- data/lib/perobs.rb +1 -0
- data/perobs.gemspec +1 -1
- data/test/FlatFileDB_spec.rb +30 -0
- data/test/FuzzyStringMatcher_spec.rb +94 -4
- data/test/Hash_spec.rb +12 -1
- data/test/Store_spec.rb +14 -0
- metadata +8 -10
- data/lib/perobs/BTreeNodeCache.rb +0 -109
data/lib/perobs/FlatFile.rb
CHANGED
@@ -221,6 +221,7 @@ module PEROBS
|
|
221
221
|
flags |= (1 << FlatFileBlobHeader::COMPRESSED_FLAG_BIT) if compressed
|
222
222
|
FlatFileBlobHeader.new(@f, addr, flags, raw_obj_bytesize, id, crc).write
|
223
223
|
@f.write(raw_obj)
|
224
|
+
@f.flush
|
224
225
|
if length != -1 && raw_obj_bytesize < length
|
225
226
|
# The new object was not appended and it did not completely fill the
|
226
227
|
# free space. So we have to write a new header to mark the remaining
|
@@ -247,12 +248,11 @@ module PEROBS
|
|
247
248
|
# If we had an existing object stored for the ID we have to mark
|
248
249
|
# this entry as deleted now.
|
249
250
|
old_header.clear_flags
|
251
|
+
@f.flush
|
250
252
|
# And register the newly freed space with the space list.
|
251
253
|
if @space_list.is_open?
|
252
254
|
@space_list.add_space(old_addr, old_header.length)
|
253
255
|
end
|
254
|
-
else
|
255
|
-
@f.flush
|
256
256
|
end
|
257
257
|
rescue IOError => e
|
258
258
|
PEROBS.log.fatal "Cannot write blob for ID #{id} to FlatFileDB: " +
|
@@ -293,7 +293,7 @@ module PEROBS
|
|
293
293
|
header = FlatFileBlobHeader.read(@f, addr, id)
|
294
294
|
if header.id != id
|
295
295
|
PEROBS.log.fatal "Database index corrupted: Index for object " +
|
296
|
-
"#{id} points to object with ID #{header.id}"
|
296
|
+
"#{id} points to object with ID #{header.id} at address #{addr}"
|
297
297
|
end
|
298
298
|
|
299
299
|
buf = nil
|
@@ -302,7 +302,8 @@ module PEROBS
|
|
302
302
|
@f.seek(addr + FlatFileBlobHeader::LENGTH)
|
303
303
|
buf = @f.read(header.length)
|
304
304
|
rescue IOError => e
|
305
|
-
PEROBS.log.fatal "Cannot read blob for ID #{id}
|
305
|
+
PEROBS.log.fatal "Cannot read blob for ID #{id} at address #{addr}: " +
|
306
|
+
e.message
|
306
307
|
end
|
307
308
|
|
308
309
|
# Uncompress the data if the compression bit is set in the flags byte.
|
@@ -311,12 +312,13 @@ module PEROBS
|
|
311
312
|
buf = Zlib.inflate(buf)
|
312
313
|
rescue Zlib::BufError, Zlib::DataError
|
313
314
|
PEROBS.log.fatal "Corrupted compressed block with ID " +
|
314
|
-
"#{
|
315
|
+
"#{id} found at address #{addr}."
|
315
316
|
end
|
316
317
|
end
|
317
318
|
|
318
319
|
if checksum(buf) != header.crc
|
319
|
-
PEROBS.log.fatal "Checksum failure while reading blob ID #{id}"
|
320
|
+
PEROBS.log.fatal "Checksum failure while reading blob ID #{id} " +
|
321
|
+
"at address #{addr}"
|
320
322
|
end
|
321
323
|
|
322
324
|
buf
|
@@ -339,7 +341,7 @@ module PEROBS
|
|
339
341
|
if @marks
|
340
342
|
@marks.clear
|
341
343
|
else
|
342
|
-
@marks = IDList.new(@db_dir, 'marks',
|
344
|
+
@marks = IDList.new(@db_dir, 'marks', item_counter)
|
343
345
|
end
|
344
346
|
end
|
345
347
|
|
@@ -353,7 +355,7 @@ module PEROBS
|
|
353
355
|
valid_blobs = 0
|
354
356
|
|
355
357
|
# Iterate over all entries.
|
356
|
-
@progressmeter.start('
|
358
|
+
@progressmeter.start('Defragmenting blobs file', @f.size) do |pm|
|
357
359
|
each_blob_header do |header|
|
358
360
|
# If we have stumbled over a corrupted blob we treat it similar to a
|
359
361
|
# deleted blob and reuse the space.
|
@@ -452,16 +454,14 @@ module PEROBS
|
|
452
454
|
regenerate_index_and_spaces
|
453
455
|
end
|
454
456
|
|
455
|
-
# Check
|
456
|
-
# @param repair [Boolean] True if errors should be fixed.
|
457
|
+
# Check the FlatFile.
|
457
458
|
# @return [Integer] Number of errors found
|
458
|
-
def check(
|
459
|
+
def check()
|
459
460
|
errors = 0
|
460
461
|
return errors unless @f
|
461
462
|
|
462
463
|
t = Time.now
|
463
|
-
PEROBS.log.info "Checking FlatFile database"
|
464
|
-
"#{repair ? ' in repair mode' : ''}..."
|
464
|
+
PEROBS.log.info "Checking FlatFile database..."
|
465
465
|
|
466
466
|
# First check the database blob file. Each entry should be readable and
|
467
467
|
# correct and all IDs must be unique. We use a shadow index to keep
|
@@ -483,7 +483,6 @@ module PEROBS
|
|
483
483
|
if buf.bytesize != header.length
|
484
484
|
PEROBS.log.error "Premature end of file in blob with ID " +
|
485
485
|
"#{header.id}."
|
486
|
-
discard_damaged_blob(header) if repair
|
487
486
|
errors += 1
|
488
487
|
next
|
489
488
|
end
|
@@ -496,7 +495,6 @@ module PEROBS
|
|
496
495
|
rescue Zlib::BufError, Zlib::DataError
|
497
496
|
PEROBS.log.error "Corrupted compressed block with ID " +
|
498
497
|
"#{header.id} found."
|
499
|
-
discard_damaged_blob(header) if repair
|
500
498
|
errors += 1
|
501
499
|
next
|
502
500
|
end
|
@@ -505,7 +503,6 @@ module PEROBS
|
|
505
503
|
if header.crc && checksum(buf) != header.crc
|
506
504
|
PEROBS.log.error "Checksum failure while checking blob " +
|
507
505
|
"with ID #{header.id}"
|
508
|
-
discard_damaged_blob(header) if repair
|
509
506
|
errors += 1
|
510
507
|
next
|
511
508
|
end
|
@@ -521,22 +518,6 @@ module PEROBS
|
|
521
518
|
errors += 1
|
522
519
|
previous_header = FlatFileBlobHeader.read(@f, previous_address,
|
523
520
|
header.id)
|
524
|
-
if repair
|
525
|
-
# We have two blobs with the same ID and we must discard one of
|
526
|
-
# them.
|
527
|
-
if header.is_outdated?
|
528
|
-
discard_damaged_blob(header)
|
529
|
-
elsif previous_header.is_outdated?
|
530
|
-
discard_damaged_blob(previous_header)
|
531
|
-
else
|
532
|
-
PEROBS.log.error "None of the blobs with same ID have " +
|
533
|
-
"the outdated flag set. Deleting the smaller one."
|
534
|
-
errors += 1
|
535
|
-
discard_damaged_blob(header.length < previous_header.length ?
|
536
|
-
header : previous_header)
|
537
|
-
end
|
538
|
-
next
|
539
|
-
end
|
540
521
|
else
|
541
522
|
# ID is unique so far. Add it to the shadow index.
|
542
523
|
new_index.insert(header.id, header.addr)
|
@@ -553,12 +534,6 @@ module PEROBS
|
|
553
534
|
PEROBS.log.error "#{@f.size - end_of_last_healthy_blob} corrupted " +
|
554
535
|
'bytes found at the end of FlatFile.'
|
555
536
|
corrupted_blobs += 1
|
556
|
-
if repair
|
557
|
-
PEROBS.log.error "Truncating FlatFile to " +
|
558
|
-
"#{end_of_last_healthy_blob} bytes by discarding " +
|
559
|
-
"#{@f.size - end_of_last_healthy_blob} bytes"
|
560
|
-
@f.truncate(end_of_last_healthy_blob)
|
561
|
-
end
|
562
537
|
end
|
563
538
|
|
564
539
|
errors += corrupted_blobs
|
@@ -568,17 +543,19 @@ module PEROBS
|
|
568
543
|
new_index.close
|
569
544
|
new_index.erase
|
570
545
|
|
571
|
-
if
|
572
|
-
erase_index_files
|
573
|
-
defragmentize
|
574
|
-
regenerate_index_and_spaces
|
575
|
-
elsif corrupted_blobs == 0
|
546
|
+
if corrupted_blobs == 0
|
576
547
|
# Now we check the index data. It must be correct and the entries must
|
577
548
|
# match the blob file. All entries in the index must be in the blob file
|
578
549
|
# and vise versa.
|
579
550
|
begin
|
580
551
|
index_ok = @index.check do |id, address|
|
581
|
-
has_id_at?(id, address)
|
552
|
+
unless has_id_at?(id, address)
|
553
|
+
PEROBS.log.error "Index contains an entry for " +
|
554
|
+
"ID #{id} at address #{address} that is not in FlatFile"
|
555
|
+
false
|
556
|
+
else
|
557
|
+
true
|
558
|
+
end
|
582
559
|
end
|
583
560
|
x_check_errs = 0
|
584
561
|
space_check_ok = true
|
@@ -586,16 +563,13 @@ module PEROBS
|
|
586
563
|
(x_check_errs = cross_check_entries) == 0
|
587
564
|
errors += 1 unless index_ok && space_check_ok
|
588
565
|
errors += x_check_errs
|
589
|
-
regenerate_index_and_spaces if repair
|
590
566
|
end
|
591
567
|
rescue PEROBS::FatalError
|
592
568
|
errors += 1
|
593
|
-
regenerate_index_and_spaces if repair
|
594
569
|
end
|
595
570
|
end
|
596
571
|
|
597
|
-
|
598
|
-
PEROBS.log.info "check_db completed in #{Time.now - t} seconds. " +
|
572
|
+
PEROBS.log.info "FlatFile check completed in #{Time.now - t} seconds. " +
|
599
573
|
"#{errors} errors found."
|
600
574
|
|
601
575
|
errors
|
@@ -604,7 +578,6 @@ module PEROBS
|
|
604
578
|
# Repair the FlatFile. In contrast to the repair functionality in the
|
605
579
|
# check() method this method is much faster. It simply re-creates the
|
606
580
|
# index and space list from the blob file.
|
607
|
-
# @param repair [Boolean] True if errors should be fixed.
|
608
581
|
# @return [Integer] Number of errors found
|
609
582
|
def repair
|
610
583
|
errors = 0
|
@@ -687,17 +660,7 @@ module PEROBS
|
|
687
660
|
header.id)
|
688
661
|
# We have two blobs with the same ID and we must discard one of
|
689
662
|
# them.
|
690
|
-
|
691
|
-
discard_damaged_blob(header)
|
692
|
-
elsif previous_header.is_outdated?
|
693
|
-
discard_damaged_blob(previous_header)
|
694
|
-
else
|
695
|
-
PEROBS.log.error "None of the blobs with same ID have " +
|
696
|
-
"the outdated flag set. Deleting the smaller one."
|
697
|
-
errors += 1
|
698
|
-
discard_damaged_blob(header.length < previous_header.length ?
|
699
|
-
header : previous_header)
|
700
|
-
end
|
663
|
+
discard_duplicate_blobs(header, previous_header)
|
701
664
|
else
|
702
665
|
# ID is unique so far. Add it to the shadow index.
|
703
666
|
@index.insert(header.id, header.addr)
|
@@ -927,6 +890,23 @@ module PEROBS
|
|
927
890
|
header.clear_flags
|
928
891
|
end
|
929
892
|
|
893
|
+
def discard_duplicate_blobs(header, previous_header)
|
894
|
+
if header.is_outdated?
|
895
|
+
discard_damaged_blob(header)
|
896
|
+
elsif previous_header.is_outdated?
|
897
|
+
discard_damaged_blob(previous_header)
|
898
|
+
else
|
899
|
+
smaller, larger = header.length < previous_header.length ?
|
900
|
+
[ header, previous_header ] : [ previous_header, header ]
|
901
|
+
PEROBS.log.error "None of the blobs with same ID have " +
|
902
|
+
"the outdated flag set. Deleting the smaller one " +
|
903
|
+
"at address #{smaller.addr}"
|
904
|
+
discard_damaged_blob(smaller)
|
905
|
+
@space_list.add_space(smaller.addr, smaller.length)
|
906
|
+
@index.insert(larger.id, larger.addr)
|
907
|
+
end
|
908
|
+
end
|
909
|
+
|
930
910
|
def open_index_files(abort_on_missing_files = false)
|
931
911
|
begin
|
932
912
|
@index.open(abort_on_missing_files)
|
@@ -26,40 +26,42 @@
|
|
26
26
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
27
27
|
|
28
28
|
require 'perobs/Log'
|
29
|
-
require 'perobs/
|
29
|
+
require 'perobs/Object'
|
30
30
|
|
31
31
|
module PEROBS
|
32
32
|
|
33
33
|
# The fuzzy string matcher can be used to perform a fuzzy string search
|
34
34
|
# against a known set of strings. The dictionary of known strings does not
|
35
|
-
# store the actual strings but references to
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
|
35
|
+
# store the actual strings but references to String or PEROBS objects.
|
36
|
+
# Once the dictionary has been established, fuzzy matches can be done. Since
|
37
|
+
# the actual input strings are not directly stored, you cannot remove or
|
38
|
+
# modified already stored strings. To remove strings, you have to clear the
|
39
|
+
# matcher and add the strings again that you want to keep.
|
40
|
+
class FuzzyStringMatcher < PEROBS::Object
|
41
|
+
|
42
|
+
attr_persist :case_sensitive, :n, :dict
|
40
43
|
|
41
44
|
# Create a new FuzzyStringMatcher.
|
42
|
-
# @param
|
43
|
-
# @param name [String] Unique name of the string matcher
|
45
|
+
# @param p [PEROBS::Store] place to store the dictionary
|
44
46
|
# @param case_sensitive [Boolean] True if case matters for matching
|
45
47
|
# @param n [Integer] Determines what kind of n-gramm is used to store the
|
46
48
|
# references in the dictionary. It also determines the minimum word
|
47
|
-
# length that can be used for fuzzy matches.
|
48
|
-
|
49
|
-
|
50
|
-
|
49
|
+
# length that can be used for fuzzy matches. Values between 2 and
|
50
|
+
# 10 are supported. The default is 4.
|
51
|
+
def initialize(p, case_sensitive = false, n = 4)
|
52
|
+
super(p)
|
51
53
|
if n < 2 || n > 10
|
52
54
|
raise ArgumentError, 'n must be between 2 and 10'
|
53
55
|
end
|
54
|
-
|
55
|
-
|
56
|
+
self.case_sensitive = case_sensitive
|
57
|
+
self.n = n
|
56
58
|
|
57
|
-
clear unless
|
59
|
+
clear unless @dict
|
58
60
|
end
|
59
61
|
|
60
62
|
# Wipe the dictionary.
|
61
63
|
def clear
|
62
|
-
|
64
|
+
self.dict = @store.new(BigHash)
|
63
65
|
end
|
64
66
|
|
65
67
|
# Add a string with its reference to the dictionary.
|
@@ -79,11 +81,8 @@ module PEROBS
|
|
79
81
|
@dict[n_gramm] = ng_list = @store.new(Hash)
|
80
82
|
end
|
81
83
|
|
82
|
-
|
83
|
-
|
84
|
-
else
|
85
|
-
ng_list[reference] = 0
|
86
|
-
end
|
84
|
+
# We use the Hash as a Set. The value doesn't matter.
|
85
|
+
ng_list[reference] = true unless ng_list.include?(reference)
|
87
86
|
end
|
88
87
|
|
89
88
|
nil
|
@@ -109,22 +108,12 @@ module PEROBS
|
|
109
108
|
|
110
109
|
matches = {}
|
111
110
|
|
112
|
-
# This will be the best possible score for a perfect match.
|
113
|
-
best_possible_score = 0
|
114
111
|
each_n_gramm(string) do |n_gramm|
|
115
|
-
best_possible_score += 1
|
116
112
|
if (ng_list = @dict[n_gramm])
|
117
|
-
ng_list.each do |reference,
|
113
|
+
ng_list.each do |reference, dummy|
|
118
114
|
if matches.include?(reference)
|
119
115
|
matches[reference] += 1
|
120
116
|
else
|
121
|
-
# We use internally a 10 times larger list so that we don't
|
122
|
-
# throw away good matches too early. If the max_count value is
|
123
|
-
# chosen too small there is a risk of not finding the best
|
124
|
-
# matches!
|
125
|
-
if matches.size > 10 * max_count
|
126
|
-
matches = discard_worst_match(matches)
|
127
|
-
end
|
128
117
|
matches[reference] = 1
|
129
118
|
end
|
130
119
|
end
|
@@ -133,19 +122,23 @@ module PEROBS
|
|
133
122
|
|
134
123
|
return [] if matches.empty?
|
135
124
|
|
136
|
-
|
137
|
-
match_list = matches.to_a.sort do |a, b|
|
138
|
-
b[1] <=> a[1]
|
139
|
-
end
|
125
|
+
match_list = matches.to_a
|
140
126
|
|
141
127
|
# Set occurance counters to scores relative to the best possible score.
|
128
|
+
# This will be the best possible score for a perfect match.
|
129
|
+
best_possible_score = string.length - @n + 1
|
142
130
|
match_list.map! { |a, b| [ a, b.to_f / best_possible_score ] }
|
143
131
|
|
144
|
-
# Delete all matches that
|
145
|
-
# top match.
|
132
|
+
# Delete all matches that don't have the required minimum match score.
|
146
133
|
match_list.delete_if { |a| a[1] < min_score }
|
147
134
|
|
148
|
-
|
135
|
+
# Sort the list best to worst match
|
136
|
+
match_list.sort! do |a, b|
|
137
|
+
b[1] <=> a[1]
|
138
|
+
end
|
139
|
+
|
140
|
+
# Return the top max_count matches.
|
141
|
+
match_list[0..max_count - 1]
|
149
142
|
end
|
150
143
|
|
151
144
|
# Returns some internal stats about the dictionary.
|
@@ -176,16 +169,6 @@ module PEROBS
|
|
176
169
|
end
|
177
170
|
end
|
178
171
|
|
179
|
-
def discard_worst_match(matches)
|
180
|
-
# Sort in the order of occurance count downwards.
|
181
|
-
match_list = matches.to_a.sort do |a, b|
|
182
|
-
b[1] <=> a[1]
|
183
|
-
end
|
184
|
-
# Discard the lowest half of the matches
|
185
|
-
match_list = match_list[0..match_list.length / 2]
|
186
|
-
match_list.to_h
|
187
|
-
end
|
188
|
-
|
189
172
|
end
|
190
173
|
|
191
174
|
end
|
data/lib/perobs/Hash.rb
CHANGED
@@ -124,9 +124,9 @@ module PEROBS
|
|
124
124
|
|
125
125
|
# Proxy for assignment method.
|
126
126
|
def []=(key, value)
|
127
|
-
unless key.is_a?(String)
|
128
|
-
raise ArgumentError, "PEROBS::Hash[] key must be a String
|
129
|
-
"#{key.class}"
|
127
|
+
unless key.is_a?(String) || key.respond_to?(:is_poxreference?)
|
128
|
+
raise ArgumentError, "PEROBS::Hash[] key must be a String or " +
|
129
|
+
"a PEROBS object but is a #{key.class}"
|
130
130
|
end
|
131
131
|
_check_assignment_value(value)
|
132
132
|
@store.cache.cache_write(self)
|
@@ -143,18 +143,33 @@ module PEROBS
|
|
143
143
|
# is referencing.
|
144
144
|
# @return [Array of Integer] IDs of referenced objects
|
145
145
|
def _referenced_object_ids
|
146
|
-
|
147
|
-
|
146
|
+
ids = []
|
147
|
+
@data.each do |k, v|
|
148
|
+
if k && k.respond_to?(:is_poxreference?)
|
149
|
+
ids << k.id
|
150
|
+
end
|
151
|
+
if v && v.respond_to?(:is_poxreference?)
|
152
|
+
ids << v.id
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
ids
|
148
157
|
end
|
149
158
|
|
150
159
|
# This method should only be used during store repair operations. It will
|
151
160
|
# delete all referenced to the given object ID.
|
152
161
|
# @param id [Integer] targeted object ID
|
153
162
|
def _delete_reference_to_id(id)
|
163
|
+
original_length = @data.length
|
164
|
+
|
154
165
|
@data.delete_if do |k, v|
|
155
|
-
|
166
|
+
(k && k.respond_to?(:is_poxreference?) && k.id == id) ||
|
167
|
+
(v && v.respond_to?(:is_poxreference?) && v.id == id)
|
168
|
+
end
|
169
|
+
|
170
|
+
if @data.length != original_length
|
171
|
+
@store.cache.cache_write(self)
|
156
172
|
end
|
157
|
-
@store.cache.cache_write(self)
|
158
173
|
end
|
159
174
|
|
160
175
|
# Restore the persistent data from a single data structure.
|
@@ -163,8 +178,18 @@ module PEROBS
|
|
163
178
|
# @private
|
164
179
|
def _deserialize(data)
|
165
180
|
@data = {}
|
166
|
-
|
167
|
-
|
181
|
+
|
182
|
+
data.each do |k, v|
|
183
|
+
# References to other PEROBS Objects are marshalled with our own
|
184
|
+
# format. If we detect such a marshalled String we convert it into a
|
185
|
+
# POXReference object.
|
186
|
+
if (match = /^#<PEROBS::POReference id=([0-9]+)>$/.match(k))
|
187
|
+
k = POXReference.new(@store, match[1].to_i)
|
188
|
+
end
|
189
|
+
dv = v.is_a?(POReference) ? POXReference.new(@store, v.id) : v
|
190
|
+
@data[k] = dv
|
191
|
+
end
|
192
|
+
|
168
193
|
@data
|
169
194
|
end
|
170
195
|
|
@@ -185,26 +210,46 @@ module PEROBS
|
|
185
210
|
data = {}
|
186
211
|
|
187
212
|
@data.each do |k, v|
|
188
|
-
if
|
189
|
-
|
190
|
-
|
191
|
-
#
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
v.inspect
|
200
|
-
end
|
201
|
-
data[k] = v
|
213
|
+
if k.respond_to?(:is_poxreference?)
|
214
|
+
# JSON only supports Strings as hash keys. Since JSON is the default
|
215
|
+
# internal storage format in the database, we have to marshall
|
216
|
+
# PEROBS::Object references ourselves.
|
217
|
+
k = "#<PEROBS::POReference id=#{k.id}>"
|
218
|
+
elsif k[0..24] == '#<PEROBS::POReference id='
|
219
|
+
# This could obviously result in conflicts with 'normal' String hash
|
220
|
+
# keys. This is extremely unlikely, but we better catch this case
|
221
|
+
# before it causes hard to debug trouble.
|
222
|
+
raise ArgumentError, "Hash key #{k} conflicts with PEROBS " +
|
223
|
+
"internal representation of marshalled hash keys!"
|
202
224
|
end
|
225
|
+
data[k] = serialize_helper(v)
|
203
226
|
end
|
204
227
|
|
205
228
|
data
|
206
229
|
end
|
207
230
|
|
231
|
+
def serialize_helper(v)
|
232
|
+
if v.respond_to?(:is_poxreference?)
|
233
|
+
# References to other PEROBS objects (POXReference) are stored as
|
234
|
+
# POReference in the database.
|
235
|
+
return POReference.new(v.id)
|
236
|
+
else
|
237
|
+
# Outside of the PEROBS library all PEROBS::ObjectBase derived
|
238
|
+
# objects should not be used directly. The library only exposes them
|
239
|
+
# via POXReference proxy objects.
|
240
|
+
if v.is_a?(ObjectBase)
|
241
|
+
PEROBS.log.fatal 'A PEROBS::ObjectBase object escaped! ' +
|
242
|
+
"It is stored in a PEROBS::Hash. " +
|
243
|
+
'Have you used self() instead of myself() to ' +
|
244
|
+
"get the reference of this PEROBS object?\n" +
|
245
|
+
v.inspect
|
246
|
+
end
|
247
|
+
|
248
|
+
# All other objects are serialized by their native methods.
|
249
|
+
return v
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
208
253
|
end
|
209
254
|
|
210
255
|
end
|
@@ -54,7 +54,8 @@ module PEROBS
|
|
54
54
|
@file_name = File.join(dir, name + '.cache')
|
55
55
|
@page_size = page_size
|
56
56
|
open
|
57
|
-
@pages = PersistentObjectCache.new(max_in_memory,
|
57
|
+
@pages = PersistentObjectCache.new(max_in_memory, max_in_memory,
|
58
|
+
IDListPage, self)
|
58
59
|
@page_counter = 0
|
59
60
|
end
|
60
61
|
|
@@ -65,7 +65,7 @@ module PEROBS
|
|
65
65
|
end
|
66
66
|
|
67
67
|
# Insert an ID into the page.
|
68
|
-
# @param
|
68
|
+
# @param id [Integer] The ID to store
|
69
69
|
def insert(id)
|
70
70
|
unless @min_id <= id && id <= @max_id
|
71
71
|
raise ArgumentError, "IDs for this page must be between #{@min_id} " +
|
data/lib/perobs/Log.rb
CHANGED
@@ -42,6 +42,11 @@ module PEROBS
|
|
42
42
|
# are caused by user error rather than program logic errors.
|
43
43
|
class UsageError < StandardError ; end
|
44
44
|
|
45
|
+
# This is the Exception type that will be thrown when a transaction start
|
46
|
+
# failed because there is an ongoing transaction from another thread in
|
47
|
+
# progress.
|
48
|
+
class TransactionInOtherThread < StandardError ; end
|
49
|
+
|
45
50
|
# The ILogger class is a singleton that provides a common logging mechanism
|
46
51
|
# to all objects. It exposes essentially the same interface as the Logger
|
47
52
|
# class, just as a singleton and extends fatal to raise an FatalError
|
data/lib/perobs/ObjectBase.rb
CHANGED
@@ -102,6 +102,13 @@ module PEROBS
|
|
102
102
|
end
|
103
103
|
end
|
104
104
|
|
105
|
+
# To allow POXReference objects to be used as Hash keys we need to
|
106
|
+
# implement this function. Conveniently, we can just use the PEROBS object
|
107
|
+
# ID since that is unique.
|
108
|
+
def hash
|
109
|
+
@id
|
110
|
+
end
|
111
|
+
|
105
112
|
# Shortcut to access the _id() method of the referenced object.
|
106
113
|
def _id
|
107
114
|
@id
|
data/lib/perobs/SpaceTree.rb
CHANGED
@@ -54,7 +54,7 @@ module PEROBS
|
|
54
54
|
|
55
55
|
# Benchmark runs showed a cache size of 128 to be a good compromise
|
56
56
|
# between read and write performance trade-offs and memory consumption.
|
57
|
-
@cache = PersistentObjectCache.new(256,
|
57
|
+
@cache = PersistentObjectCache.new(256, 256, SpaceTreeNode, self)
|
58
58
|
end
|
59
59
|
|
60
60
|
# Open the SpaceTree file.
|