82 lines
2.8 KiB
Ruby
82 lines
2.8 KiB
Ruby
module ActiveRecord
|
|
class Relation
|
|
module Union
|
|
|
|
SET_OPERATION_TO_AREL_CLASS = {
|
|
union: Arel::Nodes::Union,
|
|
union_all: Arel::Nodes::UnionAll
|
|
}
|
|
|
|
def union(relation_or_where_arg, *args)
|
|
set_operation(:union, relation_or_where_arg, *args)
|
|
end
|
|
|
|
def union_all(relation_or_where_arg, *args)
|
|
set_operation(:union_all, relation_or_where_arg, *args)
|
|
end
|
|
|
|
private
|
|
|
|
def set_operation(operation, relation_or_where_arg, *args)
|
|
other = if args.size == 0 && Relation === relation_or_where_arg
|
|
relation_or_where_arg
|
|
else
|
|
@klass.where(relation_or_where_arg, *args)
|
|
end
|
|
|
|
verify_relations_for_set_operation!(operation, self, other)
|
|
|
|
# Postgres allows ORDER BY in the UNION subqueries if each subquery is surrounded by parenthesis
|
|
# but SQLite does not allow parens around the subqueries; you will have to explicitly do `relation.reorder(nil)` in SQLite
|
|
if Arel::Visitors::SQLite === self.connection.visitor
|
|
left, right = self.ast, other.ast
|
|
else
|
|
left, right = Arel::Nodes::Grouping.new(self.ast), Arel::Nodes::Grouping.new(other.ast)
|
|
end
|
|
|
|
set = SET_OPERATION_TO_AREL_CLASS[operation].new(left, right)
|
|
from = Arel::Nodes::TableAlias.new(set, @klass.arel_table.name)
|
|
if ActiveRecord::VERSION::MAJOR >= 5
|
|
relation = @klass.unscoped.spawn
|
|
relation.from_clause = UnionFromClause.new(from, nil, self.bound_attributes + other.bound_attributes)
|
|
else
|
|
relation = @klass.unscoped.from(from)
|
|
relation.bind_values = self.arel.bind_values + self.bind_values + other.arel.bind_values + other.bind_values
|
|
end
|
|
relation
|
|
end
|
|
|
|
def verify_relations_for_set_operation!(operation, *relations)
|
|
includes_relations = relations.select { |r| r.includes_values.any? }
|
|
|
|
if includes_relations.any?
|
|
raise ArgumentError.new("Cannot #{operation} relation with includes.")
|
|
end
|
|
|
|
preload_relations = relations.select { |r| r.preload_values.any? }
|
|
if preload_relations.any?
|
|
raise ArgumentError.new("Cannot #{operation} relation with preload.")
|
|
end
|
|
|
|
eager_load_relations = relations.select { |r| r.eager_load_values.any? }
|
|
if eager_load_relations.any?
|
|
raise ArgumentError.new("Cannot #{operation} relation with eager load.")
|
|
end
|
|
end
|
|
|
|
if ActiveRecord::VERSION::MAJOR >= 5
|
|
class UnionFromClause < ActiveRecord::Relation::FromClause
|
|
def initialize(value, name, bound_attributes)
|
|
super(value, name)
|
|
@bound_attributes = bound_attributes
|
|
end
|
|
|
|
def binds
|
|
@bound_attributes
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|