Skip to content

Commit

Permalink
PostgreSQL determine Column#type through corresponding OID. #7814
Browse files Browse the repository at this point in the history
I ran the whole test suite and compared the old to the new types.
Following is the list of types that did change with this patch:

```
DIFFERENT TYPE FOR mood: NEW: enum, BEFORE:
DIFFERENT TYPE FOR floatrange: NEW: floatrange, BEFORE: float
```

The `floatrange` is a custom type. The old type `float` was simply a coincidence
form the name `floatrange` and our type-guessing.
  • Loading branch information
senny committed Apr 1, 2014
1 parent 5eb13fc commit 4d344bb
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 107 deletions.
8 changes: 8 additions & 0 deletions activerecord/CHANGELOG.md
@@ -1,3 +1,11 @@
* PostgreSQL `Column#type` is now determined through the corresponding OID.
The column types stay the same except for enum columns. They no longer have
`nil` as type but `enum`.

See #7814.

*Yves Senn*

* Fixed error when specifying a non-empty default value on a PostgreSQL array column. * Fixed error when specifying a non-empty default value on a PostgreSQL array column.


Fixes #10613. Fixes #10613.
Expand Down
Expand Up @@ -151,70 +151,7 @@ def extract_precision(sql_type)


# Maps PostgreSQL-specific data types to logical Rails types. # Maps PostgreSQL-specific data types to logical Rails types.
def simplified_type(field_type) def simplified_type(field_type)
case field_type @oid_type.simplified_type(field_type) || super
# Numeric and monetary types
when /^(?:real|double precision)$/
:float
# Monetary types
when 'money'
:decimal
when 'hstore'
:hstore
when 'ltree'
:ltree
# Network address types
when 'inet'
:inet
when 'cidr'
:cidr
when 'macaddr'
:macaddr
# Character types
when /^(?:character varying|bpchar)(?:\(\d+\))?$/
:string
when /^citext(?:\(\d+\))?$/
:citext
# Binary data types
when 'bytea'
:binary
# Date/time types
when /^timestamp with(?:out)? time zone$/
:datetime
when /^interval(?:|\(\d+\))$/
:string
# Geometric types
when /^(?:point|line|lseg|box|"?path"?|polygon|circle)$/
:string
# Bit strings
when /^bit(?: varying)?(?:\(\d+\))?$/
:string
# XML type
when 'xml'
:xml
# tsvector type
when 'tsvector'
:tsvector
# Arrays
when /^\D+\[\]$/
:string
# Object identifier types
when 'oid'
:integer
# UUID type
when 'uuid'
:uuid
# JSON type
when 'json'
:json
# Small and big integer types
when /^(?:small|big)int$/
:integer
when /(num|date|tstz|ts|int4|int8)range$/
field_type.to_sym
# Pass through all types that are not specific to PostgreSQL.
else
super
end
end end
end end
end end
Expand Down
127 changes: 90 additions & 37 deletions activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
Expand Up @@ -6,6 +6,7 @@ class PostgreSQLAdapter < AbstractAdapter
module OID module OID
class Type class Type
def type; end def type; end
def simplified_type(sql_type); type end


def infinity(options = {}) def infinity(options = {})
::Float::INFINITY * (options[:negative] ? -1 : 1) ::Float::INFINITY * (options[:negative] ? -1 : 1)
Expand All @@ -18,17 +19,33 @@ def type_cast(value)
end end
end end


class Text < Type class String < Type
def type; :string end

def type_cast(value) def type_cast(value)
return if value.nil? return if value.nil?


value.to_s value.to_s
end end
end end


class SpecializedString < OID::String
def type; @type end

def initialize(type)
@type = type
end
end

class Text < OID::String
def type; :text end
end

class Bit < Type class Bit < Type
def type; :string end

def type_cast(value) def type_cast(value)
if String === value if ::String === value
ConnectionAdapters::PostgreSQLColumn.string_to_bit value ConnectionAdapters::PostgreSQLColumn.string_to_bit value
else else
value value
Expand All @@ -37,16 +54,20 @@ def type_cast(value)
end end


