1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
|
# frozen_string_literal: true
require "cases/helper"
require "models/binary"
require "models/author"
require "models/post"
require "models/customer"
class SanitizeTest < ActiveRecord::TestCase
def setup
end
def test_sanitize_sql_array_handles_string_interpolation
quoted_bambi = ActiveRecord::Base.connection.quote_string("Bambi")
assert_equal "name='#{quoted_bambi}'", Binary.sanitize_sql_array(["name='%s'", "Bambi"])
assert_equal "name='#{quoted_bambi}'", Binary.sanitize_sql_array(["name='%s'", "Bambi".mb_chars])
quoted_bambi_and_thumper = ActiveRecord::Base.connection.quote_string("Bambi\nand\nThumper")
assert_equal "name='#{quoted_bambi_and_thumper}'", Binary.sanitize_sql_array(["name='%s'", "Bambi\nand\nThumper"])
assert_equal "name='#{quoted_bambi_and_thumper}'", Binary.sanitize_sql_array(["name='%s'", "Bambi\nand\nThumper".mb_chars])
end
def test_sanitize_sql_array_handles_bind_variables
quoted_bambi = ActiveRecord::Base.connection.quote("Bambi")
assert_equal "name=#{quoted_bambi}", Binary.sanitize_sql_array(["name=?", "Bambi"])
assert_equal "name=#{quoted_bambi}", Binary.sanitize_sql_array(["name=?", "Bambi".mb_chars])
quoted_bambi_and_thumper = ActiveRecord::Base.connection.quote("Bambi\nand\nThumper")
assert_equal "name=#{quoted_bambi_and_thumper}", Binary.sanitize_sql_array(["name=?", "Bambi\nand\nThumper"])
assert_equal "name=#{quoted_bambi_and_thumper}", Binary.sanitize_sql_array(["name=?", "Bambi\nand\nThumper".mb_chars])
end
def test_sanitize_sql_array_handles_named_bind_variables
quoted_bambi = ActiveRecord::Base.connection.quote("Bambi")
assert_equal "name=#{quoted_bambi}", Binary.sanitize_sql_array(["name=:name", name: "Bambi"])
assert_equal "name=#{quoted_bambi} AND id=1", Binary.sanitize_sql_array(["name=:name AND id=:id", name: "Bambi", id: 1])
quoted_bambi_and_thumper = ActiveRecord::Base.connection.quote("Bambi\nand\nThumper")
assert_equal "name=#{quoted_bambi_and_thumper}", Binary.sanitize_sql_array(["name=:name", name: "Bambi\nand\nThumper"])
assert_equal "name=#{quoted_bambi_and_thumper} AND name2=#{quoted_bambi_and_thumper}", Binary.sanitize_sql_array(["name=:name AND name2=:name", name: "Bambi\nand\nThumper"])
end
def test_sanitize_sql_array_handles_relations
david = Author.create!(name: "David")
david_posts = david.posts.select(:id)
sub_query_pattern = /\(\bselect\b.*?\bwhere\b.*?\)/i
select_author_sql = Post.sanitize_sql_array(["id in (?)", david_posts])
assert_match(sub_query_pattern, select_author_sql, "should sanitize `Relation` as subquery for bind variables")
select_author_sql = Post.sanitize_sql_array(["id in (:post_ids)", post_ids: david_posts])
assert_match(sub_query_pattern, select_author_sql, "should sanitize `Relation` as subquery for named bind variables")
end
def test_sanitize_sql_array_handles_empty_statement
select_author_sql = Post.sanitize_sql_array([""])
assert_equal("", select_author_sql)
end
def test_sanitize_sql_like
assert_equal '100\%', Binary.sanitize_sql_like("100%")
assert_equal 'snake\_cased\_string', Binary.sanitize_sql_like("snake_cased_string")
assert_equal 'C:\\\\Programs\\\\MsPaint', Binary.sanitize_sql_like('C:\\Programs\\MsPaint')
assert_equal "normal string 42", Binary.sanitize_sql_like("normal string 42")
end
def test_sanitize_sql_like_with_custom_escape_character
assert_equal "100!%", Binary.sanitize_sql_like("100%", "!")
assert_equal "snake!_cased!_string", Binary.sanitize_sql_like("snake_cased_string", "!")
assert_equal "great!!", Binary.sanitize_sql_like("great!", "!")
assert_equal 'C:\\Programs\\MsPaint', Binary.sanitize_sql_like('C:\\Programs\\MsPaint', "!")
assert_equal "normal string 42", Binary.sanitize_sql_like("normal string 42", "!")
end
def test_sanitize_sql_like_example_use_case
searchable_post = Class.new(Post) do
def self.search_as_method(term)
where("title LIKE ?", sanitize_sql_like(term, "!"))
end
scope :search_as_scope, -> (term) {
where("title LIKE ?", sanitize_sql_like(term, "!"))
}
end
assert_sql(/LIKE '20!% !_reduction!_!!'/) do
searchable_post.search_as_method("20% _reduction_!").to_a
end
assert_sql(/LIKE '20!% !_reduction!_!!'/) do
searchable_post.search_as_scope("20% _reduction_!").to_a
end
end
def test_bind_arity
assert_nothing_raised { bind "" }
assert_raise(ActiveRecord::PreparedStatementInvalid) { bind "", 1 }
assert_raise(ActiveRecord::PreparedStatementInvalid) { bind "?" }
assert_nothing_raised { bind "?", 1 }
assert_raise(ActiveRecord::PreparedStatementInvalid) { bind "?", 1, 1 }
end
def test_named_bind_variables
assert_equal "1", bind(":a", a: 1) # ' ruby-mode
assert_equal "1 1", bind(":a :a", a: 1) # ' ruby-mode
assert_nothing_raised { bind("'+00:00'", foo: "bar") }
end
def test_named_bind_arity
assert_nothing_raised { bind "name = :name", name: "37signals" }
assert_nothing_raised { bind "name = :name", name: "37signals", id: 1 }
assert_raise(ActiveRecord::PreparedStatementInvalid) { bind "name = :name", id: 1 }
end
class SimpleEnumerable
include Enumerable
def initialize(ary)
@ary = ary
end
def each(&b)
@ary.each(&b)
end
end
def test_bind_enumerable
quoted_abc = %(#{ActiveRecord::Base.connection.quote('a')},#{ActiveRecord::Base.connection.quote('b')},#{ActiveRecord::Base.connection.quote('c')})
assert_equal "1,2,3", bind("?", [1, 2, 3])
assert_equal quoted_abc, bind("?", %w(a b c))
assert_equal "1,2,3", bind(":a", a: [1, 2, 3])
assert_equal quoted_abc, bind(":a", a: %w(a b c)) # '
assert_equal "1,2,3", bind("?", SimpleEnumerable.new([1, 2, 3]))
assert_equal quoted_abc, bind("?", SimpleEnumerable.new(%w(a b c)))
assert_equal "1,2,3", bind(":a", a: SimpleEnumerable.new([1, 2, 3]))
assert_equal quoted_abc, bind(":a", a: SimpleEnumerable.new(%w(a b c))) # '
end
def test_bind_empty_enumerable
quoted_nil = ActiveRecord::Base.connection.quote(nil)
assert_equal quoted_nil, bind("?", [])
assert_equal " in (#{quoted_nil})", bind(" in (?)", [])
assert_equal "foo in (#{quoted_nil})", bind("foo in (?)", [])
end
def test_bind_empty_string
quoted_empty = ActiveRecord::Base.connection.quote("")
assert_equal quoted_empty, bind("?", "")
end
def test_bind_chars
quoted_bambi = ActiveRecord::Base.connection.quote("Bambi")
quoted_bambi_and_thumper = ActiveRecord::Base.connection.quote("Bambi\nand\nThumper")
assert_equal "name=#{quoted_bambi}", bind("name=?", "Bambi")
assert_equal "name=#{quoted_bambi_and_thumper}", bind("name=?", "Bambi\nand\nThumper")
assert_equal "name=#{quoted_bambi}", bind("name=?", "Bambi".mb_chars)
assert_equal "name=#{quoted_bambi_and_thumper}", bind("name=?", "Bambi\nand\nThumper".mb_chars)
end
def test_named_bind_with_postgresql_type_casts
l = Proc.new { bind(":a::integer '2009-01-01'::date", a: "10") }
assert_nothing_raised(&l)
assert_equal "#{ActiveRecord::Base.connection.quote('10')}::integer '2009-01-01'::date", l.call
end
def test_deprecated_expand_hash_conditions_for_aggregates
assert_deprecated do
assert_equal({ "balance" => 50 }, Customer.send(:expand_hash_conditions_for_aggregates, balance: Money.new(50)))
end
end
private
def bind(statement, *vars)
if vars.first.is_a?(Hash)
ActiveRecord::Base.send(:replace_named_bind_variables, statement, vars.first)
else
ActiveRecord::Base.send(:replace_bind_variables, statement, vars)
end
end
end
|