1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
|
# frozen_string_literal: true
module ActiveRecord
class Relation
class WhereClause # :nodoc:
delegate :any?, :empty?, to: :predicates
def initialize(predicates)
@predicates = predicates
end
def +(other)
WhereClause.new(
predicates + other.predicates,
)
end
def -(other)
WhereClause.new(
predicates - other.predicates,
)
end
def merge(other)
WhereClause.new(
predicates_unreferenced_by(other) + other.predicates,
)
end
def except(*columns)
WhereClause.new(except_predicates(columns))
end
def or(other)
left = self - other
common = self - left
right = other - common
if left.empty? || right.empty?
common
else
or_clause = WhereClause.new(
[left.ast.or(right.ast)],
)
common + or_clause
end
end
def to_h(table_name = nil)
equalities = equalities(predicates)
if table_name
equalities = equalities.select do |node|
node.left.relation.name == table_name
end
end
equalities.map { |node|
name = node.left.name.to_s
value = extract_node_value(node.right)
[name, value]
}.to_h
end
def ast
Arel::Nodes::And.new(predicates_with_wrapped_sql_literals)
end
def ==(other)
other.is_a?(WhereClause) &&
predicates == other.predicates
end
def invert(as = :nand)
if predicates.size == 1
inverted_predicates = [ invert_predicate(predicates.first) ]
elsif as == :nor
inverted_predicates = predicates.map { |node| invert_predicate(node) }
else
inverted_predicates = [ Arel::Nodes::Not.new(ast) ]
end
WhereClause.new(inverted_predicates)
end
def self.empty
@empty ||= new([])
end
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 equalities(predicates)
equalities = []
predicates.each do |node|
case node
when Arel::Nodes::Equality
equalities << node
when Arel::Nodes::And
equalities.concat equalities(node.children)
end
end
equalities
end
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 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::IsNotDistinctFrom
Arel::Nodes::IsDistinctFrom.new(node.left, node.right)
when Arel::Nodes::IsDistinctFrom
Arel::Nodes::IsNotDistinctFrom.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(columns)
predicates.reject do |node|
Arel.fetch_attribute(node) { |attr| columns.include?(attr.name.to_s) }
end
end
def predicates_with_wrapped_sql_literals
non_empty_predicates.map do |node|
case node
when Arel::Nodes::SqlLiteral, ::String
wrap_sql_literal(node)
else 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
def extract_node_value(node)
case node
when Array
node.map { |v| extract_node_value(v) }
when Arel::Nodes::Casted, Arel::Nodes::Quoted
node.val
when Arel::Nodes::BindParam
value = node.value
if value.respond_to?(:value_before_type_cast)
value.value_before_type_cast
else
value
end
end
end
end
end
end
|