diff options
Diffstat (limited to 'activerecord/lib/active_record/connection_adapters/postgresql/oid.rb')
-rw-r--r-- | activerecord/lib/active_record/connection_adapters/postgresql/oid.rb | 378 |
1 files changed, 25 insertions, 353 deletions
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb index 1d0384c3df..e54c092bf6 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb @@ -1,360 +1,32 @@ +require 'active_record/connection_adapters/postgresql/oid/infinity' + +require 'active_record/connection_adapters/postgresql/oid/array' +require 'active_record/connection_adapters/postgresql/oid/bit' +require 'active_record/connection_adapters/postgresql/oid/bytea' +require 'active_record/connection_adapters/postgresql/oid/cidr' +require 'active_record/connection_adapters/postgresql/oid/date' +require 'active_record/connection_adapters/postgresql/oid/date_time' +require 'active_record/connection_adapters/postgresql/oid/decimal' +require 'active_record/connection_adapters/postgresql/oid/enum' +require 'active_record/connection_adapters/postgresql/oid/float' +require 'active_record/connection_adapters/postgresql/oid/hstore' +require 'active_record/connection_adapters/postgresql/oid/inet' +require 'active_record/connection_adapters/postgresql/oid/integer' +require 'active_record/connection_adapters/postgresql/oid/json' +require 'active_record/connection_adapters/postgresql/oid/money' +require 'active_record/connection_adapters/postgresql/oid/point' +require 'active_record/connection_adapters/postgresql/oid/range' +require 'active_record/connection_adapters/postgresql/oid/specialized_string' +require 'active_record/connection_adapters/postgresql/oid/time' +require 'active_record/connection_adapters/postgresql/oid/uuid' +require 'active_record/connection_adapters/postgresql/oid/vector' + +require 'active_record/connection_adapters/postgresql/oid/type_map_initializer' + module ActiveRecord module ConnectionAdapters module PostgreSQL module OID # :nodoc: - module Infinity - def infinity(options = {}) - options[:negative] ? -::Float::INFINITY : ::Float::INFINITY - end - end - - class SpecializedString < Type::String - attr_reader :type - - def initialize(type) - @type = type - end - - def text? - false - end - end - - class Bit < Type::String - def type_cast(value) - if ::String === value - ConnectionAdapters::PostgreSQLColumn.string_to_bit value - else - value - end - end - end - - class Bytea < Type::Binary - def cast_value(value) - PGconn.unescape_bytea value - end - end - - class Money < Type::Decimal - include Infinity - - def extract_scale(sql_type) - 2 - end - - def cast_value(value) - return value unless ::String === value - - # 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 - # Negative values are represented as follows: - # (3) -$2.55 - # (4) ($2.55) - - value.sub!(/^\((.+)\)$/, '-\1') # (4) - case value - when /^-?\D+[\d,]+\.\d{2}$/ # (1) - value.gsub!(/[^-\d.]/, '') - when /^-?\D+[\d.]+,\d{2}$/ # (2) - value.gsub!(/[^-\d,]/, '').sub!(/,/, '.') - end - - super(value) - end - end - - class Vector < Type::Value - 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 Point < Type::String - def type_cast(value) - if ::String === value - ConnectionAdapters::PostgreSQLColumn.string_to_point value - else - value - end - end - end - - class Array < Type::Value - attr_reader :subtype - delegate :type, to: :subtype - - def initialize(subtype) - @subtype = subtype - end - - def type_cast(value) - if ::String === value - ConnectionAdapters::PostgreSQLColumn.string_to_array value, @subtype - else - value - end - end - end - - class Range < Type::Value - attr_reader :subtype, :type - - def initialize(subtype, type) - @subtype = subtype - @type = type - end - - def extract_bounds(value) - from, to = value[1..-2].split(',') - { - from: (value[1] == ',' || from == '-infinity') ? @subtype.infinity(negative: true) : from, - to: (value[-2] == ',' || to == 'infinity') ? @subtype.infinity : to, - exclude_start: (value[0] == '('), - exclude_end: (value[-1] == ')') - } - end - - def infinity?(value) - value.respond_to?(:infinite?) && value.infinite? - end - - def type_cast_single(value) - infinity?(value) ? value : @subtype.type_cast(value) - end - - def cast_value(value) - return if value == 'empty' - return value if value.is_a?(::Range) - - extracted = extract_bounds(value) - from = type_cast_single extracted[:from] - to = type_cast_single extracted[:to] - - if !infinity?(from) && extracted[:exclude_start] - if from.respond_to?(:succ) - from = from.succ - ActiveSupport::Deprecation.warn <<-MESSAGE -Excluding the beginning of a Range is only partialy supported through `#succ`. -This is not reliable and will be removed in the future. - MESSAGE - else - raise ArgumentError, "The Ruby Range object does not support excluding the beginning of a Range. (unsupported value: '#{value}')" - end - end - ::Range.new(from, to, extracted[:exclude_end]) - end - end - - class Integer < Type::Integer - include Infinity - end - - class DateTime < Type::DateTime - include Infinity - - def cast_value(value) - if value.is_a?(::String) - case value - when 'infinity' then ::Float::INFINITY - when '-infinity' then -::Float::INFINITY - when / BC$/ - super("-" + value.sub(/ BC$/, "")) - else - super - end - else - value - end - end - end - - class Date < Type::Date - include Infinity - end - - class Time < Type::Time - include Infinity - end - - class Float < Type::Float - include Infinity - - def type_cast(value) - case value - when nil then nil - when 'Infinity' then ::Float::INFINITY - when '-Infinity' then -::Float::INFINITY - when 'NaN' then ::Float::NAN - else value.to_f - end - end - end - - class Decimal < Type::Decimal - def infinity(options = {}) - BigDecimal.new("Infinity") * (options[:negative] ? -1 : 1) - end - end - - class Enum < Type::Value - def type - :enum - end - - def type_cast(value) - value.to_s - end - end - - class Hstore < Type::Value - def type - :hstore - end - - def type_cast_for_write(value) - ConnectionAdapters::PostgreSQLColumn.hstore_to_string value - end - - def cast_value(value) - ConnectionAdapters::PostgreSQLColumn.string_to_hstore value - end - - def accessor - ActiveRecord::Store::StringKeyedHashAccessor - end - end - - class Cidr < Type::Value - def type - :cidr - end - - def cast_value(value) - ConnectionAdapters::PostgreSQLColumn.string_to_cidr value - end - end - - class Inet < Cidr - def type - :inet - end - end - - class Json < Type::Value - def type - :json - end - - def type_cast_for_write(value) - ConnectionAdapters::PostgreSQLColumn.json_to_string value - end - - def cast_value(value) - ConnectionAdapters::PostgreSQLColumn.string_to_json value - end - - def accessor - ActiveRecord::Store::StringKeyedHashAccessor - end - end - - class Uuid < Type::Value - def type - :uuid - end - - def type_cast(value) - value.presence - end - end - - # This class uses the data from PostgreSQL pg_type table to build - # the OID -> Type mapping. - # - OID is and integer representing the type. - # - Type is an OID::Type object. - # This class has side effects on the +store+ passed during initialization. - class TypeMapInitializer # :nodoc: - def initialize(store) - @store = store - end - - def run(records) - mapped, nodes = records.partition { |row| OID.registered_type? row['typname'] } - ranges, nodes = nodes.partition { |row| row['typtype'] == 'r' } - enums, nodes = nodes.partition { |row| row['typtype'] == 'e' } - domains, nodes = nodes.partition { |row| row['typtype'] == 'd' } - arrays, nodes = nodes.partition { |row| row['typinput'] == 'array_in' } - composites, nodes = nodes.partition { |row| row['typelem'] != '0' } - - mapped.each { |row| register_mapped_type(row) } - enums.each { |row| register_enum_type(row) } - domains.each { |row| register_domain_type(row) } - arrays.each { |row| register_array_type(row) } - ranges.each { |row| register_range_type(row) } - composites.each { |row| register_composite_type(row) } - end - - private - def register_mapped_type(row) - register row['oid'], OID::NAMES[row['typname']] - end - - def register_enum_type(row) - register row['oid'], OID::Enum.new - end - - def register_array_type(row) - if subtype = @store.lookup(row['typelem'].to_i) - register row['oid'], OID::Array.new(subtype) - end - end - - def register_range_type(row) - if subtype = @store.lookup(row['rngsubtype'].to_i) - register row['oid'], OID::Range.new(subtype, row['typname'].to_sym) - end - end - - def register_domain_type(row) - if base_type = @store.lookup(row["typbasetype"].to_i) - register row['oid'], base_type - else - warn "unknown base type (OID: #{row["typbasetype"]}) for domain #{row["typname"]}." - end - end - - def register_composite_type(row) - if subtype = @store.lookup(row['typelem'].to_i) - register row['oid'], OID::Vector.new(row['typdelim'], subtype) - end - end - - def register(oid, oid_type) - oid = oid.to_i - - raise ArgumentError, "can't register nil type for OID #{oid}" if oid_type.nil? - return if @store.key?(oid) - - @store.register_type(oid, oid_type) - end - end - # When the PG adapter connects, the pg_type table is queried. The # key of this hash maps to the `typname` column from the table. # type_map is then dynamically built with oids as the key and type |