diff options
6 files changed, 48 insertions, 22 deletions
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index 6022d0102b..c3e5e9ad61 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -286,7 +286,8 @@ module ActiveRecord columns = schema_cache.columns_hash(table_name) binds = fixture.map do |name, value| - Relation::QueryAttribute.new(name, value, columns[name].cast_type) + type = lookup_cast_type_from_column(columns[name]) + Relation::QueryAttribute.new(name, value, type) end key_list = fixture.keys.map { |name| quote_column_name(name) } value_list = prepare_binds_for_database(binds).map do |value| diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index 62123cdc1f..2456376d6d 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -16,7 +16,7 @@ module ActiveRecord https://github.com/rails/arel/commit/6160bfbda1d1781c3b08a33ec4955f170e95be11 for more information. MSG - value = column.cast_type.type_cast_for_database(value) + value = type_cast_from_column(column, value) end _quote(value) @@ -31,7 +31,7 @@ module ActiveRecord end if column - value = column.cast_type.type_cast_for_database(value) + value = type_cast_from_column(column, value) end _type_cast(value) @@ -40,6 +40,29 @@ module ActiveRecord raise TypeError, "can't cast #{value.class}#{to_type}" end + # If you are having to call this function, you are likely doing something + # wrong. The column does not have sufficient type information if the user + # provided a custom type on the class level either explicitly (via + # `attribute`) or implicitly (via `serialize`, + # `time_zone_aware_attributes`). In almost all cases, the sql type should + # only be used to change quoting behavior, when the primitive to + # represent the type doesn't sufficiently reflect the differences + # (varchar vs binary) for example. The type used to get this primitive + # should have been provided before reaching the connection adapter. + def type_cast_from_column(column, value) # :nodoc: + if column + type = lookup_cast_type_from_column(column) + type.type_cast_for_database(value) + else + value + end + end + + # See docs for +type_cast_from_column+ + def lookup_cast_type_from_column(column) # :nodoc: + lookup_cast_type(column.sql_type) + end + # Quotes a string, escaping any ' (single quote) and \ (backslash) # characters. def quote_string(s) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb index acb1278499..8f3628ec0e 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb @@ -2,17 +2,19 @@ module ActiveRecord module ConnectionAdapters # PostgreSQL-specific extensions to column definitions in a table. class PostgreSQLColumn < Column #:nodoc: - attr_reader :array + attr_reader :array, :oid, :fmod alias :array? :array - def initialize(name, default, cast_type, sql_type = nil, null = true, default_function = nil) + def initialize(name, default, cast_type, sql_type = nil, null = true, default_function = nil, oid = nil, fmod = nil) if sql_type =~ /\[\]$/ @array = true sql_type = sql_type[0..sql_type.length - 3] else @array = false end - super + @oid = oid + @fmod = fmod + super(name, default, cast_type, sql_type, null, default_function) end def serial? diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index 9de9e2c7dc..464adb4e23 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -56,11 +56,15 @@ module ActiveRecord if column.type == :uuid && value =~ /\(\)/ value else - value = column.cast_type.type_cast_for_database(value) + value = type_cast_from_column(column, value) quote(value) end end + def lookup_cast_type_from_column(column) # :nodoc: + type_map.lookup(column.oid, column.fmod, column.sql_type) + end + private def _quote(value) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb index afd7a17c03..519a9727fb 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -183,15 +183,17 @@ module ActiveRecord def columns(table_name) # Limit, precision, and scale are all handled by the superclass. column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod| - oid = get_oid_type(oid.to_i, fmod.to_i, column_name, type) - default_value = extract_value_from_default(oid, default) + oid = oid.to_i + fmod = fmod.to_i + cast_type = get_oid_type(oid, fmod, column_name, type) + default_value = extract_value_from_default(cast_type, default) default_function = extract_default_function(default_value, default) - new_column(column_name, default_value, oid, type, notnull == 'f', default_function) + new_column(column_name, default_value, cast_type, type, notnull == 'f', default_function, oid, fmod) end end - def new_column(name, default, cast_type, sql_type = nil, null = true, default_function = nil) # :nodoc: - PostgreSQLColumn.new(name, default, cast_type, sql_type, null, default_function) + def new_column(name, default, cast_type, sql_type = nil, null = true, default_function = nil, oid = nil, fmod = nil) # :nodoc: + PostgreSQLColumn.new(name, default, cast_type, sql_type, null, default_function, oid, fmod) end # Returns the current database name. diff --git a/activerecord/lib/active_record/type_caster/connection.rb b/activerecord/lib/active_record/type_caster/connection.rb index 1d204edb76..3878270770 100644 --- a/activerecord/lib/active_record/type_caster/connection.rb +++ b/activerecord/lib/active_record/type_caster/connection.rb @@ -8,8 +8,8 @@ module ActiveRecord def type_cast_for_database(attribute_name, value) return value if value.is_a?(Arel::Nodes::BindParam) - type = type_for(attribute_name) - type.type_cast_for_database(value) + column = column_for(attribute_name) + connection.type_cast_from_column(column, value) end protected @@ -19,17 +19,11 @@ module ActiveRecord private - def type_for(attribute_name) + def column_for(attribute_name) if connection.schema_cache.table_exists?(table_name) - column_for(attribute_name).cast_type - else - Type::Value.new + connection.schema_cache.columns_hash(table_name)[attribute_name.to_s] end end - - def column_for(attribute_name) - connection.schema_cache.columns_hash(table_name)[attribute_name.to_s] - end end end end |