diff options
-rw-r--r-- | activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb | 35 | ||||
-rw-r--r-- | activerecord/test/cases/adapters/postgresql/hstore_test.rb | 28 |
2 files changed, 57 insertions, 6 deletions
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 6e169ae5c5..705fc8135d 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -52,12 +52,39 @@ module ActiveRecord def cast_hstore(object) if Hash === object - object.map { |k,v| "#{k}=>#{v}" }.join ', ' + object.map { |k,v| + "#{escape_hstore(k)}=>#{escape_hstore(v)}" + }.join ', ' else - kvs = object.split(', ').map { |kv| - kv.split('=>').map { |k| k[1...-1] } + kvs = object.scan(/(?<!\\)".*?(?<!\\)"/).map { |o| + unescape_hstore(o[1...-1]) } - Hash[kvs] + Hash[kvs.each_slice(2).to_a] + end + end + + private + def unescape_hstore(value) + escape_values = { + '\\ ' => ' ', + '\\\\' => '\\', + '\\"' => '"', + '\\=' => '=', + } + value.gsub(Regexp.union(escape_values.keys)) do |match| + escape_values[match] + end + end + + def escape_hstore(value) + escape_values = { + ' ' => '\\ ', + '\\' => '\\\\', + '"' => '\\"', + '=' => '\\=', + } + value.gsub(Regexp.union(escape_values.keys)) do |match| + escape_values[match] end end end diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb index 7267536142..e03c938a25 100644 --- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb +++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb @@ -45,9 +45,33 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase end def test_create - hash = { 'a' => 'b', '1' => '2' } + assert_cycle_hstore('a' => 'b', '1' => '2') + end + + def test_quotes + assert_cycle_hstore('a' => 'b"ar', '1"foo' => '2') + end + + def test_whitespace + assert_cycle_hstore('a b' => 'b ar', '1"foo' => '2') + end + + def test_backslash + assert_cycle_hstore('a\\b' => 'b\\ar', '1"foo' => '2') + end + + def test_comma + assert_cycle_hstore('a, b' => 'bar', '1"foo' => '2') + end + + def test_arrow + assert_cycle_hstore('a=>b' => 'bar', '1"foo' => '2') + end + + private + def assert_cycle_hstore hash x = Hstore.create!(:tags => hash) x.reload - assert_equal hash, x.tags + assert_equal(hash, x.tags) end end |