Class: Rod::AbstractDatabase

Inherits:
Object
  • Object
show all
Includes:
Utils, Singleton
Defined in:
lib/rod/abstract_database.rb

Overview

This class implements the database abstraction, i.e. it is a mediator between some model (a set of classes) and the generated C code, implementing the data storage functionality.

Direct Known Subclasses

Database

Constant Summary collapse

@@rod_development_mode =

This flag indicates, if Database and Model works in development mode, i.e. the dynamically loaded library has a unique, different id each time the rod library is used.

false

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Utils

#remove_file, #remove_files, #remove_files_but, #report_progress

Constructor Details

#initializeAbstractDatabase

Initializes the classes linked with this database and the handler.



30
31
32
33
# File 'lib/rod/abstract_database.rb', line 30

def initialize
  @classes ||= self.special_classes
  @handler = nil
end

Instance Attribute Details

#metadataObject (readonly)

The meta-data of the DataBase.



19
20
21
# File 'lib/rod/abstract_database.rb', line 19

def 
  @metadata
end

#pathObject (readonly)

The path which the database instance is located on.



22
23
24
# File 'lib/rod/abstract_database.rb', line 22

def path
  @path
end

Class Method Details

.development_modeObject

Reader of the rod_development_mode flag.



41
42
43
# File 'lib/rod/abstract_database.rb', line 41

def self.development_mode
  @@rod_development_mode
end

.development_mode=(value) ⇒ Object

Writer of the rod_development_mode flag.



36
37
38
# File 'lib/rod/abstract_database.rb', line 36

def self.development_mode=(value)
  @@rod_development_mode = value
end

Instance Method Details

#add_class(klass) ⇒ Object

Adds the klass to the set of classes linked with this database.



287
288
289
# File 'lib/rod/abstract_database.rb', line 287

def add_class(klass)
  @classes << klass unless @classes.include?(klass)
end

#allocate_join_elements(size) ⇒ Object

Allocates space for join elements.

Raises:



339
340
341
342
# File 'lib/rod/abstract_database.rb', line 339

def allocate_join_elements(size)
  raise DatabaseError.new("Readonly database.") if readonly_data?
  _allocate_join_elements(size,@handler)
end

#allocate_polymorphic_join_elements(size) ⇒ Object

Allocates space for polymorphic join elements.

Raises:



333
334
335
336
# File 'lib/rod/abstract_database.rb', line 333

def allocate_polymorphic_join_elements(size)
  raise DatabaseError.new("Readonly database.") if readonly_data?
  _allocate_polymorphic_join_elements(size,@handler)
end

#clear_cacheObject

Clears the cache of the database.



259
260
261
# File 'lib/rod/abstract_database.rb', line 259

def clear_cache
  classes.each{|c| c.cache.clear }
end

#close_database(purge_classes = false, skip_indices = false) ⇒ Object

Closes the database.

If the purge_classes flag is set to true, the information about the classes linked with this database is removed. This is important for testing, when classes with same names have different definitions.

If the skip_indeces flat is set to true, the indices are not written.

Raises:



233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/rod/abstract_database.rb', line 233

def close_database(purge_classes=false,skip_indices=false)
  raise DatabaseError.new("Database not opened.") unless opened?

  unless readonly_data?
    unless referenced_objects.select{|k, v| not v.empty?}.size == 0
      raise DatabaseError.new("Not all associations have been stored: #{referenced_objects.size} objects")
    end
    unless skip_indices
      self.classes.each do |klass|
        klass.indexed_properties.each do |property|
          property.index.save
        end
      end
    end
    
  end
  _close(@handler)
  @handler = nil
  # clear cached data
  self.clear_cache
  if purge_classes
    @classes = self.special_classes
  end
end

#count(klass) ⇒ Object

Returns the number of objects for given klass.



362
363
364
# File 'lib/rod/abstract_database.rb', line 362

