require 'active_record/connection_adapters/abstract_adapter'
module ActiveRecord
module ConnectionAdapters
class PostgreSQLAdapter < AbstractAdapter
module OID
class Type
def type; end
def type_cast_for_write(value)
value
end
end
class Identity < Type
def type_cast(value)
value
end
end
class Bytea < Type
def type_cast(value)
PGconn.unescape_bytea value if value
end
end
class Money < Type
def type_cast(value)
return if value.nil?
# Because money output is formatted according to the locale, there are two
# cases to consider (note the decimal separators):
# (1) $12,345,678.12
# (2) $12.345.678,12
case value
when /^-?\D+[\d,]+\.\d{2}$/ # (1)
value.gsub!(/[^-\d.]/, '')
when /^-?\D+[\d.]+,\d{2}$/ # (2)
value.gsub!(/[^-\d,]/, '').sub!(/,/, '.')
end
ConnectionAdapters::Column.value_to_decimal value
end
end
class Vector < Type
attr_reader :delim, :subtype
# +delim+ corresponds to the `typdelim` column in the pg_types
# table. +subtype+ is derived from the `typelem` column in the
# pg_types table.
def initialize(delim, subtype)
@delim = delim
@subtype = subtype
end
# FIXME: this should probably split on +delim+ and use +subtype+
# to cast the values. Unfortunately, the current Rails behavior
# is to just return the string.
def type_cast(value)
value
end
end
class Integer < Type
def type_cast(value)
return if value.nil?
value.to_i rescue value ? 1 : 0
end
end
class Boolean < Type
def type_cast(value)
return if value.nil?
ConnectionAdapters::Column.value_to_boolean value
end
end
class Timestamp < Type
def type; :timestamp; end
def type_cast(value)
return if value.nil?
# FIXME: probably we can improve this since we know it is PG
# specific
ConnectionAdapters::PostgreSQLColumn.string_to_time value
end
end
class Date < Type
def type; :datetime; end
def type_cast(value)
return if value.nil?
# FIXME: probably we can improve this since we know it is PG
# specific
ConnectionAdapters::Column.value_to_date value
end
end
class Time < Type
def type_cast(value)
return if value.nil?
# FIXME: probably we can improve this since we know it is PG
# specific
ConnectionAdapters::Column.string_to_dummy_time value
end
end
class Float < Type
def type_cast(value)
return if value.nil?
value.to_f
end
end
class Decimal < Type
def type_cast(value)
return if value.nil?
ConnectionAdapters::Column.value_to_decimal value
end
end
class Hstore < Type
def type_cast(value)
return if value.nil?
ConnectionAdapters::PostgreSQLColumn.cast_hstore value
end
end
class TypeMap
def initialize
@mapping = {}
end
def []=(oid, type)
@mapping[oid] = type
end
def [](oid)
@mapping[oid]
end
def fetch(ftype, fmod)
# The type for the numeric depends on the width of the field,
# so we'll do something special here.
#
# When dealing with decimal columns:
#
# places after decimal = fmod - 4 & 0xffff
# places before decimal = (fmod - 4) >> 16 & 0xffff
if ftype == 1700 && (fmod - 4 & 0xffff).zero?
ftype = 23
end
@mapping.fetch(ftype) { |oid| yield oid, fmod }
end
end
TYPE_MAP = TypeMap.new # :nodoc:
TYPE_MAP[23] = OID::Integer.new # int4
TYPE_MAP[20] = TYPE_MAP[23] # int8
TYPE_MAP[21] = TYPE_MAP[23] # int2
TYPE_MAP[26] = TYPE_MAP[23] # oid
TYPE_MAP[1700] = OID::Decimal.new # decimal
TYPE_MAP[25] = OID::Identity.new # text
TYPE_MAP[19] = TYPE_MAP[25] # name
TYPE_MAP[1043] = TYPE_MAP[25] # varchar
# FIXME: why are we keeping these types as strings?
TYPE_MAP[3614] = TYPE_MAP[25] # tsvector
TYPE_MAP[1186] = TYPE_MAP[25] # interval
TYPE_MAP[650] = TYPE_MAP[25] # cidr
TYPE_MAP[869] = TYPE_MAP[25] # inet
TYPE_MAP[829] = TYPE_MAP[25] # macaddr
TYPE_MAP[1560] = TYPE_MAP[25] # bit
TYPE_MAP[1562] = TYPE_MAP[25] # varbit
# FIXME: I don't think this is correct. We should probably be returning a parsed date,
# but the tests pass with a string returned.
TYPE_MAP[1184] = OID::Identity.new # timestamptz
TYPE_MAP[790] = OID::Money.new # money
TYPE_MAP[17] = OID::Bytea.new # bytea
TYPE_MAP[16] = OID::Boolean.new # bool
TYPE_MAP[700] = OID::Float.new # float4
TYPE_MAP[701] = TYPE_MAP[700] # float8
TYPE_MAP[1114] = OID::Timestamp.new # timestamp
TYPE_MAP[1082] = OID::Date.new # date
TYPE_MAP[1083] = OID::Time.new # time
TYPE_MAP[1009] = OID::Vector.new(',', TYPE_MAP[25]) # _text
TYPE_MAP[1007] = OID::Vector.new(',', TYPE_MAP[23]) # _int4
TYPE_MAP[600] = OID::Vector.new(',', TYPE_MAP[701]) # point
TYPE_MAP[601] = OID::Vector.new(',', TYPE_MAP[600]) # lseg
TYPE_MAP[602] = OID::Identity.new # path
TYPE_MAP[603] = OID::Vector.new(';', TYPE_MAP[600]) # box
TYPE_MAP[604] = OID::Identity.new # polygon
TYPE_MAP[718] = OID::Identity.new # circle
TYPE_MAP[1399854] = OID::Hstore.new # hstore
end
end
end
end