class PgFuncall

Constants

Literal
Typed
VERSION

Public Class Methods

_assign_pg_type_map_to_res(res, conn) click to toggle source
# File lib/pg_funcall.rb, line 245
def self._assign_pg_type_map_to_res(res, conn)
  return res

  ## this appears to fail to roundtrip on bytea and date types
  ##
  #if res.respond_to?(:type_map=)
  #  res.type_map = PG::BasicTypeMapForResults.new(conn)
  #end
  #res
end
default_instance() click to toggle source
# File lib/pg_funcall.rb, line 22
def self.default_instance
  @default_instance ||= PgFuncall.new(ActiveRecord::Base.connection)
end
default_instance=(instance) click to toggle source
# File lib/pg_funcall.rb, line 26
def self.default_instance=(instance)
  @default_instance = instance
end
literal(arg) click to toggle source

wrap a value so that it is inserted into the query as-is

# File lib/pg_funcall.rb, line 119
def self.literal(arg)
  Literal.new(arg)
end
new(connection) click to toggle source
# File lib/pg_funcall.rb, line 30
def initialize(connection)
  raise ArgumentError, "Requires ActiveRecord PG connection" unless
      connection.is_a?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)

  @ar_connection = connection

  clear_cache
end
tag_pg_type(value, tagtype, pgvalue = nil) click to toggle source
# File lib/pg_funcall.rb, line 98
def self.tag_pg_type(value, tagtype, pgvalue = nil)
  pgvalue ||= value

  # XXX: this is going to blow the method cache every time it runs
  value.class_eval do
    include PGTyped

    define_method(:__pg_value, lambda do
      pgvalue
    end)

    define_method(:__pg_type, lambda do
      tagtype
    end)
  end
  value
end

Public Instance Methods

_ar_conn() click to toggle source
# File lib/pg_funcall.rb, line 371
def _ar_conn
  @ar_connection
end
_cast_pgresult(res) click to toggle source

Take a PGResult and cast the first column of each tuple to the Ruby equivalent of the PG type as described in the PGResult.

# File lib/pg_funcall.rb, line 260
def _cast_pgresult(res)
  PgFuncall._assign_pg_type_map_to_res(res, _pg_conn)
  res.column_values(0).map do |val|
    type_map.type_cast_from_database(val,
                                     type_for_typeid(res.ftype(0)))
  end
end
_format_param_for_descriptor(param, type=nil) click to toggle source

Represent a Ruby object in a string form to be passed as a parameter within a descriptor hash, rather than substituted into a string-form query.

# File lib/pg_funcall.rb, line 161
def _format_param_for_descriptor(param, type=nil)
  return param.value if param.is_a?(Literal)

  case param
    when TypedArray
      _format_param_for_descriptor(param.value, param.type + "[]")
    when Typed
      _format_param_for_descriptor(param.value, param.type)
    when PGTyped
      param.respond_to?(:__pg_value) ?
          param.__pg_value :
          _format_param_for_descriptor(param, type)
    when TrueClass
      'true'
    when FalseClass
      'false'
    when String
      if type == 'bytea' || param.encoding == Encoding::BINARY
        '\x' + param.unpack('C*').map {|x| sprintf("%02X", x)}.join("")
      else
        param
      end
    when Array
      "{" + param.map {|p| _format_param_for_descriptor(p)}.join(",") + "}"
    when IPAddr
      param.to_cidr_string
    when Range
      last_char = param.exclude_end? ? ')' : ']'
      case type
        when 'tsrange', 'tstzrange'
          "[#{param.first.utc},#{param.last.utc}#{last_char}"
        else
          "[#{param.first},#{param.last}#{last_char}"
      end
    when Set
      _format_param_for_descriptor(param.to_a)
    when Hash
      param.map do |k,v|
        "#{k} => #{v}"
      end.join(',')
    else
      ActiveRecord::Base.connection.quote(param)
  end
end
_pg_conn() click to toggle source
# File lib/pg_funcall.rb, line 375
def _pg_conn
  _ar_conn.raw_connection
end
_pg_param_descriptors(params) click to toggle source
# File lib/pg_funcall.rb, line 281
def _pg_param_descriptors(params)
  params.map do |p|
    pgtype = _pgtype_for_value(p)
    typeinfo = type_map.resolve(pgtype)
    {
        # value: typeinfo.cast_to_database(p),
        value: _format_param_for_descriptor(p, pgtype),
        # if we can't find a type, let PG guess
        type:  (typeinfo && typeinfo.oid) || 0,
        format: 0
    }
  end
