# * George Moschovitis # (c) 2004-2005 Navel, all rights reserved. # $Id: database.rb 255 2005-02-10 12:45:32Z gmosx $ require 'glue/logger' require 'glue/attribute' require 'glue/property' require 'glue/array' require 'glue/hash' require 'glue/time' require 'glue/pool' require 'og/enchant' require 'og/meta' class Og # Encapsulates an Og Database. class Database include Og::Enchant # Managed class metadata class ManagedClassMeta # The managed class. attr_accessor :klass # A mapping of the database fields to the object properties. attr_accessor :field_index def initialize(klass = nil) @klass = klass @field_index = {} end end # hash of configuration options. attr_accessor :config # The adapter that comunicates with the backend # datastore. attr_accessor :adapter # Pool of connections. attr_accessor :connection_pool # Managed classes. attr_accessor :managed_classes # Initialize the database interface. def initialize(config) @config = config # populate with default options if needed. @config[:connection_count] ||= 1 Logger.info "Connecting to database '#{@config[:database]}' using the '#{@config[:adapter]}' adapter." @adapter = Adapter.for_name(@config[:adapter]) @connection_pool = N::Pool.new @managed_classes = N::SafeHash.new @config[:connection_count].times do # @connection_pool << Og::Connection.new(self) @connection_pool << @adapter.new_connection(self) end # gmosx, FIXME: this automanage code is not elegant and slow # should probably recode this, along with glue/property.rb if Og.auto_manage_classes # automatically manage classes with properties and metadata. # gmosx: Any idea how to optimize this? classes_to_manage = [] ObjectSpace.each_object(Class) do |c| if c.respond_to?(:__props) and c.__props classes_to_manage << c end end Logger.info "Og auto manages the following classes:" Logger.info "#{classes_to_manage.inspect}" manage_classes(*classes_to_manage) end # use the newly created database. Og.use(self) end # Shutdown the database interface. def shutdown for con in @connection_pool con.close() end end alias_method :close, :shutdown # Get a connection from the pool to access the database. # Stores the connection in a thread-local variable. def get_connection thread = Thread.current unless conn = thread[:og_conn] conn = @connection_pool.pop() thread[:og_conn] = conn end return conn end alias_method :connection, :get_connection # Restore an unused connection to the pool. def put_connection thread = Thread.current if conn = thread[:og_conn] thread[:og_conn] = nil return @connection_pool.push(conn) end end # Utility method, automatically restores a connection to the pool. def connect(&block) result = nil begin conn = get_connection result = yield(conn) ensure put_connection end return result end alias_method :open, :connect # Register a standard Ruby class as managed. def manage(klass) return if managed?(klass) or klass.ancestors.include?(Og::Unmanageable) @managed_classes[klass] = ManagedClassMeta.new(klass) # Add standard og methods to the class. convert(klass) # Add helper methods to the class. enchant(klass) if Og.enchant_managed_classes end # Helper method to set multiple managed classes. def manage_classes(*klasses) for klass in klasses manage(klass) end end # Stop managing a Ruby class def unmanage(klass) @managed_classes.delete(klass) end # Is this class managed? # def managed?(klass) return @managed_classes.include?(klass) end # Add standard og functionality to the class def convert(klass) # gmosx: this check is needed to allow the developer to customize # the sql generated for oid @adapter.eval_og_oid(klass) unless klass.instance_methods.include?(:oid) klass.class_eval %{ DBTABLE = "#{Adapter.table(klass)}" DBSEQ = "#{Adapter.table(klass)}_oid_seq" def to_i() @oid end } # Create the schema for this class if not available. @adapter.create_table(klass, self) # Precompile some code that gets executed all the time. # Deletion code is not precompiled, because it is not used # as frequently. @adapter.eval_og_insert(klass, self) @adapter.eval_og_update(klass, self) @adapter.eval_og_read(klass, self) end # Automatically wrap connection methods. def self.wrap_method(method, args) args = args.split(/,/) class_eval %{ def #{method}(#{args.join(", ")}) thread = Thread.current unless conn = thread[:og_conn] conn = @connection_pool.pop() thread[:og_conn] = conn end return conn.#{method}(#{args.collect {|a| a.split(/=/)[0]}.join(", ")}) end } end wrap_method :db, '' wrap_method :create_table, 'klass' wrap_method :drop_table, 'klass' wrap_method :save, 'obj'; alias_method :<<, :save; alias_method :put, :save wrap_method :insert, 'obj' wrap_method :update, 'obj' wrap_method :update_properties, 'update_sql, obj_or_oid, klass = nil' wrap_method :pupdate, 'update_sql, obj_or_oid, klass = nil' wrap_method :load, 'oid, klass'; alias_method :get, :load wrap_method :load_by_oid, 'oid, klass' wrap_method :load_by_name, 'name, klass' wrap_method :load_all, 'klass, extrasql = nil' wrap_method :select, 'sql, klass' wrap_method :select_one, 'sql, klass' wrap_method :count, 'sql, klass = nil' wrap_method :delete, 'obj_or_oid, klass = nil' wrap_method :prepare, 'sql' wrap_method :query, 'sql' wrap_method :exec, 'sql' class << self def create_db!(config) adapter = Adapter.for_name(config[:adapter]) adapter.create_db(config[:database], config[:user], config[:password]) end alias_method :create!, :create_db! def drop_db!(config) adapter = Adapter.for_name(config[:adapter]) adapter.drop_db(config[:database], config[:user], config[:password]) end alias_method :drop!, :drop_db! end end end