lib/pg_conn.rb in pg_conn-0.20.0 vs lib/pg_conn.rb in pg_conn-0.21.0
- old
+ new
@@ -65,11 +65,11 @@
# The transaction timestamp of the most recent SQL statement executed by
# #exec or #transaction block. The timestamp is without time zone
attr_reader :timestamp
- # The transaction timestamp of the most recent SQL statement executed by
+ # The transaction timestamp of the most recent SQL statement executed by
# #exec or #transaction block. The timestamp includes the current time zone
attr_reader :timestamptz
# PG::Error object of the first failed statement in the transaction;
# otherwise nil. It is cleared at the beginning of a transaction so be sure
@@ -188,19 +188,19 @@
if @pg_connection && !using_existing_connection
# Set a dummy notice processor to avoid warnings on stderr
@pg_connection.set_notice_processor { |message| ; } # Intentionally a nop
# Auto-convert to ruby types
- type_map = PG::BasicTypeMapForResults.new(@pg_connection)
+ type_map = PG::BasicTypeMapForResults.new(@pg_connection)
# Use String as default type. Kills 'Warning: no type cast defined for
# type "uuid" with oid 2950..' warnings
type_map.default_type_map = PG::TypeMapAllStrings.new
# Timestamp decoder
type_map.add_coder PG::TextDecoder::Timestamp.new( # Timestamp without time zone
- oid: 1114,
+ oid: 1114,
flags: PG::Coder::TIMESTAMP_DB_UTC | PG::Coder::TIMESTAMP_APP_UTC)
# Decode anonymous records but note that this is only useful to convert the
# outermost structure into an array, the elements are not decoded and are
# returned as strings. It is best to avoid anonymous records if possible
@@ -220,11 +220,11 @@
@savepoints = nil # Stack of savepoint names. Nil if no transaction in progress
end
# Close the database connection. TODO: Rename 'close'
def terminate()
- @pg_connection.close if @pg_connection && !@pg_connection.finished?
+ @pg_connection.close if @pg_connection && !@pg_connection.finished?
end
def self.new(*args, **opts, &block)
if block_given?
begin
@@ -249,11 +249,15 @@
#
# 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_value(value)
+ #
+ # Note that a tuple value (an array) must be quoted using #quote_tuple
+ # because #quote_value would quote the tuple as an array instead of a list
+ # of values
+ 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'
@@ -263,48 +267,32 @@
else
@pg_connection.escape_literal(value.to_s)
end
end
- # Quote array as a comma-separated sequence of identifiers
- def quote_identifier_seq(identifiers) = identifiers.map { quote_identifier(_1) }.join(', ')
+ # Quote an array of values as a tuple. Just an alias for #quote_values
+ def quote_tuple(tuple) = quote_values(tuple)
- # Quote array as a parenthesis-enclosed sequence of identifiers. TODO: Rename quote_identifier_tuple
- def quote_identifier_list(identifiers) = "(#{quote_identifier_seq(identifiers)})"
+ # Quote identifiers and concatenate them using ',' as separator
+ def quote_identifiers(idents) = idents.map { |ident| quote_identifier(ident) }.join(", ")
- # Quote array as a bracket-enclosed sequence of identifiers
-# def quote_identifier_array(identifiers) = "(#{quote_identifier_seq(identifiers)})"
+ # Quote values and concatenate them using ',' as separator
+ def quote_values(values) = values.map { |value| quote_value(value) }.join(", ")
- # Quote array as a curly-bracket-enclosed sequence of identifiers
-# def quote_identifier_hash(identifiers) = "(#{quote_identifier_seq(identifiers)})"
+ # Quote an array of tuples
+ def quote_tuples(tuples) = tuples.map { |tuple| "(#{quote_values(tuple)})" }.join(", ")
- # Quote array as a comma-separated sequence of quoted values
- def quote_value_seq(values) = values.map { quote_literal(_1) }.join(', ')
-
- # Quote array as a parenthesis-enclosed list of quoted values. TODO: Rename to quote_value_tuple
- def quote_value_list(values) = "(#{quote_value_seq(values)})"
-
- # Quote array as a bracket-enclosed sequence of values
-# def quote_identifier_array(values) = "(#{quote_identifier_seq(values)})"
-
- # Quote array as a curly-bracket-enclosed sequence of values
-# def quote_identifier_hash(values) = "(#{quote_identifier_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)
#
# Return true iff the query returns exactly one record. Use '!empty?' to
# check if the query returns one or more records
def exist?(*args)
arg1, arg2 = *args
- query =
+ query =
case arg2
when Integer; "select from #{arg1} where id = #{arg2}"
when String; "select from #{arg1} where #{arg2}"
when NilClass; arg1
end
@@ -314,11 +302,11 @@
# :call-seq:
# count(query)
# count(table, where_clause = nil)
#
# Return true if the table or the result of the query is empty
- def empty?(arg, where_clause = nil)
+ def empty?(arg, where_clause = nil)
if arg =~ /\s/
value "select count(*) from (#{arg} limit 1) as inner_query"
elsif where_clause
value "select count(*) from (select 1 from #{arg} where #{where_clause} limit 1) as inner_query"
else
@@ -570,27 +558,16 @@
# a tuple if the record has multiple columns (like #tuple), and an array of
# of tuples if the result contained more than one record with multiple
# columns (like #tuples)
#
def call(name, *args, proc: false) # :proc may interfere with hashes
- args_sql = args.map { |arg| # TODO: Use pg's encoder
- case arg
- when NilClass; "null"
- when String; "'#{arg}'"
- when Integer; arg
- when TrueClass, FalseClass; arg
- when Array; "Array['#{arg.join("', '")}']" # Quick and dirty # FIXME
- when Hash; raise NotImplementedError
- else
- raise ArgumentError, "Unrecognized value: #{arg.inspect}"
- end
- }.join(", ")
+ args_seq = quote_values(args)
if proc
- pg_exec "call #{name}(#{args_sql})"
+ pg_exec "call #{name}(#{args_seq})"
return nil
else
- r = pg_exec "select * from #{name}(#{args_sql})"
+ r = pg_exec "select * from #{name}(#{args_seq})"
if r.ntuples == 0
raise Error, "No records returned"
elsif r.ntuples == 1
if r.nfields == 1
r.values[0][0]
@@ -630,19 +607,18 @@
# Normalize table
table = schema ? "#{schema}.#{table}" : table
# Find method and normalize data
- if data.is_a?(Array)
+ if data.is_a?(Array) # Array of tuples
+ method = :values
if data.empty?
return []
- elsif data.first.is_a?(Array)
- method = :values
+ elsif data.first.is_a?(Array) # Tuple (array) element. Requires the 'fields' argument
fields or raise ArgumentError
tuples = data
- elsif data.first.is_a?(Hash)
- method = :values
+ elsif data.first.is_a?(Hash) # Hash element
fields ||= data.first.keys
tuples = data.map { |record| fields.map { |field| record[field] } }
else
raise ArgumentError
end
@@ -652,39 +628,41 @@
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)
+ # Execute SQL statement using either :value or :values depending on data arity
+ self.send method, %(
+ insert into #{table} (#{quote_identifiers(fields)})
+ values #{quote_tuples(tuples)}
+ returning id
+ )
end
# Update record(s)
def update(schema = nil, table, expr, hash)
table = [schema, table].compact.join(".")
- assignments = hash.map { |k,v| "#{k} = #{quote_literal(v)}" }.join(", ")
- constraint =
+ assignments = hash.map { |k,v| "#{k} = #{quote_value(v)}" }.join(", ")
+ constraint =
case expr
when String; expr
- when Integer; "id = #{quote_literal(expr)}"
- when Array; "id in #{quote_literal_list(expr)}"
+ when Integer; "id = #{quote_value(expr)}"
+ when Array; "id in (#{quote_values(expr)})"
else
raise ArgumentError
end
exec %(update #{table} set #{assignments} where #{constraint})
end
# Delete record(s)
def delete(schema = nil, table, expr)
table = [schema, table].compact.join(".")
- constraint =
+ constraint =
case expr
when String; expr
- when Integer; "id = #{quote_literal(expr)}"
- when Array; "id in #{quote_literal_list(expr)}"
+ when Integer; "id = #{quote_value(expr)}"
+ when Array; "id in (#{quote_values(expr)})"
else
raise ArgumentError
end
exec %(delete from #{table} where #{constraint})
end
@@ -695,11 +673,11 @@
# arbitrarily nested array of commands. Note that you can't have commands
# that span multiple lines. The empty array is a NOP but the empty string
# is not.
#
# #exec pass Postgres exceptions to the caller unless :fail is false in which case
- # it returns nil.
+ # it returns nil.
#
# Note that postgres crashes the whole transaction stack if any error is
# met so if you're inside a transaction, the transaction will be in an
# error state and if you're also using subtransactions the whole
# transaction stack has collapsed
@@ -711,11 +689,11 @@
# Like #exec but returns true/false depending on if the command succeeded.
# There is not a corresponding #execute? method because any failure rolls
# back the whole transaction stack. TODO: Check which exceptions that
# should be captured
- def exec?(sql, commit: true, silent: true)
+ def exec?(sql, commit: true, silent: true)
begin
exec(sql, commit: commit, fail: true, silent: silent)
rescue PG::Error
return false
end
@@ -746,11 +724,11 @@
# Switch user to the given user and execute the statement before swithcing
# back to the original user
#
# FIXME: The out-commented transaction block makes postspec fail for some reason
- # TODO: Rename 'sudo' because it acts just like it.
+ # TODO: Rename 'sudo' because it acts just like it.
def su(username, &block)
raise Error, "Missing block in call to PgConn::Connection#su" if !block_given?
realuser = self.value "select current_user"
result = nil
# transaction(commit: false) {
@@ -796,17 +774,17 @@
else
@savepoints = []
pg_exec("begin")
@error = @err = nil
# FIXME This special-cases the situation where commands are logged to a
- # file instead of being executed. Maybe remove logging (or execute always
+ # file instead of being executed. Maybe remove logging (or execute always
# and log as a side-effect)
if @pg_connection
@timestamp, @timestamptz = @pg_connection.exec(
'select current_timestamp, current_timestamp::timestamp without time zone'
).tuple_values(0)
- end
+ end
end
end
def pop_transaction(commit: true, fail: true, exception: true)
if transaction?
@@ -939,11 +917,11 @@
rescue PG::Error => ex
if @error.nil?
@error = ex
@err = nil
end
- if !silent # FIXME Why do we handle this?
+ if !silent # FIXME Why do we handle this?
$stderr.puts arg
$stderr.puts
$stderr.puts ex.message
$stderr.flush
end
@@ -959,23 +937,23 @@
end
nil
end
end
- def check_1c(r)
+ def check_1c(r)
case r.nfields
when 0; raise Error, "No columns returned"
- when 1;
+ when 1;
else
- raise Error, "More than one column returned"
+ raise Error, "More than one column returned"
end
end
- def check_1r(r)
+ def check_1r(r)
if r.ntuples == 0
raise Error, "No records returned"
- elsif r.ntuples > 1
- raise Error, "More than one record returned"
+ elsif r.ntuples > 1
+ raise Error, "More than one record returned"
end
end
end
def self.sql_values(values) "'" + values.join("', '") + "'" end