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 | 348 |
1 files changed, 193 insertions, 155 deletions
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb index 5d32aaed50..a951fa1522 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb @@ -1,34 +1,28 @@ -require 'active_record/connection_adapters/abstract_adapter' - module ActiveRecord module ConnectionAdapters - class PostgreSQLAdapter < AbstractAdapter - module OID - class Type - def type; end - + module PostgreSQL + module OID # :nodoc: + module Infinity def infinity(options = {}) - ::Float::INFINITY * (options[:negative] ? -1 : 1) + options[:negative] ? -::Float::INFINITY : ::Float::INFINITY end end - class Identity < Type - def type_cast(value) - value - end - end + class SpecializedString < Type::String + attr_reader :type - class Text < Type - def type_cast(value) - return if value.nil? + def initialize(type) + @type = type + end - value.to_s + def text? + false end end - class Bit < Type + class Bit < Type::String def type_cast(value) - if String === value + if ::String === value ConnectionAdapters::PostgreSQLColumn.string_to_bit value else value @@ -36,17 +30,17 @@ module ActiveRecord end end - class Bytea < Type - def type_cast(value) - return if value.nil? + class Bytea < Type::Binary + def cast_value(value) PGconn.unescape_bytea value end end - class Money < Type - def type_cast(value) - return if value.nil? - return value unless String === value + class Money < Type::Decimal + include Infinity + + 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): @@ -64,11 +58,11 @@ module ActiveRecord value.gsub!(/[^-\d,]/, '').sub!(/,/, '.') end - ConnectionAdapters::Column.value_to_decimal value + super(value) end end - class Vector < Type + class Vector < Type::Value attr_reader :delim, :subtype # +delim+ corresponds to the `typdelim` column in the pg_types @@ -87,9 +81,9 @@ module ActiveRecord end end - class Point < Type + class Point < Type::String def type_cast(value) - if String === value + if ::String === value ConnectionAdapters::PostgreSQLColumn.string_to_point value else value @@ -97,14 +91,16 @@ module ActiveRecord end end - class Array < Type + class Array < Type::Value attr_reader :subtype + delegate :type, to: :subtype + def initialize(subtype) @subtype = subtype end def type_cast(value) - if String === value + if ::String === value ConnectionAdapters::PostgreSQLColumn.string_to_array value, @subtype else value @@ -112,10 +108,12 @@ module ActiveRecord end end - class Range < Type - attr_reader :subtype - def initialize(subtype) + class Range < Type::Value + attr_reader :subtype, :type + + def initialize(subtype, type) @subtype = subtype + @type = type end def extract_bounds(value) @@ -136,8 +134,8 @@ module ActiveRecord infinity?(value) ? value : @subtype.type_cast(value) end - def type_cast(value) - return if value.nil? || value == 'empty' + def cast_value(value) + return if value == 'empty' return value if value.is_a?(::Range) extracted = extract_bounds(value) @@ -159,90 +157,77 @@ This is not reliable and will be removed in the future. end end - class Integer < Type - def type_cast(value) - return if value.nil? - - ConnectionAdapters::Column.value_to_integer value - end + class Integer < Type::Integer + include Infinity end - class Boolean < Type - def type_cast(value) - return if value.nil? + class DateTime < Type::DateTime + include Infinity - ConnectionAdapters::Column.value_to_boolean value + 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 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 + class Date < Type::Date + include Infinity 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 + class Time < Type::Time + include Infinity end - class Time < Type - def type_cast(value) - return if value.nil? + class Float < Type::Float + include Infinity - # 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 + 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 - def type_cast(value) - return if value.nil? - - ConnectionAdapters::Column.value_to_decimal value - end - + class Decimal < Type::Decimal def infinity(options = {}) BigDecimal.new("Infinity") * (options[:negative] ? -1 : 1) end end - class Enum < Type + class Enum < Type::Value + def type + :enum + end + def type_cast(value) value.to_s end end - class Hstore < Type + class Hstore < Type::Value + def type + :hstore + end + def type_cast_for_write(value) ConnectionAdapters::PostgreSQLColumn.hstore_to_string value end - def type_cast(value) - return if value.nil? - + def cast_value(value) ConnectionAdapters::PostgreSQLColumn.string_to_hstore value end @@ -251,22 +236,32 @@ This is not reliable and will be removed in the future. end end - class Cidr < Type - def type_cast(value) - return if value.nil? + class Cidr < Type::Value + def type + :cidr + end + def cast_value(value) ConnectionAdapters::PostgreSQLColumn.string_to_cidr value end end - class Json < Type + 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 type_cast(value) - return if value.nil? - + def cast_value(value) ConnectionAdapters::PostgreSQLColumn.string_to_json value end @@ -275,40 +270,84 @@ This is not reliable and will be removed in the future. end end - class TypeMap - def initialize - @mapping = {} + 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 []=(oid, type) - @mapping[oid] = type + def register_enum_type(row) + register row['oid'], OID::Enum.new end - def [](oid) - @mapping[oid] + def register_array_type(row) + if subtype = @store.lookup(row['typelem'].to_i) + register row['oid'], OID::Array.new(subtype) + end end - def clear - @mapping.clear + 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 key?(oid) - @mapping.key? oid + 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 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 + def register_composite_type(row) + if subtype = @store.lookup(row['typelem'].to_i) + register row['oid'], OID::Vector.new(row['typdelim'], subtype) end + end - @mapping.fetch(ftype) { |oid| yield oid, fmod } + 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 @@ -317,11 +356,11 @@ This is not reliable and will be removed in the future. # type_map is then dynamically built with oids as the key and type # objects as values. NAMES = Hash.new { |h,k| # :nodoc: - h[k] = OID::Identity.new + h[k] = Type::Value.new } # Register an OID type named +name+ with a typecasting object in - # +type+. +name+ should correspond to the `typname` column in + # +type+. +name+ should correspond to the `typname` column in # the `pg_type` table. def self.register_type(name, type) NAMES[name] = type @@ -338,48 +377,47 @@ This is not reliable and will be removed in the future. end register_type 'int2', OID::Integer.new - alias_type 'int4', 'int2' - alias_type 'int8', 'int2' - alias_type 'oid', 'int2' - + alias_type 'int4', 'int2' + alias_type 'int8', 'int2' + alias_type 'oid', 'int2' register_type 'numeric', OID::Decimal.new - register_type 'text', OID::Text.new - alias_type 'varchar', 'text' - alias_type 'char', 'text' - alias_type 'bpchar', 'text' - alias_type 'xml', 'text' - - # FIXME: why are we keeping these types as strings? - alias_type 'tsvector', 'text' - alias_type 'interval', 'text' - alias_type 'macaddr', 'text' - alias_type 'uuid', 'text' - - register_type 'money', OID::Money.new - register_type 'bytea', OID::Bytea.new - register_type 'bool', OID::Boolean.new - register_type 'bit', OID::Bit.new - register_type 'varbit', OID::Bit.new - register_type 'float4', OID::Float.new alias_type 'float8', 'float4' - - register_type 'timestamp', OID::Timestamp.new - register_type 'timestamptz', OID::Timestamp.new + register_type 'text', Type::Text.new + register_type 'varchar', Type::String.new + alias_type 'char', 'varchar' + alias_type 'name', 'varchar' + alias_type 'bpchar', 'varchar' + register_type 'bool', Type::Boolean.new + register_type 'bit', OID::Bit.new + alias_type 'varbit', 'bit' + register_type 'timestamp', OID::DateTime.new + alias_type 'timestamptz', 'timestamp' register_type 'date', OID::Date.new register_type 'time', OID::Time.new - register_type 'path', OID::Text.new + register_type 'money', OID::Money.new + register_type 'bytea', OID::Bytea.new register_type 'point', OID::Point.new - register_type 'polygon', OID::Text.new - register_type 'circle', OID::Text.new register_type 'hstore', OID::Hstore.new register_type 'json', OID::Json.new - register_type 'citext', OID::Text.new - register_type 'ltree', OID::Text.new - register_type 'cidr', OID::Cidr.new - alias_type 'inet', 'cidr' + register_type 'inet', OID::Inet.new + register_type 'uuid', OID::Uuid.new + register_type 'xml', SpecializedString.new(:xml) + register_type 'tsvector', SpecializedString.new(:tsvector) + register_type 'macaddr', SpecializedString.new(:macaddr) + register_type 'citext', SpecializedString.new(:citext) + register_type 'ltree', SpecializedString.new(:ltree) + + # FIXME: why are we keeping these types as strings? + alias_type 'interval', 'varchar' + alias_type 'path', 'varchar' + alias_type 'line', 'varchar' + alias_type 'polygon', 'varchar' + alias_type 'circle', 'varchar' + alias_type 'lseg', 'varchar' + alias_type 'box', 'varchar' end end end |