def count(klass)
  send("_#{klass.struct_name}_count",@handler)
end

#create_database(path) ⇒ Object

Creates the database at specified path, which allows for Rod::Model#store calls to be performed.

The database is created for all classes, which have this database configured via Rod::Model#database_class call (this configuration is by default inherited in subclasses, so it have to be called only in the root class of given model).

WARNING: all files in the DB directory are removed during DB creation!



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/rod/abstract_database.rb', line 68

def create_database(path)

  if block_given?

    create_database(path)

    begin
      yield
    ensure
      close_database
    end
    
  else

    raise DatabaseError.new("Database already opened.") if opened?
    @readonly = false
    @path = canonicalize_path(path)
    if File.exist?(@path)
      remove_file("#{@path}database.yml")
    else
      FileUtils.mkdir_p(@path)
    end
    self.classes.each do |klass|
      klass.send(:build_structure)
      remove_file(klass.path_for_data(@path))
      klass.indexed_properties.each do |property|
        property.index.destroy
      end
      next if special_class?(klass)
      remove_files_but(klass.inline_library)
    end
    remove_files(self.inline_library)
    generate_c_code(@path, classes)
    remove_files_but(self.inline_library)
    @metadata = {}
    @metadata["Rod"] = {}
    @metadata["Rod"][:created_at] = Time.now
    @handler = _init_handler(@path)
    _create(@handler)

  end
  
end

#fast_intersection_size(first_offset, first_length, second_offset, second_length) ⇒ Object

Computes fast intersection for sorted join elements.



345
346
347
348
# File 'lib/rod/abstract_database.rb', line 345

def fast_intersection_size(first_offset,first_length,second_offset,second_length)
  _fast_intersection_size(first_offset,first_length,second_offset,
                          second_length,@handler)
end

#join_index(offset, index) ⇒ Object

Returns join index with index and offset.



300
301
302
# File 'lib/rod/abstract_database.rb', line 300

def join_index(offset, index)
  _join_element_index(offset, index, @handler)
end

#legacy_class?(klass) ⇒ Boolean

Returns true if the klass is a legacy class, i.e. a class generated during migration used to access the legacy data.

Returns:

  • (Boolean)


282
283
284
# File 'lib/rod/abstract_database.rb', line 282

def legacy_class?(klass)
  klass.name =~ LEGACY_RE
end

#migrate_database(path) ⇒ Object

Migrates the database, which is located at path. The old version of the DB is placed at path/backup.

Raises:



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/rod/abstract_database.rb', line 171

def migrate_database(path)
  raise DatabaseError.new("Database already opened.") if opened?
  @readonly = false
  @path = canonicalize_path(path)
  @metadata = 
  create_legacy_classes
  FileUtils.mkdir_p(@path + BACKUP_PREFIX)
  # Copy special classes data.
  special_classes.each do |klass|
    file = klass.path_for_data(@path)
    puts "Copying #{file} to #{@path + BACKUP_PREFIX}" if $ROD_DEBUG
    FileUtils.cp(file,@path + BACKUP_PREFIX)
  end
  Dir.glob(@path + "*").each do |file|
    # Don't move the directory itself and speciall classes data.
    unless file.to_s == @path + BACKUP_PREFIX[0..-2] ||
      special_classes.map{|c| c.path_for_data(@path)}.include?(file.to_s)
      puts "Moving #{file} to #{@path + BACKUP_PREFIX}" if $ROD_DEBUG
      FileUtils.mv(file,@path + BACKUP_PREFIX)
    end
  end
  remove_files(self.inline_library)
  self.classes.each do |klass|
    klass.send(:build_structure)
  end
  generate_c_code(@path, self.classes)
  @handler = _init_handler(@path)
  self.classes.each do |klass|
    next unless special_class?(klass) or legacy_class?(klass)
    meta = @metadata[klass.name]
    set_count(klass,meta[:count])
    file_size = File.new(klass.path_for_data(@path)).size
    unless file_size % _page_size == 0
      raise DatabaseError.new("Size of data file of #{klass} is invalid: #{file_size}")
    end
    set_page_count(klass,file_size / _page_size)
    next unless legacy_class?(klass)
    new_class = klass.name.sub(LEGACY_RE,"").constantize
    set_count(new_class,meta[:count])
    pages = (meta[:count] * new_class.struct_size / _page_size.to_f).ceil
    set_page_count(new_class,pages)
  end
  _open(@handler)
  self.classes.each do |klass|
    next unless legacy_class?(klass)
    klass.migrate
    @classes.delete(klass)
  end
  path_with_date = @path + BACKUP_PREFIX[0..-2] + "_" +
    Time.new.strftime("%Y_%m_%d_%H_%M_%S") + "/"
  puts "Moving #{@path + BACKUP_PREFIX} to #{path_with_date}" if $ROD_DEBUG
  FileUtils.mv(@path + BACKUP_PREFIX,path_with_date)
  close_database
