# * George Moschovitis # (c) 2004-2005 Navel, all rights reserved. # $Id: connection.rb 248 2005-01-31 13:38:34Z gmosx $ class 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 frontend (Og) contains useful strucutres. attr_reader :og # The backend attr_reader :db # If set to true, the select methods deserialize the # resultset to create entities. attr_accessor :deserialize # Initialize a connection to the database. def initialize(og) @og = og @db = @og.config[:backend].new(@og.config) @deserialize = true Logger.debug "Created DB connection." if $DBG end # Close the connection to the database. def close() @db.close() Logger.debug "Closed DB connection." if $DBG end # 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}" @deserialize? @db.deserialize_one(res, klass) : res 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}'" @deserialize? @db.deserialize_one(res, klass) : res 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}" @deserialize? @db.deserialize_all(res, klass) : res 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 = @db.safe_query(sql) @deserialize? @db.deserialize_all(res, klass) : res end # Optimized for one result. def select_one(sql, klass) unless sql =~ /SELECT/i sql = "SELECT * FROM #{klass::DBTABLE} WHERE #{sql}" end res = @db.safe_query(sql) @deserialize? @db.deserialize_one(res, klass) : res end # Perform a count query. def count(sql, klass = nil) unless sql =~ /SELECT/i sql = "SELECT COUNT(*) FROM #{klass::DBTABLE} WHERE #{sql}" end res = @db.safe_query(sql) return @db.get_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) 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. transaction do |tx| tx.exec "DELETE FROM #{klass::DBTABLE} WHERE oid=#{oid}" if cascade and klass.__meta.include?(:has) klass.__meta[:has].each do |dclass, linkback| tx.exec "DELETE FROM #{dclass::DBTABLE} WHERE #{linkback}=#{oid}" end end end end alias_method :delete!, :delete # 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) @db.create_table(klass) end # Drop the managed object table. def drop_table(klass) @db.drop_table(klass) end # Execute an SQL query and return the result def query(sql) @db.safe_query(sql) end # Execute an SQL query, no result returned. def exec(sql) @db.safe_exec(sql) end # Start a new transaction. def start @db.start end # Commit a transaction. def commit @db.commit end # Rollback transaction. def rollback @db.rollback end # Transaction helper. In the transaction block use # the db pointer to the backend. def transaction(&block) begin @db.start yield(@db) @db.commit rescue => ex Logger.error "DB Error: ERROR IN TRANSACTION" Logger.error #{ex} Logger.error #{ex.backtrace} @db.rollback end end end end