diff options
author | Paul Nikitochkin <paul.nikitochkin@gmail.com> | 2013-10-12 14:38:37 +0300 |
---|---|---|
committer | Paul Nikitochkin <paul.nikitochkin@gmail.com> | 2013-10-13 22:13:50 +0300 |
commit | bc293ff690e5478b99a55594f9fa8fe0e709941b (patch) | |
tree | 3f059f76dedbf144139369e9b735091909e112cb | |
parent | 365110196afcf952bc22729d4467d579b708328f (diff) | |
download | rails-bc293ff690e5478b99a55594f9fa8fe0e709941b.tar.gz rails-bc293ff690e5478b99a55594f9fa8fe0e709941b.tar.bz2 rails-bc293ff690e5478b99a55594f9fa8fe0e709941b.zip |
Generate subquery for Relation passed as array condition for where
Instead of executing 2 queries for fetching records filtered by array condition with Relation,
added generation of subquery to current query.
This behaviour will be consistent when passes Relation as hash condition to where
Closes: #12415
-rw-r--r-- | activerecord/CHANGELOG.md | 17 | ||||
-rw-r--r-- | activerecord/lib/active_record/sanitization.rb | 14 | ||||
-rw-r--r-- | activerecord/test/cases/relations_test.rb | 5 | ||||
-rw-r--r-- | activerecord/test/cases/sanitize_test.rb | 6 |
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 |