end

#open_database(path, options = {:readonly => true}) ⇒ Object

Opens the database at path with options. This allows for Rod::Model.count, Rod::Model.each, and similar calls. Options:

  • :readonly - no modifiaction (append of models and has many association) is allowed (defaults to true)

  • :generate - value could be true or a module. If present, generates the classes from the database metadata. If module given, the classes are generated withing the module.

Raises:



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/rod/abstract_database.rb', line 120

def open_database(path,options={:readonly => true})
  raise DatabaseError.new("Database already opened.") if opened?
  options = convert_options(options)
  @readonly = options[:readonly]
  @path = canonicalize_path(path)
  @metadata = 
  if options[:generate]
    module_instance = (options[:generate] == true ? Object : options[:generate])
    generate_classes(module_instance)
  end
  self.classes.each do |klass|
    klass.send(:build_structure)
    next if special_class?(klass)
    if options[:generate] && module_instance != Object
      remove_files_but(klass.inline_library)
    end
  end
  generate_c_code(@path, self.classes)
  @handler = _init_handler(@path)
   = @metadata.dup
  .delete("Rod")
  self.classes.each do |klass|
    meta = .delete(klass.name)
    if meta.nil?
      # new class
      next
    end
    unless klass.compatible?(meta) || options[:generate] || options[:migrate]
        raise IncompatibleVersion.
          new("Incompatible definition of '#{klass.name}' class.\n" +
              "Database and runtime versions are different:\n  " +
              klass.difference(meta).
              map{|e1,e2| "DB: #{e1} vs. RT: #{e2}"}.join("\n  "))
    end
    set_count(klass,meta[:count])
    file_size = File.new(klass.path_for_data(@path)).size
    unless file_size % _page_size == 0
      raise DatabaseError.new("Size of data file of #{klass} is invalid: #{file_size}")
    end
    set_page_count(klass,file_size / _page_size)
  end
  if .size > 0
    @handler = nil
    raise DatabaseError.new("The following classes are missing in runtime:\n - " +
                            .keys.join("\n - "))
  end
  _open(@handler)
end

#opened?Boolean

Returns whether the database is opened.

Returns:

  • (Boolean)


50
51
52
# File 'lib/rod/abstract_database.rb', line 50

def opened?
  not @handler.nil?
end

#polymorphic_join_class(offset, index) ⇒ Object

Returns polymorphic join class id with index and offset. This is the class_id (name_hash) of the object referenced via a polymorphic has many association for one instance.



314
315
316
# File 'lib/rod/abstract_database.rb', line 314

def polymorphic_join_class(offset, index)
  _polymorphic_join_element_class(offset, index, @handler)
end

#polymorphic_join_index(offset, index) ⇒ Object

Returns polymorphic join index with index and offset. This is the rod_id of the object referenced via a polymorphic has many association for one instance.



307
308
309
# File 'lib/rod/abstract_database.rb', line 307

