diff options
author | Sean Griffin <sean@thoughtbot.com> | 2015-03-26 17:40:36 -0600 |
---|---|---|
committer | Sean Griffin <sean@thoughtbot.com> | 2015-03-26 17:41:47 -0600 |
commit | cd09261846053bb40f8c859b8644b28208b6d7b9 (patch) | |
tree | fac9abae08696d097401c7703c474305a1e23215 | |
parent | 3b50a7a3e18e1ae5ea1d76927e37021f50389abc (diff) | |
parent | 647eb2cf1cd65b0391e3584361f0fc76246e64f3 (diff) | |
download | rails-cd09261846053bb40f8c859b8644b28208b6d7b9.tar.gz rails-cd09261846053bb40f8c859b8644b28208b6d7b9.tar.bz2 rails-cd09261846053bb40f8c859b8644b28208b6d7b9.zip |
Partially merge #17650
Merges 647eb2cf1cd65b0391e3584361f0fc76246e64f3. The pull request as a
whole is quite large, and I'm reviewing the smaller pieces individually.
3 files changed, 20 insertions, 147 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 deleted file mode 100644 index 1b74c039ce..0000000000 --- a/activerecord/lib/active_record/connection_adapters/postgresql/array_parser.rb +++ /dev/null @@ -1,93 +0,0 @@ -module ActiveRecord - module ConnectionAdapters - module PostgreSQL - module ArrayParser # :nodoc: - - DOUBLE_QUOTE = '"' - BACKSLASH = "\\" - COMMA = ',' - BRACKET_OPEN = '{' - BRACKET_CLOSE = '}' - - def parse_pg_array(string) # :nodoc: - local_index = 0 - array = [] - while(local_index < string.length) - case string[local_index] - when BRACKET_OPEN - local_index,array = parse_array_contents(array, string, local_index + 1) - when BRACKET_CLOSE - return array - end - local_index += 1 - end - - array - end - - private - - def parse_array_contents(array, string, index) - is_escaping = false - is_quoted = false - was_quoted = false - current_item = '' - - local_index = index - while local_index - token = string[local_index] - if is_escaping - current_item << token - is_escaping = false - else - if is_quoted - case token - when DOUBLE_QUOTE - is_quoted = false - was_quoted = true - when BACKSLASH - is_escaping = true - else - current_item << token - end - else - case token - when BACKSLASH - is_escaping = true - when COMMA - add_item_to_array(array, current_item, was_quoted) - current_item = '' - was_quoted = false - when DOUBLE_QUOTE - is_quoted = true - when BRACKET_OPEN - internal_items = [] - local_index,internal_items = parse_array_contents(internal_items, string, local_index + 1) - array.push(internal_items) - when BRACKET_CLOSE - add_item_to_array(array, current_item, was_quoted) - return local_index,array - else - current_item << token - end - end - end - - local_index += 1 - end - return local_index,array - end - - def add_item_to_array(array, current_item, quoted) - return if !quoted && current_item.length == 0 - - if !quoted && current_item == 'NULL' - array.push nil - else - array.push current_item - end - end - end - end - end -end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb index f486c5ecb7..3de794f797 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb @@ -5,29 +5,20 @@ module ActiveRecord class Array < Type::Value # :nodoc: include Type::Helpers::Mutable - # Loads pg_array_parser if available. String parsing can be - # performed quicker by a native extension, which will not create - # a large amount of Ruby objects that will need to be garbage - # collected. pg_array_parser has a C and Java extension - begin - require 'pg_array_parser' - include PgArrayParser - rescue LoadError - require 'active_record/connection_adapters/postgresql/array_parser' - include PostgreSQL::ArrayParser - end - attr_reader :subtype, :delimiter delegate :type, :user_input_in_time_zone, :limit, to: :subtype def initialize(subtype, delimiter = ',') @subtype = subtype @delimiter = delimiter + + @pg_encoder = PG::TextEncoder::Array.new name: "#{type}[]", delimiter: delimiter + @pg_decoder = PG::TextDecoder::Array.new name: "#{type}[]", delimiter: delimiter end def deserialize(value) if value.is_a?(::String) - type_cast_array(parse_pg_array(value), :deserialize) + type_cast_array(@pg_decoder.decode(value), :deserialize) else super end @@ -35,14 +26,14 @@ module ActiveRecord def cast(value) if value.is_a?(::String) - value = parse_pg_array(value) + value = @pg_decoder.decode(value) end type_cast_array(value, :cast) end def serialize(value) if value.is_a?(::Array) - cast_value_for_database(value) + @pg_encoder.encode(type_cast_array(value, :serialize)) else super end @@ -63,41 +54,6 @@ module ActiveRecord @subtype.public_send(method, value) end end - - def cast_value_for_database(value) - if value.is_a?(::Array) - casted_values = value.map { |item| cast_value_for_database(item) } - "{#{casted_values.join(delimiter)}}" - else - quote_and_escape(subtype.serialize(value)) - end - end - - ARRAY_ESCAPE = "\\" * 2 * 2 # escape the backslash twice for PG arrays - - def quote_and_escape(value) - case value - when ::String - if string_requires_quoting?(value) - value = value.gsub(/\\/, ARRAY_ESCAPE) - value.gsub!(/"/,"\\\"") - %("#{value}") - else - value - end - when nil then "NULL" - else value - end - end - - # See http://www.postgresql.org/docs/9.2/static/arrays.html#ARRAYS-IO - # for a list of all cases in which strings will be quoted. - def string_requires_quoting?(string) - string.empty? || - string == "NULL" || - string =~ /[\{\}"\\\s]/ || - string.include?(delimiter) - end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 37bf4d77f8..e616cee5eb 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -1,3 +1,7 @@ +# Make sure we're using pg high enough for type casts and Ruby 2.2+ compatibility +gem 'pg', '~> 0.18' +require 'pg' + require "active_record/connection_adapters/abstract_adapter" require "active_record/connection_adapters/postgresql/column" require "active_record/connection_adapters/postgresql/database_statements" @@ -12,10 +16,6 @@ require "active_record/connection_adapters/statement_pool" require 'arel/visitors/bind_visitor' -# Make sure we're using pg high enough for Ruby 2.2+ compatibility -gem 'pg', '~> 0.18' -require 'pg' - require 'ipaddr' module ActiveRecord @@ -278,6 +278,7 @@ module ActiveRecord @table_alias_length = nil connect + add_pg_encoders @statements = StatementPool.new @connection, self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 }) @@ -803,6 +804,15 @@ module ActiveRecord end end + def add_pg_encoders + map = PG::TypeMapByClass.new + map[Integer] = PG::TextEncoder::Integer.new + map[TrueClass] = PG::TextEncoder::Boolean.new + map[FalseClass] = PG::TextEncoder::Boolean.new + map[Float] = PG::TextEncoder::Float.new + @connection.type_map_for_queries = map + end + def add_pg_decoders coders_by_name = { 'int2' => PG::TextDecoder::Integer, |