begin require 'kirbybase' rescue Object => ex Logger.error "KirbyBase is not installed. Please run 'gem install KirbyBase'" Logger.error ex end require 'fileutils' require 'og/store/sql' module Og # A Store that persists objects into a KirbyBase database. # To read documentation about the methods, consult the documentation # for SqlStore and Store. class KirbyStore < SqlStore # Override if needed. def self.base_dir(options) options[:base_dir] || './kirbydb' end def self.destroy(options) begin FileUtils.rm_rf(base_dir(options)) super rescue Object Logger.info 'Cannot drop database!' end end def initialize(options) super @typemap = { :Fixnum => :Integer, :TrueClass => :Boolean } mode = options[:mode] || :local if mode == :client # Use a client/server configuration. @conn = KirbyBase.new(:client, options[:address], options[:port]) else # Use an in-process configuration. base_dir = self.class.base_dir(options) FileUtils.mkdir_p(base_dir) unless File.exist?(base_dir) @conn = KirbyBase.new( :local, nil, nil, base_dir, options[:ext] || '.tbl' ) end end def close # Nothing to do. super end def enchant(klass, manager) klass.send :attr_accessor, :oid klass.send :alias_method, :recno, :oid klass.send :alias_method, :recno=, :oid= unless klass.properties.include? :recno klass.property :recno, Fixnum end symbols = klass.properties.keys klass.module_eval %{ def self.kb_create(#{symbols.join(', ')}) obj = self.allocate obj.recno = recno #{ symbols.map { |s| "obj.#{s} = #{s}; "} } return obj end } super end def get_table(klass) @conn.get_table(klass.table.to_sym) end # :section: Lifecycle methods. def load(pk, klass) get_table(klass)[pk.to_i] end alias_method :exist?, :load def reload(obj, pk) raise 'Cannot reload unmanaged object' unless obj.saved? new_obj = load(pk, obj.class) obj.clone(new_obj) end def find(options) query(options) end def find_one(options) query(options).first end #-- # FIXME: optimize me! #++ def count(options) find(options).size end def query(options) Logger.debug "Querying with #{options.inspect}." if $DBG klass = options[:class] table = get_table(klass) objects = [] if condition = options[:condition] || options[:where] condition.gsub!(/=/, '==') condition.gsub!(/LIKE '(.*?)'/, '=~ /\1/') condition.gsub!(/\%/, '') condition.gsub!(/(\w*?)\s?=(.)/, 'o.\1 =\2') objects = eval("table.select { |o| #{condition} }") else objects = table.select end if order = options[:order] order = order.to_s desc = (order =~ /DESC/) order = order.gsub(/DESC/, '').gsub(/ASC/, '') eval "objects.sort { |x, y| x.#{order} <=> y.#{order} }" objects.reverse! if desc end return objects end def start # nop end # Commit a transaction. def commit # nop, not supported? end # Rollback a transaction. def rollback # nop, not supported? end def sql_update(sql) # nop, not supported. end def join(obj1, obj2, table, options = nil) first, second = join_object_ordering(obj1, obj2) @conn.get_table(table.to_sym).insert(first.pk, second.pk) end def unjoin(obj1, obj2, table) first, second = join_object_ordering(obj1, obj2) @conn.get_table(table.to_sym).delete do |r| require 'dev-utils/debug' breakpoint r.send(:first_key) == first.pk and r.send(:second_key) == second.pk end end private def typemap(key) @typemap[key] || key end def create_table(klass) if @conn.table_exists?(klass.table.to_sym) get_table(klass).pack # Kirby specific method of database cleanup. field_names = field_names_for_class(klass) actual_fields = get_table(klass).field_names field_names.each do |needed_field| next if actual_fields.include?(needed_field) if @options[:evolve_schema] == true Logger.debug "Adding field '#{needed_field}' to '#{klass.table}'" field_type = typemap(klass.properties[needed_field].klass.name.to_sym) if get_table(klass).respond_to?(:add_column) get_table(klass).add_column(needed_field, field_type) else @conn.add_table_column(klass.table, needed_field, field_type) end else Logger.warn "Table '#{klass.table}' is missing field '#{needed_field}' and :evolve_schema is not set to true!" end end actual_fields.each do |obsolete_field| next if field_names.include?(obsolete_field) if @options[:evolve_schema] == true and @options[:evolve_schema_cautious] == false Logger.debug "Removing obsolete field '#{obsolete_field}' from '#{klass.table}'" if get_table(klass).respond_to?(:drop_column) get_table(klass).drop_column(obsolete_field) else @conn.drop_table_column(klass.table, obsolete_field) end else Logger.warn "You have an obsolete field '#{obsolete_field}' on table '#{klass.table}' and :evolve_schema is not set or is in cautious mode!" end end else Logger.debug "Creating table '#{klass.table}'" fields = fields_for_class(klass) table = @conn.create_table(klass.table.to_sym, *fields) do |t| t.record_class = klass end end =begin # Create join tables if needed. Join tables are used in # 'many_to_many' relations. if join_tables = klass.ann.self[:join_tables] for info in join_tables unless @conn.table_exists?(info[:table].to_sym) @conn.create_table(info[:table].to_sym, *create_join_table_sql(info)) Logger.debug "Created jointable '#{info[:table]}'." end end end =end end def create_join_table_sql(join_info) [join_info[:first_key].to_sym, :Integer, join_info[:second_key].to_sym, :Integer] end def drop_table(klass) @conn.drop_table(klass.table) if @conn.table_exists?(klass.table) end def field_names_for_class(klass) klass.properties.values.map {|p| p.symbol } end def fields_for_class(klass) fields = [] klass.properties.values.each do |p| klass.index(p.symbol) if p.index fields << p.symbol type = typemap(p.klass.name.to_sym) fields << type end return fields end def eval_og_insert(klass) pk = klass.primary_key.symbol if klass.schema_inheritance? props << Property.new(:symbol => :ogtype, :klass => String) values << ", '#{klass}'" end klass.class_eval %{ def og_insert(store) #{Glue::Aspects.gen_advice_code(:og_insert, klass.advices, :pre) if klass.respond_to?(:advices)} Logger.debug "Inserting \#{self}." if $DBG @#{pk} = store.get_table(#{klass}).insert(self) #{Glue::Aspects.gen_advice_code(:og_insert, klass.advices, :post) if klass.respond_to?(:advices)} end } end # Compile the og_update method for the class. def eval_og_update(klass) pk = klass.pk_symbol updates = klass.properties.keys.collect { |p| ":#{p} => @#{p}" } klass.module_eval %{ def og_update(store, options = nil) #{Glue::Aspects.gen_advice_code(:og_update, klass.advices, :pre) if klass.respond_to?(:advices)} store.get_table(#{klass}).update { |r| r.recno == #{pk} }.set(#{updates.join(', ')}) #{Glue::Aspects.gen_advice_code(:og_update, klass.advices, :post) if klass.respond_to?(:advices)} end } end def eval_og_read(klass) klass.module_eval %{ def og_read(res, row = 0, offset = 0) #{Glue::Aspects.gen_advice_code(:og_read, klass.advices, :pre) if klass.respond_to?(:advices)} #{Glue::Aspects.gen_advice_code(:og_read, klass.advices, :post) if klass.respond_to?(:advices)} end } end def eval_og_delete(klass) klass.module_eval %{ def og_delete(store, pk, cascade = true) pk ||= self.pk pk = pk.to_i #{Glue::Aspects.gen_advice_code(:og_delete, klass.advices, :pre) if klass.respond_to?(:advices)} table = store.get_table(self.class) transaction do |tx| table.delete { |r| r.recno == pk } if cascade and #{klass}.ann.self[:descendants] #{klass}.ann.self.descendants.each do |dclass, foreign_key| dtable = store.get_table(dclass) dtable.delete { |r| foreign_key == pk } end end end #{Glue::Aspects.gen_advice_code(:og_delete, klass.advices, :post) if klass.respond_to?(:advices)} end } end end end # * George Moschovitis