rod 0.7.1 → 0.7.2
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/.travis.yml +1 -1
- data/README.rdoc +38 -10
- data/Rakefile +20 -9
- data/changelog.txt +25 -0
- data/contributors.txt +1 -0
- data/data/backward/0.7.0/_join_element.dat +0 -0
- data/data/backward/0.7.0/_polymorphic_join_element.dat +0 -0
- data/data/backward/0.7.0/char.dat +0 -0
- data/data/backward/0.7.0/database.yml +58 -0
- data/data/backward/0.7.0/rod_test__automobile.dat +0 -0
- data/data/backward/0.7.0/rod_test__caveman.dat +0 -0
- data/data/backward/0.7.0/rod_test__dog.dat +0 -0
- data/data/backward/0.7.0/rod_test__test_model.dat +0 -0
- data/data/portability/_join_element.dat +0 -0
- data/data/portability/_polymorphic_join_element.dat +0 -0
- data/data/portability/char.dat +0 -0
- data/data/portability/database.yml +49 -0
- data/data/portability/rod_test__automobile.dat +0 -0
- data/data/portability/rod_test__caveman.dat +0 -0
- data/data/portability/rod_test__dog.dat +0 -0
- data/data/portability/rod_test__test_model.dat +0 -0
- data/features/backward.feature +33 -0
- data/features/basic.feature +3 -0
- data/features/collection_proxy.feature +95 -0
- data/features/flat_indexing.feature +44 -2
- data/features/hash_indexing.feature +63 -9
- data/features/portability.feature +72 -0
- data/features/segmented_indexing.feature +45 -2
- data/features/steps/collection_proxy.rb +1 -1
- data/features/steps/model.rb +48 -5
- data/features/steps/rod.rb +15 -16
- data/lib/rod.rb +11 -1
- data/lib/rod/abstract_database.rb +52 -42
- data/lib/rod/berkeley/collection_proxy.rb +96 -0
- data/lib/rod/berkeley/database.rb +337 -0
- data/lib/rod/berkeley/environment.rb +209 -0
- data/lib/rod/berkeley/sequence.rb +222 -0
- data/lib/rod/berkeley/transaction.rb +233 -0
- data/lib/rod/collection_proxy.rb +76 -1
- data/lib/rod/constants.rb +3 -2
- data/lib/rod/database.rb +127 -14
- data/lib/rod/index/base.rb +12 -3
- data/lib/rod/index/hash_index.rb +295 -70
- data/lib/rod/index/segmented_index.rb +3 -0
- data/lib/rod/model.rb +154 -531
- data/lib/rod/property/base.rb +190 -0
- data/lib/rod/property/field.rb +258 -0
- data/lib/rod/property/plural_association.rb +145 -0
- data/lib/rod/property/singular_association.rb +139 -0
- data/rod.gemspec +6 -4
- data/spec/berkeley/database.rb +83 -0
- data/spec/berkeley/environment.rb +58 -0
- data/spec/berkeley/sequence.rb +101 -0
- data/spec/berkeley/transaction.rb +92 -0
- data/spec/collection_proxy.rb +38 -0
- data/spec/database.rb +36 -0
- data/spec/model.rb +26 -0
- data/spec/property/base.rb +73 -0
- data/spec/property/field.rb +244 -0
- data/spec/property/plural_association.rb +67 -0
- data/spec/property/singular_association.rb +65 -0
- data/tests/class_compatibility_create.rb +2 -2
- data/tests/eff1_test.rb +1 -1
- data/tests/eff2_test.rb +1 -1
- data/tests/full_runs.rb +1 -1
- data/tests/generate_classes_create.rb +14 -14
- data/tests/migration_create.rb +47 -47
- data/tests/migration_verify.rb +1 -1
- data/tests/missing_class_create.rb +6 -6
- data/tests/properties_order_create.rb +4 -4
- data/tests/read_on_create.rb +33 -34
- data/tests/save_struct.rb +40 -39
- data/tests/unit/database.rb +1 -1
- data/tests/unit/model_tests.rb +73 -65
- metadata +71 -15
- data/tests/unit/model.rb +0 -36
data/lib/rod/model.rb
CHANGED
@@ -70,22 +70,22 @@ module Rod
|
|
70
70
|
end
|
71
71
|
# The default values doesn't have to be persisted, since they
|
72
72
|
# are returned by default by the accessors.
|
73
|
-
self.changed.each do |
|
74
|
-
property = property.to_sym
|
75
|
-
if
|
73
|
+
self.changed.each do |property_name|
|
74
|
+
property = self.class.property(property_name.to_sym)
|
75
|
+
if property.field?
|
76
76
|
# store field value
|
77
77
|
update_field(property)
|
78
|
-
elsif
|
78
|
+
elsif property.singular?
|
79
79
|
# store singular association value
|
80
|
-
update_singular_association(property,send(
|
80
|
+
update_singular_association(property,send(property_name))
|
81
81
|
else
|
82
82
|
# Plural associations are not tracked.
|
83
83
|
raise RodException.new("Invalid changed property #{self.class}##{property}'")
|
84
84
|
end
|
85
85
|
end
|
86
86
|
# store plural associations in the DB
|
87
|
-
self.class.plural_associations.each do |property
|
88
|
-
collection = send(property)
|
87
|
+
self.class.plural_associations.each do |property|
|
88
|
+
collection = send(property.name)
|
89
89
|
offset = collection.save
|
90
90
|
update_count_and_offset(property,collection.size,offset)
|
91
91
|
end
|
@@ -112,9 +112,9 @@ module Rod
|
|
112
112
|
|
113
113
|
# Default implementation of +inspect+.
|
114
114
|
def inspect
|
115
|
-
fields = self.class.fields.map{|
|
116
|
-
singular = self.class.singular_associations.map{|
|
117
|
-
plural = self.class.plural_associations.map{|
|
115
|
+
fields = self.class.fields.map{|p| "#{p.name}:#{self.send(p.name)}"}.join(",")
|
116
|
+
singular = self.class.singular_associations.map{|p| "#{p.name}:#{self.send(p.name).class}"}.join(",")
|
117
|
+
plural = self.class.plural_associations.map{|p| "#{p.name}:#{self.send(p.name).size}"}.join(",")
|
118
118
|
"#{self.class}:<#{fields}><#{singular}><#{plural}>"
|
119
119
|
end
|
120
120
|
|
@@ -127,11 +127,9 @@ module Rod
|
|
127
127
|
# has_one relationships values. This is required by ActiveModel::Dirty.
|
128
128
|
def attributes
|
129
129
|
result = {}
|
130
|
-
self.class.
|
131
|
-
|
132
|
-
|
133
|
-
self.class.singular_associations.each do |name,options|
|
134
|
-
result[name.to_s] = self.send(name)
|
130
|
+
self.class.properties.each do |property|
|
131
|
+
next if property.association? && property.plural?
|
132
|
+
result[property.name.to_s] = self.send(property.name)
|
135
133
|
end
|
136
134
|
result
|
137
135
|
end
|
@@ -161,63 +159,40 @@ module Rod
|
|
161
159
|
# Returns n-th (+index+) object of this class stored in the database.
|
162
160
|
# This call is scope-checked.
|
163
161
|
def self.[](index)
|
164
|
-
|
162
|
+
begin
|
165
163
|
get(index+1)
|
166
|
-
|
167
|
-
|
168
|
-
new("The index #{index} is out of the scope [0...#{self.count}] for #{self}")
|
164
|
+
rescue IndexError
|
165
|
+
nil
|
169
166
|
end
|
170
167
|
end
|
171
168
|
|
172
169
|
protected
|
173
170
|
# Sets the default values for fields.
|
174
171
|
def initialize_fields
|
175
|
-
self.class.fields.each do |
|
176
|
-
next if name ==
|
177
|
-
|
178
|
-
case options[:type]
|
179
|
-
when :integer
|
180
|
-
0
|
181
|
-
when :ulong
|
182
|
-
0
|
183
|
-
when :float
|
184
|
-
0.0
|
185
|
-
when :string
|
186
|
-
''
|
187
|
-
when :object
|
188
|
-
nil
|
189
|
-
else
|
190
|
-
raise InvalidArgument.new(options[:type],"field type")
|
191
|
-
end
|
192
|
-
send("#{name}=",value)
|
172
|
+
self.class.fields.each do |field|
|
173
|
+
next if field.name == :rod_id
|
174
|
+
send("#{field.name}=",field.default_value)
|
193
175
|
end
|
194
176
|
end
|
195
177
|
|
196
178
|
# A macro-style function used to indicate that given piece of data
|
197
|
-
# is stored in the database.
|
198
|
-
#
|
199
|
-
# * +:integer+
|
200
|
-
# * +:ulong+
|
201
|
-
# * +:float+
|
202
|
-
# * +:string+
|
203
|
-
# * +:object+ (value is marshaled durign storage, and unmarshaled during read)
|
204
|
-
# Options:
|
205
|
-
# * +:index+ builds an index for the field and might be:
|
206
|
-
# ** +:flat+ simple hash index (+true+ works as well for backwards compatiblity)
|
207
|
-
# ** +:segmented+ index split for 1001 pieces for shorter load times (only
|
208
|
-
# one piece is loaded on one look-up)
|
179
|
+
# is stored in the database. See Rod::Property::Field for valid
|
180
|
+
# types and options.
|
209
181
|
#
|
210
182
|
# Warning!
|
211
|
-
# rod_id is a predefined field
|
183
|
+
# :rod_id is a predefined field
|
212
184
|
def self.field(name, type, options={})
|
213
|
-
|
214
|
-
|
215
|
-
|
185
|
+
if self.property(name)
|
186
|
+
raise InvalidArgument.new(name,"doubled property name")
|
187
|
+
end
|
188
|
+
self.fields << Property::Field.new(self,name,type,options)
|
189
|
+
# clear cached properties
|
190
|
+
@properties = nil
|
216
191
|
end
|
217
192
|
|
218
193
|
# A macro-style function used to indicate that instances of this
|
219
194
|
# class are associated with many instances of some other class. The
|
220
|
-
# name of the class is guessed from the
|
195
|
+
# name of the class is guessed from the property name, but you can
|
221
196
|
# change it via options.
|
222
197
|
# Options:
|
223
198
|
# * +:class_name+ - the name of the class (as String) associated
|
@@ -225,22 +200,25 @@ module Rod
|
|
225
200
|
# * +:polymorphic+ - if set to +true+ the association is polymorphic (allows to acess
|
226
201
|
# objects of different classes via this association)
|
227
202
|
def self.has_many(name, options={})
|
228
|
-
|
229
|
-
|
203
|
+
if self.property(name)
|
204
|
+
raise InvalidArgument.new(name,"doubled property name")
|
205
|
+
end
|
206
|
+
self.plural_associations << Property::PluralAssociation.new(self,name,options)
|
207
|
+
# clear cached properties
|
208
|
+
@properties = nil
|
230
209
|
end
|
231
210
|
|
232
211
|
# A macro-style function used to indicate that instances of this
|
233
212
|
# class are associated with one instance of some other class. The
|
234
|
-
# name of the class is guessed from the
|
235
|
-
# change it via options.
|
236
|
-
# Options:
|
237
|
-
# * +:class_name+ - the name of the class (as String) associated
|
238
|
-
# with this class
|
239
|
-
# * +:polymorphic+ - if set to +true+ the association is polymorphic (allows to acess
|
240
|
-
# objects of different classes via this association)
|
213
|
+
# name of the class is guessed from the property name, but you can
|
214
|
+
# change it via options. See Rod::Property::SingularAssociation for details.
|
241
215
|
def self.has_one(name, options={})
|
242
|
-
|
243
|
-
|
216
|
+
if self.property(name)
|
217
|
+
raise InvalidArgument.new(name,"doubled property name")
|
218
|
+
end
|
219
|
+
self.singular_associations << Property::SingularAssociation.new(self,name,options)
|
220
|
+
# clear cached properties
|
221
|
+
@properties = nil
|
244
222
|
end
|
245
223
|
|
246
224
|
# A macro-style function used to link the model with specific
|
@@ -257,13 +235,13 @@ module Rod
|
|
257
235
|
# Rebuild the index for given +property+. If the property
|
258
236
|
# doesn't have an index, an exception is raised.
|
259
237
|
def self.rebuild_index(property)
|
260
|
-
if
|
261
|
-
raise RodException.new("Property '#{property}' doesn't have an index!")
|
238
|
+
if property.options[:index].nil?
|
239
|
+
raise RodException.new("Property '#{property.name}' doesn't have an index!")
|
262
240
|
end
|
263
|
-
index =
|
241
|
+
index = property.index
|
264
242
|
index.destroy
|
265
243
|
self.each.with_index do |object,position|
|
266
|
-
index[object.send(property)] << object
|
244
|
+
index[object.send(property.name)] << object
|
267
245
|
report_progress(position,self.count) if $ROD_DEBUG
|
268
246
|
end
|
269
247
|
end
|
@@ -274,17 +252,17 @@ module Rod
|
|
274
252
|
|
275
253
|
public
|
276
254
|
# Update the DB information about the +object+ which
|
277
|
-
# is referenced via singular association
|
255
|
+
# is referenced via singular association +property+.
|
278
256
|
# If the object is not yet stored, a reference updater
|
279
257
|
# is registered to update the DB when it is stored.
|
280
|
-
def update_singular_association(
|
258
|
+
def update_singular_association(property, object)
|
281
259
|
if object.nil?
|
282
260
|
rod_id = 0
|
283
261
|
else
|
284
262
|
if object.new?
|
285
263
|
# There is a referenced object, but its rod_id is not set.
|
286
264
|
object.reference_updaters << ReferenceUpdater.
|
287
|
-
for_singular(self,
|
265
|
+
for_singular(self,property,self.database)
|
288
266
|
return
|
289
267
|
else
|
290
268
|
rod_id = object.rod_id
|
@@ -293,39 +271,28 @@ module Rod
|
|
293
271
|
# WARNING: don't use writer, since we don't want this change to be tracked
|
294
272
|
#object.instance_variable_set("@#{name}",nil)
|
295
273
|
end
|
296
|
-
send("_#{name}=", @rod_id, rod_id)
|
297
|
-
if
|
274
|
+
send("_#{property.name}=", @rod_id, rod_id)
|
275
|
+
if property.polymorphic?
|
298
276
|
class_id = object.nil? ? 0 : object.class.name_hash
|
299
|
-
send("_#{name}__class=", @rod_id, class_id)
|
277
|
+
send("_#{property.name}__class=", @rod_id, class_id)
|
300
278
|
end
|
301
279
|
end
|
302
280
|
|
303
|
-
# Updates in the DB the +count+ and +offset+ of elements for +
|
304
|
-
def update_count_and_offset(
|
305
|
-
send("_#{name}_count=",@rod_id,count)
|
306
|
-
send("_#{name}_offset=",@rod_id,offset)
|
281
|
+
# Updates in the DB the +count+ and +offset+ of elements for +property+ association.
|
282
|
+
def update_count_and_offset(property,count,offset)
|
283
|
+
send("_#{property.name}_count=",@rod_id,count)
|
284
|
+
send("_#{property.name}_offset=",@rod_id,offset)
|
307
285
|
end
|
308
286
|
|
309
|
-
# Updates in the DB the field +
|
310
|
-
def update_field(
|
311
|
-
if
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
value = Marshal.dump(value)
|
317
|
-
else
|
318
|
-
raise RodException.new("Unrecognised field type '#{self.class.fields[name][:type]}'!")
|
319
|
-
end
|
320
|
-
options = {}
|
321
|
-
if self.class.fields[name][:type] == :object
|
322
|
-
options[:skip_encoding] = true
|
323
|
-
end
|
324
|
-
length, offset = database.set_string(value,options)
|
325
|
-
send("_#{name}_length=",@rod_id,length)
|
326
|
-
send("_#{name}_offset=",@rod_id,offset)
|
287
|
+
# Updates in the DB the field +property+ to the actual value.
|
288
|
+
def update_field(property)
|
289
|
+
if property.variable_size?
|
290
|
+
value = property.dump(send(property.name))
|
291
|
+
length, offset = database.set_string(value)
|
292
|
+
send("_#{property.name}_length=",@rod_id,length)
|
293
|
+
send("_#{property.name}_offset=",@rod_id,offset)
|
327
294
|
else
|
328
|
-
send("_#{name}=",@rod_id,send(name))
|
295
|
+
send("_#{property.name}=",@rod_id,send(property.name))
|
329
296
|
end
|
330
297
|
end
|
331
298
|
|
@@ -344,29 +311,28 @@ module Rod
|
|
344
311
|
cache[object.rod_id] = object
|
345
312
|
|
346
313
|
# update class indices
|
347
|
-
indexed_properties.each do |property
|
314
|
+
indexed_properties.each do |property|
|
348
315
|
# WARNING: singular and plural associations with nil as value are not indexed!
|
349
316
|
# TODO #156 think over this constraint, write specs in persistence.feature
|
350
|
-
if field?
|
351
|
-
if stored_now || object.changes.has_key?(property)
|
317
|
+
if property.field? || property.singular?
|
318
|
+
if stored_now || object.changes.has_key?(property.name.to_s)
|
352
319
|
unless stored_now
|
353
|
-
old_value = object.changes[property][0]
|
354
|
-
|
320
|
+
old_value = object.changes[property.name.to_s][0]
|
321
|
+
property.index[old_value].delete(object)
|
355
322
|
end
|
356
|
-
new_value = object.send(property)
|
357
|
-
if field?
|
358
|
-
|
323
|
+
new_value = object.send(property.name)
|
324
|
+
if property.field? || new_value
|
325
|
+
property.index[new_value] << object
|
359
326
|
end
|
360
327
|
end
|
361
|
-
|
362
|
-
|
363
|
-
|
328
|
+
else
|
329
|
+
# plural
|
330
|
+
object.send(property.name).deleted.each do |deleted|
|
331
|
+
property.index[deleted].delete(object) unless deleted.nil?
|
364
332
|
end
|
365
|
-
object.send(property).added.each do |added|
|
366
|
-
|
333
|
+
object.send(property.name).added.each do |added|
|
334
|
+
property.index[added] << object unless added.nil?
|
367
335
|
end
|
368
|
-
else
|
369
|
-
raise RodException.new("Unknown property type for #{self}##{property}")
|
370
336
|
end
|
371
337
|
end
|
372
338
|
end
|
@@ -398,51 +364,45 @@ module Rod
|
|
398
364
|
# Returns the fields of this class.
|
399
365
|
def self.fields
|
400
366
|
if self == Rod::Model
|
401
|
-
@fields ||=
|
367
|
+
@fields ||= [Property::Field.new(self,:rod_id,:ulong)]
|
402
368
|
else
|
403
|
-
@fields ||= superclass.fields.
|
369
|
+
@fields ||= superclass.fields.map{|p| p.copy(self)}
|
404
370
|
end
|
405
371
|
end
|
406
372
|
|
407
373
|
# Returns singular associations of this class.
|
408
374
|
def self.singular_associations
|
409
375
|
if self == Rod::Model
|
410
|
-
@singular_associations ||=
|
376
|
+
@singular_associations ||= []
|
411
377
|
else
|
412
|
-
@singular_associations ||= superclass.singular_associations.
|
378
|
+
@singular_associations ||= superclass.singular_associations.map{|p| p.copy(self)}
|
413
379
|
end
|
414
380
|
end
|
415
381
|
|
416
382
|
# Returns plural associations of this class.
|
417
383
|
def self.plural_associations
|
418
384
|
if self == Rod::Model
|
419
|
-
@plural_associations ||=
|
385
|
+
@plural_associations ||= []
|
420
386
|
else
|
421
|
-
@plural_associations ||= superclass.plural_associations.
|
387
|
+
@plural_associations ||= superclass.plural_associations.map{|p| p.copy(self)}
|
422
388
|
end
|
423
389
|
end
|
424
390
|
|
425
391
|
# Metadata for the model class.
|
426
392
|
def self.metadata
|
427
393
|
meta = super
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
end
|
441
|
-
# plural_associations
|
442
|
-
has_many = meta[:has_many] = {} unless self.plural_associations.empty?
|
443
|
-
self.plural_associations.each do |name,options|
|
444
|
-
has_many[name] = {}
|
445
|
-
has_many[name][:options] = options
|
394
|
+
{:fields => :fields,
|
395
|
+
:has_one => :singular_associations,
|
396
|
+
:has_many => :plural_associations}.each do |type,method|
|
397
|
+
# fields
|
398
|
+
metadata = {}
|
399
|
+
self.send(method).each do |property|
|
400
|
+
next if property.field? && property.identifier?
|
401
|
+
metadata[property.name] = property.metadata
|
402
|
+
end
|
403
|
+
unless metadata.empty?
|
404
|
+
meta[type] = metadata
|
405
|
+
end
|
446
406
|
end
|
447
407
|
meta
|
448
408
|
end
|
@@ -456,12 +416,13 @@ module Rod
|
|
456
416
|
namespace.const_set(class_name.split("::")[-1],klass)
|
457
417
|
[:fields,:has_one,:has_many].each do |type|
|
458
418
|
(metadata[type] || []).each do |name,options|
|
419
|
+
next if superclass.property(name)
|
459
420
|
if type == :fields
|
460
|
-
internal_options = options
|
421
|
+
internal_options = options.dup
|
461
422
|
field_type = internal_options.delete(:type)
|
462
423
|
klass.send(:field,name,field_type,internal_options)
|
463
424
|
else
|
464
|
-
klass.send(type,name,options
|
425
|
+
klass.send(type,name,options)
|
465
426
|
end
|
466
427
|
end
|
467
428
|
end
|
@@ -482,9 +443,9 @@ module Rod
|
|
482
443
|
new_path = new_class.path_for_data(database.path)
|
483
444
|
puts "Copying #{backup_path} to #{new_path}" if $ROD_DEBUG
|
484
445
|
FileUtils.cp(backup_path,new_path)
|
485
|
-
new_class.indexed_properties.each do |
|
486
|
-
backup_path = self.
|
487
|
-
new_path =
|
446
|
+
new_class.indexed_properties.each do |property|
|
447
|
+
backup_path = self.property(property.name).index.path
|
448
|
+
new_path = property.index.path
|
488
449
|
puts "Copying #{backup_path} to #{new_path}" if $ROD_DEBUG
|
489
450
|
FileUtils.cp(backup_path,new_path)
|
490
451
|
end
|
@@ -494,9 +455,9 @@ module Rod
|
|
494
455
|
|
495
456
|
puts "Migrating #{new_class}" if $ROD_DEBUG
|
496
457
|
# Check for incompatible properties.
|
497
|
-
self.properties.each do |name,
|
498
|
-
next unless new_class.
|
499
|
-
difference =
|
458
|
+
self.properties.each do |name,property|
|
459
|
+
next unless new_class.property(name)
|
460
|
+
difference = property.difference(new_class.properties[name])
|
500
461
|
difference.delete(:index)
|
501
462
|
# Check if there are some options which we cannot migrate at the
|
502
463
|
# moment.
|
@@ -512,11 +473,13 @@ module Rod
|
|
512
473
|
# initialize prototype objects
|
513
474
|
old_object = self.new
|
514
475
|
new_object = new_class.new
|
515
|
-
self.properties.each do |
|
516
|
-
|
476
|
+
self.properties.each do |property|
|
477
|
+
# optimization
|
478
|
+
name = property.name.to_s
|
479
|
+
next unless new_class.property(name.to_sym)
|
517
480
|
print "- #{name}... " if $ROD_DEBUG
|
518
|
-
if
|
519
|
-
if
|
481
|
+
if property.field?
|
482
|
+
if property.variable_size?
|
520
483
|
self.count.times do |position|
|
521
484
|
new_object.send("_#{name}_length=",position+1,
|
522
485
|
old_object.send("_#{name}_length",position+1))
|
@@ -531,13 +494,13 @@ module Rod
|
|
531
494
|
report_progress(position,self.count) if $ROD_DEBUG
|
532
495
|
end
|
533
496
|
end
|
534
|
-
elsif
|
497
|
+
elsif property.singular?
|
535
498
|
self.count.times do |position|
|
536
499
|
new_object.send("_#{name}=",position + 1,
|
537
500
|
old_object.send("_#{name}",position + 1))
|
538
501
|
report_progress(position,self.count) if $ROD_DEBUG
|
539
502
|
end
|
540
|
-
if
|
503
|
+
if property.polymorphic?
|
541
504
|
self.count.times do |position|
|
542
505
|
new_object.send("_#{name}__class=",position + 1,
|
543
506
|
old_object.send("_#{name}__class",position + 1))
|
@@ -556,48 +519,28 @@ module Rod
|
|
556
519
|
puts " done" if $ROD_DEBUG
|
557
520
|
end
|
558
521
|
# Migrate the indices.
|
559
|
-
new_class.indexed_properties.each do |
|
522
|
+
new_class.indexed_properties.each do |property|
|
560
523
|
# Migrate to new options.
|
561
|
-
old_index_type = self.
|
524
|
+
old_index_type = self.property(property.name) && self.property(property.name).options[:index]
|
562
525
|
if old_index_type.nil?
|
563
|
-
print "- building index #{options[:index]} for '#{name}'... " if $ROD_DEBUG
|
564
|
-
new_class.rebuild_index(
|
526
|
+
print "- building index #{property.options[:index]} for '#{property.name}'... " if $ROD_DEBUG
|
527
|
+
new_class.rebuild_index(property)
|
565
528
|
puts " done" if $ROD_DEBUG
|
566
|
-
elsif options[:index] == old_index_type
|
567
|
-
backup_path = self.
|
568
|
-
new_path =
|
529
|
+
elsif property.options[:index] == old_index_type
|
530
|
+
backup_path = self.property(property.name).index.path
|
531
|
+
new_path = property.index.path
|
569
532
|
puts "Copying #{backup_path} to #{new_path}" if $ROD_DEBUG
|
570
533
|
FileUtils.cp(backup_path,new_path)
|
571
534
|
else
|
572
|
-
print "- copying
|
573
|
-
new_index =
|
574
|
-
old_index =
|
535
|
+
print "- copying #{property.options[:index]} index for '#{property.name}'... " if $ROD_DEBUG
|
536
|
+
new_index = property.index
|
537
|
+
old_index = self.property(property.name).index
|
575
538
|
new_index.copy(old_index)
|
576
539
|
puts " done" if $ROD_DEBUG
|
577
540
|
end
|
578
541
|
end
|
579
542
|
end
|
580
543
|
|
581
|
-
# Returns the difference between +options1+ and +options2+.
|
582
|
-
def self.options_difference(options1,options2)
|
583
|
-
old_options = {}
|
584
|
-
options1.each{|k,v| old_options[k] = v.to_s.sub(LEGACY_RE,"")}
|
585
|
-
new_options = {}
|
586
|
-
options2.each{|k,v| new_options[k] = v.to_s}
|
587
|
-
differences = {}
|
588
|
-
old_options.each do |option,value|
|
589
|
-
if new_options[option] != value
|
590
|
-
differences[option] = [value,new_options[option]]
|
591
|
-
end
|
592
|
-
end
|
593
|
-
new_options.each do |option,value|
|
594
|
-
if old_options[option] != value && !differences.has_key?(option)
|
595
|
-
differences[option] = [old_options[option],value]
|
596
|
-
end
|
597
|
-
end
|
598
|
-
differences
|
599
|
-
end
|
600
|
-
|
601
544
|
protected
|
602
545
|
# The pointer to the mmaped table of C structs.
|
603
546
|
def self.rod_pointer
|
@@ -667,20 +610,6 @@ module Rod
|
|
667
610
|
self.class.database
|
668
611
|
end
|
669
612
|
|
670
|
-
# Checks if the name of the field or association is valid.
|
671
|
-
def self.ensure_valid_name(name)
|
672
|
-
if name.to_s.empty? || INVALID_NAMES.has_key?(name)
|
673
|
-
raise InvalidArgument.new(name,"field/association name")
|
674
|
-
end
|
675
|
-
end
|
676
|
-
|
677
|
-
# Checks if the type of the field is valid.
|
678
|
-
def self.ensure_valid_type(type)
|
679
|
-
unless TYPE_MAPPING.has_key?(type)
|
680
|
-
raise InvalidArgument.new(type,"field type")
|
681
|
-
end
|
682
|
-
end
|
683
|
-
|
684
613
|
# Returns the database this class is linked to.
|
685
614
|
# The database class is configured with the call to
|
686
615
|
# macro-style function +database_class+. This information
|
@@ -697,7 +626,6 @@ module Rod
|
|
697
626
|
end
|
698
627
|
|
699
628
|
# The object cache of this class.
|
700
|
-
# XXX consider moving it to the database.
|
701
629
|
def self.cache
|
702
630
|
@cache ||= Cache.new
|
703
631
|
end
|
@@ -769,49 +697,12 @@ module Rod
|
|
769
697
|
"#{relative_path}#{model_path}.dat"
|
770
698
|
end
|
771
699
|
|
772
|
-
# The name of the file or directory (for given +relative_path+), which the
|
773
|
-
# index of the +field+ of this class is stored in.
|
774
|
-
def self.path_for_index(relative_path,field)
|
775
|
-
"#{relative_path}#{model_path}_#{field}"
|
776
|
-
end
|
777
|
-
|
778
|
-
# Returns true if the type of the filed is string-like (i.e. stored as
|
779
|
-
# StringElement).
|
780
|
-
def self.string_field?(type)
|
781
|
-
string_types.include?(type)
|
782
|
-
end
|
783
|
-
|
784
|
-
# Types which are stored as strings.
|
785
|
-
def self.string_types
|
786
|
-
[:string, :object]
|
787
|
-
end
|
788
|
-
|
789
700
|
# The C structure representing this class.
|
790
701
|
def self.typedef_struct
|
791
702
|
result = <<-END
|
792
703
|
|typedef struct {
|
793
|
-
| \n#{self.
|
794
|
-
|
795
|
-
"| #{TYPE_MAPPING[options[:type]]} #{field};"
|
796
|
-
else
|
797
|
-
<<-SUBEND
|
798
|
-
| unsigned long #{field}_length;
|
799
|
-
| unsigned long #{field}_offset;
|
800
|
-
SUBEND
|
801
|
-
end
|
802
|
-
end.join("\n| \n") }
|
803
|
-
| #{singular_associations.map do |name, options|
|
804
|
-
result = "unsigned long #{name};"
|
805
|
-
if options[:polymorphic]
|
806
|
-
result += " unsigned long #{name}__class;"
|
807
|
-
end
|
808
|
-
result
|
809
|
-
end.join("\n| ")}
|
810
|
-
| \n#{plural_associations.map do |name, options|
|
811
|
-
result =
|
812
|
-
"| unsigned long #{name}_offset;\n"+
|
813
|
-
"| unsigned long #{name}_count;"
|
814
|
-
result
|
704
|
+
| \n#{self.properties.map do |property|
|
705
|
+
property.to_c_struct
|
815
706
|
end.join("\n| \n")}
|
816
707
|
|} #{struct_name()};
|
817
708
|
END
|
@@ -820,62 +711,11 @@ module Rod
|
|
820
711
|
|
821
712
|
# Prints the memory layout of the structure.
|
822
713
|
def self.layout
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
"| printf(\" size of '#{field}': %lu\\n\"," +
|
827
|
-
"(unsigned long)sizeof(#{TYPE_MAPPING[options[:type]]}));"
|
828
|
-
else
|
829
|
-
<<-SUBEND
|
830
|
-
| printf(" string '#{field}' length: %lu offset: %lu page: %lu\\n",
|
831
|
-
| (unsigned long)sizeof(unsigned long), (unsigned long)sizeof(unsigned long),
|
832
|
-
| (unsigned long)sizeof(unsigned long));
|
833
|
-
SUBEND
|
834
|
-
end
|
835
|
-
end.join("\n") }
|
836
|
-
| \n#{singular_associations.map do |name, options|
|
837
|
-
" printf(\" singular assoc '#{name}': %lu\\n\","+
|
838
|
-
"(unsigned long)sizeof(unsigned long));"
|
839
|
-
end.join("\n| ")}
|
840
|
-
| \n#{plural_associations.map do |name, options|
|
841
|
-
"| printf(\" plural assoc '#{name}' offset: %lu, count %lu\\n\",\n"+
|
842
|
-
"| (unsigned long)sizeof(unsigned long),(unsigned long)sizeof(unsigned long));"
|
843
|
-
end.join("\n| \n")}
|
844
|
-
END
|
845
|
-
result.margin
|
714
|
+
self.properties.map do |property|
|
715
|
+
property.layout
|
716
|
+
end.join("\n")
|
846
717
|
end
|
847
718
|
|
848
|
-
# Reads the value of a specified field of the C-structure.
|
849
|
-
def self.field_reader(name,result_type,builder)
|
850
|
-
str =<<-END
|
851
|
-
|#{result_type} _#{name}(unsigned long object_rod_id){
|
852
|
-
| if(object_rod_id == 0){
|
853
|
-
| rb_raise(rodException(), "Invalid object rod_id (0)");
|
854
|
-
| }
|
855
|
-
| VALUE klass = rb_funcall(self,rb_intern("class"),0);
|
856
|
-
| #{struct_name} * pointer = (#{struct_name} *)
|
857
|
-
| NUM2ULONG(rb_funcall(klass,rb_intern("rod_pointer"),0));
|
858
|
-
| return (pointer + object_rod_id - 1)->#{name};
|
859
|
-
|}
|
860
|
-
END
|
861
|
-
builder.c(str.margin)
|
862
|
-
end
|
863
|
-
|
864
|
-
# Writes the value of a specified field of the C-structure.
|
865
|
-
def self.field_writer(name,arg_type,builder)
|
866
|
-
str =<<-END
|
867
|
-
|void _#{name}_equals(unsigned long object_rod_id,#{arg_type} value){
|
868
|
-
| if(object_rod_id == 0){
|
869
|
-
| rb_raise(rodException(), "Invalid object rod_id (0)");
|
870
|
-
| }
|
871
|
-
| VALUE klass = rb_funcall(self,rb_intern("class"),0);
|
872
|
-
| #{struct_name} * pointer = (#{struct_name} *)
|
873
|
-
| NUM2ULONG(rb_funcall(klass,rb_intern("rod_pointer"),0));
|
874
|
-
| (pointer + object_rod_id - 1)->#{name} = value;
|
875
|
-
|}
|
876
|
-
END
|
877
|
-
builder.c(str.margin)
|
878
|
-
end
|
879
719
|
|
880
720
|
#########################################################################
|
881
721
|
# Generated methods
|
@@ -883,14 +723,15 @@ module Rod
|
|
883
723
|
|
884
724
|
# This code intializes the class. It adds C routines and dynamic Ruby accessors.
|
885
725
|
def self.build_structure
|
886
|
-
self.
|
887
|
-
|
888
|
-
instance_variable_set("@#{name}_index",nil)
|
889
|
-
end
|
726
|
+
self.indexed_properties.each do |property|
|
727
|
+
property.reset_index
|
890
728
|
end
|
891
729
|
return if @structure_built
|
892
730
|
|
893
731
|
inline(:C) do |builder|
|
732
|
+
builder.include '<byteswap.h>'
|
733
|
+
builder.include '<endian.h>'
|
734
|
+
builder.include '<stdint.h>'
|
894
735
|
builder.prefix(typedef_struct)
|
895
736
|
builder.prefix(Database.rod_exception)
|
896
737
|
if Database.development_mode
|
@@ -899,32 +740,8 @@ module Rod
|
|
899
740
|
builder.c_singleton("void __unused_method_#{rand(1000000)}(){}")
|
900
741
|
end
|
901
742
|
|
902
|
-
self.
|
903
|
-
|
904
|
-
field_reader(name,TYPE_MAPPING[options[:type]],builder)
|
905
|
-
field_writer(name,TYPE_MAPPING[options[:type]],builder)
|
906
|
-
else
|
907
|
-
field_reader("#{name}_length","unsigned long",builder)
|
908
|
-
field_reader("#{name}_offset","unsigned long",builder)
|
909
|
-
field_writer("#{name}_length","unsigned long",builder)
|
910
|
-
field_writer("#{name}_offset","unsigned long",builder)
|
911
|
-
end
|
912
|
-
end
|
913
|
-
|
914
|
-
singular_associations.each do |name, options|
|
915
|
-
field_reader(name,"unsigned long",builder)
|
916
|
-
field_writer(name,"unsigned long",builder)
|
917
|
-
if options[:polymorphic]
|
918
|
-
field_reader("#{name}__class","unsigned long",builder)
|
919
|
-
field_writer("#{name}__class","unsigned long",builder)
|
920
|
-
end
|
921
|
-
end
|
922
|
-
|
923
|
-
plural_associations.each do |name, options|
|
924
|
-
field_reader("#{name}_count","unsigned long",builder)
|
925
|
-
field_reader("#{name}_offset","unsigned long",builder)
|
926
|
-
field_writer("#{name}_count","unsigned long",builder)
|
927
|
-
field_writer("#{name}_offset","unsigned long",builder)
|
743
|
+
self.properties.each do |property|
|
744
|
+
property.define_c_accessors(builder)
|
928
745
|
end
|
929
746
|
|
930
747
|
str=<<-END
|
@@ -946,246 +763,52 @@ module Rod
|
|
946
763
|
|
947
764
|
attribute_methods = []
|
948
765
|
## accessors for fields, plural and singular relationships follow
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
field = field.to_s
|
953
|
-
# adding new private fields visible from Ruby
|
954
|
-
# they are lazily initialized based on the C representation
|
955
|
-
unless string_field?(options[:type])
|
956
|
-
private "_#{field}", "_#{field}="
|
957
|
-
else
|
958
|
-
private "_#{field}_length", "_#{field}_offset"
|
959
|
-
end
|
960
|
-
|
961
|
-
unless string_field?(options[:type])
|
962
|
-
# getter
|
963
|
-
define_method(field) do
|
964
|
-
value = instance_variable_get("@#{field}")
|
965
|
-
if value.nil?
|
966
|
-
if self.new?
|
967
|
-
value = nil
|
968
|
-
else
|
969
|
-
value = send("_#{field}",@rod_id)
|
970
|
-
end
|
971
|
-
instance_variable_set("@#{field}",value)
|
972
|
-
end
|
973
|
-
value
|
974
|
-
end
|
975
|
-
|
976
|
-
# setter
|
977
|
-
define_method("#{field}=") do |value|
|
978
|
-
old_value = send(field)
|
979
|
-
send("#{field}_will_change!") unless old_value == value
|
980
|
-
instance_variable_set("@#{field}",value)
|
981
|
-
value
|
982
|
-
end
|
983
|
-
else
|
984
|
-
# string-type fields
|
985
|
-
# getter
|
986
|
-
define_method(field) do
|
987
|
-
value = instance_variable_get("@#{field}")
|
988
|
-
if value.nil? # first call
|
989
|
-
if self.new?
|
990
|
-
return (options[:type] == :object ? nil : "")
|
991
|
-
else
|
992
|
-
length = send("_#{field}_length", @rod_id)
|
993
|
-
if length == 0
|
994
|
-
return (options[:type] == :object ? nil : "")
|
995
|
-
end
|
996
|
-
offset = send("_#{field}_offset", @rod_id)
|
997
|
-
read_options = {}
|
998
|
-
if options[:type] == :object
|
999
|
-
read_options[:skip_encoding] = true
|
1000
|
-
end
|
1001
|
-
value = database.read_string(length, offset, options)
|
1002
|
-
if options[:type] == :object
|
1003
|
-
value = Marshal.load(value) rescue nil
|
1004
|
-
end
|
1005
|
-
# caching Ruby representation
|
1006
|
-
# don't use writer - avoid change tracking
|
1007
|
-
instance_variable_set("@#{field}",value)
|
1008
|
-
end
|
1009
|
-
end
|
1010
|
-
value
|
1011
|
-
end
|
1012
|
-
|
1013
|
-
# setter
|
1014
|
-
define_method("#{field}=") do |value|
|
1015
|
-
old_value = send(field)
|
1016
|
-
send("#{field}_will_change!") unless old_value == value
|
1017
|
-
instance_variable_set("@#{field}",value)
|
1018
|
-
end
|
1019
|
-
end
|
1020
|
-
|
1021
|
-
end
|
1022
|
-
|
1023
|
-
singular_associations.each do |name, options|
|
1024
|
-
attribute_methods << name
|
1025
|
-
# optimization
|
1026
|
-
name = name.to_s
|
1027
|
-
private "_#{name}", "_#{name}="
|
1028
|
-
class_name =
|
1029
|
-
if options[:class_name]
|
1030
|
-
options[:class_name]
|
1031
|
-
else
|
1032
|
-
"#{self.scope_name}::#{name.camelcase}"
|
1033
|
-
end
|
1034
|
-
|
1035
|
-
#getter
|
1036
|
-
define_method(name) do
|
1037
|
-
value = instance_variable_get("@#{name}")
|
1038
|
-
if value.nil?
|
1039
|
-
return nil if self.new?
|
1040
|
-
rod_id = send("_#{name}",@rod_id)
|
1041
|
-
# the indices are shifted by 1, to leave 0 for nil
|
1042
|
-
if rod_id == 0
|
1043
|
-
value = nil
|
1044
|
-
else
|
1045
|
-
if options[:polymorphic]
|
1046
|
-
klass = Model.get_class(send("_#{name}__class",@rod_id))
|
1047
|
-
value = klass.find_by_rod_id(rod_id)
|
1048
|
-
else
|
1049
|
-
value = class_name.constantize.find_by_rod_id(rod_id)
|
1050
|
-
end
|
1051
|
-
end
|
1052
|
-
# avoid change tracking
|
1053
|
-
instance_variable_set("@#{name}",value)
|
1054
|
-
end
|
1055
|
-
value
|
1056
|
-
end
|
1057
|
-
|
1058
|
-
#setter
|
1059
|
-
define_method("#{name}=") do |value|
|
1060
|
-
old_value = send(name)
|
1061
|
-
send("#{name}_will_change!") unless old_value == value
|
1062
|
-
instance_variable_set("@#{name}", value)
|
1063
|
-
end
|
1064
|
-
end
|
1065
|
-
|
1066
|
-
plural_associations.each do |name, options|
|
1067
|
-
# optimization
|
1068
|
-
name = name.to_s
|
1069
|
-
class_name =
|
1070
|
-
if options[:class_name]
|
1071
|
-
options[:class_name]
|
1072
|
-
else
|
1073
|
-
"#{self.scope_name}::#{::English::Inflect.singular(name).camelcase}"
|
1074
|
-
end
|
1075
|
-
klass = options[:polymorphic] ? nil : class_name.constantize
|
1076
|
-
|
1077
|
-
# getter
|
1078
|
-
define_method("#{name}") do
|
1079
|
-
proxy = instance_variable_get("@#{name}")
|
1080
|
-
if proxy.nil?
|
1081
|
-
if self.new?
|
1082
|
-
count = 0
|
1083
|
-
offset = 0
|
1084
|
-
else
|
1085
|
-
count = self.send("_#{name}_count",@rod_id)
|
1086
|
-
offset = self.send("_#{name}_offset",@rod_id)
|
1087
|
-
end
|
1088
|
-
proxy = CollectionProxy.new(count,database,offset,klass)
|
1089
|
-
instance_variable_set("@#{name}", proxy)
|
1090
|
-
end
|
1091
|
-
proxy
|
1092
|
-
end
|
1093
|
-
|
1094
|
-
# count getter
|
1095
|
-
define_method("#{name}_count") do
|
1096
|
-
if (instance_variable_get("@#{name}") != nil)
|
1097
|
-
return instance_variable_get("@#{name}").count
|
1098
|
-
else
|
1099
|
-
if self.new?
|
1100
|
-
return 0
|
1101
|
-
else
|
1102
|
-
return send("_#{name}_count",@rod_id)
|
1103
|
-
end
|
1104
|
-
end
|
1105
|
-
end
|
1106
|
-
|
1107
|
-
# setter
|
1108
|
-
define_method("#{name}=") do |value|
|
1109
|
-
proxy = send(name)
|
1110
|
-
proxy.clear
|
1111
|
-
value.each do |object|
|
1112
|
-
proxy << object
|
1113
|
-
end
|
1114
|
-
proxy
|
766
|
+
properties.each do |property|
|
767
|
+
if property.field? || property.singular?
|
768
|
+
attribute_methods << property.name
|
1115
769
|
end
|
770
|
+
property.seal_c_accessors
|
771
|
+
property.define_getter
|
772
|
+
property.define_setter
|
773
|
+
property.define_finders
|
1116
774
|
end
|
1117
775
|
|
1118
776
|
# dirty tracking
|
1119
777
|
define_attribute_methods(attribute_methods)
|
1120
778
|
|
1121
|
-
# indices
|
1122
|
-
indexed_properties.each do |property,options|
|
1123
|
-
# optimization
|
1124
|
-
property = property.to_s
|
1125
|
-
(class << self; self; end).class_eval do
|
1126
|
-
# Find all objects with given +value+ of the +property+.
|
1127
|
-
define_method("find_all_by_#{property}") do |value|
|
1128
|
-
index_for(property,options)[value]
|
1129
|
-
end
|
1130
|
-
|
1131
|
-
# Find first object with given +value+ of the +property+.
|
1132
|
-
define_method("find_by_#{property}") do |value|
|
1133
|
-
index_for(property,options)[value][0]
|
1134
|
-
end
|
1135
|
-
end
|
1136
|
-
end
|
1137
779
|
@structure_built = true
|
1138
780
|
end
|
1139
781
|
|
1140
782
|
class << self
|
1141
783
|
# Fields, singular and plural associations.
|
1142
784
|
def properties
|
1143
|
-
self.fields
|
1144
|
-
end
|
1145
|
-
|
1146
|
-
# Returns (and caches) only properties which are indexed.
|
1147
|
-
def indexed_properties
|
1148
|
-
@indexed_properties ||= self.properties.select{|p,o| o[:index]}
|
1149
|
-
end
|
1150
|
-
|
1151
|
-
# Returns true if the +name+ (as symbol) is a name of a field.
|
1152
|
-
def field?(name)
|
1153
|
-
self.fields.keys.include?(name)
|
785
|
+
@properties ||= self.fields + self.singular_associations + self.plural_associations
|
1154
786
|
end
|
1155
787
|
|
1156
|
-
# Returns
|
1157
|
-
def
|
1158
|
-
|
788
|
+
# Returns the property with given +name+ or nil if it doesn't exist.
|
789
|
+
def property(name)
|
790
|
+
properties.find{|p| p.name == name}
|
1159
791
|
end
|
1160
792
|
|
1161
|
-
# Returns
|
1162
|
-
def
|
1163
|
-
self.
|
1164
|
-
end
|
1165
|
-
|
1166
|
-
# Read index for the +property+ with +options+ from the database.
|
1167
|
-
def index_for(property,options)
|
1168
|
-
index = instance_variable_get("@#{property}_index")
|
1169
|
-
if index.nil?
|
1170
|
-
path = path_for_index(database.path,property)
|
1171
|
-
index = Index::Base.create(path,self,options)
|
1172
|
-
instance_variable_set("@#{property}_index",index)
|
1173
|
-
end
|
1174
|
-
index
|
793
|
+
# Returns (and caches) only properties which are indexed.
|
794
|
+
def indexed_properties
|
795
|
+
@indexed_properties ||= self.properties.select{|p| p.options[:index]}
|
1175
796
|
end
|
1176
797
|
|
1177
798
|
private
|
1178
799
|
# Returns object of this class stored in the DB with given +rod_id+.
|
1179
|
-
# Warning! If wrong rod_id is specified it might cause segmentation fault
|
800
|
+
# Warning! If wrong rod_id is specified it might cause segmentation fault error!
|
1180
801
|
def get(rod_id)
|
1181
802
|
object = cache[rod_id]
|
1182
803
|
if object.nil?
|
804
|
+
if rod_id <= 0 || rod_id > self.count
|
805
|
+
raise IndexError.new("Invalid rod_id #{rod_id} for #{self}")
|
806
|
+
end
|
1183
807
|
object = self.new(rod_id)
|
1184
808
|
cache[rod_id] = object
|
1185
809
|
end
|
1186
810
|
object
|
1187
811
|
end
|
1188
|
-
|
1189
812
|
end
|
1190
813
|
end
|
1191
814
|
end
|