提交 def2879d 编写于 作者: S Sean Griffin

Move where merging logic over to `WhereClause`

This object being a black box, it knows the details of how to merge
itself with another where clause. This removes all references to where
values or bind values in `Relation::Merger`
上级 2c46d6db
...@@ -50,7 +50,7 @@ def initialize(relation, other) ...@@ -50,7 +50,7 @@ def initialize(relation, other)
end end
NORMAL_VALUES = Relation::VALUE_METHODS - NORMAL_VALUES = Relation::VALUE_METHODS -
[:joins, :where, :order, :bind, :reverse_order, :lock, :create_with, :reordering, :from] # :nodoc: [:joins, :where, :order, :reverse_order, :lock, :create_with, :reordering, :from] # :nodoc:
def normal_values def normal_values
NORMAL_VALUES NORMAL_VALUES
...@@ -106,19 +106,7 @@ def merge_joins ...@@ -106,19 +106,7 @@ def merge_joins
end end
def merge_multi_values def merge_multi_values
lhs_wheres = relation.where_values relation.where_clause = relation.where_clause.merge(other.where_clause)
rhs_wheres = other.where_values
lhs_binds = relation.bind_values
rhs_binds = other.bind_values
removed, kept = partition_overwrites(lhs_wheres, rhs_wheres)
where_values = kept + rhs_wheres
bind_values = filter_binds(lhs_binds, removed) + rhs_binds
relation.where_values = where_values
relation.bind_values = bind_values
if other.reordering_value if other.reordering_value
# override any order specified in the original relation # override any order specified in the original relation
...@@ -139,31 +127,6 @@ def merge_single_values ...@@ -139,31 +127,6 @@ def merge_single_values
relation.create_with_value = (relation.create_with_value || {}).merge(other.create_with_value) relation.create_with_value = (relation.create_with_value || {}).merge(other.create_with_value)
end end
end end
def filter_binds(lhs_binds, removed_wheres)
return lhs_binds if removed_wheres.empty?
set = Set.new removed_wheres.map { |x| x.left.name.to_s }
lhs_binds.dup.delete_if { |col,_| set.include? col.name }
end
# Remove equalities from the existing relation with a LHS which is
# present in the relation being merged in.
# returns [things_to_remove, things_to_keep]
def partition_overwrites(lhs_wheres, rhs_wheres)
if lhs_wheres.empty? || rhs_wheres.empty?
return [[], lhs_wheres]
end
nodes = rhs_wheres.find_all do |w|
w.respond_to?(:operator) && w.operator == :==
end
seen = Set.new(nodes) { |node| node.left }
lhs_wheres.partition do |w|
w.respond_to?(:operator) && w.operator == :== && seen.include?(w.left)
end
end
end end
end end
end end
module ActiveRecord module ActiveRecord
class Relation class Relation
class WhereClause class WhereClause # :nodoc:
attr_reader :parts, :binds attr_reader :parts, :binds
def initialize(parts, binds) def initialize(parts, binds)
...@@ -15,6 +15,13 @@ def +(other) ...@@ -15,6 +15,13 @@ def +(other)
) )
end end
def merge(other)
WhereClause.new(
parts_unreferenced_by(other) + other.parts,
non_conflicting_binds(other) + other.binds,
)
end
def ==(other) def ==(other)
other.is_a?(WhereClause) && other.is_a?(WhereClause) &&
parts == other.parts && parts == other.parts &&
...@@ -24,6 +31,33 @@ def ==(other) ...@@ -24,6 +31,33 @@ def ==(other)
def self.empty def self.empty
new([], []) new([], [])
end end
protected
def referenced_columns
@referenced_columns ||= begin
equality_nodes = parts.select { |n| equality_node?(n) }
Set.new(equality_nodes, &:left)
end
end
private
def parts_unreferenced_by(other)
parts.reject do |n|
equality_node?(n) && other.referenced_columns.include?(n.left)
end
end
def equality_node?(node)
node.respond_to?(:operator) && node.operator == :==
end
def non_conflicting_binds(other)
conflicts = referenced_columns & other.referenced_columns
conflicts.map! { |node| node.name.to_s }
binds.reject { |col, _| conflicts.include?(col.name) }
end
end end
end end
end end
...@@ -28,6 +28,44 @@ class WhereClauseTest < ActiveRecord::TestCase ...@@ -28,6 +28,44 @@ class WhereClauseTest < ActiveRecord::TestCase
assert_equal clause, clause + WhereClause.empty assert_equal clause, clause + WhereClause.empty
end 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)],
[[column("id"), 1], [column("name"), "Sean"]],
)
b = WhereClause.new(
[table["name"].eq(bind_param)],
[[column("name"), "Jim"]]
)
expected = WhereClause.new(
[table["id"].eq(bind_param), table["name"].eq(bind_param)],
[[column("id"), 1], [column("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
private private
def table def table
...@@ -37,5 +75,9 @@ def table ...@@ -37,5 +75,9 @@ def table
def bind_param def bind_param
Arel::Nodes::BindParam.new Arel::Nodes::BindParam.new
end end
def column(name)
ActiveRecord::ConnectionAdapters::Column.new(name, nil, nil)
end
end end
end end
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册