class Bytea < Type class Bytea < Type
def type; :binary end

def type_cast(value) def type_cast(value)
return if value.nil? return if value.nil?
PGconn.unescape_bytea value PGconn.unescape_bytea value
end end
end end


class Money < Type class Money < Type
def type; :decimal end

def type_cast(value) def type_cast(value)
return if value.nil? return if value.nil?
return value unless String === value return value unless ::String === value


# Because money output is formatted according to the locale, there are two # Because money output is formatted according to the locale, there are two
# cases to consider (note the decimal separators): # cases to consider (note the decimal separators):
Expand Down Expand Up @@ -88,8 +109,10 @@ def type_cast(value)
end end


class Point < Type class Point < Type
def type; :string end

def type_cast(value) def type_cast(value)
if String === value if ::String === value
ConnectionAdapters::PostgreSQLColumn.string_to_point value ConnectionAdapters::PostgreSQLColumn.string_to_point value
else else
value value
Expand All @@ -98,13 +121,15 @@ def type_cast(value)
end end


class Array < Type class Array < Type
def type; @subtype.type end

attr_reader :subtype attr_reader :subtype
def initialize(subtype) def initialize(subtype)
@subtype = subtype @subtype = subtype
end end


def type_cast(value) def type_cast(value)
if String === value if ::String === value
ConnectionAdapters::PostgreSQLColumn.string_to_array value, @subtype ConnectionAdapters::PostgreSQLColumn.string_to_array value, @subtype
else else
value value
Expand All @@ -114,6 +139,8 @@ def type_cast(value)


class Range < Type class Range < Type
attr_reader :subtype attr_reader :subtype
def simplified_type(sql_type); sql_type.to_sym end

def initialize(subtype) def initialize(subtype)
@subtype = subtype @subtype = subtype
end end
Expand Down Expand Up @@ -160,6 +187,8 @@ def type_cast(value)
end end


class Integer < Type class Integer < Type
def type; :integer end

def type_cast(value) def type_cast(value)
return if value.nil? return if value.nil?


Expand All @@ -168,6 +197,8 @@ def type_cast(value)
end end


class Boolean < Type class Boolean < Type
def type; :boolean end

def type_cast(value) def type_cast(value)
return if value.nil? return if value.nil?


Expand All @@ -177,6 +208,14 @@ def type_cast(value)


class Timestamp < Type class Timestamp < Type
def type; :timestamp; end def type; :timestamp; end
def simplified_type(sql_type)
case sql_type
when /^timestamp with(?:out)? time zone$/
:datetime
else
:timestamp
end
end


def type_cast(value) def type_cast(value)
return if value.nil? return if value.nil?
Expand All @@ -188,7 +227,7 @@ def type_cast(value)
end end


class Date < Type class Date < Type
def type; :datetime; end def type; :date; end


def type_cast(value) def type_cast(value)
return if value.nil? return if value.nil?
Expand All @@ -200,6 +239,8 @@ def type_cast(value)
end end


class Time < Type class Time < Type
def type; :time end

def type_cast(value) def type_cast(value)
return if value.nil? return if value.nil?


Expand All @@ -210,6 +251,8 @@ def type_cast(value)
end end


class Float < Type class Float < Type
def type; :float end

def type_cast(value) def type_cast(value)
return if value.nil? return if value.nil?


Expand All @@ -218,6 +261,8 @@ def type_cast(value)
end end


class Decimal < Type class Decimal < Type
def type; :decimal end

def type_cast(value) def type_cast(value)
return if value.nil? return if value.nil?


Expand All @@ -230,12 +275,16 @@ def infinity(options = {})
end end


class Enum < Type class Enum < Type
def type; :enum end

def type_cast(value) def type_cast(value)
value.to_s value.to_s
end end
end end


class Hstore < Type class Hstore < Type
def type; :hstore end

