diff --git a/lib/activerecord-postgres-hstore/hash.rb b/lib/activerecord-postgres-hstore/hash.rb index a3df13b..a5573d2 100644 --- a/lib/activerecord-postgres-hstore/hash.rb +++ b/lib/activerecord-postgres-hstore/hash.rb @@ -1,24 +1,34 @@ class Hash + HSTORE_ESCAPED = /[,\s=>\\]/ + + # Escapes values such that they will work in an hstore string + def hstore_escape(str) + if str.nil? + return 'NULL' + end + + str = str.to_s.dup + # backslash is an escape character for strings, and an escape character for gsub, so you need 6 backslashes to get 2 in the output. + # see http://stackoverflow.com/questions/1542214/weird-backslash-substitution-in-ruby for the gory details + str.gsub!(/\\/, '\\\\\\') + # escape backslashes before injecting more backslashes + str.gsub!(/"/, '\"') + + if str =~ HSTORE_ESCAPED or str.empty? + str = '"%s"' % str + end + + return str + end # Generates an hstore string format. This is the format used # to insert or update stuff in the database. def to_hstore return "" if empty? - map { |idx, val| - iv = [idx,val].map { |_| - e = _.to_s.gsub(/"/, '\"') - if _.nil? - 'NULL' - elsif e =~ /[,\s=>]/ || e.blank? - '"%s"' % e - else - e - end - } - - "%s=>%s" % iv - } * "," + map do |idx, val| + "%s=>%s" % [hstore_escape(idx), hstore_escape(val)] + end * "," end # If the method from_hstore is called in a Hash, it just returns self. diff --git a/spec/activerecord-postgres-hstore_spec.rb b/spec/activerecord-postgres-hstore_spec.rb index 56a6503..ec5799d 100644 --- a/spec/activerecord-postgres-hstore_spec.rb +++ b/spec/activerecord-postgres-hstore_spec.rb @@ -63,13 +63,29 @@ end it "should quote keys and values correctly with combinations of single and double quotes" do - { %q("a') => %q(b "a' b) }.to_hstore.should eq(%q(\"a'=>"b \"a' b")) + { %q("a') => %q(b "a' b) }.to_hstore.should eq(%q("\"a'"=>"b \"a' b")) end it "should unquote keys and values correctly with combinations of single and double quotes" do %q("\"a'"=>"b \"a' b").from_hstore.should eq({%q("a') => %q(b "a' b)}) end + it "should quote keys and values correctly with backslashes" do + { %q(\\) => %q(\\) }.to_hstore.should eq(%q("\\\\"=>"\\\\")) + end + + it "should unquote keys and values correctly with backslashes" do + %q("\\\\"=>"\\\\").from_hstore.should eq({ %q(\\) => %q(\\) }) + end + + it "should quote keys and values correctly with combinations of backslashes and quotes" do + { %q(' \\ ") => %q(" \\ ') }.to_hstore.should eq(%q("' \\\\ \""=>"\" \\\\ '")) + end + + it "should unquote keys and values correctly with combinations of backslashes and quotes" do + %q("' \\\\ \""=>"\" \\\\ '").from_hstore.should eq({ %q(' \\ ") => %q(" \\ ') }) + end + it "should convert empty hash" do {}.to_hstore.should eq("") end