rod 0.7.0 → 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|