提交 62bcf814 编写于 作者: R Rafael Mendonça França

Merge pull request #15218 from sgrif/sg-move-oid-types

Move PG OID types to their own files
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
......
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
module OID # :nodoc:
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
end
end
end
end
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
module OID # :nodoc:
class Bit < Type::String
def type_cast(value)
if ::String === value
ConnectionAdapters::PostgreSQLColumn.string_to_bit value
else
value
end
end
end
end
end
end
end
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
module OID # :nodoc:
class Bytea < Type::Binary
def cast_value(value)
PGconn.unescape_bytea value
end
end
end
end
end
end
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
module OID # :nodoc:
class Cidr < Type::Value
def type
:cidr
end
def cast_value(value)
ConnectionAdapters::PostgreSQLColumn.string_to_cidr value
end
end
end
end
end
end
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
module OID # :nodoc:
class Date < Type::Date
include Infinity
end
end
end
end
end
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
module OID # :nodoc:
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
end
end
end
end
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
module OID # :nodoc:
class Decimal < Type::Decimal
def infinity(options = {})
BigDecimal.new("Infinity") * (options[:negative] ? -1 : 1)
end
end
end
end
end
end
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
module OID # :nodoc:
class Enum < Type::Value
def type
:enum
end
def type_cast(value)
value.to_s
end
end
end
end
end
end
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
module OID # :nodoc:
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
end
end
end
end
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
module OID # :nodoc:
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
end
end
end
end
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
module OID # :nodoc:
class Inet < Cidr
def type
:inet
end
end
end
end
end
end
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
module OID # :nodoc:
module Infinity
def infinity(options = {})
options[:negative] ? -::Float::INFINITY : ::Float::INFINITY
end
end
end
end
end
end
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
module OID # :nodoc:
class Integer < Type::Integer
include Infinity
end
end
end
end
end
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
module OID # :nodoc:
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
end
end
end
end
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
module OID # :nodoc:
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
end
end
end
end
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
module OID # :nodoc:
class Point < Type::String
def type_cast(value)
if ::String === value
ConnectionAdapters::PostgreSQLColumn.string_to_point value
else
value
end
end
end
end
end
end
end
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
module OID # :nodoc:
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
end
end
end
end
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
module OID # :nodoc:
class SpecializedString < Type::String
attr_reader :type
def initialize(type)
@type = type
end
def text?
false
end
end
end
end
end
end
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
module OID # :nodoc:
class Time < Type::Time
include Infinity
end
end
end
end
end
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
module OID # :nodoc:
# 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
end
end
end
end
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
module OID # :nodoc:
class Uuid < Type::Value
def type
:uuid
end
def type_cast(value)
value.presence
end
end
end
end
end
end
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
module OID # :nodoc:
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
end
end
end
end
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册