diff options
Diffstat (limited to 'activerecord/test/cases/relation')
-rw-r--r-- | activerecord/test/cases/relation/merging_test.rb | 15 | ||||
-rw-r--r-- | activerecord/test/cases/relation/mutation_test.rb | 10 | ||||
-rw-r--r-- | activerecord/test/cases/relation/or_test.rb | 84 | ||||
-rw-r--r-- | activerecord/test/cases/relation/predicate_builder_test.rb | 2 | ||||
-rw-r--r-- | activerecord/test/cases/relation/record_fetch_warning_test.rb | 28 | ||||
-rw-r--r-- | activerecord/test/cases/relation/where_chain_test.rb | 122 | ||||
-rw-r--r-- | activerecord/test/cases/relation/where_clause_test.rb | 182 | ||||
-rw-r--r-- | activerecord/test/cases/relation/where_test.rb | 76 |
8 files changed, 391 insertions, 128 deletions
diff --git a/activerecord/test/cases/relation/merging_test.rb b/activerecord/test/cases/relation/merging_test.rb index eb76ef6328..0a2e874e4f 100644 --- a/activerecord/test/cases/relation/merging_test.rb +++ b/activerecord/test/cases/relation/merging_test.rb @@ -82,26 +82,15 @@ class RelationMergingTest < ActiveRecord::TestCase left = Post.where(title: "omg").where(comments_count: 1) right = Post.where(title: "wtf").where(title: "bbq") - expected = [left.bind_values[1]] + right.bind_values + expected = [left.bound_attributes[1]] + right.bound_attributes merged = left.merge(right) - assert_equal expected, merged.bind_values + assert_equal expected, merged.bound_attributes assert !merged.to_sql.include?("omg") assert merged.to_sql.include?("wtf") assert merged.to_sql.include?("bbq") end - def test_merging_keeps_lhs_bind_parameters - column = Post.columns_hash['id'] - binds = [[column, 20]] - - right = Post.where(id: 20) - left = Post.where(id: 10) - - merged = left.merge(right) - assert_equal binds, merged.bind_values - end - def test_merging_reorders_bind_params post = Post.first right = Post.where(id: 1) diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb index 2443f10269..45ead08bd5 100644 --- a/activerecord/test/cases/relation/mutation_test.rb +++ b/activerecord/test/cases/relation/mutation_test.rb @@ -81,7 +81,7 @@ module ActiveRecord assert_equal [], relation.extending_values end - (Relation::SINGLE_VALUE_METHODS - [:from, :lock, :reordering, :reverse_order, :create_with]).each do |method| + (Relation::SINGLE_VALUE_METHODS - [:lock, :reordering, :reverse_order, :create_with]).each do |method| test "##{method}!" do assert relation.public_send("#{method}!", :foo).equal?(relation) assert_equal :foo, relation.public_send("#{method}_value") @@ -90,7 +90,7 @@ module ActiveRecord test '#from!' do assert relation.from!('foo').equal?(relation) - assert_equal ['foo', nil], relation.from_value + assert_equal 'foo', relation.from_clause.value end test '#lock!' do @@ -136,12 +136,12 @@ module ActiveRecord end test 'test_merge!' do - assert relation.merge!(where: :foo).equal?(relation) - assert_equal [:foo], relation.where_values + assert relation.merge!(select: :foo).equal?(relation) + assert_equal [:foo], relation.select_values end test 'merge with a proc' do - assert_equal [:foo], relation.merge(-> { where(:foo) }).where_values + assert_equal [:foo], relation.merge(-> { select(:foo) }).select_values end test 'none!' do diff --git a/activerecord/test/cases/relation/or_test.rb b/activerecord/test/cases/relation/or_test.rb new file mode 100644 index 0000000000..2006fc9611 --- /dev/null +++ b/activerecord/test/cases/relation/or_test.rb @@ -0,0 +1,84 @@ +require "cases/helper" +require 'models/post' + +module ActiveRecord + class OrTest < ActiveRecord::TestCase + fixtures :posts + + def test_or_with_relation + expected = Post.where('id = 1 or id = 2').to_a + assert_equal expected, Post.where('id = 1').or(Post.where('id = 2')).to_a + end + + def test_or_identity + expected = Post.where('id = 1').to_a + assert_equal expected, Post.where('id = 1').or(Post.where('id = 1')).to_a + end + + def test_or_with_null_left + expected = Post.where('id = 1').to_a + assert_equal expected, Post.none.or(Post.where('id = 1')).to_a + end + + def test_or_with_null_right + expected = Post.where('id = 1').to_a + assert_equal expected, Post.where('id = 1').or(Post.none).to_a + end + + def test_or_with_bind_params + assert_equal Post.find([1, 2]), Post.where(id: 1).or(Post.where(id: 2)).to_a + end + + def test_or_with_null_both + expected = Post.none.to_a + assert_equal expected, Post.none.or(Post.none).to_a + end + + def test_or_without_left_where + expected = Post.all + assert_equal expected, Post.or(Post.where('id = 1')).to_a + end + + def test_or_without_right_where + expected = Post.all + assert_equal expected, Post.where('id = 1').or(Post.all).to_a + end + + def test_or_preserves_other_querying_methods + expected = Post.where('id = 1 or id = 2 or id = 3').order('body asc').to_a + partial = Post.order('body asc') + assert_equal expected, partial.where('id = 1').or(partial.where(:id => [2, 3])).to_a + assert_equal expected, Post.order('body asc').where('id = 1').or(Post.order('body asc').where(:id => [2, 3])).to_a + end + + def test_or_with_incompatible_relations + assert_raises ArgumentError do + Post.order('body asc').where('id = 1').or(Post.order('id desc').where(:id => [2, 3])).to_a + end + end + + def test_or_when_grouping + groups = Post.where('id < 10').group('body').select('body, COUNT(*) AS c') + expected = groups.having("COUNT(*) > 1 OR body like 'Such%'").to_a.map {|o| [o.body, o.c] } + assert_equal expected, groups.having('COUNT(*) > 1').or(groups.having("body like 'Such%'")).to_a.map {|o| [o.body, o.c] } + end + + def test_or_with_named_scope + expected = Post.where("id = 1 or body LIKE '\%a\%'").to_a + assert_equal expected, Post.where('id = 1').or(Post.containing_the_letter_a) + end + + def test_or_inside_named_scope + expected = Post.where("body LIKE '\%a\%' OR title LIKE ?", "%'%").order('id DESC').to_a + assert_equal expected, Post.order(id: :desc).typographically_interesting + end + + def test_or_on_loaded_relation + expected = Post.where('id = 1 or id = 2').to_a + p = Post.where('id = 1') + p.load + assert_equal p.loaded?, true + assert_equal expected, p.or(Post.where('id = 2')).to_a + end + end +end diff --git a/activerecord/test/cases/relation/predicate_builder_test.rb b/activerecord/test/cases/relation/predicate_builder_test.rb index 0cc081fced..8f62014622 100644 --- a/activerecord/test/cases/relation/predicate_builder_test.rb +++ b/activerecord/test/cases/relation/predicate_builder_test.rb @@ -8,7 +8,7 @@ module ActiveRecord Arel::Nodes::InfixOperation.new('~', column, Arel.sql(value.source)) end) - assert_match %r{["`]topics["`].["`]title["`] ~ rails}i, Topic.where(title: /rails/).to_sql + assert_match %r{["`]topics["`]\.["`]title["`] ~ rails}i, Topic.where(title: /rails/).to_sql ensure Topic.reset_column_information end diff --git a/activerecord/test/cases/relation/record_fetch_warning_test.rb b/activerecord/test/cases/relation/record_fetch_warning_test.rb new file mode 100644 index 0000000000..62f0a7cc49 --- /dev/null +++ b/activerecord/test/cases/relation/record_fetch_warning_test.rb @@ -0,0 +1,28 @@ +require 'cases/helper' +require 'models/post' + +module ActiveRecord + class RecordFetchWarningTest < ActiveRecord::TestCase + fixtures :posts + + def test_warn_on_records_fetched_greater_than + original_logger = ActiveRecord::Base.logger + orginal_warn_on_records_fetched_greater_than = ActiveRecord::Base.warn_on_records_fetched_greater_than + + log = StringIO.new + ActiveRecord::Base.logger = ActiveSupport::Logger.new(log) + ActiveRecord::Base.logger.level = Logger::WARN + + require 'active_record/relation/record_fetch_warning' + + ActiveRecord::Base.warn_on_records_fetched_greater_than = 1 + + Post.all.to_a + + assert_match(/Query fetched/, log.string) + ensure + ActiveRecord::Base.logger = original_logger + ActiveRecord::Base.warn_on_records_fetched_greater_than = orginal_warn_on_records_fetched_greater_than + end + end +end diff --git a/activerecord/test/cases/relation/where_chain_test.rb b/activerecord/test/cases/relation/where_chain_test.rb index 619055f1e7..27bbd80f79 100644 --- a/activerecord/test/cases/relation/where_chain_test.rb +++ b/activerecord/test/cases/relation/where_chain_test.rb @@ -11,22 +11,11 @@ module ActiveRecord @name = 'title' end - def test_not_eq + def test_not_inverts_where_clause relation = Post.where.not(title: 'hello') + expected_where_clause = Post.where(title: 'hello').where_clause.invert - assert_equal 1, relation.where_values.length - - value = relation.where_values.first - bind = relation.bind_values.first - - assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::NotEqual - assert_equal 'hello', bind.last - end - - def test_not_null - expected = Post.arel_table[@name].not_eq(nil) - relation = Post.where.not(title: nil) - assert_equal([expected], relation.where_values) + assert_equal expected_where_clause, relation.where_clause end def test_not_with_nil @@ -35,146 +24,81 @@ module ActiveRecord end end - def test_not_in - expected = Post.arel_table[@name].not_in(%w[hello goodbye]) - relation = Post.where.not(title: %w[hello goodbye]) - assert_equal([expected], relation.where_values) - end - def test_association_not_eq - expected = Comment.arel_table[@name].not_eq('hello') + expected = Arel::Nodes::Grouping.new(Comment.arel_table[@name].not_eq(Arel::Nodes::BindParam.new)) relation = Post.joins(:comments).where.not(comments: {title: 'hello'}) - assert_equal(expected.to_sql, relation.where_values.first.to_sql) + assert_equal(expected.to_sql, relation.where_clause.ast.to_sql) end def test_not_eq_with_preceding_where relation = Post.where(title: 'hello').where.not(title: 'world') + expected_where_clause = + Post.where(title: 'hello').where_clause + + Post.where(title: 'world').where_clause.invert - value = relation.where_values.first - bind = relation.bind_values.first - assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::Equality - assert_equal 'hello', bind.last - - value = relation.where_values.last - bind = relation.bind_values.last - assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::NotEqual - assert_equal 'world', bind.last + assert_equal expected_where_clause, relation.where_clause end def test_not_eq_with_succeeding_where relation = Post.where.not(title: 'hello').where(title: 'world') + expected_where_clause = + Post.where(title: 'hello').where_clause.invert + + Post.where(title: 'world').where_clause - value = relation.where_values.first - bind = relation.bind_values.first - assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::NotEqual - assert_equal 'hello', bind.last - - value = relation.where_values.last - bind = relation.bind_values.last - assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::Equality - assert_equal 'world', bind.last - end - - def test_not_eq_with_string_parameter - expected = Arel::Nodes::Not.new("title = 'hello'") - relation = Post.where.not("title = 'hello'") - assert_equal([expected], relation.where_values) - end - - def test_not_eq_with_array_parameter - expected = Arel::Nodes::Not.new("title = 'hello'") - relation = Post.where.not(['title = ?', 'hello']) - assert_equal([expected], relation.where_values) + assert_equal expected_where_clause, relation.where_clause end def test_chaining_multiple relation = Post.where.not(author_id: [1, 2]).where.not(title: 'ruby on rails') + expected_where_clause = + Post.where(author_id: [1, 2]).where_clause.invert + + Post.where(title: 'ruby on rails').where_clause.invert - expected = Post.arel_table['author_id'].not_in([1, 2]) - assert_equal(expected, relation.where_values[0]) - - value = relation.where_values[1] - bind = relation.bind_values.first - - assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::NotEqual - assert_equal 'ruby on rails', bind.last + assert_equal expected_where_clause, relation.where_clause end def test_rewhere_with_one_condition relation = Post.where(title: 'hello').where(title: 'world').rewhere(title: 'alone') + expected = Post.where(title: 'alone') - assert_equal 1, relation.where_values.size - value = relation.where_values.first - bind = relation.bind_values.first - assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::Equality - assert_equal 'alone', bind.last + assert_equal expected.where_clause, relation.where_clause end def test_rewhere_with_multiple_overwriting_conditions relation = Post.where(title: 'hello').where(body: 'world').rewhere(title: 'alone', body: 'again') + expected = Post.where(title: 'alone', body: 'again') - assert_equal 2, relation.where_values.size - - value = relation.where_values.first - bind = relation.bind_values.first - assert_bound_ast value, Post.arel_table['title'], Arel::Nodes::Equality - assert_equal 'alone', bind.last - - value = relation.where_values[1] - bind = relation.bind_values[1] - assert_bound_ast value, Post.arel_table['body'], Arel::Nodes::Equality - assert_equal 'again', bind.last - end - - def assert_bound_ast value, table, type - assert_equal table, value.left - assert_kind_of type, value - assert_kind_of Arel::Nodes::BindParam, value.right + assert_equal expected.where_clause, relation.where_clause end def test_rewhere_with_one_overwriting_condition_and_one_unrelated relation = Post.where(title: 'hello').where(body: 'world').rewhere(title: 'alone') + expected = Post.where(body: 'world', title: 'alone') - assert_equal 2, relation.where_values.size - - value = relation.where_values.first - bind = relation.bind_values.first - - assert_bound_ast value, Post.arel_table['body'], Arel::Nodes::Equality - assert_equal 'world', bind.last - - value = relation.where_values.second - bind = relation.bind_values.second - - assert_bound_ast value, Post.arel_table['title'], Arel::Nodes::Equality - assert_equal 'alone', bind.last + assert_equal expected.where_clause, relation.where_clause end def test_rewhere_with_range relation = Post.where(comments_count: 1..3).rewhere(comments_count: 3..5) - assert_equal 1, relation.where_values.size assert_equal Post.where(comments_count: 3..5), relation end def test_rewhere_with_infinite_upper_bound_range relation = Post.where(comments_count: 1..Float::INFINITY).rewhere(comments_count: 3..5) - assert_equal 1, relation.where_values.size assert_equal Post.where(comments_count: 3..5), relation end def test_rewhere_with_infinite_lower_bound_range relation = Post.where(comments_count: -Float::INFINITY..1).rewhere(comments_count: 3..5) - assert_equal 1, relation.where_values.size assert_equal Post.where(comments_count: 3..5), relation end def test_rewhere_with_infinite_range relation = Post.where(comments_count: -Float::INFINITY..Float::INFINITY).rewhere(comments_count: 3..5) - assert_equal 1, relation.where_values.size assert_equal Post.where(comments_count: 3..5), relation end end diff --git a/activerecord/test/cases/relation/where_clause_test.rb b/activerecord/test/cases/relation/where_clause_test.rb new file mode 100644 index 0000000000..c20ed94d90 --- /dev/null +++ b/activerecord/test/cases/relation/where_clause_test.rb @@ -0,0 +1,182 @@ +require "cases/helper" + +class ActiveRecord::Relation + class WhereClauseTest < ActiveRecord::TestCase + test "+ combines two where clauses" do + first_clause = WhereClause.new([table["id"].eq(bind_param)], [["id", 1]]) + second_clause = WhereClause.new([table["name"].eq(bind_param)], [["name", "Sean"]]) + combined = WhereClause.new( + [table["id"].eq(bind_param), table["name"].eq(bind_param)], + [["id", 1], ["name", "Sean"]], + ) + + assert_equal combined, first_clause + second_clause + end + + test "+ is associative, but not commutative" do + a = WhereClause.new(["a"], ["bind a"]) + b = WhereClause.new(["b"], ["bind b"]) + c = WhereClause.new(["c"], ["bind c"]) + + assert_equal a + (b + c), (a + b) + c + assert_not_equal a + b, b + a + end + + test "an empty where clause is the identity value for +" do + clause = WhereClause.new([table["id"].eq(bind_param)], [["id", 1]]) + + assert_equal clause, clause + WhereClause.empty + end + + test "merge combines two where clauses" do + a = WhereClause.new([table["id"].eq(1)], []) + b = WhereClause.new([table["name"].eq("Sean")], []) + expected = WhereClause.new([table["id"].eq(1), table["name"].eq("Sean")], []) + + assert_equal expected, a.merge(b) + end + + test "merge keeps the right side, when two equality clauses reference the same column" do + a = WhereClause.new([table["id"].eq(1), table["name"].eq("Sean")], []) + b = WhereClause.new([table["name"].eq("Jim")], []) + expected = WhereClause.new([table["id"].eq(1), table["name"].eq("Jim")], []) + + assert_equal expected, a.merge(b) + end + + test "merge removes bind parameters matching overlapping equality clauses" do + a = WhereClause.new( + [table["id"].eq(bind_param), table["name"].eq(bind_param)], + [attribute("id", 1), attribute("name", "Sean")], + ) + b = WhereClause.new( + [table["name"].eq(bind_param)], + [attribute("name", "Jim")] + ) + expected = WhereClause.new( + [table["id"].eq(bind_param), table["name"].eq(bind_param)], + [attribute("id", 1), attribute("name", "Jim")], + ) + + assert_equal expected, a.merge(b) + end + + test "merge allows for columns with the same name from different tables" do + skip "This is not possible as of 4.2, and the binds do not yet contain sufficient information for this to happen" + # We might be able to change the implementation to remove conflicts by index, rather than column name + end + + test "a clause knows if it is empty" do + assert WhereClause.empty.empty? + assert_not WhereClause.new(["anything"], []).empty? + end + + test "invert cannot handle nil" do + where_clause = WhereClause.new([nil], []) + + assert_raises ArgumentError do + where_clause.invert + end + end + + test "invert replaces each part of the predicate with its inverse" do + random_object = Object.new + original = WhereClause.new([ + table["id"].in([1, 2, 3]), + table["id"].eq(1), + "sql literal", + random_object + ], []) + expected = WhereClause.new([ + table["id"].not_in([1, 2, 3]), + table["id"].not_eq(1), + Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new("sql literal")), + Arel::Nodes::Not.new(random_object) + ], []) + + assert_equal expected, original.invert + end + + test "accept removes binary predicates referencing a given column" do + where_clause = WhereClause.new([ + table["id"].in([1, 2, 3]), + table["name"].eq(bind_param), + table["age"].gteq(bind_param), + ], [ + attribute("name", "Sean"), + attribute("age", 30), + ]) + expected = WhereClause.new([table["age"].gteq(bind_param)], [attribute("age", 30)]) + + assert_equal expected, where_clause.except("id", "name") + end + + test "ast groups its predicates with AND" do + predicates = [ + table["id"].in([1, 2, 3]), + table["name"].eq(bind_param), + ] + where_clause = WhereClause.new(predicates, []) + expected = Arel::Nodes::And.new(predicates) + + assert_equal expected, where_clause.ast + end + + test "ast wraps any SQL literals in parenthesis" do + random_object = Object.new + where_clause = WhereClause.new([ + table["id"].in([1, 2, 3]), + "foo = bar", + random_object, + ], []) + expected = Arel::Nodes::And.new([ + table["id"].in([1, 2, 3]), + Arel::Nodes::Grouping.new(Arel.sql("foo = bar")), + Arel::Nodes::Grouping.new(random_object), + ]) + + assert_equal expected, where_clause.ast + end + + test "ast removes any empty strings" do + where_clause = WhereClause.new([table["id"].in([1, 2, 3])], []) + where_clause_with_empty = WhereClause.new([table["id"].in([1, 2, 3]), ''], []) + + assert_equal where_clause.ast, where_clause_with_empty.ast + end + + test "or joins the two clauses using OR" do + where_clause = WhereClause.new([table["id"].eq(bind_param)], [attribute("id", 1)]) + other_clause = WhereClause.new([table["name"].eq(bind_param)], [attribute("name", "Sean")]) + expected_ast = + Arel::Nodes::Grouping.new( + Arel::Nodes::Or.new(table["id"].eq(bind_param), table["name"].eq(bind_param)) + ) + expected_binds = where_clause.binds + other_clause.binds + + assert_equal expected_ast.to_sql, where_clause.or(other_clause).ast.to_sql + assert_equal expected_binds, where_clause.or(other_clause).binds + end + + test "or returns an empty where clause when either side is empty" do + where_clause = WhereClause.new([table["id"].eq(bind_param)], [attribute("id", 1)]) + + assert_equal WhereClause.empty, where_clause.or(WhereClause.empty) + assert_equal WhereClause.empty, WhereClause.empty.or(where_clause) + end + + private + + def table + Arel::Table.new("table") + end + + def bind_param + Arel::Nodes::BindParam.new + end + + def attribute(name, value) + ActiveRecord::Attribute.with_cast_value(name, value, ActiveRecord::Type::Value.new) + end + end +end diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb index d675953da6..6af31017d6 100644 --- a/activerecord/test/cases/relation/where_test.rb +++ b/activerecord/test/cases/relation/where_test.rb @@ -1,17 +1,20 @@ require "cases/helper" -require 'models/author' -require 'models/price_estimate' -require 'models/treasure' -require 'models/post' -require 'models/comment' -require 'models/edge' -require 'models/topic' -require 'models/binary' -require 'models/vertex' +require "models/author" +require "models/binary" +require "models/cake_designer" +require "models/chef" +require "models/comment" +require "models/edge" +require "models/essay" +require "models/post" +require "models/price_estimate" +require "models/topic" +require "models/treasure" +require "models/vertex" module ActiveRecord class WhereTest < ActiveRecord::TestCase - fixtures :posts, :edges, :authors, :binaries + fixtures :posts, :edges, :authors, :binaries, :essays def test_where_copies_bind_params author = authors(:david) @@ -26,6 +29,24 @@ module ActiveRecord } end + def test_where_copies_bind_params_in_the_right_order + author = authors(:david) + posts = author.posts.where.not(id: 1) + joined = Post.where(id: posts, title: posts.first.title) + + assert_equal joined, [posts.first] + end + + def test_where_copies_arel_bind_params + chef = Chef.create! + CakeDesigner.create!(chef: chef) + + cake_designers = CakeDesigner.joins(:chef).where(chefs: { id: chef.id }) + chefs = Chef.where(employable: cake_designers) + + assert_equal [chef], chefs.to_a + end + def test_rewhere_on_root assert_equal posts(:welcome), Post.rewhere(title: 'Welcome to the weblog').first end @@ -220,5 +241,40 @@ module ActiveRecord count = Binary.where(:data => 0).count assert_equal 0, count end + + def test_where_on_association_with_custom_primary_key + author = authors(:david) + essay = Essay.where(writer: author).first + + assert_equal essays(:david_modest_proposal), essay + end + + def test_where_on_association_with_custom_primary_key_with_relation + author = authors(:david) + essay = Essay.where(writer: Author.where(id: author.id)).first + + assert_equal essays(:david_modest_proposal), essay + end + + def test_where_on_association_with_relation_performs_subselect_not_two_queries + author = authors(:david) + + assert_queries(1) do + Essay.where(writer: Author.where(id: author.id)).to_a + end + end + + def test_where_on_association_with_custom_primary_key_with_array_of_base + author = authors(:david) + essay = Essay.where(writer: [author]).first + + assert_equal essays(:david_modest_proposal), essay + end + + def test_where_on_association_with_custom_primary_key_with_array_of_ids + essay = Essay.where(writer: ["David"]).first + + assert_equal essays(:david_modest_proposal), essay + end end end |