rod 0.7.0 → 0.7.1
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.
- data/changelog.txt +21 -0
- data/features/collection_proxy.feature +5 -0
- data/features/steps/collection_proxy.rb +13 -0
- data/lib/rod.rb +2 -0
- data/lib/rod/abstract_database.rb +19 -43
- data/lib/rod/collection_proxy.rb +20 -2
- data/lib/rod/constants.rb +1 -1
- data/lib/rod/index/base.rb +18 -3
- data/lib/rod/index/flat_index.rb +8 -1
- data/lib/rod/index/hash_index.rb +98 -28
- data/lib/rod/index/segmented_index.rb +5 -1
- data/lib/rod/model.rb +32 -9
- data/lib/rod/utils.rb +15 -0
- data/tests/migration_create.rb +3 -0
- data/tests/migration_migrate.rb +3 -0
- data/tests/migration_model1.rb +4 -0
- data/tests/migration_model2.rb +4 -0
- data/tests/migration_verify.rb +15 -1
- metadata +2 -2
data/changelog.txt
CHANGED
@@ -1,3 +1,24 @@
|
|
1
|
+
0.7.1
|
2
|
+
- #189 guard object load against empty strings.
|
3
|
+
- Don't close hash index when it is destroyed.
|
4
|
+
- #188 don't raise exception when HashIndex closed
|
5
|
+
- Make the HashIndex exceptions more informative.
|
6
|
+
- #187 collection proxy is cleared in assignment
|
7
|
+
- Tiny change of diagnostic messages
|
8
|
+
- #174 progress for fields migration
|
9
|
+
- #181 store only added/updated hash index pairs
|
10
|
+
- #180 save collections in index no in Abstract DB
|
11
|
+
- #179 raise exception during iteration
|
12
|
+
- Fix saving of empty collection proxy.
|
13
|
+
- #176 copy index file if its type is the same
|
14
|
+
- #168 copy unmodified classes during migration
|
15
|
+
- #166 true index type still supported
|
16
|
+
- #171 encode strings to utf-8 when searching in hash index
|
17
|
+
- #170 fix save for hash index
|
18
|
+
- #169 missing each implementation in hash index
|
19
|
+
- #167 copy special classes data to backup
|
20
|
+
- #165 fix invalid topological class sorting algorithm
|
21
|
+
- #164 invalid variable in load metadata
|
1
22
|
0.7.0
|
2
23
|
- #142 migrate indices during model migration
|
3
24
|
- #134 Berkeley DB Hash for indexing properties
|
@@ -138,3 +138,8 @@ Feature: collection proxy specification
|
|
138
138
|
When I delete an item at position 0 5 times
|
139
139
|
Then the collection proxy should be empty
|
140
140
|
And the collection proxy should behave like an array
|
141
|
+
|
142
|
+
Scenario: modifying collection during interation
|
143
|
+
Given the initial size of the collection proxy is 5
|
144
|
+
# Can't figure out anything better.
|
145
|
+
Then an exception should be raised when the collection is modified during iteration
|
@@ -87,3 +87,16 @@ Then /^the collection proxy should be empty$/ do
|
|
87
87
|
@proxy.to_a.should be_empty
|
88
88
|
end
|
89
89
|
|
90
|
+
#Then an exception should be raised when the collection is modified during iteration
|
91
|
+
Then /^an exception should be raised when the collection is modified during iteration$/ do
|
92
|
+
(lambda do
|
93
|
+
@proxy.each do |item|
|
94
|
+
@proxy << create_item(@offset)
|
95
|
+
end
|
96
|
+
end).should raise_exception
|
97
|
+
(lambda do
|
98
|
+
@proxy.each do |item|
|
99
|
+
@proxy.detete_at(0)
|
100
|
+
end
|
101
|
+
end).should raise_exception
|
102
|
+
end
|
data/lib/rod.rb
CHANGED
@@ -7,6 +7,8 @@ require 'active_model/translation'
|
|
7
7
|
require 'active_model/validations'
|
8
8
|
require 'active_model/dirty'
|
9
9
|
require 'active_support/dependencies'
|
10
|
+
require 'active_support/deprecation/behaviors'
|
11
|
+
require 'active_support/deprecation/reporting'
|
10
12
|
|
11
13
|
# XXX This should be done in a different way, since a library should not
|
12
14
|
# impose on a user of another library specific way of using it.
|
@@ -159,6 +159,12 @@ module Rod
|
|
159
159
|
@metadata = load_metadata
|
160
160
|
create_legacy_classes
|
161
161
|
FileUtils.mkdir_p(@path + BACKUP_PREFIX)
|
162
|
+
# Copy special classes data.
|
163
|
+
special_classes.each do |klass|
|
164
|
+
file = klass.path_for_data(@path)
|
165
|
+
puts "Copying #{file} to #{@path + BACKUP_PREFIX}" if $ROD_DEBUG
|
166
|
+
FileUtils.cp(file,@path + BACKUP_PREFIX)
|
167
|
+
end
|
162
168
|
Dir.glob(@path + "*").each do |file|
|
163
169
|
# Don't move the directory itself and speciall classes data.
|
164
170
|
unless file.to_s == @path + BACKUP_PREFIX[0..-2] ||
|
@@ -218,7 +224,7 @@ module Rod
|
|
218
224
|
unless skip_indices
|
219
225
|
self.classes.each do |klass|
|
220
226
|
klass.indexed_properties.each do |property,options|
|
221
|
-
|
227
|
+
klass.index_for(property,options).save
|
222
228
|
end
|
223
229
|
end
|
224
230
|
end
|
@@ -357,27 +363,6 @@ module Rod
|
|
357
363
|
send("_#{klass.struct_name}_page_count=",@handler,value)
|
358
364
|
end
|
359
365
|
|
360
|
-
# Store index of +field+ (with +options+) of +klass+ in the database.
|
361
|
-
# There are two types of indices:
|
362
|
-
# * +:flat+ - marshalled index is stored in one file
|
363
|
-
# * +:segmented+ - marshalled index is stored in many files
|
364
|
-
def write_index(klass,property,options)
|
365
|
-
raise DatabaseError.new("Readonly database.") if readonly_data?
|
366
|
-
class_index = klass.index_for(property,options)
|
367
|
-
if options[:convert]
|
368
|
-
# Only convert the index, without (re)storing the values.
|
369
|
-
index = Index::Base.create(klass.path_for_index(@path,property),klass,options)
|
370
|
-
index.copy(class_index)
|
371
|
-
class_index = index
|
372
|
-
else
|
373
|
-
class_index.each do |key,proxy|
|
374
|
-
proxy.save
|
375
|
-
end
|
376
|
-
end
|
377
|
-
class_index.save
|
378
|
-
class_index = nil
|
379
|
-
end
|
380
|
-
|
381
366
|
# Store the object in the database.
|
382
367
|
def store(klass,object)
|
383
368
|
raise DatabaseError.new("Readonly database.") if readonly_data?
|
@@ -411,7 +396,7 @@ module Rod
|
|
411
396
|
end
|
412
397
|
unless valid_version?(metadata["Rod"][:version])
|
413
398
|
raise IncompatibleVersion.new("Incompatible versions - library #{VERSION} vs. " +
|
414
|
-
"file #{
|
399
|
+
"file #{metadata["Rod"][:version]}")
|
415
400
|
end
|
416
401
|
metadata
|
417
402
|
end
|
@@ -467,29 +452,20 @@ module Rod
|
|
467
452
|
special_names = special_classes.map{|k| k.name}
|
468
453
|
special_names << "Rod"
|
469
454
|
superclasses = {}
|
455
|
+
inverted_superclasses = {}
|
470
456
|
@metadata.reject{|k,o| special_names.include?(k)}.each do |k,o|
|
471
457
|
superclasses[k] = o[:superclass]
|
458
|
+
inverted_superclasses[o[:superclass]] ||= []
|
459
|
+
inverted_superclasses[o[:superclass]] << k
|
472
460
|
end
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
current_superclass = superclasses[current_superclass]
|
482
|
-
end
|
483
|
-
end
|
484
|
-
superclasses.keys.sort do |klass1,klass2|
|
485
|
-
if superclass_tree[klass1].include?(klass2)
|
486
|
-
1
|
487
|
-
elsif superclass_tree[klass2].include?(klass1)
|
488
|
-
-1
|
489
|
-
else
|
490
|
-
klass1 <=> klass2
|
491
|
-
end
|
492
|
-
end.each do |klass_name|
|
461
|
+
queue = inverted_superclasses["Rod::Model"] || []
|
462
|
+
sorted = []
|
463
|
+
begin
|
464
|
+
klass = queue.shift
|
465
|
+
sorted << klass
|
466
|
+
queue.concat(inverted_superclasses[klass] || [])
|
467
|
+
end until queue.empty?
|
468
|
+
sorted.each do |klass_name|
|
493
469
|
metadata = @metadata[klass_name]
|
494
470
|
original_name = klass_name
|
495
471
|
if module_instance != Object
|
data/lib/rod/collection_proxy.rb
CHANGED
@@ -14,11 +14,14 @@ module Rod
|
|
14
14
|
# objects. If the klass is nil, the collection holds polymorphic
|
15
15
|
# objects.
|
16
16
|
def initialize(size,database,offset,klass)
|
17
|
+
raise InvalidArgument.new("collection size",nil) if size.nil?
|
17
18
|
@size = size
|
18
19
|
@original_size = size
|
20
|
+
raise InvalidArgument.new("collection database",nil) if database.nil?
|
19
21
|
@database = database
|
20
|
-
|
22
|
+
raise InvalidArgument.new("collection offset",nil) if offset.nil?
|
21
23
|
@offset = offset
|
24
|
+
@klass = klass
|
22
25
|
#@commands = []
|
23
26
|
@added = []
|
24
27
|
@deleted = []
|
@@ -134,11 +137,26 @@ module Rod
|
|
134
137
|
element
|
135
138
|
end
|
136
139
|
|
137
|
-
#
|
140
|
+
# Clears the contents of the collection proxy.
|
141
|
+
def clear
|
142
|
+
@deleted = @original_size.times.to_a
|
143
|
+
@added.clear
|
144
|
+
@map.clear
|
145
|
+
@size = 0
|
146
|
+
end
|
147
|
+
|
148
|
+
# Iterator implementation. It raises an exception when
|
149
|
+
# the collection is modified during iteration.
|
150
|
+
# WARNING: This is not compliant with an Array class!
|
138
151
|
def each
|
139
152
|
if block_given?
|
140
153
|
@size.times do |index|
|
154
|
+
added_size = @added.size
|
155
|
+
deleted_size = @deleted.size
|
141
156
|
yield self[index]
|
157
|
+
if added_size != @added.size || deleted_size != @deleted.size
|
158
|
+
raise "Can't modify collection during iteration!"
|
159
|
+
end
|
142
160
|
end
|
143
161
|
else
|
144
162
|
enum_for(:each)
|
data/lib/rod/constants.rb
CHANGED
data/lib/rod/index/base.rb
CHANGED
@@ -13,6 +13,10 @@ module Rod
|
|
13
13
|
# which are used to retrive and assign the values respectively.
|
14
14
|
class Base
|
15
15
|
include Utils
|
16
|
+
|
17
|
+
# The path of the index.
|
18
|
+
attr_reader :path
|
19
|
+
|
16
20
|
# Sets the class this index belongs to.
|
17
21
|
def initialize(klass)
|
18
22
|
@klass = klass
|
@@ -38,7 +42,7 @@ module Rod
|
|
38
42
|
proxy = get(key)
|
39
43
|
end
|
40
44
|
if proxy.nil?
|
41
|
-
proxy = CollectionProxy.new(0,@klass.database,
|
45
|
+
proxy = CollectionProxy.new(0,@klass.database,0,@klass)
|
42
46
|
else
|
43
47
|
unless proxy.is_a?(CollectionProxy)
|
44
48
|
offset, count = proxy
|
@@ -56,8 +60,10 @@ module Rod
|
|
56
60
|
|
57
61
|
# Copies the values from +index+ to this index.
|
58
62
|
def copy(index)
|
59
|
-
index.each do |
|
60
|
-
self.set(
|
63
|
+
index.each.with_index do |key_value,position|
|
64
|
+
self.set(key_value[0],key_value[1])
|
65
|
+
# TODO #182 implement size for index
|
66
|
+
# report_progress(position,index.size) if $ROD_DEBUG
|
61
67
|
end
|
62
68
|
end
|
63
69
|
|
@@ -74,6 +80,11 @@ module Rod
|
|
74
80
|
set(key,proxy)
|
75
81
|
end
|
76
82
|
|
83
|
+
# The default representation shows the index class and path.
|
84
|
+
def to_s
|
85
|
+
"#{self.class}@#{@path}"
|
86
|
+
end
|
87
|
+
|
77
88
|
class << self
|
78
89
|
# Creats the proper instance of Index or one of its sublcasses.
|
79
90
|
# The +path+ is the path were the index is stored, while +index+ is the previous index instance.
|
@@ -89,6 +100,10 @@ module Rod
|
|
89
100
|
SegmentedIndex.new(path,klass,options)
|
90
101
|
when :hash
|
91
102
|
HashIndex.new(path,klass,options)
|
103
|
+
when true
|
104
|
+
ActiveSupport::Deprecation.
|
105
|
+
warn("Index type 'true' is deprecated. It will be removed in ROD 0.8.0")
|
106
|
+
FlatIndex.new(path,klass,options)
|
92
107
|
else
|
93
108
|
raise RodException.new("Invalid index type #{type}")
|
94
109
|
end
|
data/lib/rod/index/flat_index.rb
CHANGED
@@ -16,11 +16,18 @@ module Rod
|
|
16
16
|
|
17
17
|
# Stores the index on disk.
|
18
18
|
def save
|
19
|
+
# The index was not loaded nor modified.
|
20
|
+
return if @index.nil?
|
19
21
|
File.open(@path,"w") do |out|
|
20
22
|
proxy_index = {}
|
21
23
|
#load_index unless loaded?
|
22
|
-
@index.
|
24
|
+
@index.each_key do |key|
|
25
|
+
col = self[key]
|
26
|
+
col.save
|
27
|
+
proxy_index[key] = [col.offset,col.size]
|
28
|
+
end
|
23
29
|
out.puts(Marshal.dump(proxy_index))
|
30
|
+
@index = nil
|
24
31
|
end
|
25
32
|
end
|
26
33
|
|
data/lib/rod/index/hash_index.rb
CHANGED
@@ -6,7 +6,7 @@ module Rod
|
|
6
6
|
# This implementation of index is based on the
|
7
7
|
# Berkeley DB Hash access method.
|
8
8
|
class HashIndex < Base
|
9
|
-
# Wrapper class for the database struct.
|
9
|
+
# Wrapper class for the database C struct.
|
10
10
|
class Handle
|
11
11
|
end
|
12
12
|
|
@@ -15,23 +15,30 @@ module Rod
|
|
15
15
|
def initialize(path,klass,options={})
|
16
16
|
@path = path + ".db"
|
17
17
|
@klass = klass
|
18
|
-
|
19
|
-
@index = {}
|
18
|
+
open(@path,:create => true)
|
20
19
|
end
|
21
20
|
|
22
21
|
# Stores the index on disk.
|
23
22
|
def save
|
24
|
-
|
23
|
+
raise RodException.new("The index #{self} is not opened!") unless opened?
|
24
|
+
if @index.empty?
|
25
|
+
close
|
26
|
+
return
|
27
|
+
end
|
28
|
+
@index.keys.each do |key|
|
29
|
+
collection = self[key]
|
30
|
+
key = key.encode("utf-8") if key.is_a?(String)
|
25
31
|
key = Marshal.dump(key)
|
32
|
+
collection.save
|
26
33
|
_put(key,collection.offset,collection.size)
|
27
34
|
end
|
28
|
-
|
35
|
+
close
|
29
36
|
end
|
30
37
|
|
31
38
|
# Clears the contents of the index.
|
32
39
|
def destroy
|
33
|
-
|
34
|
-
|
40
|
+
close if opened?
|
41
|
+
open(@path,:truncate => true)
|
35
42
|
end
|
36
43
|
|
37
44
|
# Simple iterator.
|
@@ -40,7 +47,9 @@ module Rod
|
|
40
47
|
@index.each do |key,value|
|
41
48
|
yield key,value
|
42
49
|
end
|
43
|
-
|
50
|
+
open(@path) unless opened?
|
51
|
+
_each_key do |key|
|
52
|
+
next if key.empty?
|
44
53
|
key = Marshal.load(key)
|
45
54
|
unless @index[key]
|
46
55
|
yield key,self[key]
|
@@ -51,10 +60,46 @@ module Rod
|
|
51
60
|
end
|
52
61
|
end
|
53
62
|
|
63
|
+
# Copies the index from the given +index+.
|
64
|
+
# The index have to cleared before being copied.
|
65
|
+
def copy(index)
|
66
|
+
close if opened?
|
67
|
+
open(@path,:truncate => true)
|
68
|
+
super(index)
|
69
|
+
end
|
70
|
+
|
54
71
|
protected
|
72
|
+
# Opens the index - initializes the index C structures
|
73
|
+
# and the cache.
|
74
|
+
# Options:
|
75
|
+
# * +:truncate+ - clears the contents of the index
|
76
|
+
# * +:create+ - creates the index if it doesn't exist
|
77
|
+
def open(path,options={})
|
78
|
+
raise RodException.new("The index #{@path} is already opened!") if opened?
|
79
|
+
_open(path,options)
|
80
|
+
@opened = true
|
81
|
+
@index = {} if @index.nil?
|
82
|
+
end
|
83
|
+
|
84
|
+
# Closes the disk - frees the C structure and clears the cache.
|
85
|
+
def close
|
86
|
+
return unless opened?
|
87
|
+
_close()
|
88
|
+
@opened = false
|
89
|
+
@index.clear
|
90
|
+
end
|
91
|
+
|
92
|
+
# Checks if the index is opened.
|
93
|
+
def opened?
|
94
|
+
@opened
|
95
|
+
end
|
96
|
+
|
97
|
+
# Returns a value of the index for a given +key+.
|
55
98
|
def get(key)
|
56
99
|
return @index[key] if @index.has_key?(key)
|
57
100
|
begin
|
101
|
+
open(@path) unless opened?
|
102
|
+
key = key.encode("utf-8") if key.is_a?(String)
|
58
103
|
value = _get(Marshal.dump(key))
|
59
104
|
rescue Rod::KeyMissing => ex
|
60
105
|
value = nil
|
@@ -62,11 +107,12 @@ module Rod
|
|
62
107
|
@index[key] = value
|
63
108
|
end
|
64
109
|
|
110
|
+
# Sets the +value+ for the +key+ in the internal cache.
|
65
111
|
def set(key,value)
|
66
112
|
@index[key] = value
|
67
113
|
end
|
68
114
|
|
69
|
-
|
115
|
+
# C definition of the RodException.
|
70
116
|
def self.rod_exception
|
71
117
|
str =<<-END
|
72
118
|
|VALUE rodException(){
|
@@ -78,6 +124,7 @@ module Rod
|
|
78
124
|
str.margin
|
79
125
|
end
|
80
126
|
|
127
|
+
# C definition of the index struct.
|
81
128
|
def self.entry_struct
|
82
129
|
str =<<-END
|
83
130
|
|typedef struct rod_entry {
|
@@ -88,33 +135,24 @@ module Rod
|
|
88
135
|
str.margin
|
89
136
|
end
|
90
137
|
|
138
|
+
# Converts the key to the C representation.
|
91
139
|
def self.convert_key
|
92
140
|
str =<<-END
|
93
|
-
|
|
141
|
+
|void _convert_key(VALUE key, DBT *db_key_p){
|
94
142
|
| long int_key;
|
95
143
|
| double float_key;
|
96
144
|
| DBT db_key;
|
97
145
|
|
|
146
|
+
| db_key = *db_key_p;
|
98
147
|
| memset(&db_key, 0, sizeof(DBT));
|
99
|
-
|
|
100
|
-
|
|
101
|
-
| db_key.data = &int_key;
|
102
|
-
| db_key.size = sizeof(long);
|
103
|
-
| } else if(rb_obj_is_kind_of(key,rb_cFloat)){
|
104
|
-
| float_key = NUM2DBL(key);
|
105
|
-
| db_key.data = &float_key;
|
106
|
-
| db_key.size = sizeof(double);
|
107
|
-
| } else {
|
108
|
-
| db_key.data = RSTRING_PTR(key);
|
109
|
-
| db_key.size = RSTRING_LEN(key);
|
110
|
-
| }
|
111
|
-
| // is it legal?
|
112
|
-
| return db_key;
|
148
|
+
| db_key.data = RSTRING_PTR(key);
|
149
|
+
| db_key.size = RSTRING_LEN(key);
|
113
150
|
|}
|
114
151
|
END
|
115
152
|
str.margin
|
116
153
|
end
|
117
154
|
|
155
|
+
# The C definition of the KeyMissing exception.
|
118
156
|
def self.key_missing_exception
|
119
157
|
str =<<-END
|
120
158
|
|VALUE keyMissingException(){
|
@@ -191,8 +229,34 @@ module Rod
|
|
191
229
|
builder.c(str.margin)
|
192
230
|
|
193
231
|
str =<<-END
|
194
|
-
|void
|
232
|
+
|void _each_key(){
|
233
|
+
| VALUE handle;
|
234
|
+
| DB *db_pointer;
|
235
|
+
| DBC *cursor;
|
236
|
+
| DBT db_key, db_value;
|
237
|
+
| int return_value;
|
238
|
+
| rod_entry_struct *entry;
|
239
|
+
| VALUE key;
|
195
240
|
|
|
241
|
+
| handle = rb_iv_get(self,"@handle");
|
242
|
+
| Data_Get_Struct(handle,DB,db_pointer);
|
243
|
+
| if(db_pointer != NULL){
|
244
|
+
| db_pointer->cursor(db_pointer,NULL,&cursor,0);
|
245
|
+
| memset(&db_key, 0, sizeof(DBT));
|
246
|
+
| memset(&db_value, 0, sizeof(DBT));
|
247
|
+
| db_key.flags = DB_DBT_MALLOC;
|
248
|
+
| while((return_value = cursor->get(cursor, &db_key, &db_value, DB_NEXT)) == 0){
|
249
|
+
| key = rb_str_new((char *)db_key.data,db_key.size);
|
250
|
+
| free(db_key.data);
|
251
|
+
| rb_yield(key);
|
252
|
+
| }
|
253
|
+
| if(return_value != DB_NOTFOUND){
|
254
|
+
| rb_raise(rodException(),"%s",db_strerror(return_value));
|
255
|
+
| }
|
256
|
+
| cursor->close(cursor);
|
257
|
+
| } else {
|
258
|
+
| rb_raise(rodException(),"DB handle is NULL\\n");
|
259
|
+
| }
|
196
260
|
|}
|
197
261
|
END
|
198
262
|
builder.c(str.margin)
|
@@ -210,10 +274,14 @@ module Rod
|
|
210
274
|
| Data_Get_Struct(handle,DB,db_pointer);
|
211
275
|
| if(db_pointer != NULL){
|
212
276
|
| memset(&db_value, 0, sizeof(DBT));
|
213
|
-
| db_key = _convert_key(key);
|
214
277
|
| db_value.data = &entry;
|
215
278
|
| db_value.ulen = sizeof(rod_entry_struct);
|
216
279
|
| db_value.flags = DB_DBT_USERMEM;
|
280
|
+
|
|
281
|
+
| memset(&db_key, 0, sizeof(DBT));
|
282
|
+
| db_key.data = RSTRING_PTR(key);
|
283
|
+
| db_key.size = RSTRING_LEN(key);
|
284
|
+
|
|
217
285
|
| return_value = db_pointer->get(db_pointer, NULL, &db_key, &db_value, 0);
|
218
286
|
| if(return_value == DB_NOTFOUND){
|
219
287
|
| rb_raise(keyMissingException(),"%s",db_strerror(return_value));
|
@@ -243,16 +311,18 @@ module Rod
|
|
243
311
|
|
|
244
312
|
| handle = rb_iv_get(self,"@handle");
|
245
313
|
| Data_Get_Struct(handle,DB,db_pointer);
|
314
|
+
| memset(&db_key, 0, sizeof(DBT));
|
315
|
+
| db_key.data = RSTRING_PTR(key);
|
316
|
+
| db_key.size = RSTRING_LEN(key);
|
246
317
|
| memset(&db_value, 0, sizeof(DBT));
|
247
318
|
| entry.offset = offset;
|
248
319
|
| entry.size = size;
|
249
|
-
| db_key = _convert_key(key);
|
250
320
|
| db_value.data = &entry;
|
251
321
|
| db_value.size = sizeof(rod_entry_struct);
|
252
322
|
| if(db_pointer != NULL){
|
253
323
|
| return_value = db_pointer->put(db_pointer, NULL, &db_key, &db_value, 0);
|
254
324
|
| if(return_value != 0){
|
255
|
-
| rb_raise(
|
325
|
+
| rb_raise(rodException(),"%s",db_strerror(return_value));
|
256
326
|
| }
|
257
327
|
| } else {
|
258
328
|
| rb_raise(rodException(),"DB handle is NULL\\n");
|
@@ -27,7 +27,11 @@ module Rod
|
|
27
27
|
@buckets.each do |bucket_number,hash|
|
28
28
|
File.open(path_for(bucket_number),"w") do |out|
|
29
29
|
proxy_index = {}
|
30
|
-
hash.
|
30
|
+
hash.each_key do |key|
|
31
|
+
col = self[key]
|
32
|
+
col.save
|
33
|
+
proxy_index[key] = [col.offset,col.size]
|
34
|
+
end
|
31
35
|
out.puts(Marshal.dump(proxy_index))
|
32
36
|
end
|
33
37
|
end
|
data/lib/rod/model.rb
CHANGED
@@ -8,6 +8,7 @@ module Rod
|
|
8
8
|
class Model < AbstractModel
|
9
9
|
include ActiveModel::Validations
|
10
10
|
include ActiveModel::Dirty
|
11
|
+
extend Utils
|
11
12
|
extend Enumerable
|
12
13
|
|
13
14
|
# A list of updaters that has to be notified when the +rod_id+
|
@@ -259,11 +260,11 @@ module Rod
|
|
259
260
|
if properties[property][:index].nil?
|
260
261
|
raise RodException.new("Property '#{property}' doesn't have an index!")
|
261
262
|
end
|
262
|
-
index_for(property,properties[property]).destroy
|
263
|
-
instance_variable_set("@#{property}_index",nil)
|
264
263
|
index = index_for(property,properties[property])
|
265
|
-
|
264
|
+
index.destroy
|
265
|
+
self.each.with_index do |object,position|
|
266
266
|
index[object.send(property)] << object
|
267
|
+
report_progress(position,self.count) if $ROD_DEBUG
|
267
268
|
end
|
268
269
|
end
|
269
270
|
|
@@ -476,7 +477,19 @@ module Rod
|
|
476
477
|
old_metadata = self.metadata
|
477
478
|
old_metadata.merge!({:superclass => old_metadata[:superclass].sub(LEGACY_RE,"")})
|
478
479
|
new_class = self.name.sub(LEGACY_RE,"").constantize
|
479
|
-
|
480
|
+
if new_class.compatible?(old_metadata)
|
481
|
+
backup_path = self.path_for_data(database.path)
|
482
|
+
new_path = new_class.path_for_data(database.path)
|
483
|
+
puts "Copying #{backup_path} to #{new_path}" if $ROD_DEBUG
|
484
|
+
FileUtils.cp(backup_path,new_path)
|
485
|
+
new_class.indexed_properties.each do |name,options|
|
486
|
+
backup_path = self.index_for(name,options).path
|
487
|
+
new_path = new_class.index_for(name,options).path
|
488
|
+
puts "Copying #{backup_path} to #{new_path}" if $ROD_DEBUG
|
489
|
+
FileUtils.cp(backup_path,new_path)
|
490
|
+
end
|
491
|
+
return
|
492
|
+
end
|
480
493
|
database.send(:allocate_space,new_class)
|
481
494
|
|
482
495
|
puts "Migrating #{new_class}" if $ROD_DEBUG
|
@@ -509,22 +522,26 @@ module Rod
|
|
509
522
|
old_object.send("_#{name}_length",position+1))
|
510
523
|
new_object.send("_#{name}_offset=",position+1,
|
511
524
|
old_object.send("_#{name}_offset",position+1))
|
525
|
+
report_progress(position,self.count) if $ROD_DEBUG
|
512
526
|
end
|
513
527
|
else
|
514
528
|
self.count.times do |position|
|
515
529
|
new_object.send("_#{name}=",position + 1,
|
516
530
|
old_object.send("_#{name}",position + 1))
|
531
|
+
report_progress(position,self.count) if $ROD_DEBUG
|
517
532
|
end
|
518
533
|
end
|
519
534
|
elsif self.singular_association?(name)
|
520
535
|
self.count.times do |position|
|
521
536
|
new_object.send("_#{name}=",position + 1,
|
522
537
|
old_object.send("_#{name}",position + 1))
|
538
|
+
report_progress(position,self.count) if $ROD_DEBUG
|
523
539
|
end
|
524
540
|
if options[:polymorphic]
|
525
541
|
self.count.times do |position|
|
526
542
|
new_object.send("_#{name}__class=",position + 1,
|
527
543
|
old_object.send("_#{name}__class",position + 1))
|
544
|
+
report_progress(position,self.count) if $ROD_DEBUG
|
528
545
|
end
|
529
546
|
end
|
530
547
|
else
|
@@ -533,9 +550,10 @@ module Rod
|
|
533
550
|
old_object.send("_#{name}_count",position + 1))
|
534
551
|
new_object.send("_#{name}_offset=",position + 1,
|
535
552
|
old_object.send("_#{name}_offset",position + 1))
|
553
|
+
report_progress(position,self.count) if $ROD_DEBUG
|
536
554
|
end
|
537
555
|
end
|
538
|
-
puts "done" if $ROD_DEBUG
|
556
|
+
puts " done" if $ROD_DEBUG
|
539
557
|
end
|
540
558
|
# Migrate the indices.
|
541
559
|
new_class.indexed_properties.each do |name,options|
|
@@ -544,14 +562,18 @@ module Rod
|
|
544
562
|
if old_index_type.nil?
|
545
563
|
print "- building index #{options[:index]} for '#{name}'... " if $ROD_DEBUG
|
546
564
|
new_class.rebuild_index(name)
|
547
|
-
puts "done" if $ROD_DEBUG
|
565
|
+
puts " done" if $ROD_DEBUG
|
566
|
+
elsif options[:index] == old_index_type
|
567
|
+
backup_path = self.index_for(name,options).path
|
568
|
+
new_path = new_class.index_for(name,options).path
|
569
|
+
puts "Copying #{backup_path} to #{new_path}" if $ROD_DEBUG
|
570
|
+
FileUtils.cp(backup_path,new_path)
|
548
571
|
else
|
549
|
-
# TODO if index is the same, its file should be copied
|
550
572
|
print "- copying index #{options[:index]} for '#{name}'... " if $ROD_DEBUG
|
551
573
|
new_index = new_class.index_for(name,options)
|
552
574
|
old_index = index_for(name,self.properties[name])
|
553
575
|
new_index.copy(old_index)
|
554
|
-
puts "done" if $ROD_DEBUG
|
576
|
+
puts " done" if $ROD_DEBUG
|
555
577
|
end
|
556
578
|
end
|
557
579
|
end
|
@@ -978,7 +1000,7 @@ module Rod
|
|
978
1000
|
end
|
979
1001
|
value = database.read_string(length, offset, options)
|
980
1002
|
if options[:type] == :object
|
981
|
-
value = Marshal.load(value)
|
1003
|
+
value = Marshal.load(value) rescue nil
|
982
1004
|
end
|
983
1005
|
# caching Ruby representation
|
984
1006
|
# don't use writer - avoid change tracking
|
@@ -1085,6 +1107,7 @@ module Rod
|
|
1085
1107
|
# setter
|
1086
1108
|
define_method("#{name}=") do |value|
|
1087
1109
|
proxy = send(name)
|
1110
|
+
proxy.clear
|
1088
1111
|
value.each do |object|
|
1089
1112
|
proxy << object
|
1090
1113
|
end
|
data/lib/rod/utils.rb
CHANGED
@@ -25,5 +25,20 @@ module Rod
|
|
25
25
|
def remove_files_but(name)
|
26
26
|
remove_files(name.sub(INLINE_PATTERN_RE,"*"),name)
|
27
27
|
end
|
28
|
+
|
29
|
+
# Reports progress of long-running operation.
|
30
|
+
# The +index+ is the current operation's step
|
31
|
+
# of +count+ steps.
|
32
|
+
def report_progress(index,count)
|
33
|
+
step = (count.to_f/50).to_i
|
34
|
+
return if step == 0
|
35
|
+
if index % step == 0
|
36
|
+
if index % (5 * step) == 0
|
37
|
+
print "#{(index / (5 * step) * 10)}%"
|
38
|
+
else
|
39
|
+
print "."
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
28
43
|
end
|
29
44
|
end
|
data/tests/migration_create.rb
CHANGED
data/tests/migration_migrate.rb
CHANGED
data/tests/migration_model1.rb
CHANGED
data/tests/migration_model2.rb
CHANGED
data/tests/migration_verify.rb
CHANGED
@@ -8,9 +8,17 @@ Rod::Database.development_mode = true
|
|
8
8
|
Database.instance.open_database("tmp/migration")
|
9
9
|
|
10
10
|
count = (ARGV[0] || 10).to_i
|
11
|
+
key_count = 0
|
12
|
+
User.index_for(:street,:index => :hash).each do |key,proxy|
|
13
|
+
key_count += 1
|
14
|
+
proxy.to_a.should == User.find_all_by_street(key).to_a
|
15
|
+
proxy.count.should > 0
|
16
|
+
end
|
17
|
+
key_count.should > 0
|
11
18
|
count.times do |index|
|
12
19
|
user1 = User[index*2]
|
13
20
|
user1.should_not == nil
|
21
|
+
user1.name.should == "John#{index}"
|
14
22
|
user = User.find_by_name("John#{index}")
|
15
23
|
user.should == user1
|
16
24
|
users = User.find_all_by_city("City#{index}")
|
@@ -41,7 +49,7 @@ count.times do |index|
|
|
41
49
|
user = User.find_by_name("Amanda#{index}")
|
42
50
|
user.should == user2
|
43
51
|
user = User.find_by_city("Bigcity#{index}")
|
44
|
-
|
52
|
+
user.should == user2
|
45
53
|
user = User.find_by_street("Small street#{index}")
|
46
54
|
user.should == user2
|
47
55
|
user2.name.should == "Amanda#{index}"
|
@@ -72,4 +80,10 @@ users.size.should == 2
|
|
72
80
|
users[0].should == User[0]
|
73
81
|
users[1].should == User[1]
|
74
82
|
|
83
|
+
house = House.find_by_name("cottage house")
|
84
|
+
house.should_not == nil
|
85
|
+
house.name.should == "cottage house"
|
86
|
+
house = House.find_by_name("doesn't exist")
|
87
|
+
house.should == nil
|
88
|
+
|
75
89
|
Database.instance.close_database
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: rod
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.7.
|
5
|
+
version: 0.7.1
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Aleksander Pohl
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2011-
|
13
|
+
date: 2011-11-01 00:00:00 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: RubyInline
|