Skip to content

Commit

Permalink
Merge pull request #32 from skandragon/fix_string_array
Browse files Browse the repository at this point in the history
String encoding fix, part 2
  • Loading branch information
danmcclain committed Nov 3, 2012
2 parents 429d447 + e4d4aec commit 483a823
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 12 deletions.
Expand Up @@ -259,8 +259,10 @@ def type_cast_with_extended_types(value, column, part_array = false)
alias_method_chain :type_cast, :extended_types

def quote_with_extended_types(value, column = nil)
if [Array, IPAddr].include? value.class
if value.is_a? IPAddr
"'#{type_cast(value, column)}'"
elsif value.is_a? Array
"'#{array_to_string(value, column, true)}'"
else
quote_without_extended_types(value, column)
end
Expand Down Expand Up @@ -319,15 +321,28 @@ def ipaddr_to_string(value)
"#{value.to_s}/#{value.instance_variable_get(:@mask_addr).to_s(2).count('1')}"
end

def array_to_string(value, column)
"{#{value.map { |val| item_to_string(val, column) }.join(',')}}"
def array_to_string(value, column, encode_single_quotes = false)
"{#{value.map { |val| item_to_string(val, column, encode_single_quotes) }.join(',')}}"
end

def item_to_string(value, column)
def item_to_string(value, column, encode_single_quotes = false)
if value.nil?
'NULL'
elsif value.is_a?String
'"' + type_cast(value, column, true).gsub('"', '\\"') + '"'
elsif value.is_a? String
value = type_cast(value, column, true).dup
# Encode backslashes. One backslash becomes 4 in the resulting SQL.
# (why 4, and not 2? Trial and error shows 4 works, 2 fails to parse.)
value.gsub!('\\', '\\\\\\\\')
# Encode a bare " in the string as \"
value.gsub!('"', '\\"')
# PostgreSQL parses the string values differently if they are quoted for
# use in a statement, or if it will be used as part of a bound argument.
# For directly-inserted values (UPDATE foo SET bar='{"array"}') we need to
# escape ' as ''. For bound arguments, do not escape them.
if encode_single_quotes
value.gsub!("'", "''")
end
"\"#{value}\""
else
type_cast(value, column, true)
end
Expand Down
6 changes: 1 addition & 5 deletions lib/postgres_ext/arel/visitors/to_sql.rb
Expand Up @@ -22,11 +22,7 @@ def visit_IPAddr value

def change_string value
return value unless value.is_a?(String)
if value.match /"|,|\{/
value.gsub(/"/, "\"").gsub(/'/,'"')
else
value.gsub(/'/,'')
end
value.gsub(/^\'/, '"').gsub(/\'$/, '"')
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion spec/arel/array_spec.rb
Expand Up @@ -23,7 +23,7 @@ class ArelArray < ActiveRecord::Base
it 'converts Arel array_overlap statment' do
arel_table = ArelArray.arel_table

arel_table.where(arel_table[:tags].array_overlap(['tag','tag 2'])).to_sql.should match /&& '\{tag,tag 2\}'/
arel_table.where(arel_table[:tags].array_overlap(['tag','tag 2'])).to_sql.should match /&& '\{"tag","tag 2"\}'/
end

it 'converts Arel array_overlap statment' do
Expand Down
99 changes: 99 additions & 0 deletions spec/models/array_spec.rb
Expand Up @@ -87,6 +87,105 @@ class User < ActiveRecord::Base
end
end
end

describe 'strings contain special characters' do
context '#save' do
it 'contains: \'' do
data = ['some\'thing']
u = User.create
u.nick_names = data
u.save!
u.reload
u.nick_names.should eq data
end

it 'contains: {' do
data = ['some{thing']
u = User.create
u.nick_names = data
u.save!
u.reload
u.nick_names.should eq data
end

it 'contains: }' do
data = ['some}thing']
u = User.create
u.nick_names = data
u.save!
u.reload
u.nick_names.should eq data
end

it 'contains: backslash' do
data = ['some\\thing']
u = User.create
u.nick_names = data
u.save!
u.reload
u.nick_names.should eq data
end

it 'contains: "' do
data = ['some"thing']
u = User.create
u.nick_names = data
u.save!
u.reload
u.nick_names.should eq data
end
end

context '#create' do
it 'contains: \'' do
data = ['some\'thing']
u = User.create(:nick_names => data)
u.reload
u.nick_names.should eq data
end

it 'contains: {' do
data = ['some{thing']
u = User.create(:nick_names => data)
u.reload
u.nick_names.should eq data
end

it 'contains: }' do
data = ['some}thing']
u = User.create(:nick_names => data)
u.reload
u.nick_names.should eq data
end

it 'contains: backslash' do
data = ['some\\thing']
u = User.create(:nick_names => data)
u.reload
u.nick_names.should eq data
end

it 'contains: "' do
data = ['some"thing']
u = User.create(:nick_names => data)
u.reload
u.nick_names.should eq data
end
end
end

describe 'array_overlap' do
it "works" do
arel = User.arel_table
User.create(:nick_names => ['this'])
x = User.create
x.nick_names = ["s'o{m}e", 'thing']
x.save
u = User.where(arel[:nick_names].array_overlap(["s'o{m}e"]))
u.first.should_not be_nil
u.first.nick_names.should eq ["s'o{m}e", 'thing']
end
end
end

context 'default values' do
Expand Down

0 comments on commit 483a823

Please sign in to comment.