Allow use of backslashes to escape literal colons
Despite the inconvenience of double-backslashing, the backslash was chosen because it's a very common char to use in escaping across multiple programming languages. A developer, without looking at documentation, may intuitively try to use it to achieve the desired results in this scenario. Fixes #37779
This commit is contained in:
parent
c78432b33f
commit
694376f15e
@ -1,3 +1,7 @@
|
||||
* Allow escaping of literal colon characters in `sanitize_sql_*` methods when named bind variables are used
|
||||
|
||||
*Justin Bull*
|
||||
|
||||
* Fix `#previously_new_record?` to return true for destroyed records.
|
||||
|
||||
Before, if a record was created and then destroyed, `#previously_new_record?` would return true.
|
||||
|
@ -137,7 +137,9 @@ def sanitize_sql_like(string, escape_character = "\\")
|
||||
end
|
||||
|
||||
# Accepts an array of conditions. The array has each value
|
||||
# sanitized and interpolated into the SQL statement.
|
||||
# sanitized and interpolated into the SQL statement. If using named bind
|
||||
# variables in SQL statements where a colon is required verbatim use a
|
||||
# backslash to escape.
|
||||
#
|
||||
# sanitize_sql_array(["name=? and group_id=?", "foo'bar", 4])
|
||||
# # => "name='foo''bar' and group_id=4"
|
||||
@ -145,6 +147,9 @@ def sanitize_sql_like(string, escape_character = "\\")
|
||||
# sanitize_sql_array(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
|
||||
# # => "name='foo''bar' and group_id=4"
|
||||
#
|
||||
# sanitize_sql_array(["TO_TIMESTAMP(:date, 'YYYY/MM/DD HH12\\:MI\\:SS')", date: "foo"])
|
||||
# # => "TO_TIMESTAMP('foo', 'YYYY/MM/DD HH12:MI:SS')"
|
||||
#
|
||||
# sanitize_sql_array(["name='%s' and group_id='%s'", "foo'bar", 4])
|
||||
# # => "name='foo''bar' and group_id='4'"
|
||||
#
|
||||
@ -206,9 +211,11 @@ def replace_bind_variable(value, c = connection)
|
||||
end
|
||||
|
||||
def replace_named_bind_variables(statement, bind_vars)
|
||||
statement.gsub(/(:?):([a-zA-Z]\w*)/) do |match|
|
||||
statement.gsub(/([:\\]?):([a-zA-Z]\w*)/) do |match|
|
||||
if $1 == ":" # skip postgresql casts
|
||||
match # return the whole match
|
||||
elsif $1 == "\\" # escaped literal colon
|
||||
match[1..-1] # return match with escaping backlash char removed
|
||||
elsif bind_vars.include?(match = $2.to_sym)
|
||||
replace_bind_variable(bind_vars[match])
|
||||
else
|
||||
|
@ -224,6 +224,11 @@ def test_named_bind_with_postgresql_type_casts
|
||||
assert_equal "#{ActiveRecord::Base.connection.quote('10')}::integer '2009-01-01'::date", l.call
|
||||
end
|
||||
|
||||
def test_named_bind_with_literal_colons
|
||||
assert_equal "TO_TIMESTAMP('2017/08/02 10:59:00', 'YYYY/MM/DD HH12:MI:SS')", bind("TO_TIMESTAMP(:date, 'YYYY/MM/DD HH12\\:MI\\:SS')", date: "2017/08/02 10:59:00")
|
||||
assert_raise(ActiveRecord::PreparedStatementInvalid) { bind "TO_TIMESTAMP(:date, 'YYYY/MM/DD HH12:MI:SS')", date: "2017/08/02 10:59:00" }
|
||||
end
|
||||
|
||||
private
|
||||
def bind(statement, *vars)
|
||||
if vars.first.is_a?(Hash)
|
||||
|
Loading…
Reference in New Issue
Block a user