end
_pgtype_for_value(value) click to toggle source
# File lib/pg_funcall.rb, line 303
def _pgtype_for_value(value)
  case value
    # type-forcing wrapper for arrays
    when TypedArray
      value.type + '[]'

    # type-forcing wrapper
    when Typed
      value.type

    # marker ancestor
    when PGTyped
      value.__pg_type

    when String
      if value.encoding == Encoding::BINARY
        'bytea'
      else
        'text'
      end
    when Fixnum, Bignum
      'int4'
    when Float
      'float4'
    when TrueClass, FalseClass
      'bool'
    when BigDecimal
      'numeric'
    when Hash
      'hstore'
    when UUID
      'uuid'
    when Time, DateTime
      'timestamp'
    when Date
      'date'
    when IPAddr
      if value.host?
        'inet'
      else
        'cidr'
      end
    when Range
      case value.last
        when Fixnum
          if value.last > (2**31)-1
            'int8range'
          else
            'int4range'
          end
        when Bignum then 'int8range'
        when DateTime, Time then 'tsrange'
        when Date then 'daterange'
        when Float, BigDecimal, Numeric then 'numrange'
        else
          raise "Unknown range type: #{value.first.type}"
      end
    when Array, Set
      first = value.flatten.first
      raise "Empty untyped array" if first.nil?
      _pgtype_for_value(first) + '[]'
    else
      'text'
  end
end
_quote_param(param, type=nil) click to toggle source

“Quote”, which means to format and quote, a parameter for inclusion into a SQL query as a string.

# File lib/pg_funcall.rb, line 139
def _quote_param(param, type=nil)
  return param.value if param.is_a?(Literal)

  case param
    when Array
      "ARRAY[" + param.map {|p| _quote_param(p)}.join(",") + "]"
    when Set
      _quote_param(param.to_a)
    when Hash
      '$$' + param.map do |k,v|
        "#{k} => #{v}"
      end.join(',') + '$$::hstore'
    else
      ActiveRecord::Base.connection.quote(param)
  end
end
call(fn, *args)
Alias for: call_cast
call_cast(fn, *args) click to toggle source
# File lib/pg_funcall.rb, line 268
def call_cast(fn, *args)
  fn_sig = type_map.function_types(fn)

  ## TODO: finish this with the new type info class
  # unwrap = fn_sig && type_map.is_scalar_type?(type_map.lookup_by_oid(fn_sig.ret_type))

  call_raw_pg(fn, *args) do |res|
    results = _cast_pgresult(res)
    # unwrap && results.ntuples < 2 ? results.first : results
    results.first
  end
end
Also aliased as: call
call_raw(fn, *args)
Alias for: call_raw_inline
call_raw_inline(fn, *args) click to toggle source
# File lib/pg_funcall.rb, line 206
def call_raw_inline(fn, *args)
  query = "SELECT #{fn}(" +
       args.map {|arg| _quote_param(arg) }.join(", ") + ") as res;"

  ActiveRecord::Base.connection.exec_query(query,
                                           "calling for DB function #{fn}")
end
Also aliased as: call_raw
call_raw_pg(fn, *args, &blk) click to toggle source
# File lib/pg_funcall.rb, line 214
def call_raw_pg(fn, *args, &blk)
  query = "SELECT #{fn}(" +
       args.map {|arg| _quote_param(arg) }.join(", ") + ") as res;"

  _pg_conn.query(query, &blk).tap do |res|
    PgFuncall._assign_pg_type_map_to_res(res, _pg_conn)
  end
end
call_returning_array(fn, *args) click to toggle source
# File lib/pg_funcall.rb, line 131
def call_returning_array(fn, *args)
  call_raw(fn, *args).rows
end
call_returning_type(fn, ret_type, *args) click to toggle source

Force a typecast of the return value

# File lib/pg_funcall.rb, line 240
def call_returning_type(fn, ret_type, *args)
  type_map.type_cast_from_database(call(fn, *args),
                                   type_for_name(ret_type))
end
call_scalar(fn, *args)
Alias for: call_uncast
call_uncast(fn, *args) click to toggle source

Calls Database function with a given set of arguments. Returns result as a string.

# File lib/pg_funcall.rb, line 126
def call_uncast(fn, *args)
  call_raw(fn, *args).rows.first.first
end
Also aliased as: call_scalar
casting_query(query, params) click to toggle source
# File lib/pg_funcall.rb, line 295
def casting_query(query, params)
  # puts "param descriptors = #{_pg_param_descriptors(params)}.inspect"
  _pg_conn.exec_params(query, _pg_param_descriptors(params)) do |res|
    _cast_pgresult(res)
  end
end
clear_cache() click to toggle source
# File lib/pg_funcall.rb, line 39
def clear_cache
  (@ftype_cache ||= {}).clear
  @type_map = nil
  true
end
search_path() click to toggle source

Return an array of schema names for the current session’s search path

# File lib/pg_funcall.rb, line 382
def search_path
  _pg_conn.query("SHOW search_path;") do |res|
    res.column_values(0).first.split(/, ?/)
  end
end
type_for_name(name) click to toggle source
# File lib/pg_funcall.rb, line 229
def type_for_name(name)
  type_map.resolve(name)
end
type_for_typeid(typeid) click to toggle source
# File lib/pg_funcall.rb, line 225
def type_for_typeid(typeid)
  type_map.resolve(typeid.to_i)
end
type_map() click to toggle source
# File lib/pg_funcall.rb, line 233
def type_map
  @type_map ||= TypeMap.fetch(@ar_connection, search_path: search_path)
end