# * George Moschovitis # (c) 2004-2005 Navel, all rights reserved. # $Id: connection.rb 281 2005-03-10 12:24:14Z gmosx $ module Og; require 'glue/property' require 'glue/array' require 'glue/time' # A Connection to the Database. This file defines the skeleton # functionality. A backend specific implementation file (driver) # implements all methods. # # === Future # # - support caching. # - support prepared statements. class Connection # The Og database object. attr_reader :db # The actual connection to the backend store. attr_accessor :store # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # :section: Backend connection methods. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # Initialize a connection to the database. def initialize(db) @db = db Logger.debug "Created DB connection." if $DBG end # Close the connection to the database. def close Logger.debug "Closed DB connection." if $DBG end # Create the managed object table. The properties of the # object are mapped to the table columns. Additional sql relations # and constrains are created (indicices, sequences, etc). def create_table(klass) raise 'Not implemented!' end # Drop the managed object table. def drop_table(klass) exec "DROP TABLE #{klass::DBTABLE}" end # Prepare an sql statement. def prepare(sql) raise 'Not implemented!' end # Execute an SQL query and return the result. def query(sql) raise 'Not implemented!' end # Execute an SQL query, no result returned. def exec(sql) raise 'Not implemented!' end alias_method :execute, :exec # Start a new transaction. def start exec 'START TRANSACTION' end # Commit a transaction. def commit exec 'COMMIT' end # Rollback a transaction. def rollback exec 'ROLLBACK' end # Transaction helper. In the transaction block use # the db pointer to the backend. def transaction(&block) begin start yield(self) commit rescue => ex Logger.error "DB Error: ERROR IN TRANSACTION" Logger.error "#{ex}" Logger.error "#{ex.backtrace}" rollback end end # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # :section: Deserialization methods. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # Is the given resultset valid? def valid_res?(res) return !(res.nil?) end # Read (deserialize) one row of the resultset. def read_one(res, klass) raise 'Not implemented!' end # Read (deserialize) all rows of the resultset. def read_all(res, klass) raise 'Not implemented!' end # Read the first column of the resultset as an Integer. def read_int(res, idx = 0) raise 'Not implemented!' end # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # :section: Managed object methods. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # Save an object to the database. Insert if this is a new object or # update if this is already stored in the database. def save(obj) if obj.oid # object allready inserted, update! obj.og_update(self) else # not in the database, insert! obj.og_insert(self) end end alias_method :<<, :save alias_method :put, :save # Force insertion of managed object. def insert(obj) obj.og_insert(self) end # Force update of managed object. def update(obj) obj.og_update(self) end # Update only specific fields of the managed object. # # Input: # sql = the sql code to updated the properties. # # WARNING: the object in memory is not updated. #-- # TODO: should update the object in memory. #++ def update_properties(update_sql, obj_or_oid, klass = nil) oid = obj_or_oid.to_i klass = obj_or_oid.class unless klass exec "UPDATE #{klass::DBTABLE} SET #{update_sql} WHERE oid=#{oid}" end alias_method :pupdate, :update_properties # Load an object from the database. # # Input: # oid = the object oid, OR the object name. def load(oid, klass) if oid.to_i > 0 # a valid Fixnum ? load_by_oid(oid, klass) else load_by_name(oid, klass) end end alias_method :get, :load # Load an object by oid. def load_by_oid(oid, klass) res = query "SELECT * FROM #{klass::DBTABLE} WHERE oid=#{oid}" read_one(res, klass) end alias_method :get_by_oid, :load_by_oid # Load an object by name. def load_by_name(name, klass) res = query "SELECT * FROM #{klass::DBTABLE} WHERE name='#{name}'" read_one(res, klass) end alias_method :get_by_name, :load_by_name # Load all objects of the given klass. # Used to be called 'collect' in an earlier version. def load_all(klass, extrasql = nil) res = query "SELECT * FROM #{klass::DBTABLE} #{extrasql}" read_all(res, klass) end alias_method :get_all, :load_all # Perform a standard SQL query to the database. Deserializes the # results. def select(sql, klass) unless sql =~ /SELECT/i sql = "SELECT * FROM #{klass::DBTABLE} WHERE #{sql}" end res = query(sql) read_all(res, klass) end # Optimized for one result. def select_one(sql, klass) unless sql =~ /SELECT/i sql = "SELECT * FROM #{klass::DBTABLE} WHERE #{sql}" end res = query(sql) read_one(res, klass) end # Perform a count query. def count(sql, klass = nil) unless sql =~ /SELECT/i sql = "SELECT COUNT(*) FROM #{klass::DBTABLE} WHERE #{sql}" end res = query(sql) return read_int(res) end # Delete an object from the database. Allways perform a deep delete. # # No need to optimize here with pregenerated code. Deletes are # not used as much as reads or writes. # # Input: # # obj_or_oid = Object or oid to delete. # klass = Class of object (can be nil if an object is passed) # #-- # TODO: pre evaluate for symmetry to the other methods #++ def delete(obj_or_oid, klass = nil, cascade = true) oid = obj_or_oid.to_i klass = obj_or_oid.class unless klass # this is a class callback! if klass.respond_to?(:og_pre_delete) klass.og_pre_delete(self, oid) end # TODO: implement this as stored procedure? naaah. # TODO: also handle many_to_many relations. transaction do |tx| tx.exec "DELETE FROM #{klass::DBTABLE} WHERE oid=#{oid}" if cascade and klass.__meta.include?(:descendants) klass.__meta[:descendants].each do |dclass, linkback| tx.exec "DELETE FROM #{dclass::DBTABLE} WHERE #{linkback}=#{oid}" end end end end alias_method :delete!, :delete end end