def type_cast_for_write(value) def type_cast_for_write(value)
ConnectionAdapters::PostgreSQLColumn.hstore_to_string value ConnectionAdapters::PostgreSQLColumn.hstore_to_string value
end end
Expand All @@ -252,14 +301,20 @@ def accessor
end end


class Cidr < Type class Cidr < Type
def type; :cidr end
def type_cast(value) def type_cast(value)
return if value.nil? return if value.nil?


ConnectionAdapters::PostgreSQLColumn.string_to_cidr value ConnectionAdapters::PostgreSQLColumn.string_to_cidr value
end end
end end
class Inet < Cidr
def type; :inet end
end


class Json < Type class Json < Type
def type; :json end

def type_cast_for_write(value) def type_cast_for_write(value)
ConnectionAdapters::PostgreSQLColumn.json_to_string value ConnectionAdapters::PostgreSQLColumn.json_to_string value
end end
Expand Down Expand Up @@ -321,7 +376,7 @@ def fetch(ftype, fmod)
} }


# Register an OID type named +name+ with a typecasting object in # 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. # the `pg_type` table.
def self.register_type(name, type) def self.register_type(name, type)
NAMES[name] = type NAMES[name] = type
Expand All @@ -338,48 +393,46 @@ def self.registered_type?(name)
end end


register_type 'int2', OID::Integer.new register_type 'int2', OID::Integer.new
alias_type 'int4', 'int2' alias_type 'int4', 'int2'
alias_type 'int8', 'int2' alias_type 'int8', 'int2'
alias_type 'oid', 'int2' alias_type 'oid', 'int2'

register_type 'numeric', OID::Decimal.new register_type 'numeric', OID::Decimal.new
register_type 'float4', OID::Float.new
alias_type 'float8', 'float4'
register_type 'text', OID::Text.new register_type 'text', OID::Text.new
alias_type 'varchar', 'text' register_type 'varchar', OID::String.new
alias_type 'char', 'text' alias_type 'char', 'varchar'
alias_type 'bpchar', 'text' alias_type 'bpchar', 'varchar'
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 'bool', OID::Boolean.new
register_type 'bit', OID::Bit.new register_type 'bit', OID::Bit.new
register_type 'varbit', OID::Bit.new alias_type 'varbit', 'bit'

register_type 'float4', OID::Float.new
alias_type 'float8', 'float4'

register_type 'timestamp', OID::Timestamp.new register_type 'timestamp', OID::Timestamp.new
register_type 'timestamptz', OID::Timestamp.new alias_type 'timestamptz', 'timestamp'
register_type 'date', OID::Date.new register_type 'date', OID::Date.new
register_type 'time', OID::Time.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 'point', OID::Point.new
register_type 'polygon', OID::Text.new
register_type 'circle', OID::Text.new
register_type 'hstore', OID::Hstore.new register_type 'hstore', OID::Hstore.new
register_type 'json', OID::Json.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 register_type 'cidr', OID::Cidr.new
alias_type 'inet', 'cidr' register_type 'inet', OID::Inet.new
register_type 'xml', SpecializedString.new(:xml)
register_type 'tsvector', SpecializedString.new(:tsvector)
register_type 'macaddr', SpecializedString.new(:macaddr)
register_type 'uuid', SpecializedString.new(:uuid)
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 end
end end
Expand Down
3 changes: 1 addition & 2 deletions activerecord/test/cases/adapters/postgresql/enum_test.rb
Expand Up @@ -29,8 +29,7 @@ def setup


def test_column def test_column
column = PostgresqlEnum.columns_hash["current_mood"] column = PostgresqlEnum.columns_hash["current_mood"]
# TODO: enum columns should be of type enum or string, not nil. assert_equal :enum, column.type
assert_nil column.type
assert_equal "mood", column.sql_type assert_equal "mood", column.sql_type
assert_not column.number? assert_not column.number?
assert_not column.text? assert_not column.text?
Expand Down

0 comments on commit 4d344bb

Please sign in to comment.