rod 0.6.0 → 0.6.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/README +26 -22
- data/Rakefile +18 -4
- data/changelog.txt +33 -0
- data/lib/rod.rb +2 -1
- data/lib/rod/abstract_database.rb +267 -74
- data/lib/rod/abstract_model.rb +66 -0
- data/lib/rod/cache.rb +81 -0
- data/lib/rod/collection_proxy.rb +43 -14
- data/lib/rod/constants.rb +8 -1
- data/lib/rod/database.rb +15 -7
- data/lib/rod/exception.rb +20 -0
- data/lib/rod/join_element.rb +3 -21
- data/lib/rod/model.rb +219 -57
- data/lib/rod/string_element.rb +3 -27
- data/rod.gemspec +0 -1
- metadata +10 -19
@@ -0,0 +1,66 @@
|
|
1
|
+
module Rod
|
2
|
+
# A base class for all classes stored in the DataBase
|
3
|
+
# (both user defined and DB defined).
|
4
|
+
class AbstractModel
|
5
|
+
# Empty string.
|
6
|
+
def self.typedef_struct
|
7
|
+
raise RodException.new("#typdef_struct called for AbstractModel")
|
8
|
+
end
|
9
|
+
|
10
|
+
# C-struct name of the model.
|
11
|
+
def self.struct_name
|
12
|
+
raise RodException.new("#typdef_struct called for AbstractModel")
|
13
|
+
end
|
14
|
+
|
15
|
+
# Path to the file storing the model data.
|
16
|
+
def self.path_for_data(path)
|
17
|
+
"#{path}#{self.struct_name}.dat"
|
18
|
+
end
|
19
|
+
|
20
|
+
# Default implementation prints nothing.
|
21
|
+
def self.layout
|
22
|
+
end
|
23
|
+
|
24
|
+
# By default there are no fields.
|
25
|
+
def self.fields
|
26
|
+
[]
|
27
|
+
end
|
28
|
+
|
29
|
+
# By default nothing is built.
|
30
|
+
def self.build_structure
|
31
|
+
end
|
32
|
+
|
33
|
+
# Default cache for models.
|
34
|
+
def self.cache
|
35
|
+
@cache ||= Cache.new
|
36
|
+
end
|
37
|
+
|
38
|
+
# There are no indexed properties.
|
39
|
+
def self.indexed_properties
|
40
|
+
[]
|
41
|
+
end
|
42
|
+
|
43
|
+
# By default properties are empty.
|
44
|
+
def self.properties
|
45
|
+
[]
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns meta-data (in the form of a hash) for the model.
|
49
|
+
def self.metadata(database)
|
50
|
+
meta = {}
|
51
|
+
meta[:count] = database.count(self)
|
52
|
+
meta[:superclass] = self.superclass.name
|
53
|
+
meta
|
54
|
+
end
|
55
|
+
|
56
|
+
# Checks if the +metadata+ are compatible with the class definition.
|
57
|
+
def self.compatible?(metadata,database)
|
58
|
+
self_metadata = self.metadata(database)
|
59
|
+
other_metadata = metadata.dup
|
60
|
+
self_metadata.delete(:count)
|
61
|
+
other_metadata.delete(:count)
|
62
|
+
self_metadata == other_metadata
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
data/lib/rod/cache.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
module Rod
|
4
|
+
class Cache
|
5
|
+
class InternalMap
|
6
|
+
include Singleton
|
7
|
+
attr_reader :mutex
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@mutex = Mutex.new
|
11
|
+
@object_to_caches = Hash.new{|h,v| h[v] = []}
|
12
|
+
@finalizer = lambda do |object_id|
|
13
|
+
@object_to_caches[object_id].each do |cache,key|
|
14
|
+
cache.delete(key)
|
15
|
+
end
|
16
|
+
@object_to_caches.delete(object_id)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def register(value,key,cache)
|
21
|
+
@object_to_caches[value.object_id] << [cache,key]
|
22
|
+
ObjectSpace.define_finalizer(value,@finalizer)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize
|
27
|
+
@map = {}
|
28
|
+
@direct_values_map = {}
|
29
|
+
end
|
30
|
+
|
31
|
+
def [](key)
|
32
|
+
if @direct_values_map[key]
|
33
|
+
return @map[key]
|
34
|
+
else
|
35
|
+
value_id = nil
|
36
|
+
value_id = @map[key]
|
37
|
+
return value_id if value_id.nil?
|
38
|
+
begin
|
39
|
+
return ObjectSpace._id2ref(value_id)
|
40
|
+
rescue RangeError
|
41
|
+
@map.delete(key)
|
42
|
+
return nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def []=(key,value)
|
48
|
+
@direct_values_map.delete(key)
|
49
|
+
case value
|
50
|
+
when Fixnum, Symbol, FalseClass, TrueClass, NilClass
|
51
|
+
@map[key] = value
|
52
|
+
@direct_values_map[key] = true
|
53
|
+
else
|
54
|
+
@map[key] = value.object_id
|
55
|
+
InternalMap.instance.register(value,key,@map)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def clear
|
60
|
+
@map.clear
|
61
|
+
@direct_values_map.clear
|
62
|
+
end
|
63
|
+
|
64
|
+
def delete(key)
|
65
|
+
@map.delete(key)
|
66
|
+
@direct_values_map.delete(key)
|
67
|
+
end
|
68
|
+
|
69
|
+
def each
|
70
|
+
if block_given?
|
71
|
+
@map.each do |key,value|
|
72
|
+
begin
|
73
|
+
yield self[key]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
else
|
77
|
+
enum_for(:each)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/lib/rod/collection_proxy.rb
CHANGED
@@ -9,27 +9,35 @@ module Rod
|
|
9
9
|
|
10
10
|
# Intializes the proxy with +size+ of the collection
|
11
11
|
# and +fetch+ block for retrieving the object from the database.
|
12
|
-
def initialize(size
|
12
|
+
def initialize(size,database,offset,klass)
|
13
13
|
@size = size
|
14
14
|
@original_size = size
|
15
|
-
@
|
15
|
+
@database = database
|
16
|
+
@klass = klass
|
17
|
+
@offset = offset
|
16
18
|
@appended = []
|
17
|
-
raise InvalidArgument.new("Cannot use proxy collection without a block!") unless block_given?
|
18
|
-
@proxy = SimpleWeakHash.new
|
19
19
|
end
|
20
20
|
|
21
21
|
# Returns an object with given +index+.
|
22
22
|
def [](index)
|
23
23
|
return nil if index >= @size
|
24
|
-
|
25
|
-
rod_id
|
26
|
-
|
27
|
-
|
24
|
+
rod_id = id_for(index)
|
25
|
+
if rod_id.is_a?(Model)
|
26
|
+
rod_id
|
27
|
+
elsif rod_id == 0
|
28
|
+
nil
|
29
|
+
else
|
30
|
+
class_for(index).find_by_rod_id(rod_id)
|
31
|
+
end
|
28
32
|
end
|
29
33
|
|
30
34
|
# Appends element to the end of the collection.
|
31
|
-
def <<(
|
32
|
-
|
35
|
+
def <<(element)
|
36
|
+
if element.rod_id == 0
|
37
|
+
@appended << [element,element.class]
|
38
|
+
else
|
39
|
+
@appended << [element.rod_id,element.class]
|
40
|
+
end
|
33
41
|
@size += 1
|
34
42
|
end
|
35
43
|
|
@@ -48,7 +56,12 @@ module Rod
|
|
48
56
|
def each_id
|
49
57
|
if block_given?
|
50
58
|
@size.times do |index|
|
51
|
-
|
59
|
+
id = id_for(index)
|
60
|
+
if id.is_a?(Model)
|
61
|
+
raise IdException.new(id)
|
62
|
+
else
|
63
|
+
yield id
|
64
|
+
end
|
52
65
|
end
|
53
66
|
else
|
54
67
|
enum_for(:each_id)
|
@@ -61,11 +74,27 @@ module Rod
|
|
61
74
|
end
|
62
75
|
|
63
76
|
protected
|
64
|
-
def
|
77
|
+
def id_for(index)
|
65
78
|
if index >= @original_size && !@appended[index - @original_size].nil?
|
66
|
-
@appended[index - @original_size]
|
79
|
+
@appended[index - @original_size][0]
|
67
80
|
else
|
68
|
-
@
|
81
|
+
if @klass.nil?
|
82
|
+
@database.polymorphic_join_index(@offset,index)
|
83
|
+
else
|
84
|
+
@database.join_index(@offset,index)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def class_for(index)
|
90
|
+
if index >= @original_size && !@appended[index - @original_size].nil?
|
91
|
+
@appended[index - @original_size][1]
|
92
|
+
else
|
93
|
+
if @klass.nil?
|
94
|
+
Model.get_class(@database.polymorphic_join_class(@offset,index))
|
95
|
+
else
|
96
|
+
@klass
|
97
|
+
end
|
69
98
|
end
|
70
99
|
end
|
71
100
|
end
|
data/lib/rod/constants.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
module Rod
|
2
|
-
VERSION = "0.6.
|
2
|
+
VERSION = "0.6.1"
|
3
3
|
|
4
4
|
# The name of file containing the data base.
|
5
5
|
DATABASE_FILE = "database.yml"
|
@@ -29,4 +29,11 @@ module Rod
|
|
29
29
|
:ulong => 'ULONG2NUM'
|
30
30
|
}
|
31
31
|
|
32
|
+
INLINE_PATTERN_RE = /\h+\.\w+$/
|
33
|
+
|
34
|
+
LEGACY_DATA_SUFFIX = ".old"
|
35
|
+
NEW_DATA_SUFFIX = ".new"
|
36
|
+
LEGACY_MODULE = "Legacy"
|
37
|
+
LEGACY_RE = /^#{LEGACY_MODULE}::/
|
38
|
+
|
32
39
|
end
|
data/lib/rod/database.rb
CHANGED
@@ -146,7 +146,7 @@ module Rod
|
|
146
146
|
# Returns true if the class is one of speciall classes
|
147
147
|
# (JoinElement, PolymorphicJoinElement, StringElement).
|
148
148
|
def special_class?(klass)
|
149
|
-
self.
|
149
|
+
self.special_classes.include?(klass)
|
150
150
|
end
|
151
151
|
|
152
152
|
#########################################################################
|
@@ -280,14 +280,14 @@ module Rod
|
|
280
280
|
builder.c(str.margin)
|
281
281
|
|
282
282
|
str =<<-END
|
283
|
-
|unsigned long _allocate_join_elements(
|
283
|
+
|unsigned long _allocate_join_elements(unsigned long size, VALUE handler){
|
284
284
|
| _join_element * element;
|
285
285
|
| unsigned long index;
|
286
286
|
| #{model_struct} * model_p;
|
287
287
|
| Data_Get_Struct(handler,#{model_struct},model_p);
|
288
288
|
| unsigned long result = model_p->_join_element_count;
|
289
289
|
| for(index = 0; index < size; index++){
|
290
|
-
| if(model_p->_join_element_count * sizeof(_join_element) >=
|
290
|
+
| if((model_p->_join_element_count + 1) * sizeof(_join_element) >=
|
291
291
|
| page_size() * model_p->_join_element_page_count){
|
292
292
|
| \n#{mmap_class(JoinElement)}
|
293
293
|
| }
|
@@ -302,14 +302,14 @@ module Rod
|
|
302
302
|
builder.c(str.margin)
|
303
303
|
|
304
304
|
str =<<-END
|
305
|
-
|unsigned long _allocate_polymorphic_join_elements(
|
305
|
+
|unsigned long _allocate_polymorphic_join_elements(unsigned long size, VALUE handler){
|
306
306
|
| _polymorphic_join_element * element;
|
307
307
|
| unsigned long index;
|
308
308
|
| #{model_struct} * model_p;
|
309
309
|
| Data_Get_Struct(handler,#{model_struct},model_p);
|
310
310
|
| unsigned long result = model_p->_polymorphic_join_element_count;
|
311
311
|
| for(index = 0; index < size; index++){
|
312
|
-
| if(model_p->_polymorphic_join_element_count *
|
312
|
+
| if((model_p->_polymorphic_join_element_count + 1) *
|
313
313
|
| sizeof(_polymorphic_join_element) >=
|
314
314
|
| page_size() * model_p->_polymorphic_join_element_page_count){
|
315
315
|
| \n#{mmap_class(PolymorphicJoinElement)}
|
@@ -476,7 +476,6 @@ module Rod
|
|
476
476
|
| \n#{classes.map do |klass|
|
477
477
|
<<-SUBEND
|
478
478
|
| model_p->#{klass.struct_name}_lib_file = -1;
|
479
|
-
|
|
480
479
|
| if(model_p->#{klass.struct_name}_page_count > 0){
|
481
480
|
| \n#{open_class_file(klass)}
|
482
481
|
| if((model_p->#{klass.struct_name}_table = mmap(NULL,
|
@@ -487,7 +486,7 @@ module Rod
|
|
487
486
|
| }
|
488
487
|
| #{update_pointer(klass) unless special_class?(klass)}
|
489
488
|
| } else {
|
490
|
-
|
|
489
|
+
| #{mmap_class(klass)}
|
491
490
|
| }
|
492
491
|
SUBEND
|
493
492
|
end.join("\n")}
|
@@ -566,6 +565,15 @@ module Rod
|
|
566
565
|
# it is rebuild on the basis of methods' signatures change.
|
567
566
|
builder.c_singleton("void __unused_method_#{rand(1000)}(){}")
|
568
567
|
end
|
568
|
+
|
569
|
+
# This has to be at the very end of builder definition!
|
570
|
+
self.instance_variable_set("@inline_library",builder.so_name)
|
571
|
+
|
572
|
+
# Ruby inline generated shared library.
|
573
|
+
def self.inline_library
|
574
|
+
@inline_library
|
575
|
+
end
|
576
|
+
|
569
577
|
end
|
570
578
|
@code_generated = true
|
571
579
|
end
|
data/lib/rod/exception.rb
CHANGED
@@ -53,4 +53,24 @@ module Rod
|
|
53
53
|
"The value '#@value' of the #@type is invalid!"
|
54
54
|
end
|
55
55
|
end
|
56
|
+
|
57
|
+
# This exception is throw when an objects id is needed, but it is not
|
58
|
+
# yet stored in the database. The object might be accessed via +object+
|
59
|
+
# attribute of the exception.
|
60
|
+
class IdException < RodException
|
61
|
+
attr_reader :object
|
62
|
+
|
63
|
+
def initialize(message,object)
|
64
|
+
super("The object has not been stored in the DB and its rod_id == 0")
|
65
|
+
@object = object
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# This exception is thrown if the database is not compatible with
|
70
|
+
# the library or the runtime definition of classes.
|
71
|
+
class IncompatibleVersion < RodException
|
72
|
+
def initialize(message)
|
73
|
+
super(message)
|
74
|
+
end
|
75
|
+
end
|
56
76
|
end
|
data/lib/rod/join_element.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
+
require 'rod/abstract_model'
|
2
|
+
|
1
3
|
module Rod
|
2
|
-
class JoinElement
|
4
|
+
class JoinElement < AbstractModel
|
3
5
|
def self.typedef_struct
|
4
6
|
str = <<-END
|
5
7
|
|typedef struct {
|
@@ -17,26 +19,6 @@ module Rod
|
|
17
19
|
def self.struct_name
|
18
20
|
"_join_element"
|
19
21
|
end
|
20
|
-
|
21
|
-
def self.path_for_data(path)
|
22
|
-
"#{path}#{self.struct_name}.dat"
|
23
|
-
end
|
24
|
-
|
25
|
-
def self.page_offsets
|
26
|
-
@page_offsets ||= []
|
27
|
-
end
|
28
|
-
|
29
|
-
def self.fields
|
30
|
-
[]
|
31
|
-
end
|
32
|
-
|
33
|
-
def self.build_structure
|
34
|
-
# does nothing, the structure is not needed
|
35
|
-
end
|
36
|
-
|
37
|
-
def self.cache
|
38
|
-
@cache ||= SimpleWeakHash.new
|
39
|
-
end
|
40
22
|
end
|
41
23
|
|
42
24
|
class PolymorphicJoinElement < JoinElement
|
data/lib/rod/model.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
require 'rod/constants'
|
2
2
|
require 'rod/collection_proxy'
|
3
|
+
require 'rod/abstract_model'
|
3
4
|
|
4
5
|
module Rod
|
5
6
|
|
6
7
|
# Abstract class representing a model entity. Each storable class has to derieve from +Model+.
|
7
|
-
class Model
|
8
|
+
class Model < AbstractModel
|
8
9
|
include ActiveModel::Validations
|
9
10
|
extend Enumerable
|
10
11
|
|
@@ -245,43 +246,49 @@ module Rod
|
|
245
246
|
unless object.is_a?(self)
|
246
247
|
raise RodException.new("Incompatible object class #{object.class}.")
|
247
248
|
end
|
248
|
-
|
249
|
-
raise RodException.new("The object #{object} is allready stored!")
|
250
|
-
end
|
249
|
+
new_object = (object.rod_id == 0)
|
251
250
|
database.store(self,object)
|
251
|
+
cache[object.rod_id] = object
|
252
|
+
|
253
|
+
referenced_objects ||= database.referenced_objects
|
252
254
|
|
253
255
|
# update indices
|
254
|
-
|
255
|
-
|
256
|
-
|
256
|
+
indexed_properties.each do |property,options|
|
257
|
+
# singular and plural associations with nil as value are not indexed
|
258
|
+
keys =
|
259
|
+
if new_object
|
257
260
|
if field?(property)
|
258
261
|
[object.send(property)]
|
259
262
|
elsif singular_association?(property)
|
260
|
-
[object.send(property).
|
263
|
+
[object.send(property)].compact
|
261
264
|
else
|
262
|
-
object.send(property).
|
265
|
+
object.send(property).to_a.compact
|
263
266
|
end
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
267
|
+
elsif plural_association?(property)
|
268
|
+
object.send(property).to_a.compact
|
269
|
+
end
|
270
|
+
next if keys.nil?
|
271
|
+
keys.each.with_index do |key_or_object,key_index|
|
272
|
+
key = (key_or_object.is_a?(Model) ? key_or_object.rod_id : key_or_object)
|
273
|
+
proxy = self.index_for(property,options,key)
|
274
|
+
if proxy.nil?
|
275
|
+
proxy = self.set_values_for(property,options,key,0,database,nil)
|
276
|
+
else
|
277
|
+
unless proxy.is_a?(CollectionProxy)
|
278
|
+
offset, count = proxy
|
279
|
+
proxy = self.set_values_for(property,options,key,count,database,offset)
|
280
|
+
end
|
281
|
+
end
|
282
|
+
if new_object || plural_association?(property) && proxy[key_index].nil?
|
283
|
+
if plural_association?(property) && key == 0
|
284
|
+
# TODO #94 devise method for reference rebuilding
|
277
285
|
end
|
278
|
-
proxy <<
|
286
|
+
proxy << object
|
279
287
|
end
|
280
288
|
end
|
281
289
|
end
|
282
290
|
|
283
291
|
# update object that references the stored object
|
284
|
-
referenced_objects ||= database.referenced_objects
|
285
292
|
# ... via singular associations
|
286
293
|
singular_associations.each do |name, options|
|
287
294
|
referenced = object.send(name)
|
@@ -323,7 +330,7 @@ module Rod
|
|
323
330
|
unless reverse_references.blank?
|
324
331
|
reverse_references.each do |referee_rod_id, method_name, class_id, index|
|
325
332
|
referee = Model.get_class(class_id).find_by_rod_id(referee_rod_id)
|
326
|
-
self.cache.
|
333
|
+
self.cache.delete(referee_rod_id)
|
327
334
|
if index.nil?
|
328
335
|
# singular association
|
329
336
|
referee.update_singular_association(method_name, object)
|
@@ -337,7 +344,7 @@ module Rod
|
|
337
344
|
# The name of the C struct for this class.
|
338
345
|
def self.struct_name
|
339
346
|
return @struct_name unless @struct_name.nil?
|
340
|
-
name = self.to_s
|
347
|
+
name = struct_name_for(self.to_s)
|
341
348
|
unless name =~ /^\#/
|
342
349
|
# not an anonymous class
|
343
350
|
@struct_name = name
|
@@ -345,6 +352,11 @@ module Rod
|
|
345
352
|
name
|
346
353
|
end
|
347
354
|
|
355
|
+
# Returns the struct name for the class +name+.
|
356
|
+
def self.struct_name_for(name)
|
357
|
+
name.underscore.gsub(/\//,"__")
|
358
|
+
end
|
359
|
+
|
348
360
|
# Finder for rod_id.
|
349
361
|
def self.find_by_rod_id(rod_id)
|
350
362
|
if rod_id <= 0 || rod_id > self.count
|
@@ -380,6 +392,109 @@ module Rod
|
|
380
392
|
end
|
381
393
|
end
|
382
394
|
|
395
|
+
# Metadata for the model class.
|
396
|
+
def self.metadata(database)
|
397
|
+
meta = super(database)
|
398
|
+
# fields
|
399
|
+
fields = meta[:fields] = {} unless self.fields.size == 1
|
400
|
+
self.fields.each do |field,options|
|
401
|
+
next if field == "rod_id"
|
402
|
+
fields[field] = {}
|
403
|
+
fields[field][:options] = options
|
404
|
+
end
|
405
|
+
# singular_associations
|
406
|
+
has_one = meta[:has_one] = {} unless self.singular_associations.empty?
|
407
|
+
self.singular_associations.each do |name,options|
|
408
|
+
has_one[name] = {}
|
409
|
+
has_one[name][:options] = options
|
410
|
+
end
|
411
|
+
# plural_associations
|
412
|
+
has_many = meta[:has_many] = {} unless self.plural_associations.empty?
|
413
|
+
self.plural_associations.each do |name,options|
|
414
|
+
has_many[name] = {}
|
415
|
+
has_many[name][:options] = options
|
416
|
+
end
|
417
|
+
meta
|
418
|
+
end
|
419
|
+
|
420
|
+
# Generates the model class based on the metadata and places
|
421
|
+
# it in the +module_instance+ or Object (default scope) if module is nil.
|
422
|
+
def self.generate_class(class_name,metadata)
|
423
|
+
superclass = metadata[:superclass].constantize
|
424
|
+
namespace = define_context(class_name)
|
425
|
+
klass = Class.new(superclass)
|
426
|
+
namespace.const_set(class_name.split("::")[-1],klass)
|
427
|
+
[:fields,:has_one,:has_many].each do |type|
|
428
|
+
(metadata[type] || []).each do |name,options|
|
429
|
+
if type == :fields
|
430
|
+
internal_options = options[:options].dup
|
431
|
+
field_type = internal_options.delete(:type)
|
432
|
+
klass.send(:field,name,field_type,internal_options)
|
433
|
+
else
|
434
|
+
klass.send(type,name,options[:options])
|
435
|
+
end
|
436
|
+
end
|
437
|
+
end
|
438
|
+
klass
|
439
|
+
end
|
440
|
+
|
441
|
+
# Migrates the class to the new model, i.e. it copies all the
|
442
|
+
# values of properties that both belong to the class in the old
|
443
|
+
# and the new model.
|
444
|
+
def self.migrate
|
445
|
+
new_class = self.name.sub(LEGACY_RE,"").constantize
|
446
|
+
old_object = self.new
|
447
|
+
new_object = new_class.new
|
448
|
+
puts "Migrating #{new_class}" if $ROD_DEBUG
|
449
|
+
self.properties.each do |name,options|
|
450
|
+
next unless new_class.properties.keys.include?(name)
|
451
|
+
print "- #{name}... " if $ROD_DEBUG
|
452
|
+
if options.map{|k,v| [k,v.to_s.sub(LEGACY_RE,"")]} !=
|
453
|
+
new_class.properties[name].map{|k,v| [k,v.to_s]}
|
454
|
+
raise IncompatibleVersion.
|
455
|
+
new("Incompatible definition of property '#{name}'\n" +
|
456
|
+
"Definition is different in the old and "+
|
457
|
+
"the new schema for '#{new_class}':\n" +
|
458
|
+
" #{options} \n" +
|
459
|
+
" #{new_class.properties[name]}")
|
460
|
+
end
|
461
|
+
if self.field?(name)
|
462
|
+
if self.string_field?(options[:type])
|
463
|
+
self.count.times do |position|
|
464
|
+
new_object.send("_#{name}_length=",position+1,
|
465
|
+
old_object.send("_#{name}_length",position+1))
|
466
|
+
new_object.send("_#{name}_offset=",position+1,
|
467
|
+
old_object.send("_#{name}_offset",position+1))
|
468
|
+
end
|
469
|
+
else
|
470
|
+
self.count.times do |position|
|
471
|
+
new_object.send("_#{name}=",position + 1,
|
472
|
+
old_object.send("_#{name}",position + 1))
|
473
|
+
end
|
474
|
+
end
|
475
|
+
elsif self.singular_association?(name)
|
476
|
+
self.count.times do |position|
|
477
|
+
new_object.send("_#{name}=",position + 1,
|
478
|
+
old_object.send("_#{name}",position + 1))
|
479
|
+
end
|
480
|
+
if options[:polymorphic]
|
481
|
+
self.count.times do |position|
|
482
|
+
new_object.send("_#{name}__class=",position + 1,
|
483
|
+
old_object.send("_#{name}__class",position + 1))
|
484
|
+
end
|
485
|
+
end
|
486
|
+
else
|
487
|
+
self.count.times do |position|
|
488
|
+
new_object.send("_#{name}_count=",position + 1,
|
489
|
+
old_object.send("_#{name}_count",position + 1))
|
490
|
+
new_object.send("_#{name}_offset=",position + 1,
|
491
|
+
old_object.send("_#{name}_offset",position + 1))
|
492
|
+
end
|
493
|
+
end
|
494
|
+
puts "done" if $ROD_DEBUG
|
495
|
+
end
|
496
|
+
end
|
497
|
+
|
383
498
|
protected
|
384
499
|
# The pointer to the mmaped table of C structs.
|
385
500
|
def self.rod_pointer
|
@@ -480,7 +595,7 @@ module Rod
|
|
480
595
|
# The object cache of this class.
|
481
596
|
# XXX consider moving it to the database.
|
482
597
|
def self.cache
|
483
|
-
@cache ||=
|
598
|
+
@cache ||= Cache.new
|
484
599
|
end
|
485
600
|
|
486
601
|
# The module context of the class.
|
@@ -498,6 +613,22 @@ module Rod
|
|
498
613
|
end
|
499
614
|
end
|
500
615
|
|
616
|
+
# Defines the namespace (contex) for given +class_name+ - if the constants
|
617
|
+
# (modules and classes) are defined, they are just digged into,
|
618
|
+
# if not - they are defined as modules.
|
619
|
+
def self.define_context(class_name)
|
620
|
+
class_name.split("::")[0..-2].inject(Object) do |mod,segment|
|
621
|
+
begin
|
622
|
+
mod.const_get(segment,false)
|
623
|
+
rescue NameError
|
624
|
+
new_mod = Module.new
|
625
|
+
mod.const_set(segment,new_mod)
|
626
|
+
new_mod
|
627
|
+
end
|
628
|
+
end
|
629
|
+
end
|
630
|
+
|
631
|
+
|
501
632
|
#########################################################################
|
502
633
|
# DB-oriented API
|
503
634
|
#########################################################################
|
@@ -515,10 +646,23 @@ module Rod
|
|
515
646
|
to_s.to_i(16) % 2 ** 32
|
516
647
|
end
|
517
648
|
|
649
|
+
# Allows for setting arbitrary name for the model path, i.e.
|
650
|
+
# the model-specific fragment of the path to the model files.
|
651
|
+
# By default it is the same as Model.struct_name
|
652
|
+
def self.model_path=(path)
|
653
|
+
@model_path = path
|
654
|
+
end
|
655
|
+
|
656
|
+
# Returns the model path, i.e. the model-specific fragment of
|
657
|
+
# the path to the model files (data, indices, etc.).
|
658
|
+
def self.model_path
|
659
|
+
@model_path || self.struct_name
|
660
|
+
end
|
661
|
+
|
518
662
|
# The name of the file (for given +relative_path+), which the data of this class
|
519
663
|
# is stored in.
|
520
664
|
def self.path_for_data(relative_path)
|
521
|
-
"#{relative_path}#{
|
665
|
+
"#{relative_path}#{model_path}.dat"
|
522
666
|
end
|
523
667
|
|
524
668
|
# The name of the file or directory (for given +relative_path+), which the
|
@@ -526,9 +670,9 @@ module Rod
|
|
526
670
|
def self.path_for_index(relative_path,field,options)
|
527
671
|
case options[:index]
|
528
672
|
when :flat,true
|
529
|
-
"#{relative_path}#{
|
673
|
+
"#{relative_path}#{model_path}_#{field}.idx"
|
530
674
|
when :segmented
|
531
|
-
"#{relative_path}#{
|
675
|
+
"#{relative_path}#{model_path}_#{field}_idx/"
|
532
676
|
else
|
533
677
|
raise RodException.new("Invalid index type #{type}")
|
534
678
|
end
|
@@ -675,6 +819,22 @@ module Rod
|
|
675
819
|
field_writer("#{name}_count","unsigned long",builder)
|
676
820
|
field_writer("#{name}_offset","unsigned long",builder)
|
677
821
|
end
|
822
|
+
|
823
|
+
str=<<-END
|
824
|
+
|unsigned int struct_size(){
|
825
|
+
| return sizeof(#{self.struct_name});
|
826
|
+
|}
|
827
|
+
END
|
828
|
+
|
829
|
+
builder.c_singleton(str.margin)
|
830
|
+
|
831
|
+
# This has to be the last position in the builder!
|
832
|
+
self.instance_variable_set("@inline_library",builder.so_name)
|
833
|
+
|
834
|
+
# Ruby inline generated shared library.
|
835
|
+
def self.inline_library
|
836
|
+
@inline_library
|
837
|
+
end
|
678
838
|
end
|
679
839
|
|
680
840
|
## accessors for fields, plural and singular relationships follow
|
@@ -789,7 +949,7 @@ module Rod
|
|
789
949
|
else
|
790
950
|
"#{self.scope_name}::#{::English::Inflect.singular(name).camelcase}"
|
791
951
|
end
|
792
|
-
klass =
|
952
|
+
klass = options[:polymorphic] ? nil : class_name.constantize
|
793
953
|
|
794
954
|
# getter
|
795
955
|
define_method("#{name}") do
|
@@ -802,17 +962,7 @@ module Rod
|
|
802
962
|
end
|
803
963
|
return instance_variable_set("@#{name}",[]) if count == 0
|
804
964
|
offset = self.send("_#{name}_offset",@rod_id)
|
805
|
-
|
806
|
-
proxy = CollectionProxy.new(count) do |index|
|
807
|
-
[database.join_index(offset,index), klass]
|
808
|
-
end
|
809
|
-
else
|
810
|
-
proxy = CollectionProxy.new(count) do |index|
|
811
|
-
rod_id = database.polymorphic_join_index(offset,index)
|
812
|
-
class_id = database.polymorphic_join_class(offset,index)
|
813
|
-
[rod_id, rod_id == 0 ? 0 : Model.get_class(class_id)]
|
814
|
-
end
|
815
|
-
end
|
965
|
+
proxy = CollectionProxy.new(count,database,offset,klass)
|
816
966
|
instance_variable_set("@#{name}", proxy)
|
817
967
|
end
|
818
968
|
proxy
|
@@ -834,24 +984,31 @@ module Rod
|
|
834
984
|
end
|
835
985
|
|
836
986
|
# indices
|
837
|
-
|
987
|
+
indexed_properties.each do |property,options|
|
838
988
|
# optimization
|
839
989
|
property = property.to_s
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
990
|
+
(class << self; self; end).class_eval do
|
991
|
+
# Find all objects with given +value+ of the +property+.
|
992
|
+
define_method("find_all_by_#{property}") do |value|
|
993
|
+
value = value.rod_id if value.is_a?(Model)
|
994
|
+
proxy = index_for(property,options,value)
|
995
|
+
if proxy.is_a?(CollectionProxy)
|
996
|
+
proxy
|
997
|
+
else
|
998
|
+
offset,count = proxy
|
846
999
|
return [] if offset.nil?
|
847
|
-
CollectionProxy.new(count)
|
848
|
-
[database.join_index(offset,index),self]
|
849
|
-
end
|
1000
|
+
CollectionProxy.new(count,database,offset,self)
|
850
1001
|
end
|
1002
|
+
end
|
851
1003
|
|
852
|
-
|
853
|
-
|
854
|
-
|
1004
|
+
# Find first object with given +value+ of the +property+.
|
1005
|
+
define_method("find_by_#{property}") do |value|
|
1006
|
+
value = value.rod_id if value.is_a?(Model)
|
1007
|
+
proxy = index_for(property,options)[value]
|
1008
|
+
if proxy.is_a?(CollectionProxy)
|
1009
|
+
proxy[0]
|
1010
|
+
else
|
1011
|
+
offset,count = proxy
|
855
1012
|
if offset.nil?
|
856
1013
|
nil
|
857
1014
|
else
|
@@ -870,6 +1027,11 @@ module Rod
|
|
870
1027
|
self.fields.merge(self.singular_associations.merge(self.plural_associations))
|
871
1028
|
end
|
872
1029
|
|
1030
|
+
# Returns (and caches) only properties which are indexed.
|
1031
|
+
def indexed_properties
|
1032
|
+
@indexed_properties ||= self.properties.select{|p,o| o[:index]}
|
1033
|
+
end
|
1034
|
+
|
873
1035
|
# Returns true if the +name+ (as symbol) is a name of a field.
|
874
1036
|
def field?(name)
|
875
1037
|
self.fields.keys.include?(name)
|
@@ -905,8 +1067,8 @@ module Rod
|
|
905
1067
|
# the particular +key+. Method expects +fetch+ block and
|
906
1068
|
# creates a CollectionProxy based on that block.
|
907
1069
|
# The size of the collection is given as +count+.
|
908
|
-
def set_values_for(property,options,key,count
|
909
|
-
index_for(property,options)[key] = CollectionProxy.new(count
|
1070
|
+
def set_values_for(property,options,key,count,database,offset)
|
1071
|
+
index_for(property,options)[key] = CollectionProxy.new(count,database,offset,self)
|
910
1072
|
end
|
911
1073
|
|
912
1074
|
private
|