lib/pg_conn.rb in pg_conn-0.17.1 vs lib/pg_conn.rb in pg_conn-0.19.0

- old
+ new

@@ -245,11 +245,11 @@ # # The value can be of any type but is converted to a string using #to_s # before quoting. This works by default for the regular types Integer, # true/false, Time/Date/DateTime, and arrays. Other types may require # special handling - def quote_literal(value) + def quote_value(value) case value when String; @pg_connection.escape_literal(value) when Integer, Float; value.to_s when true, false; value.to_s when nil; 'null' @@ -259,16 +259,27 @@ else @pg_connection.escape_literal(value.to_s) end end - # Quote array as a parenthesis-enclosed list of identifiers - def quote_identifier_list(identifiers) = "(#{identifiers.map { quote_identifier(_1) }.join(', ')})" - # Quote array as a parenthesis-enclosed list of literals - def quote_literal_list(values) = "(#{values.map { quote_literal(_1) }.join(', ')})" + # Quote array as a comma-separated sequence of identifiers + def quote_identifier_seq(identifiers) = identifiers.map { quote_identifier(_1) }.join(', ') + # Quote array as a parenthesis-enclosed sequence of identifiers + def quote_identifier_list(identifiers) = "(#{quote_identifier_seq(identifiers)})" + + # Quote array as a comma-separated sequence of values + def quote_value_seq(values) = values.map { quote_literal(_1) }.join(', ') + + # Quote array as a parenthesis-enclosed list of value + def quote_value_list(values) = "(#{quote_value_seq(values)})" + + # Old aliases. TODO Remove + def quote_literal(value) = quote_value(value) + def quote_literal_list(values) = quote_value_list(values) + # :call-seq: # exist?(query) # exist?(table, id) # eists?(table, where_clause) # @@ -314,12 +325,12 @@ end # Return a single value. It is an error if the query doesn't return a # single record with a single column. If :transaction is true, the query # will be executed in a transaction and also be committed if :commit is - # true (this is the default). It can be used to execute 'insert' statements - # with a 'returning' clause + # true (this is the default). It can also be used to execute 'insert' + # statements with a 'returning' clause def value(query) #, transaction: false, commit: true) r = pg_exec(query) check_1c(r) check_1r(r) r.values[0][0] @@ -494,11 +505,11 @@ end # Returns a hash from the first field to a tuple of the remaining fields. # If there is only one remaining field then that value is used instead of a # tuple of that value. The optional +key+ argument sets the mapping field - def map(query, key = nil) + def map(query, key = nil) # TODO Swap arguments r = pg_exec(query) begin key = (key || r.fname(0)).to_s key_index = r.fnumber(key.to_s) one = (r.nfields == 2) @@ -512,10 +523,27 @@ h[key_value] = (one ? row.first : row) } h end + def multimap(query, key = nil) + r = pg_exec(query) + begin + key = (key || r.fname(0)).to_s + key_index = r.fnumber(key.to_s) + one = (r.nfields == 2) + rescue ArgumentError + raise Error, "Can't find column #{key}" + end + h = {} + r.each_row { |row| + key_value = row.delete_at(key_index) + (h[key_value] ||= []) << (one ? row.first : row) + } + h + end + # Return the value of calling the given function (which can be a String or # a Symbol and can contain the schema of the function). It dynamically # detects the structure of the result and return a value or an array of # values if the result contained only one column (like #value or #values), # a tuple if the record has multiple columns (like #tuple), and an array of @@ -554,19 +582,62 @@ r&.values end end end - # Insert record(s) in a table. Returns the id of the inserted record(s) - def insert(schema = nil, table, array_or_hash) - return [] if array_or_hash.empty? - table = [schema, table].compact.join(".") - is_array = array_or_hash.is_a?(Array) - array = is_array ? array_or_hash : [array_or_hash] - identifiers = quote_identifier_list(array.first.keys) - literals = array.map { |hash| quote_literal_list(hash.values) }.join(", ") - method = (is_array ? :values : :value) - self.send method, %(insert into #{table} #{identifiers} values #{literals} returning id) + # :call-seq* + # insert(table, record|records) + # insert(table, fields, record|records|tuples) + # insert(schema, table, record|records) + # insert(schema, table, fields, record|records|tuples) + # + # Insert record(s) in table and return id(s) + # + # There is no variant that takes a single tuple because it would then be + # impossible to have array or hash field values + def insert(*args) + # Add schema (=nil) if absent + args.unshift nil if args.size == 2 || (args.size == 3 && args[1].is_a?(Array)) + + # Add fields (=nil) if absent + args.insert(-2, nil) if !args[-2].is_a?(Array) + + # Check number of arguments + args.size == 4 or raise ArgumentError, "Illegal number of arguments" + + # Extract parameters + schema, table, fields, data = args + + # Normalize table + table = schema ? "#{schema}.#{table}" : table + + # Find method and normalize data + if data.is_a?(Array) + if data.empty? + return [] + elsif data.first.is_a?(Array) + method = :values + fields or raise ArgumentError + tuples = data + elsif data.first.is_a?(Hash) + method = :values + fields ||= data.first.keys + tuples = data.map { |record| fields.map { |field| record[field] } } + else + raise ArgumentError + end + elsif data.is_a?(Hash) + method = :value + fields ||= data.keys + tuples = [fields.map { |field| data[field] }] + else + raise ArgumentError + end + + # Build and execute SQL statement + columns = quote_identifier_list(fields) + values = tuples.map { |tuple| quote_literal_list(tuple) }.join(', ') + self.send method, %(insert into #{table} #{columns} values #{values} returning id) end # Update record(s) def update(schema = nil, table, expr, hash) table = [schema, table].compact.join(".")