require 'swift/result' require 'swift/statement' module Swift # Adapter. # # @abstract # @see Swift::DB See Swift::DB for concrete adapters. class Adapter attr_reader :db def initialize db @db = db end # Select by id(s). # # @example Single key. # Swift.db.get(User, id: 12) # @example Complex primary key. # Swift.db.get(UserAddress, user_id: 12, address_id: 15) # # @param [Swift::Record] record Concrete record subclass to load. # @param [Hash] keys Hash of id(s) {id_name: value}. # @return [Swift::Record, nil] # @see Swift::Record.get #-- # NOTE: Not significantly shorter than Record.db.first(User, 'id = ?', 12) def get record, keys resource = record.new(keys) execute(record, command_get(record), *resource.tuple.values_at(*record.header.keys)).first end # Create one or more. # # @example Record. # user = User.new(name: 'Apply Arthurton', age: 32) # Swift.db.create(User, user) # #=> Swift::Record # @example Coerce hash to record. # Swif.db.create(User, name: 'Apple Arthurton', age: 32) # #=> Swift::Record # @example Multiple resources. # apple = User.new(name: 'Apple Arthurton', age: 32) # benny = User.new(name: 'Benny Arthurton', age: 30) # Swift.db.create(User, [apple, benny]) # #=> Array # @example Coerce multiple resources. # Swift.db.create(User, [{name: 'Apple Arthurton', age: 32}, {name: 'Benny Arthurton', age: 30}]) # #=> Array # # @param [Swift::Record] record Concrete record subclass to load. # @param [Swift::Record, Hash, Array] resources The resources to be saved. # @return [Swift::Record, Array] # @note Hashes will be coerced into a Swift::Record resource via Swift::Record#new # @note Passing a scalar will result in a scalar. # @see Swift::Record.create def create record, resources result = [resources].flatten.map do |resource| resource = record.new(resource) unless resource.kind_of?(record) result = execute(command_create(record), *resource.tuple.values_at(*record.header.insertable)) resource.tuple[record.header.serial] = result.insert_id if record.header.serial resource end resources.kind_of?(Array) ? result : result.first end # Update one or more. # # @example Record. # user = Swift.db.create(User, name: 'Apply Arthurton', age: 32) # user.name = 'Arthur Appleton' # Swift.db.update(User, user) # #=> Swift::Record # @example Coerce hash to record. # user = Swift.db.create(User, name: 'Apply Arthurton', age: 32) # user.name = 'Arthur Appleton' # Swif.db.update(User, user.tuple) # #=> Swift::Record # @example Multiple resources. # apple = Swift.db.create(User, name: 'Apple Arthurton', age: 32) # benny = Swift.db.create(User, name: 'Benny Arthurton', age: 30) # Swift.db.update(User, [apple, benny]) # #=> Array # @example Coerce multiple resources. # apple = Swift.db.create(User, name: 'Apple Arthurton', age: 32) # benny = Swift.db.create(User, name: 'Benny Arthurton', age: 30) # Swift.db.update(User, [apple.tuple, benny.tuple]) # #=> Array # # @param [Swift::Record] record Concrete record subclass to load. # @param [Swift::Record, Hash, Array] resources The resources to be updated. # @return [Swift::Record, Swift::Result] # @note Hashes will be coerced into a Swift::Record resource via Swift::Record#new # @note Passing a scalar will result in a scalar. # @see Swift::Record#update def update record, resources result = [resources].flatten.map do |resource| resource = record.new(resource) unless resource.kind_of?(record) keys = resource.tuple.values_at(*record.header.keys) # TODO: Name the key field(s) missing. raise ArgumentError, "#{record} resource has incomplete key: #{resource.inspect}" \ unless keys.select(&:nil?).empty? execute(command_update(record), *resource.tuple.values_at(*record.header.updatable), *keys) resource end resources.kind_of?(Array) ? result : result.first end # Delete one or more. # # @example Record. # user = Swift.db.create(User, name: 'Apply Arthurton', age: 32) # user.name = 'Arthur Appleton' # Swift.db.delete(User, user) # @example Coerce hash to record. # user = Swift.db.create(User, name: 'Apply Arthurton', age: 32) # user.name = 'Arthur Appleton' # Swif.db.delete(User, user.tuple) # @example Multiple resources. # apple = Swift.db.create(User, name: 'Apple Arthurton', age: 32) # benny = Swift.db.create(User, name: 'Benny Arthurton', age: 30) # Swift.db.delete(User, [apple, benny]) # @example Coerce multiple resources. # apple = Swift.db.create(User, name: 'Apple Arthurton', age: 32) # benny = Swift.db.create(User, name: 'Benny Arthurton', age: 30) # Swift.db.delete(User, [apple.tuple, benny.tuple]) # # @param [Swift::Record] record Concrete record subclass to load. # @param [Swift::Record, Hash, Array] resources The resources to be deleteed. # @return [Swift::Record, Array] # @note Hashes will be coerced into a Swift::Record resource via Swift::Record#new # @note Passing a scalar will result in a scalar. # @see Swift::Record#delete def delete record, resources result = [resources].flatten.map do |resource| resource = record.new(resource) unless resource.kind_of?(record) keys = resource.tuple.values_at(*record.header.keys) # TODO: Name the key field(s) missing. raise ArgumentError, "#{record} resource has incomplete key: #{resource.inspect}" \ unless keys.select(&:nil?).empty? if result = execute(command_delete(record), *keys) resource.freeze end result end resources.kind_of?(Array) ? result : result.first end # Create a server side prepared statement # # @example # finder = Swift.db.prepare(User, "select * from users where id > ?") # user = finder.execute(1).first # user.id # # @overload prepare(record, command) # @param [Swift::Record] record Concrete record subclass to load. # @param [String] command Command to be prepared by the underlying concrete adapter. # @overload prepare(command) # @param [String] command Command to be prepared by the underlying concrete adapter. # # @return [Swift::Statement, Swift::DB::Mysql::Statement, Swift::DB::Sqlite3::Statement, ...] def prepare record = nil, command record ? Statement.new(record, command) : db.prepare(command) end # Trace commands being executed. # # @example # Swift.db.trace { Swift.db.execute("select * from users") } # @example # Swift.db.trace(StringIO.new) { Swift.db.execute("select * from users") } # @example # Swift.db.trace(File.open('command.log', 'w')) { Swift.db.execute("select * from users") } # # @param [IO] io An optional IO object to log commands # @return [Object] result Result from the block yielded to def trace io = $stdout @trace = io result = yield @trace = false result end # Check if the adapter commands are being traced. # # @return [TrueClass, FalseClass] def trace? !!@trace end # Execute a command using the underlying concrete adapter. # # @example # Swift.db.execute("select * from users") # @example # Swift.db.execute(User, "select * from users where id = ?", 1) # # @overload execute(record, command, *bind) # @param [Swift::Record] record Concrete record subclass to load. # @param [String] command Command to be executed by the adapter. # @param [*Object] bind Bind values. # @overload execute(command, *bind) # @param [String] command Command to be executed by the adapter. # @param [*Object] bind Bind values. # # @return [Swift::Result, Swift::DB::Mysql::Result, Swift::DB::Sqlite3::Result, ...] def execute command, *bind start = Time.now record, command = command, bind.shift if command.kind_of?(Class) && command < Record record ? Result.new(record, db.execute(command, *bind)) : db.execute(command, *bind) ensure log_command(start, command, bind) if @trace end private def log_command start, command, bind @trace.print Time.now.strftime('%F %T.%N'), ' - ', (Time.now - start).to_f, ' - ', command, ' ', bind, $/ end end # Adapter end # Swift