module ActiveRecord class Relation class WhereClause # :nodoc: attr_reader :binds delegate :any?, :empty?, to: :predicates def initialize(predicates, binds) @predicates = predicates @binds = binds end def +(other) WhereClause.new( predicates + other.predicates, binds + other.binds, ) end def merge(other) WhereClause.new( predicates_unreferenced_by(other) + other.predicates, non_conflicting_binds(other) + other.binds, ) end def except(*columns) WhereClause.new(*except_predicates_and_binds(columns)) end def or(other) if empty? self elsif other.empty? other else WhereClause.new( [ast.or(other.ast)], binds + other.binds ) end end def to_h(table_name = nil) equalities = predicates.grep(Arel::Nodes::Equality) if table_name equalities = equalities.select do |node| node.left.relation.name == table_name end end binds = self.binds.map { |attr| [attr.name, attr.value] }.to_h equalities.map { |node| name = node.left.name.to_s [name, binds.fetch(name) { case node.right when Array then node.right.map(&:val) when Arel::Nodes::Casted, Arel::Nodes::Quoted node.right.val end }] }.to_h end def ast Arel::Nodes::And.new(predicates_with_wrapped_sql_literals) end def ==(other) other.is_a?(WhereClause) && predicates == other.predicates && binds == other.binds end def invert WhereClause.new(inverted_predicates, binds) end def self.empty @empty ||= new([], []) end # TODO Change this to private once we've dropped Ruby 2.2 support. # Workaround for Ruby 2.2 "private attribute?" warning. protected attr_reader :predicates def referenced_columns @referenced_columns ||= begin equality_nodes = predicates.select { |n| equality_node?(n) } Set.new(equality_nodes, &:left) end end private def predicates_unreferenced_by(other) predicates.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 { |attr| conflicts.include?(attr.name) } end def inverted_predicates predicates.map { |node| invert_predicate(node) } end def invert_predicate(node) case node when NilClass raise ArgumentError, "Invalid argument for .where.not(), got nil." when Arel::Nodes::In Arel::Nodes::NotIn.new(node.left, node.right) when Arel::Nodes::Equality Arel::Nodes::NotEqual.new(node.left, node.right) when String Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new(node)) else Arel::Nodes::Not.new(node) end end def except_predicates_and_binds(columns) except_binds = [] binds_index = 0 predicates = self.predicates.reject do |node| binds_contains = node.grep(Arel::Nodes::BindParam).size if node.is_a?(Arel::Nodes::Node) except = \ case node when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThan, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThan, Arel::Nodes::GreaterThanOrEqual subrelation = (node.left.kind_of?(Arel::Attributes::Attribute) ? node.left : node.right) columns.include?(subrelation.name.to_s) end if except && binds_contains > 0 (binds_index...(binds_index + binds_contains)).each do |i| except_binds[i] = true end end binds_index += binds_contains if binds_contains except end binds = self.binds.reject.with_index do |_, i| except_binds[i] end [predicates, binds] end def predicates_with_wrapped_sql_literals non_empty_predicates.map do |node| if Arel::Nodes::Equality === node node else wrap_sql_literal(node) end end end ARRAY_WITH_EMPTY_STRING = [""] def non_empty_predicates predicates - ARRAY_WITH_EMPTY_STRING end def wrap_sql_literal(node) if ::String === node node = Arel.sql(node) end Arel::Nodes::Grouping.new(node) end end end end