diff options
Diffstat (limited to 'activerecord/lib/active_record/connection_adapters/postgresql')
31 files changed, 604 insertions, 540 deletions
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/array_parser.rb b/activerecord/lib/active_record/connection_adapters/postgresql/array_parser.rb index 5394ea0b7c..1b74c039ce 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/array_parser.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/array_parser.rb @@ -1,7 +1,7 @@ module ActiveRecord module ConnectionAdapters - class PostgreSQLColumn < Column - module ArrayParser + module PostgreSQL + module ArrayParser # :nodoc: DOUBLE_QUOTE = '"' BACKSLASH = "\\" @@ -9,7 +9,7 @@ module ActiveRecord BRACKET_OPEN = '{' BRACKET_CLOSE = '}' - def parse_pg_array(string) + def parse_pg_array(string) # :nodoc: local_index = 0 array = [] while(local_index < string.length) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb index 551a9289c3..0cbedb0987 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb @@ -1,32 +1,19 @@ module ActiveRecord module ConnectionAdapters - class PostgreSQLColumn < Column - module Cast - def point_to_string(point) + module PostgreSQL + module Cast # :nodoc: + def point_to_string(point) # :nodoc: "(#{point[0]},#{point[1]})" end - def string_to_point(string) + def string_to_point(string) # :nodoc: if string[0] == '(' && string[-1] == ')' string = string[1...-1] end string.split(',').map{ |v| Float(v) } end - def string_to_time(string) - return string unless String === string - - case string - when 'infinity'; Float::INFINITY - when '-infinity'; -Float::INFINITY - when / BC$/ - super("-" + string.sub(/ BC$/, "")) - else - super - end - end - - def string_to_bit(value) + def string_to_bit(value) # :nodoc: case value when /^0x/i value[2..-1].hex.to_s(2) # Hexadecimal notation @@ -35,7 +22,7 @@ module ActiveRecord end end - def hstore_to_string(object, array_member = false) + def hstore_to_string(object, array_member = false) # :nodoc: if Hash === object string = object.map { |k, v| "#{escape_hstore(k)}=>#{escape_hstore(v)}" }.join(',') string = escape_hstore(string) if array_member @@ -45,7 +32,7 @@ module ActiveRecord end end - def string_to_hstore(string) + def string_to_hstore(string) # :nodoc: if string.nil? nil elsif String === string @@ -59,7 +46,7 @@ module ActiveRecord end end - def json_to_string(object) + def json_to_string(object) # :nodoc: if Hash === object || Array === object ActiveSupport::JSON.encode(object) else @@ -67,7 +54,7 @@ module ActiveRecord end end - def array_to_string(value, column, adapter) + def array_to_string(value, column, adapter) # :nodoc: casted_values = value.map do |val| if String === val if val == "NULL" @@ -82,13 +69,13 @@ module ActiveRecord "{#{casted_values.join(',')}}" end - def range_to_string(object) + def range_to_string(object) # :nodoc: from = object.begin.respond_to?(:infinite?) && object.begin.infinite? ? '' : object.begin to = object.end.respond_to?(:infinite?) && object.end.infinite? ? '' : object.end "[#{from},#{to}#{object.exclude_end? ? ')' : ']'}" end - def string_to_json(string) + def string_to_json(string) # :nodoc: if String === string ActiveSupport::JSON.decode(string) else @@ -96,7 +83,7 @@ module ActiveRecord end end - def string_to_cidr(string) + def string_to_cidr(string) # :nodoc: if string.nil? nil elsif String === string @@ -110,7 +97,7 @@ module ActiveRecord end end - def cidr_to_string(object) + def cidr_to_string(object) # :nodoc: if IPAddr === object "#{object.to_s}/#{object.instance_variable_get(:@mask_addr).to_s(2).count('1')}" else @@ -118,7 +105,7 @@ module ActiveRecord end end - def string_to_array(string, oid) + def string_to_array(string, oid) # :nodoc: parse_pg_array(string).map {|val| type_cast_array(oid, val)} end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb index 82785825e5..80c79642f3 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb @@ -1,35 +1,28 @@ +require 'active_record/connection_adapters/postgresql/cast' + module ActiveRecord module ConnectionAdapters # PostgreSQL-specific extensions to column definitions in a table. class PostgreSQLColumn < Column #:nodoc: attr_accessor :array - def initialize(name, default, oid_type, sql_type = nil, null = true) - @oid_type = oid_type + def initialize(name, default, cast_type, sql_type = nil, null = true) default_value = self.class.extract_value_from_default(default) if sql_type =~ /\[\]$/ @array = true - super(name, default_value, sql_type[0..sql_type.length - 3], null) + super(name, default_value, cast_type, sql_type[0..sql_type.length - 3], null) else @array = false - super(name, default_value, sql_type, null) + super(name, default_value, cast_type, sql_type, null) end @default_function = default if has_default_function?(default_value, default) end - def number? - !array && super - end - - def text? - !array && super - end - # :stopdoc: class << self - include PostgreSQLColumn::Cast + include PostgreSQL::Cast # Loads pg_array_parser if available. String parsing can be # performed quicker by a native extension, which will not create @@ -40,7 +33,7 @@ module ActiveRecord include PgArrayParser rescue LoadError require 'active_record/connection_adapters/postgresql/array_parser' - include PostgreSQLColumn::ArrayParser + include PostgreSQL::ArrayParser end attr_accessor :money_precision @@ -110,26 +103,8 @@ module ActiveRecord end end - # Casts a Ruby value to something appropriate for writing to PostgreSQL. - # see ActiveRecord::ConnectionAdapters::Class#type_cast_for_write - # see ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::OID::Type - def type_cast_for_write(value) - if @oid_type.respond_to?(:type_cast_for_write) - @oid_type.type_cast_for_write(value) - else - super - end - end - - def type_cast(value) - return if value.nil? - return super if encoded? - - @oid_type.type_cast value - end - def accessor - @oid_type.accessor + cast_type.accessor end private @@ -147,12 +122,6 @@ module ActiveRecord end end - # Extracts the scale from PostgreSQL-specific data types. - def extract_scale(sql_type) - # Money type has a fixed scale of 2. - sql_type =~ /^money/ ? 2 : super - end - # Extracts the precision from PostgreSQL-specific data types. def extract_precision(sql_type) if sql_type == 'money' @@ -163,11 +132,6 @@ module ActiveRecord super end end - - # Maps PostgreSQL-specific data types to logical Rails types. - def simplified_type(field_type) - @oid_type.simplified_type(field_type) || super - end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb index 168b08ba75..89a7257d77 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb @@ -1,6 +1,6 @@ module ActiveRecord module ConnectionAdapters - class PostgreSQLAdapter < AbstractAdapter + module PostgreSQL module DatabaseStatements def explain(arel, binds = []) sql = "EXPLAIN #{to_sql(arel, binds)}" @@ -94,6 +94,11 @@ module ActiveRecord super.insert end + # The internal PostgreSQL identifier of the money data type. + MONEY_COLUMN_TYPE_OID = 790 #:nodoc: + # The internal PostgreSQL identifier of the BYTEA data type. + BYTEA_COLUMN_TYPE_OID = 17 #:nodoc: + # create a 2D array representing the result set def result_as_array(res) #:nodoc: # check if we have any binary column and if they need escaping diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb index 1e89f8cfd6..e54c092bf6 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb @@ -1,456 +1,38 @@ -require 'active_record/connection_adapters/abstract_adapter' +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 - class PostgreSQLAdapter < AbstractAdapter - module OID - class Type - def type; end - def simplified_type(sql_type); type end - - def infinity(options = {}) - ::Float::INFINITY * (options[:negative] ? -1 : 1) - end - end - - class Identity < Type - def type_cast(value) - value - end - end - - class String < Type - def type; :string end - - def type_cast(value) - return if value.nil? - - value.to_s - 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 - def type; :string end - - def type_cast(value) - if ::String === value - ConnectionAdapters::PostgreSQLColumn.string_to_bit value - else - value - end - end - end - - class Bytea < Type - def type; :binary end - - def type_cast(value) - return if value.nil? - PGconn.unescape_bytea value - end - end - - class Money < Type - def type; :decimal end - - def type_cast(value) - return if value.nil? - 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 - - ConnectionAdapters::Column.value_to_decimal value - end - end - - class Vector < Type - 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 - def type; :string end - - def type_cast(value) - if ::String === value - ConnectionAdapters::PostgreSQLColumn.string_to_point value - else - value - end - end - end - - class Array < Type - def type; @subtype.type end - - attr_reader :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 - attr_reader :subtype - def simplified_type(sql_type); sql_type.to_sym end - - def initialize(subtype) - @subtype = subtype - 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 type_cast(value) - return if value.nil? || 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 - def type; :integer end - - def type_cast(value) - return if value.nil? - - ConnectionAdapters::Column.value_to_integer value - end - end - - class Boolean < Type - def type; :boolean end - - def type_cast(value) - return if value.nil? - - ConnectionAdapters::Column.value_to_boolean value - end - end - - class Timestamp < Type - def type; :timestamp; end - def simplified_type(sql_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::PostgreSQLColumn.string_to_time value - end - end - - class Date < Type - def type; :date; 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 - end - - class Time < Type - def type; :time end - - def type_cast(value) - return if value.nil? - - # 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; :float end - - def type_cast(value) - case value - when nil; nil - when 'Infinity'; ::Float::INFINITY - when '-Infinity'; -::Float::INFINITY - when 'NaN'; ::Float::NAN - else - value.to_f - end - end - end - - class Decimal < Type - def type; :decimal end - - def type_cast(value) - return if value.nil? - - ConnectionAdapters::Column.value_to_decimal value - end - - def infinity(options = {}) - BigDecimal.new("Infinity") * (options[:negative] ? -1 : 1) - end - end - - class Enum < Type - def type; :enum end - - def type_cast(value) - value.to_s - end - end - - class Hstore < Type - 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? - - ConnectionAdapters::PostgreSQLColumn.string_to_hstore value - end - - def accessor - ActiveRecord::Store::StringKeyedHashAccessor - end - end - - class Cidr < Type - def type; :cidr end - def type_cast(value) - return if value.nil? - - ConnectionAdapters::PostgreSQLColumn.string_to_cidr value - end - end - class Inet < Cidr - def type; :inet end - end - - class Json < Type - 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? - - ConnectionAdapters::PostgreSQLColumn.string_to_json value - end - - def accessor - ActiveRecord::Store::StringKeyedHashAccessor - end - end - - class Uuid < Type - def type; :uuid end - def type_cast(value) - value.presence - end - end - - class TypeMap - def initialize - @mapping = {} - end - - def []=(oid, type) - @mapping[oid] = type - end - - def [](oid) - @mapping[oid] - end - - def clear - @mapping.clear - end - - def key?(oid) - @mapping.key? oid - 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 - end - - @mapping.fetch(ftype) { |oid| yield oid, fmod } - 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[row['typelem'].to_i] - register row['oid'], OID::Array.new(subtype) - end - end - - def register_range_type(row) - if subtype = @store[row['rngsubtype'].to_i] - register row['oid'], OID::Range.new(subtype) - end - end - - def register_domain_type(row) - if base_type = @store[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[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[oid] = oid_type - end - end - + module PostgreSQL + module OID # :nodoc: # 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 # 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 @@ -477,15 +59,15 @@ This is not reliable and will be removed in the future. register_type 'numeric', OID::Decimal.new register_type 'float4', OID::Float.new alias_type 'float8', 'float4' - register_type 'text', OID::Text.new - register_type 'varchar', OID::String.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', OID::Boolean.new + register_type 'bool', Type::Boolean.new register_type 'bit', OID::Bit.new alias_type 'varbit', 'bit' - register_type 'timestamp', OID::Timestamp.new + register_type 'timestamp', OID::DateTime.new alias_type 'timestamptz', 'timestamp' register_type 'date', OID::Date.new register_type 'time', OID::Time.new diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb new file mode 100644 index 0000000000..0e9dcd8c0c --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb @@ -0,0 +1,24 @@ +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 diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb new file mode 100644 index 0000000000..9b2d887d07 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb @@ -0,0 +1,17 @@ +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 diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb new file mode 100644 index 0000000000..36c53d8732 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb @@ -0,0 +1,13 @@ +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 diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb new file mode 100644 index 0000000000..507c3a62b0 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb @@ -0,0 +1,17 @@ +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 diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/date.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/date.rb new file mode 100644 index 0000000000..3c30ad5fec --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/date.rb @@ -0,0 +1,11 @@ +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Date < Type::Date + include Infinity + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/date_time.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/date_time.rb new file mode 100644 index 0000000000..9ccbf71159 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/date_time.rb @@ -0,0 +1,26 @@ +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 diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/decimal.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/decimal.rb new file mode 100644 index 0000000000..ed4b8911d9 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/decimal.rb @@ -0,0 +1,13 @@ +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 diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/enum.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/enum.rb new file mode 100644 index 0000000000..5fed8b0f89 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/enum.rb @@ -0,0 +1,17 @@ +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 diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/float.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/float.rb new file mode 100644 index 0000000000..9753d71461 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/float.rb @@ -0,0 +1,21 @@ +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 diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb new file mode 100644 index 0000000000..98f369a7f8 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb @@ -0,0 +1,25 @@ +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 diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/inet.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/inet.rb new file mode 100644 index 0000000000..7ed8f5f031 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/inet.rb @@ -0,0 +1,13 @@ +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Inet < Cidr + def type + :inet + end + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/infinity.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/infinity.rb new file mode 100644 index 0000000000..d438ffa140 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/infinity.rb @@ -0,0 +1,13 @@ +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 diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/integer.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/integer.rb new file mode 100644 index 0000000000..388d3dd9ed --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/integer.rb @@ -0,0 +1,11 @@ +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Integer < Type::Integer + include Infinity + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb new file mode 100644 index 0000000000..42bf5656f4 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb @@ -0,0 +1,25 @@ +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 diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb new file mode 100644 index 0000000000..1e34c09c88 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb @@ -0,0 +1,37 @@ +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 diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb new file mode 100644 index 0000000000..2769a8d3b4 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb @@ -0,0 +1,17 @@ +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 diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb new file mode 100644 index 0000000000..c2262c1599 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb @@ -0,0 +1,56 @@ +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 diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb new file mode 100644 index 0000000000..7b1ca16bc4 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb @@ -0,0 +1,19 @@ +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 diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/time.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/time.rb new file mode 100644 index 0000000000..ea1f599b0f --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/time.rb @@ -0,0 +1,11 @@ +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Time < Type::Time + include Infinity + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb new file mode 100644 index 0000000000..27829ae1a3 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb @@ -0,0 +1,78 @@ +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 diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb new file mode 100644 index 0000000000..0ed5491887 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb @@ -0,0 +1,17 @@ +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 diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/vector.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/vector.rb new file mode 100644 index 0000000000..2f7d1be197 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/vector.rb @@ -0,0 +1,26 @@ +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 diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index fa458d0243..ad12298013 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -1,6 +1,6 @@ module ActiveRecord module ConnectionAdapters - class PostgreSQLAdapter < AbstractAdapter + module PostgreSQL module Quoting # Escapes binary strings for bytea input to the database. def escape_bytea(value) @@ -150,13 +150,11 @@ module ActiveRecord # - "schema.name".table_name # - "schema.name"."table.name" def quote_table_name(name) - schema, name_part = extract_pg_identifier_from_name(name.to_s) - - unless name_part - quote_column_name(schema) + schema, table = Utils.extract_schema_and_table(name.to_s) + if schema + "#{quote_column_name(schema)}.#{quote_column_name(table)}" else - table_name, name_part = extract_pg_identifier_from_name(name_part) - "#{quote_column_name(schema)}.#{quote_column_name(table_name)}" + quote_column_name(table) end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb b/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb index bc775394a6..52b307c432 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb @@ -1,12 +1,12 @@ module ActiveRecord module ConnectionAdapters - class PostgreSQLAdapter < AbstractAdapter - module ReferentialIntegrity - def supports_disable_referential_integrity? #:nodoc: + module PostgreSQL + module ReferentialIntegrity # :nodoc: + def supports_disable_referential_integrity? # :nodoc: true end - def disable_referential_integrity #:nodoc: + def disable_referential_integrity # :nodoc: if supports_disable_referential_integrity? begin execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";")) 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 5bf4c7afd6..539ba38c4a 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -1,6 +1,6 @@ module ActiveRecord module ConnectionAdapters - class PostgreSQLAdapter < AbstractAdapter + module PostgreSQL class SchemaCreation < AbstractAdapter::SchemaCreation private @@ -33,10 +33,6 @@ module ActiveRecord end end - def schema_creation - SchemaCreation.new self - end - module SchemaStatements # Drops the database specified on the +name+ attribute # and creates it again using the provided +options+. diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb b/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb new file mode 100644 index 0000000000..60ffd3a114 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb @@ -0,0 +1,25 @@ +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module Utils # :nodoc: + extend self + + # Returns an array of <tt>[schema_name, table_name]</tt> extracted from +name+. + # +schema_name+ is nil if not specified in +name+. + # +schema_name+ and +table_name+ exclude surrounding quotes (regardless of whether provided in +name+) + # +name+ supports the range of schema/table references understood by PostgreSQL, for example: + # + # * <tt>table_name</tt> + # * <tt>"table.name"</tt> + # * <tt>schema_name.table_name</tt> + # * <tt>schema_name."table.name"</tt> + # * <tt>"schema_name".table_name</tt> + # * <tt>"schema.name"."table name"</tt> + def extract_schema_and_table(name) + table, schema = name.scan(/[^".\s]+|"[^"]*"/)[0..1].collect{|m| m.gsub(/(^"|"$)/,'') }.reverse + [schema, table] + end + end + end + end +end |