Class: PgFuncall::TypeMap

Inherits:
Object
  • Object
show all
Defined in:
lib/pg_funcall/type_map.rb

Overview

Direct Known Subclasses

AR40TypeMap, AR41TypeMap, AR42TypeMap

Constant Summary collapse

FMETAQUERY =
<<-"SQL"
  SELECT prorettype, proargtypes
  FROM pg_proc as pgp
  JOIN pg_namespace as ns on pgp.pronamespace = ns.oid
  WHERE proname = '%s' AND ns.nspname = '%s';
SQL

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(connection, options = {}) ⇒ TypeMap

Returns a new instance of TypeMap.



49
50
51
52
53
54
55
56
57
58
# File 'lib/pg_funcall/type_map.rb', line 49

def initialize(connection, options = {})
  @ftype_cache = {}
  @ar_connection = connection
  @options = options
  @typeinfo         = []
  @typeinfo_by_name = {}
  @typeinfo_by_oid  = {}

  load_types
end

Class Method Details

.fetch(connection, options = {}) ⇒ Object



38
39
40
41
42
43
44
45
46
# File 'lib/pg_funcall/type_map.rb', line 38

def self.fetch(connection, options = {})
  case ActiveRecord.version.segments[0..1]
    when [4,0] then AR40TypeMap.new(connection, options)
    when [4,1] then AR41TypeMap.new(connection, options)
    when [4,2] then AR42TypeMap.new(connection, options)
    else
      raise ArgumentError, "Unsupported ActiveRecord version #{ActiveRecord.version}"
  end
end

Instance Method Details

#_canonicalize_type_name(name) ⇒ Object



96
97
98
99
100
101
# File 'lib/pg_funcall/type_map.rb', line 96

def _canonicalize_type_name(name)
  if name.end_with?('[]')
    name = '_' + name.gsub(/(\[\])+$/, '')
  end
  name
end

#ar_connectionObject



84
85
86
# File 'lib/pg_funcall/type_map.rb', line 84

def ar_connection
  @ar_connection
end

#function_types(fn, search_path = ) ⇒ Object

Query PostgreSQL metadata about function to find its return type and argument types



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/pg_funcall/type_map.rb', line 136

def function_types(fn, search_path = @options[:search_path])
  return @ftype_cache[fn] if @ftype_cache[fn]

  parts = fn.split('.')
  info =  if parts.length == 1
            raise ArgumentError, "Must supply search_path for non-namespaced function" unless
              search_path && search_path.is_a?(Enumerable) && !search_path.empty?
            search_path.map do |ns|
              res = pg_connection.query(FMETAQUERY % [parts[0], ns])
              PgFuncall._assign_pg_type_map_to_res(res, pg_connection)
              res.ntuples == 1 ? res : nil
            end.compact.first
          else
            PgFuncall._assign_pg_type_map_to_res(pg_connection.query(FMETAQUERY % [parts[1], parts[0]]),
                                                 pg_connection)
          end

  return nil unless info && info.ntuples >= 1

  @ftype_cache[fn] =
      FunctionSig.new(fn,
                      info.getvalue(0,0).to_i,
                      (0..info.ntuples-1).map { |row|
                        info.getvalue(row, 1).split(/ +/).map(&:to_i)
                      })
end

#load_typesObject



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/pg_funcall/type_map.rb', line 60

def load_types
  res = pg_connection.query <<-SQL
     SELECT pgt.oid, ns.nspname, *
     FROM pg_type as pgt
     JOIN pg_namespace as ns on pgt.typnamespace = ns.oid;
  SQL

  PgFuncall._assign_pg_type_map_to_res(res, pg_connection)

  fields = res.fields
  @typeinfo = res.values.map do |values|
    row = Hash[fields.zip(values)]
    TypeInfo.new(row, lookup_ar_by_oid(row['oid'].to_i))
  end

  @typeinfo_by_name.clear
  @typeinfo_by_oid.clear

  @typeinfo.each do |ti|
    @typeinfo_by_name[ti.name] = ti
    @typeinfo_by_oid[ti.oid]   = ti
  end
end

#oid_for_type(type, array = false) ⇒ Object

Given a type name, with optional appended [] or prefixed _ for array types, return the OID for it.

If array = true, find array type for given base type.



119
120
121
122
123
# File 'lib/pg_funcall/type_map.rb', line 119

def oid_for_type(type, array = false)
  type = _canonicalize_type_name(type)
  type = '_' + type if array && !type.start_with?('_')
  @typeinfo_by_name[type]
end

#pg_connectionObject



88
89
90
# File 'lib/pg_funcall/type_map.rb', line 88

def pg_connection
  @ar_connection.raw_connection
end

#resolve(oid_or_name) ⇒ Object



103
104
105
106
107
108
109
110
111
# File 'lib/pg_funcall/type_map.rb', line 103

def resolve(oid_or_name)
  if oid_or_name.is_a?(Integer) || (oid_or_name.is_a?(String) && oid_or_name.match(/^[0-9]+$/))
    @typeinfo_by_oid[oid_or_name.to_i]
  elsif oid_or_name.is_a?(String) || oid_or_name.is_a?(Symbol)
    @typeinfo_by_name[_canonicalize_type_name(oid_or_name.to_s)]
  else
    raise ArgumentError, "You must supply a numeric OID or a string Type name"
  end
end

#type_cast_from_database(value, type) ⇒ Object



92
93
94
# File 'lib/pg_funcall/type_map.rb', line 92

def type_cast_from_database(value, type)
  type.cast_from_database(value)
end