aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--activerecord/CHANGELOG.md17
-rw-r--r--activerecord/lib/active_record/sanitization.rb14
-rw-r--r--activerecord/test/cases/relations_test.rb5
-rw-r--r--activerecord/test/cases/sanitize_test.rb6
4 files changed, 40 insertions, 2 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 83c94caa53..77ce30359c 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,20 @@
+* Generate subquery for `Relation` if it passed as array condition for `where` method
+
+ Example:
+ # Before
+ Blog.where('id in (?)', Blog.where(id: 1))
+ # => SELECT "blogs".* FROM "blogs" WHERE "blogs"."id" = 1
+ # => SELECT "blogs".* FROM "blogs" WHERE (id IN (1))
+
+ # After
+ Blog.where('id in (?)', Blog.where(id: 1).select(:id))
+ # => SELECT "blogs".* FROM "blogs"
+ # WHERE "blogs"."id" IN (SELECT "blogs"."id" FROM "blogs" WHERE "blogs"."id" = 1)
+
+ Fixes: #12415
+
+ *Paul Nikitochkin*
+
* `has_and_belongs_to_many` is now transparently implemented in terms of
`has_many :through`. Behavior should remain the same, if not, it is a bug.
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index 0b87ab9926..af4ea0efec 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -127,7 +127,17 @@ module ActiveRecord
raise_if_bind_arity_mismatch(statement, statement.count('?'), values.size)
bound = values.dup
c = connection
- statement.gsub('?') { quote_bound_value(bound.shift, c) }
+ statement.gsub('?') do
+ replace_bind_variable(bound.shift, c)
+ end
+ end
+
+ def replace_bind_variable(value, c = connection)
+ if ActiveRecord::Relation === value
+ value.to_sql
+ else
+ quote_bound_value(value, c)
+ end
end
def replace_named_bind_variables(statement, bind_vars) #:nodoc:
@@ -135,7 +145,7 @@ module ActiveRecord
if $1 == ':' # skip postgresql casts
$& # return the whole match
elsif bind_vars.include?(match = $2.to_sym)
- quote_bound_value(bind_vars[match])
+ replace_bind_variable(bind_vars[match])
else
raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}"
end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index f814947ab2..0cd8595ba6 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -626,6 +626,11 @@ class RelationTest < ActiveRecord::TestCase
relation = Author.where(:id => Author.where(:id => david.id))
assert_equal [david], relation.to_a
}
+
+ assert_queries(1) {
+ relation = Author.where('id in (?)', Author.where(id: david).select(:id))
+ assert_equal [david], relation.to_a
+ }
end
def test_find_all_using_where_with_relation_and_alternate_primary_key
diff --git a/activerecord/test/cases/sanitize_test.rb b/activerecord/test/cases/sanitize_test.rb
index 082570c55b..4c0762deca 100644
--- a/activerecord/test/cases/sanitize_test.rb
+++ b/activerecord/test/cases/sanitize_test.rb
@@ -31,4 +31,10 @@ class SanitizeTest < ActiveRecord::TestCase
assert_equal "name=#{quoted_bambi_and_thumper}", Binary.send(:sanitize_sql_array, ["name=?", "Bambi\nand\nThumper"])
assert_equal "name=#{quoted_bambi_and_thumper}", Binary.send(:sanitize_sql_array, ["name=?", "Bambi\nand\nThumper".mb_chars])
end
+
+ def test_sanitize_sql_array_handles_relations
+ assert_match(/\(\bselect\b.*?\bwhere\b.*?\)/i,
+ Binary.send(:sanitize_sql_array, ["id in (?)", Binary.where(id: 1)]),
+ "should sanitize `Relation` as subquery")
+ end
end