-
Notifications
You must be signed in to change notification settings - Fork 21.4k
/
where_clause.rb
230 lines (188 loc) · 6.01 KB
/
where_clause.rb
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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# frozen_string_literal: true
require "active_support/core_ext/array/extract"
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 |(other)
WhereClause.new(predicates | other.predicates)
end
def merge(other, rewhere = nil)
predicates = if rewhere
except_predicates(other.extract_attributes)
else
predicates_unreferenced_by(other)
end
WhereClause.new(predicates | 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
left = left.ast
left = left.expr if left.is_a?(Arel::Nodes::Grouping)
right = right.ast
right = right.expr if right.is_a?(Arel::Nodes::Grouping)
or_clause = Arel::Nodes::Or.new(left, right)
common.predicates << Arel::Nodes::Grouping.new(or_clause)
common
end
end
def to_h(table_name = nil, equality_only: false)
equalities(predicates, equality_only).each_with_object({}) do |node, hash|
next if table_name&.!= node.left.relation.name
name = node.left.name.to_s
value = extract_node_value(node.right)
hash[name] = value
end
end
def ast
predicates = predicates_with_wrapped_sql_literals
predicates.one? ? predicates.first : Arel::Nodes::And.new(predicates)
end
def ==(other)
other.is_a?(WhereClause) &&
predicates == other.predicates
end
alias :eql? :==
def hash
[self.class, predicates].hash
end
def invert
if predicates.size == 1
inverted_predicates = [ invert_predicate(predicates.first) ]
else
inverted_predicates = [ Arel::Nodes::Not.new(ast) ]
end
WhereClause.new(inverted_predicates)
end
def self.empty
@empty ||= new([]).freeze
end
def contradiction?
predicates.any? do |x|
case x
when Arel::Nodes::In
Array === x.right && x.right.empty?
when Arel::Nodes::Equality
x.right.respond_to?(:unboundable?) && x.right.unboundable?
end
end
end
def extract_attributes
attrs = []
each_attributes { |attr, _| attrs << attr }
attrs
end
protected
attr_reader :predicates
def referenced_columns
hash = {}
each_attributes { |attr, node| hash[attr] = node }
hash
end
private
def each_attributes
predicates.each do |node|
attr = extract_attribute(node) || begin
node.left if equality_node?(node) && node.left.is_a?(Arel::Predications)
end
yield attr, node if attr
end
end
def extract_attribute(node)
attr_node = nil
Arel.fetch_attribute(node) do |attr|
return if attr_node&.!= attr # all attr nodes should be the same
attr_node = attr
end
attr_node
end
def equalities(predicates, equality_only)
equalities = []
predicates.each do |node|
if equality_only ? Arel::Nodes::Equality === node : equality_node?(node)
equalities << node
elsif node.is_a?(Arel::Nodes::And)
equalities.concat equalities(node.children, equality_only)
end
end
equalities
end
def predicates_unreferenced_by(other)
referenced_columns = other.referenced_columns
predicates.reject do |node|
attr = extract_attribute(node) || begin
node.left if equality_node?(node) && node.left.is_a?(Arel::Predications)
end
attr && referenced_columns[attr]
end
end
def equality_node?(node)
!node.is_a?(String) && node.equality?
end
def invert_predicate(node)
case node
when NilClass
raise ArgumentError, "Invalid argument for .where.not(), got nil."
when String
Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new(node))
else
node.invert
end
end
def except_predicates(columns)
attrs = columns.extract! { |node| node.is_a?(Arel::Attribute) }
non_attrs = columns.extract! { |node| node.is_a?(Arel::Predications) }
predicates.reject do |node|
if !non_attrs.empty? && node.equality? && node.left.is_a?(Arel::Predications)
non_attrs.include?(node.left)
end || Arel.fetch_attribute(node) do |attr|
attrs.include?(attr) || columns.include?(attr.name.to_s)
end
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)
if node.respond_to?(:value_before_type_cast)
node.value_before_type_cast
elsif Array === node
node.map { |v| extract_node_value(v) }
end
end
end
end
end