1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Change WhereClause#merge to same named columns on diff tables

While the predicates are an arel equality node where the left side is a
full arel attribute, the binds just have the name of the column and
nothing else. This means that while splitting the predicates can include
the table as a factor, the binds cannot. It's entirely possible that we
might be able to have the bind params carry a bit more information (I
don't believe the name is used for anything but logging), and that is
probably a worthwhile change to make in the future.

However the simplest (and likely slightly faster) solution is to simply
use the indices of the conflicts in both cases. This means that we only
have to compute the collision space once, instead of twice even though
we're doing an additional array iteration. Regardless, this method isn't
a performance hotspot.

Close #22823.

[Ben Woosley & Sean Griffin]
This commit is contained in:
Sean Griffin 2016-01-12 14:34:42 -07:00
parent 858b9652ce
commit 5d41cb3bfd
2 changed files with 25 additions and 13 deletions

View file

@ -18,9 +18,10 @@ module ActiveRecord
end end
def merge(other) def merge(other)
conflict_indices = indices_of_predicates_referenced_by(other).to_set
WhereClause.new( WhereClause.new(
predicates_unreferenced_by(other) + other.predicates, non_conflicting(predicates, conflict_indices) + other.predicates,
non_conflicting_binds(other) + other.binds, non_conflicting(binds, conflict_indices) + other.binds,
) )
end end
@ -97,22 +98,20 @@ module ActiveRecord
private private
def predicates_unreferenced_by(other) def indices_of_predicates_referenced_by(other)
predicates.reject do |n| predicates.each_with_index.select do |(n, _)|
equality_node?(n) && other.referenced_columns.include?(n.left) equality_node?(n) && other.referenced_columns.include?(n.left)
end.map(&:last)
end end
def non_conflicting(values, conflict_indices)
values.reject.with_index { |_, i| conflict_indices.include?(i) }
end end
def equality_node?(node) def equality_node?(node)
node.respond_to?(:operator) && node.operator == :== node.respond_to?(:operator) && node.operator == :==
end end
def non_conflicting_binds(other)
conflicts = referenced_columns & other.referenced_columns
conflicts.map! { |node| node.name.to_s }
binds.reject { |attr| conflicts.include?(attr.name) }
end
def inverted_predicates def inverted_predicates
predicates.map { |node| invert_predicate(node) } predicates.map { |node| invert_predicate(node) }
end end

View file

@ -62,8 +62,21 @@ class ActiveRecord::Relation
end end
test "merge allows for columns with the same name from different tables" do 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" table2 = Arel::Table.new("table2")
# We might be able to change the implementation to remove conflicts by index, rather than column name a = WhereClause.new(
[table["id"].eq(bind_param), table2["id"].eq(bind_param), table["name"].eq(bind_param)],
[attribute("id", 3), attribute("id", 2), attribute("name", "Jim")]
)
b = WhereClause.new(
[table["id"].eq(bind_param), table["name"].eq(bind_param)],
[attribute("id", 1), attribute("name", "Sean")],
)
expected = WhereClause.new(
[table2["id"].eq(bind_param), table["id"].eq(bind_param), table["name"].eq(bind_param)],
[attribute("id", 2), attribute("id", 1), attribute("name", "Sean")],
)
assert_equal expected, a.merge(b)
end end
test "a clause knows if it is empty" do test "a clause knows if it is empty" do