def polymorphic_join_index(offset, index)
  _polymorphic_join_element_index(offset, index, @handler)
end

Prints the layout of the pages in memory and other internal data of the model.

Raises:



387
388
389
390
# File 'lib/rod/abstract_database.rb', line 387

def print_layout
  raise DatabaseError.new("Database not opened.") unless opened?
  _print_layout(@handler)
end

Prints the last error of system call.



393
394
395
# File 'lib/rod/abstract_database.rb', line 393

def print_system_error
  _print_system_error
end

#read_string(length, offset) ⇒ Object

Returns the string of given length starting at given offset.



351
352
353
# File 'lib/rod/abstract_database.rb', line 351

def read_string(length, offset)
  value = _read_string(length, offset, @handler)
end

#readonly_data?Boolean

The DB open mode.

Returns:

  • (Boolean)


55
56
57
# File 'lib/rod/abstract_database.rb', line 55

def readonly_data?
  @readonly
end

#referenced_objectsObject

“Stack” of objects which are referenced by other objects during store, but are not yet stored.



269
270
271
# File 'lib/rod/abstract_database.rb', line 269

def referenced_objects
  @referenced_objects ||= {}
end

#remove_class(klass) ⇒ Object

Remove the klass from the set of classes linked with this database.



292
293
294
295
296
297
# File 'lib/rod/abstract_database.rb', line 292

def remove_class(klass)
  unless @classes.include?(klass)
    raise DatabaseError.new("Class #{klass} is not linked with #{self}!")
  end
  @classes.delete(klass)
end

#set_count(klass, value) ⇒ Object

Sets the number of objects for given klass.



367
368
369
# File 'lib/rod/abstract_database.rb', line 367

def set_count(klass,value)
  send("_#{klass.struct_name}_count=",@handler,value)
end

#set_join_element_id(offset, index, object_id) ⇒ Object

Sets the object_id of the join element with offset and index.

Raises:



319
320
321
322
# File 'lib/rod/abstract_database.rb', line 319

def set_join_element_id(offset,index,object_id)
  raise DatabaseError.new("Readonly database.") if readonly_data?
  _set_join_element_offset(offset, index, object_id, @handler)
end

#set_page_count(klass, value) ⇒ Object

Sets the number of pages allocated for given klass.



372
373
374
# File 'lib/rod/abstract_database.rb', line 372

def set_page_count(klass,value)
  send("_#{klass.struct_name}_page_count=",@handler,value)
end

#set_polymorphic_join_element_id(offset, index, object_id, class_id) ⇒ Object

Sets the object_id and class_id of the polymorphic join element with offset and index.

Raises:



326
327
328
329
330
# File 'lib/rod/abstract_database.rb', line 326

def set_polymorphic_join_element_id(offset,index,object_id,class_id)
  raise DatabaseError.new("Readonly database.") if readonly_data?
  _set_polymorphic_join_element_offset(offset, index, object_id,
                                       class_id, @handler)
end

#set_string(value) ⇒ Object

Stores the string in the DB.

Raises:



356
357
358
359
# File 'lib/rod/abstract_database.rb', line 356

def set_string(value)
  raise DatabaseError.new("Readonly database.") if readonly_data?
  _set_string(value,@handler)
end

#special_class?(klass) ⇒ Boolean

Returns true if the class is one of speciall classes (JoinElement, PolymorphicJoinElement, StringElement).

Returns:

  • (Boolean)


275
276
277
# File 'lib/rod/abstract_database.rb', line 275

def special_class?(klass)
  self.special_classes.include?(klass)
end

#store(klass, object) ⇒ Object

Store the object in the database.

Raises:



377
378
379
380
381
382
# File 'lib/rod/abstract_database.rb', line 377

def store(klass,object)
  raise DatabaseError.new("Readonly database.") if readonly_data?
  if object.new?
    send("_store_" + klass.struct_name,object,@handler)
  end
end