activerecord-refined 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.yardopts +1 -1
- data/README.md +19 -22
- data/activerecord-refined.gemspec +4 -3
- data/docs/conditions.md +3 -1
- data/docs/expressions.md +10 -7
- data/docs/index.md +1 -1
- data/examples/expressions.rb +8 -8
- data/lib/active_record/refined/ast/arithmetics.rb +137 -0
- data/lib/active_record/refined/ast/conditions.rb +451 -0
- data/lib/active_record/refined/ast/expressions.rb +304 -0
- data/lib/active_record/refined/ast/functions.rb +254 -0
- data/lib/active_record/refined/ast/grouping.rb +63 -0
- data/lib/active_record/refined/ast/json.rb +530 -0
- data/lib/active_record/refined/ast/node.rb +298 -0
- data/lib/active_record/refined/ast/ordering.rb +104 -0
- data/lib/active_record/refined/ast/predications.rb +384 -0
- data/lib/active_record/refined/ast/windows.rb +126 -0
- data/lib/active_record/refined/ast.rb +10 -2610
- data/lib/active_record/refined/block_context.rb +671 -0
- data/lib/active_record/refined/block_syntax.rb +128 -0
- data/lib/active_record/refined/query_methods.rb +444 -0
- data/lib/active_record/refined/version.rb +8 -0
- data/lib/active_record/refined/writes.rb +72 -0
- data/lib/active_record/refined.rb +7 -1307
- data/lib/activerecord-refined.rb +0 -4
- metadata +35 -5
- data/lib/activerecord-refined/version.rb +0 -12
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
require "
|
|
3
|
+
require "active_record/refined/ast/predications"
|
|
4
|
+
require "active_record/refined/ast/arithmetics"
|
|
5
|
+
require "active_record/refined/ast/node"
|
|
6
|
+
require "active_record/refined/ast/expressions"
|
|
7
|
+
require "active_record/refined/ast/windows"
|
|
8
|
+
require "active_record/refined/ast/functions"
|
|
9
|
+
require "active_record/refined/ast/json"
|
|
10
|
+
require "active_record/refined/ast/ordering"
|
|
11
|
+
require "active_record/refined/ast/grouping"
|
|
12
|
+
require "active_record/refined/ast/conditions"
|
|
5
13
|
|
|
6
14
|
module ActiveRecord
|
|
7
15
|
module Refined
|
|
@@ -54,20 +62,6 @@ module ActiveRecord
|
|
|
54
62
|
"#{node.is_a?(Node) ? 'an expression' : node.inspect} is not one"
|
|
55
63
|
end
|
|
56
64
|
|
|
57
|
-
# What true, false and nil say when `&`, `|` or `^` finds a query node
|
|
58
|
-
# on the right. Their own operators answer a bare boolean, so in
|
|
59
|
-
# `:active == true & cond` Ruby gives `:active == (true & cond)` and
|
|
60
|
-
# the condition would vanish from the query without a word; a number
|
|
61
|
-
# in the same place lands in NumericArithmetics' refusals, but these
|
|
62
|
-
# three have no bitwise reading to fall back on.
|
|
63
|
-
# @private
|
|
64
|
-
def self.refuse_ruby_operator(literal, operator)
|
|
65
|
-
raise ArgumentError,
|
|
66
|
-
"#{operator} on #{literal.inspect} is Ruby's own, and the query " \
|
|
67
|
-
"would lose what stands to its right; the comparison takes " \
|
|
68
|
-
"parentheses of its own: (:active == true) #{operator} ..."
|
|
69
|
-
end
|
|
70
|
-
|
|
71
65
|
# What `&` and `|` say when the left side is not a condition. They
|
|
72
66
|
# mean AND and OR, so what was meant is either the bitwise operation,
|
|
73
67
|
# which has a name of its own, or a comparison whose parentheses Ruby's
|
|
@@ -84,2600 +78,6 @@ module ActiveRecord
|
|
|
84
78
|
"#{named} is SQL's bitwise operator"
|
|
85
79
|
end
|
|
86
80
|
end
|
|
87
|
-
|
|
88
|
-
# The conditions a column or an expression can be put in. Every one
|
|
89
|
-
# gives back a condition that combines with `&`, `|` and `!`, and the
|
|
90
|
-
# comparisons quote a Ruby value on the right the way Active Record
|
|
91
|
-
# does, or take a column, an expression or a subquery there.
|
|
92
|
-
#
|
|
93
|
-
# @example
|
|
94
|
-
# Author.where { :age.between?(20, 40) & :name.like?("A%") }
|
|
95
|
-
# Author.where { :id.in?(Post.select(:author_id)) }
|
|
96
|
-
#
|
|
97
|
-
# Predicate builders shared by symbols, qualified columns and
|
|
98
|
-
# expressions. Imported into the Symbol refinement with
|
|
99
|
-
# Refinement#import_methods, so every method must be defined with def.
|
|
100
|
-
module Predications
|
|
101
|
-
# `=`. A value, a column, an expression or a scalar subquery on the right; `nil` is refused, since `= NULL` is never true -- {#null?} is the spelling.
|
|
102
|
-
# @return [AST::Predicate]
|
|
103
|
-
# @example
|
|
104
|
-
# Author.where { :name == "alice" }
|
|
105
|
-
# Author.where { :age == Author.select { max(:age) } }
|
|
106
|
-
#
|
|
107
|
-
# == and != mean SQL = and <>, and = NULL is never true there, so nil
|
|
108
|
-
# is rejected rather than silently rewritten to IS NULL. null? builds
|
|
109
|
-
# its node directly and stays clear of this check.
|
|
110
|
-
def ==(other)
|
|
111
|
-
if other.nil?
|
|
112
|
-
raise ArgumentError, "== does not take nil; use null? instead"
|
|
113
|
-
end
|
|
114
|
-
Comparison.new(self, :==, other)
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
# `!=`; `nil` is refused, as with `==`.
|
|
118
|
-
# @return [AST::Predicate]
|
|
119
|
-
def !=(other)
|
|
120
|
-
if other.nil?
|
|
121
|
-
raise ArgumentError, "!= does not take nil; use !null? instead"
|
|
122
|
-
end
|
|
123
|
-
Comparison.new(self, :!=, other)
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
# `>`.
|
|
127
|
-
# @return [AST::Predicate]
|
|
128
|
-
def >(other)
|
|
129
|
-
Comparison.new(self, :>, other)
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
# `>=`.
|
|
133
|
-
# @return [AST::Predicate]
|
|
134
|
-
def >=(other)
|
|
135
|
-
Comparison.new(self, :>=, other)
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
# `<`.
|
|
139
|
-
# @return [AST::Predicate]
|
|
140
|
-
def <(other)
|
|
141
|
-
Comparison.new(self, :<, other)
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
# `<=`.
|
|
145
|
-
# @return [AST::Predicate]
|
|
146
|
-
def <=(other)
|
|
147
|
-
Comparison.new(self, :<=, other)
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
# A regular expression match: `~` on PostgreSQL, `REGEXP` on MySQL, and what the adapter has elsewhere. A Ruby Regexp's source is the pattern.
|
|
151
|
-
# @param pattern [Regexp, String]
|
|
152
|
-
# @return [AST::Predicate]
|
|
153
|
-
# @example
|
|
154
|
-
# Author.where { :name =~ /^A/ }
|
|
155
|
-
def =~(pattern)
|
|
156
|
-
Match.new(self, pattern)
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
# The negated regular expression match.
|
|
160
|
-
# @return [AST::Predicate]
|
|
161
|
-
def !~(pattern)
|
|
162
|
-
Match.new(self, pattern, negated: true)
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
# `IS NULL`.
|
|
166
|
-
# @return [AST::Predicate]
|
|
167
|
-
# @example
|
|
168
|
-
# Author.where { :country.null? }
|
|
169
|
-
#
|
|
170
|
-
# `!` negates any predicate, so these are here for the four that SQL
|
|
171
|
-
# spells for itself: IS NOT NULL rather than NOT (... IS NULL), and
|
|
172
|
-
# likewise NOT IN and NOT LIKE. They mean the same thing either way,
|
|
173
|
-
# including when the column is NULL; what they save is the reading.
|
|
174
|
-
def null?
|
|
175
|
-
Comparison.new(self, :==, nil)
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
# `IS NOT NULL`.
|
|
179
|
-
# @return [AST::Predicate]
|
|
180
|
-
def not_null?
|
|
181
|
-
Comparison.new(self, :!=, nil)
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
# `IS TRUE`: true of the rows where the boolean is true, false where it is false or NULL -- where `== true` would be NULL.
|
|
185
|
-
# @return [AST::Predicate]
|
|
186
|
-
# @example
|
|
187
|
-
# Post.where { :published.true? }
|
|
188
|
-
#
|
|
189
|
-
# IS TRUE and IS FALSE differ from a comparison against the literal in
|
|
190
|
-
# what they make of NULL: `flag = TRUE` is itself NULL there, and a
|
|
191
|
-
# NULL predicate selects nothing, while these two answer false. So the
|
|
192
|
-
# difference shows in the negations: `not_true?` keeps the NULL rows
|
|
193
|
-
# that `!(:flag == true)` drops.
|
|
194
|
-
def true?
|
|
195
|
-
TruthValue.new(self, true)
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
# `IS NOT TRUE`: keeps the NULL rows that `!(:flag == true)` drops.
|
|
199
|
-
# @return [AST::Predicate]
|
|
200
|
-
def not_true?
|
|
201
|
-
TruthValue.new(self, true, negated: true)
|
|
202
|
-
end
|
|
203
|
-
|
|
204
|
-
# `IS FALSE`.
|
|
205
|
-
# @return [AST::Predicate]
|
|
206
|
-
def false?
|
|
207
|
-
TruthValue.new(self, false)
|
|
208
|
-
end
|
|
209
|
-
|
|
210
|
-
# `IS NOT FALSE`.
|
|
211
|
-
# @return [AST::Predicate]
|
|
212
|
-
def not_false?
|
|
213
|
-
TruthValue.new(self, false, negated: true)
|
|
214
|
-
end
|
|
215
|
-
|
|
216
|
-
# `IN (...)`: an array of values, a range, or a relation as a subquery.
|
|
217
|
-
# @param values [Array, Range, ActiveRecord::Relation]
|
|
218
|
-
# @return [AST::Predicate]
|
|
219
|
-
# @example
|
|
220
|
-
# Author.where { :country.in?(%w[JP US]) }
|
|
221
|
-
# Author.where { :id.in?(Post.select(:author_id)) }
|
|
222
|
-
def in?(values)
|
|
223
|
-
In.new(self, values)
|
|
224
|
-
end
|
|
225
|
-
|
|
226
|
-
# `NOT IN (...)`.
|
|
227
|
-
# @return [AST::Predicate]
|
|
228
|
-
def not_in?(values)
|
|
229
|
-
In.new(self, values, negated: true)
|
|
230
|
-
end
|
|
231
|
-
|
|
232
|
-
# `BETWEEN min AND max`, with either end a value, a column or an expression.
|
|
233
|
-
# @return [AST::Predicate]
|
|
234
|
-
# @example
|
|
235
|
-
# Author.where { :age.between?(20, 40) }
|
|
236
|
-
#
|
|
237
|
-
# Not min..max: an endpoint may be an expression, which Range would
|
|
238
|
-
# refuse to hold, since expressions do not compare among themselves.
|
|
239
|
-
def between?(min, max)
|
|
240
|
-
In.new(self, In::QuotedRange.new(min, max, false))
|
|
241
|
-
end
|
|
242
|
-
|
|
243
|
-
# `NOT BETWEEN min AND max`.
|
|
244
|
-
# @return [AST::Predicate]
|
|
245
|
-
def not_between?(min, max)
|
|
246
|
-
In.new(self, In::QuotedRange.new(min, max, false), negated: true)
|
|
247
|
-
end
|
|
248
|
-
|
|
249
|
-
# `CASE column WHEN value THEN ...`: a CASE with this as the operand, each `when` a value it is compared against, followed by `then` and finally `else`.
|
|
250
|
-
# @return [AST::Case::When]
|
|
251
|
-
# @example
|
|
252
|
-
# Author.select { :country.when("JP").then("Japan").else("elsewhere").as(:where) }
|
|
253
|
-
#
|
|
254
|
-
# CASE with this as the operand, compared against each `when`:
|
|
255
|
-
# `:age.when(10).then(1).else(0)`. The other shape, where each `when`
|
|
256
|
-
# carries its own condition, starts at `case_when`.
|
|
257
|
-
def when(value = nil, &block)
|
|
258
|
-
Case.new(self).when(value, &block)
|
|
259
|
-
end
|
|
260
|
-
|
|
261
|
-
# `LIKE pattern`, the pattern as written: `%` and `_` are its wildcards.
|
|
262
|
-
# @param pattern [String]
|
|
263
|
-
# @return [AST::Predicate]
|
|
264
|
-
# @example
|
|
265
|
-
# Author.where { :name.like?("A%") }
|
|
266
|
-
def like?(pattern)
|
|
267
|
-
Like.new(self, pattern)
|
|
268
|
-
end
|
|
269
|
-
|
|
270
|
-
# `NOT LIKE pattern`.
|
|
271
|
-
# @return [AST::Predicate]
|
|
272
|
-
def not_like?(pattern)
|
|
273
|
-
Like.new(self, pattern, negated: true)
|
|
274
|
-
end
|
|
275
|
-
|
|
276
|
-
# A case-insensitive `LIKE`: `ILIKE` on PostgreSQL, and `LIKE` over both sides lower-cased elsewhere.
|
|
277
|
-
# @return [AST::Predicate]
|
|
278
|
-
def ilike?(pattern)
|
|
279
|
-
Like.new(self, pattern, nil, case_sensitive: false)
|
|
280
|
-
end
|
|
281
|
-
|
|
282
|
-
# The negated case-insensitive `LIKE`.
|
|
283
|
-
# @return [AST::Predicate]
|
|
284
|
-
def not_ilike?(pattern)
|
|
285
|
-
Like.new(self, pattern, nil, case_sensitive: false, negated: true)
|
|
286
|
-
end
|
|
287
|
-
|
|
288
|
-
# Case-insensitive equality: `LOWER(column) = LOWER(value)`.
|
|
289
|
-
# @return [AST::Predicate]
|
|
290
|
-
# @example
|
|
291
|
-
# Author.where { :name.casecmp?("Alice") }
|
|
292
|
-
#
|
|
293
|
-
# Case-insensitive equality, folded on both sides rather than left to
|
|
294
|
-
# the collation, so it means the same thing on every adapter.
|
|
295
|
-
def casecmp?(value)
|
|
296
|
-
if value.nil?
|
|
297
|
-
raise ArgumentError, "casecmp? does not take nil; use null? instead"
|
|
298
|
-
end
|
|
299
|
-
Comparison.new(Function.new("LOWER", [self]), :==,
|
|
300
|
-
Function.new("LOWER", [value]))
|
|
301
|
-
end
|
|
302
|
-
|
|
303
|
-
# `IS DISTINCT FROM`: `!=` that treats NULL as a value. `IS NOT` on SQLite, `NOT <=>` on MySQL.
|
|
304
|
-
# @return [AST::Predicate]
|
|
305
|
-
#
|
|
306
|
-
# Null-safe comparison: unlike = and <>, these treat NULL as a value,
|
|
307
|
-
# so not_distinct_from? is the one equality that may take nil.
|
|
308
|
-
def distinct_from?(value)
|
|
309
|
-
DistinctFrom.new(self, value, negated: true)
|
|
310
|
-
end
|
|
311
|
-
|
|
312
|
-
# `IS NOT DISTINCT FROM`: `=` that treats NULL as a value, so this is the one equality that takes `nil`.
|
|
313
|
-
# @return [AST::Predicate]
|
|
314
|
-
# @example
|
|
315
|
-
# Author.where { :country.not_distinct_from?(nil) }
|
|
316
|
-
def not_distinct_from?(value)
|
|
317
|
-
DistinctFrom.new(self, value)
|
|
318
|
-
end
|
|
319
|
-
|
|
320
|
-
# `LIKE 'prefix%'`, the prefix escaped so that a `%` or `_` in it is itself; several prefixes are `OR`ed.
|
|
321
|
-
# @return [AST::Predicate]
|
|
322
|
-
# @example
|
|
323
|
-
# Author.where { :name.start_with?("A", "B") }
|
|
324
|
-
def start_with?(*prefixes)
|
|
325
|
-
if prefixes.empty?
|
|
326
|
-
raise ArgumentError, "start_with? needs at least one prefix"
|
|
327
|
-
end
|
|
328
|
-
Like.any(self, prefixes.map { |prefix| "#{Like.escape(prefix)}%" })
|
|
329
|
-
end
|
|
330
|
-
|
|
331
|
-
# `LIKE '%suffix'`, escaped as {#start_with?} escapes.
|
|
332
|
-
# @return [AST::Predicate]
|
|
333
|
-
def end_with?(*suffixes)
|
|
334
|
-
if suffixes.empty?
|
|
335
|
-
raise ArgumentError, "end_with? needs at least one suffix"
|
|
336
|
-
end
|
|
337
|
-
Like.any(self, suffixes.map { |suffix| "%#{Like.escape(suffix)}" })
|
|
338
|
-
end
|
|
339
|
-
|
|
340
|
-
# `LIKE '%substring%'`, escaped as {#start_with?} escapes.
|
|
341
|
-
# @return [AST::Predicate]
|
|
342
|
-
# @example
|
|
343
|
-
# Post.where { :title.include?("ruby") }
|
|
344
|
-
def include?(substring)
|
|
345
|
-
Like.new(self, "%#{Like.escape(substring)}%", Like::ESCAPE)
|
|
346
|
-
end
|
|
347
|
-
|
|
348
|
-
# Whether a PostgreSQL array column holds the element: `@> ARRAY[element]`.
|
|
349
|
-
# @return [AST::Predicate]
|
|
350
|
-
# @example
|
|
351
|
-
# Post.where { :tags.member?("ruby") }
|
|
352
|
-
#
|
|
353
|
-
# The array comparisons carry the meaning of their Ruby namesakes.
|
|
354
|
-
# member? is Enumerable's element test, so an Array argument is
|
|
355
|
-
# rejected rather than quietly meaning something Array#member? does
|
|
356
|
-
# not; whole-array comparisons go by the Set and Array names.
|
|
357
|
-
def member?(element)
|
|
358
|
-
if element.is_a?(::Array) || element.is_a?(::Set)
|
|
359
|
-
raise ArgumentError,
|
|
360
|
-
"member? takes a single element; use superset? to require every element"
|
|
361
|
-
end
|
|
362
|
-
ArrayPredicate.new(self, :"@>", [element])
|
|
363
|
-
end
|
|
364
|
-
|
|
365
|
-
# Whether an array column holds every element given: `@>`.
|
|
366
|
-
# @return [AST::Predicate]
|
|
367
|
-
def superset?(elements)
|
|
368
|
-
ArrayPredicate.new(self, :"@>", ArrayPredicate.elements(elements, "superset?"))
|
|
369
|
-
end
|
|
370
|
-
|
|
371
|
-
# Whether every element of an array column is among those given: `<@`.
|
|
372
|
-
# @return [AST::Predicate]
|
|
373
|
-
def subset?(elements)
|
|
374
|
-
ArrayPredicate.new(self, :"<@", ArrayPredicate.elements(elements, "subset?"))
|
|
375
|
-
end
|
|
376
|
-
|
|
377
|
-
# Whether an array column and the elements given share any: `&&`.
|
|
378
|
-
# @return [AST::Predicate]
|
|
379
|
-
# @example
|
|
380
|
-
# Post.where { :tags.intersect?(%w[ruby sql]) }
|
|
381
|
-
def intersect?(elements)
|
|
382
|
-
ArrayPredicate.new(self, :"&&", ArrayPredicate.elements(elements, "intersect?"))
|
|
383
|
-
end
|
|
384
|
-
|
|
385
|
-
# The JSON at a path into a JSON column, still JSON -- to be dug further, compared with a Ruby value, or asked {#key?} and the rest. A string or a symbol steps into an object, an integer into an array. `#>` on PostgreSQL, `JSON_EXTRACT` on MySQL, `->` on SQLite.
|
|
386
|
-
# @return [AST::JsonPath]
|
|
387
|
-
# @example
|
|
388
|
-
# Doc.select { :meta.dig(:author).as(:author) }
|
|
389
|
-
# Doc.where { :meta.dig(:author).key?(:name) }
|
|
390
|
-
#
|
|
391
|
-
# Reading inside a JSON document, by the name of what Hash does. A
|
|
392
|
-
# string or symbol steps into an object, an integer into an array, and
|
|
393
|
-
# what comes back is still JSON, the way Hash#dig hands back the
|
|
394
|
-
# structure itself -- for a document to be dug into further or asked
|
|
395
|
-
# the JSON questions. dig_text gives the value as text instead,
|
|
396
|
-
# which is what a comparison wants.
|
|
397
|
-
def dig(*path)
|
|
398
|
-
JsonPath.new(self, path)
|
|
399
|
-
end
|
|
400
|
-
|
|
401
|
-
# The value at a path as text, which is what a comparison against a string wants where the JSON type would not do: `#>>` on PostgreSQL, `JSON_UNQUOTE(JSON_EXTRACT(...))` on MySQL, `->>` on SQLite.
|
|
402
|
-
# @return [AST::JsonPath]
|
|
403
|
-
# @example
|
|
404
|
-
# Doc.where { :meta.dig_text(:author, :name) == "alice" }
|
|
405
|
-
def dig_text(*path)
|
|
406
|
-
JsonPath.new(self, path, json_value: false)
|
|
407
|
-
end
|
|
408
|
-
|
|
409
|
-
# The document without the keys given, as Hash#except gives it; an expression, for `update_all` to write back.
|
|
410
|
-
# @return [AST::JsonExcept]
|
|
411
|
-
# @example
|
|
412
|
-
# Doc.update_all { { meta: :meta.except(:draft) } }
|
|
413
|
-
#
|
|
414
|
-
# Keys taken out of a JSON document, by the name of what Hash does,
|
|
415
|
-
# and taking keys as Hash#except takes them. Like bury it gives back
|
|
416
|
-
# the document changed rather than writing it anywhere.
|
|
417
|
-
def except(*keys)
|
|
418
|
-
JsonExcept.new(self, keys)
|
|
419
|
-
end
|
|
420
|
-
|
|
421
|
-
# The document with a value set at a path, as {#dig} reads one; an expression, for `update_all` to write back.
|
|
422
|
-
# @return [AST::JsonSet]
|
|
423
|
-
# @example
|
|
424
|
-
# Doc.update_all { { meta: :meta.bury(:author, :name, "alice") } }
|
|
425
|
-
#
|
|
426
|
-
# What dig reads, bury sets: the last argument is the value and the
|
|
427
|
-
# rest are the path to it. The document comes back changed rather
|
|
428
|
-
# than being written anywhere, which update_all is for.
|
|
429
|
-
def bury(*path, value)
|
|
430
|
-
JsonSet.new(self, path, value)
|
|
431
|
-
end
|
|
432
|
-
|
|
433
|
-
# Whether the document contains the Ruby document given, which SQL calls containment: `@>` on PostgreSQL, `JSON_CONTAINS` on MySQL. SQLite and MariaDB have none.
|
|
434
|
-
# @return [AST::Predicate]
|
|
435
|
-
# @example
|
|
436
|
-
# Doc.where { :meta.contains?(author: { name: "alice" }) }
|
|
437
|
-
#
|
|
438
|
-
# Whether the document holds what is given, which SQL calls
|
|
439
|
-
# containment. SQLite has no equivalent.
|
|
440
|
-
def contains?(value)
|
|
441
|
-
JsonContains.new(self, value)
|
|
442
|
-
end
|
|
443
|
-
|
|
444
|
-
# Whether the object has the key, as Hash#key? asks.
|
|
445
|
-
# @return [AST::Predicate]
|
|
446
|
-
# @example
|
|
447
|
-
# Doc.where { :meta.key?(:author) }
|
|
448
|
-
#
|
|
449
|
-
# Whether the key is there at all, as Hash#key? asks. Hash has
|
|
450
|
-
# has_key? too; one name is enough, and this is the one Ruby's own
|
|
451
|
-
# style prefers.
|
|
452
|
-
def key?(key)
|
|
453
|
-
JsonHasKey.new(self, key)
|
|
454
|
-
end
|
|
455
|
-
|
|
456
|
-
# The keys of the object as a JSON array, as Hash#keys gives them. Oracle and SQL Server have none.
|
|
457
|
-
# @return [AST::JsonKeys]
|
|
458
|
-
#
|
|
459
|
-
# The keys of the document, as Hash#keys gives them: a JSON array.
|
|
460
|
-
def keys
|
|
461
|
-
JsonKeys.new(self)
|
|
462
|
-
end
|
|
463
|
-
end
|
|
464
|
-
|
|
465
|
-
# The arithmetic and the bitwise operations on a column or an
|
|
466
|
-
# expression. Ruby puts the operators above the comparisons, so
|
|
467
|
-
# `:price * :quantity > 100` groups the way it reads, and a number on
|
|
468
|
-
# the left -- `20 - :quantity` -- builds the same expression.
|
|
469
|
-
#
|
|
470
|
-
# Bitwise AND and OR are named, not spelled `&` and `|`: those two are
|
|
471
|
-
# AND and OR between conditions and mean nothing else anywhere. The
|
|
472
|
-
# rest keep their operators, with {#bitwise_xor} and {#bitwise_not}
|
|
473
|
-
# beside `^` and `~` for a reader who would rather have the name.
|
|
474
|
-
# Oracle, which has no bitwise operators at all, refuses every one.
|
|
475
|
-
#
|
|
476
|
-
# @example
|
|
477
|
-
# LineItem.where { :price * :quantity > 1000 }
|
|
478
|
-
# LineItem.select { :flags.bitwise_and(4).as(:featured) }
|
|
479
|
-
#
|
|
480
|
-
# Arithmetic builders shared by symbols, qualified columns and
|
|
481
|
-
# expressions. Imported into the Symbol refinement like Predications,
|
|
482
|
-
# so every method must be defined with def.
|
|
483
|
-
module Arithmetics
|
|
484
|
-
# `+`; with an Active Support duration on the right, a date moved: `:due_on + 3.days`.
|
|
485
|
-
# @return [AST::Arithmetic]
|
|
486
|
-
def +(other)
|
|
487
|
-
Arithmetic.new(self, :+, other)
|
|
488
|
-
end
|
|
489
|
-
|
|
490
|
-
# `-`; with a duration on the right, a date moved back.
|
|
491
|
-
# @return [AST::Arithmetic]
|
|
492
|
-
def -(other)
|
|
493
|
-
Arithmetic.new(self, :-, other)
|
|
494
|
-
end
|
|
495
|
-
|
|
496
|
-
# `*`.
|
|
497
|
-
# @return [AST::Arithmetic]
|
|
498
|
-
def *(other)
|
|
499
|
-
Arithmetic.new(self, :*, other)
|
|
500
|
-
end
|
|
501
|
-
|
|
502
|
-
# `/`.
|
|
503
|
-
# @return [AST::Arithmetic]
|
|
504
|
-
def /(other)
|
|
505
|
-
Arithmetic.new(self, :/, other)
|
|
506
|
-
end
|
|
507
|
-
|
|
508
|
-
# `&` is AND, and a column is not a condition, so this refuses:
|
|
509
|
-
# {#bitwise_and} is the SQL operator, and `.true?` makes a boolean
|
|
510
|
-
# column a condition.
|
|
511
|
-
# @raise [ArgumentError]
|
|
512
|
-
def &(other)
|
|
513
|
-
AST.refuse_logical(:&, "AND", "bitwise_and", other)
|
|
514
|
-
end
|
|
515
|
-
|
|
516
|
-
# `|` is OR, and refuses here as `&` does.
|
|
517
|
-
# @raise [ArgumentError]
|
|
518
|
-
def |(other)
|
|
519
|
-
AST.refuse_logical(:|, "OR", "bitwise_or", other)
|
|
520
|
-
end
|
|
521
|
-
|
|
522
|
-
# Bitwise AND: `"flags" & 4`. It is spelled as a name because `&`
|
|
523
|
-
# is AND, and a method binds tighter than any comparison, so nothing
|
|
524
|
-
# has to be parenthesised to be compared.
|
|
525
|
-
# @return [AST::Bitwise]
|
|
526
|
-
# @example
|
|
527
|
-
# Post.where { :flags.bitwise_and(4) > 0 }
|
|
528
|
-
def bitwise_and(other)
|
|
529
|
-
Bitwise.new(self, :&, other)
|
|
530
|
-
end
|
|
531
|
-
|
|
532
|
-
# Bitwise OR.
|
|
533
|
-
# @return [AST::Bitwise]
|
|
534
|
-
def bitwise_or(other)
|
|
535
|
-
Bitwise.new(self, :|, other)
|
|
536
|
-
end
|
|
537
|
-
|
|
538
|
-
# Bitwise XOR: `#` on PostgreSQL, `^` on MySQL, and the two operations it is made of on SQLite.
|
|
539
|
-
# @return [AST::Bitwise]
|
|
540
|
-
def ^(other)
|
|
541
|
-
Bitwise.new(self, :^, other)
|
|
542
|
-
end
|
|
543
|
-
|
|
544
|
-
# {#^} under a name.
|
|
545
|
-
# @return [AST::Bitwise]
|
|
546
|
-
def bitwise_xor(other)
|
|
547
|
-
Bitwise.new(self, :^, other)
|
|
548
|
-
end
|
|
549
|
-
|
|
550
|
-
# A shift left.
|
|
551
|
-
# @return [AST::Bitwise]
|
|
552
|
-
def <<(other)
|
|
553
|
-
Bitwise.new(self, :<<, other)
|
|
554
|
-
end
|
|
555
|
-
|
|
556
|
-
# A shift right.
|
|
557
|
-
# @return [AST::Bitwise]
|
|
558
|
-
def >>(other)
|
|
559
|
-
Bitwise.new(self, :>>, other)
|
|
560
|
-
end
|
|
561
|
-
|
|
562
|
-
# Bitwise NOT.
|
|
563
|
-
# @return [AST::BitwiseNot]
|
|
564
|
-
def ~
|
|
565
|
-
BitwiseNot.new(self)
|
|
566
|
-
end
|
|
567
|
-
|
|
568
|
-
# {#~} under a name. `!` is not the one to reach for: it is Ruby's
|
|
569
|
-
# own on a column and answers `false`, which no query ever wanted.
|
|
570
|
-
# @return [AST::BitwiseNot]
|
|
571
|
-
def bitwise_not
|
|
572
|
-
BitwiseNot.new(self)
|
|
573
|
-
end
|
|
574
|
-
end
|
|
575
|
-
|
|
576
|
-
# Arithmetic with the number on the left, imported into the numeric
|
|
577
|
-
# refinements: 20 - :quantity builds what :quantity + 20 builds. Only
|
|
578
|
-
# a column or an expression on the right means a query; anything else
|
|
579
|
-
# goes back to the number through super, so 1 + 2 is 3 inside a block
|
|
580
|
-
# too, and 4 & 5 is 4. & and | refuse a query the way they do on a
|
|
581
|
-
# column, bitwise_and and its kin carrying those two operations.
|
|
582
|
-
# @private
|
|
583
|
-
module NumericArithmetics
|
|
584
|
-
def +(other)
|
|
585
|
-
return super unless other.is_a?(::Symbol) || other.is_a?(Node)
|
|
586
|
-
Arithmetic.new(self, :+, other)
|
|
587
|
-
end
|
|
588
|
-
|
|
589
|
-
def -(other)
|
|
590
|
-
return super unless other.is_a?(::Symbol) || other.is_a?(Node)
|
|
591
|
-
Arithmetic.new(self, :-, other)
|
|
592
|
-
end
|
|
593
|
-
|
|
594
|
-
def *(other)
|
|
595
|
-
return super unless other.is_a?(::Symbol) || other.is_a?(Node)
|
|
596
|
-
Arithmetic.new(self, :*, other)
|
|
597
|
-
end
|
|
598
|
-
|
|
599
|
-
def /(other)
|
|
600
|
-
return super unless other.is_a?(::Symbol) || other.is_a?(Node)
|
|
601
|
-
Arithmetic.new(self, :/, other)
|
|
602
|
-
end
|
|
603
|
-
|
|
604
|
-
def &(other)
|
|
605
|
-
return super unless other.is_a?(::Symbol) || other.is_a?(Node)
|
|
606
|
-
AST.refuse_logical(:&, "AND", "bitwise_and", other)
|
|
607
|
-
end
|
|
608
|
-
|
|
609
|
-
def |(other)
|
|
610
|
-
return super unless other.is_a?(::Symbol) || other.is_a?(Node)
|
|
611
|
-
AST.refuse_logical(:|, "OR", "bitwise_or", other)
|
|
612
|
-
end
|
|
613
|
-
|
|
614
|
-
def ^(other)
|
|
615
|
-
return super unless other.is_a?(::Symbol) || other.is_a?(Node)
|
|
616
|
-
Bitwise.new(self, :^, other)
|
|
617
|
-
end
|
|
618
|
-
|
|
619
|
-
def <<(other)
|
|
620
|
-
return super unless other.is_a?(::Symbol) || other.is_a?(Node)
|
|
621
|
-
Bitwise.new(self, :<<, other)
|
|
622
|
-
end
|
|
623
|
-
|
|
624
|
-
def >>(other)
|
|
625
|
-
return super unless other.is_a?(::Symbol) || other.is_a?(Node)
|
|
626
|
-
Bitwise.new(self, :>>, other)
|
|
627
|
-
end
|
|
628
|
-
|
|
629
|
-
def bitwise_and(other)
|
|
630
|
-
Bitwise.new(self, :&, other)
|
|
631
|
-
end
|
|
632
|
-
|
|
633
|
-
def bitwise_or(other)
|
|
634
|
-
Bitwise.new(self, :|, other)
|
|
635
|
-
end
|
|
636
|
-
|
|
637
|
-
def bitwise_xor(other)
|
|
638
|
-
Bitwise.new(self, :^, other)
|
|
639
|
-
end
|
|
640
|
-
end
|
|
641
|
-
|
|
642
|
-
# An expression a block has built, whatever it was built from. The
|
|
643
|
-
# methods here are what every one takes; most subclasses add the
|
|
644
|
-
# conditions of {Predications} and the operators of {Arithmetics}.
|
|
645
|
-
class Node
|
|
646
|
-
# The model travels with the table because some SQL cannot be written
|
|
647
|
-
# without knowing the adapter, and a node is built before anything
|
|
648
|
-
# knows which one it will be rendered for -- a symbol becomes a node
|
|
649
|
-
# inside a refinement, where there is no model to ask. Most nodes
|
|
650
|
-
# never look at it and only pass it on.
|
|
651
|
-
# @private
|
|
652
|
-
def to_arel(table, model)
|
|
653
|
-
raise ScriptError, "subclass must override this method"
|
|
654
|
-
end
|
|
655
|
-
|
|
656
|
-
# The expression under an alias, as {BlockSyntax#as} gives a column
|
|
657
|
-
# one.
|
|
658
|
-
# @return [AST::As]
|
|
659
|
-
def as(alias_name, quote: true)
|
|
660
|
-
As.new(self, alias_name, quote: quote)
|
|
661
|
-
end
|
|
662
|
-
|
|
663
|
-
# An ascending ordering by the expression.
|
|
664
|
-
# @return [AST::Ordering]
|
|
665
|
-
def asc
|
|
666
|
-
Ordering.new(self, :asc)
|
|
667
|
-
end
|
|
668
|
-
|
|
669
|
-
# A descending ordering by the expression.
|
|
670
|
-
# @return [AST::Ordering]
|
|
671
|
-
def desc
|
|
672
|
-
Ordering.new(self, :desc)
|
|
673
|
-
end
|
|
674
|
-
|
|
675
|
-
# The expression under a collation, as {BlockSyntax#collate}.
|
|
676
|
-
# @return [AST::Collate]
|
|
677
|
-
def collate(name)
|
|
678
|
-
Collate.new(self, name)
|
|
679
|
-
end
|
|
680
|
-
|
|
681
|
-
private
|
|
682
|
-
# Resolves an operand denoting a column or an expression. A number
|
|
683
|
-
# rides along for Arel to write out, which it can do for Integer and
|
|
684
|
-
# Float alone: a BigDecimal is quoted, which the adapter spells as
|
|
685
|
-
# the exact decimal, and a Rational, which no decimal spells exactly,
|
|
686
|
-
# is refused.
|
|
687
|
-
def to_arel_operand(operand, table, model)
|
|
688
|
-
case operand
|
|
689
|
-
when Node then operand.to_arel(table, model)
|
|
690
|
-
when :* then Arel.star
|
|
691
|
-
when Symbol then table[operand]
|
|
692
|
-
when ::BigDecimal, ::Rational then quote_number(operand)
|
|
693
|
-
else operand
|
|
694
|
-
end
|
|
695
|
-
end
|
|
696
|
-
|
|
697
|
-
# A bare symbol is a column in every position, the value side of a
|
|
698
|
-
# comparison included. The name is checked against the model, since
|
|
699
|
-
# a name it has no column for is almost always an enum value spelled
|
|
700
|
-
# as a symbol -- which, taken as a column, would quietly compare
|
|
701
|
-
# against nothing anyone meant.
|
|
702
|
-
def column_operand(name, table, model)
|
|
703
|
-
unless model.column_names.include?(name.to_s)
|
|
704
|
-
raise ArgumentError,
|
|
705
|
-
"#{name.inspect} is no column of #{model.table_name}; an enum " \
|
|
706
|
-
"value is written as its string, a column of another table " \
|
|
707
|
-
"qualified"
|
|
708
|
-
end
|
|
709
|
-
table[name]
|
|
710
|
-
end
|
|
711
|
-
|
|
712
|
-
# A number compares as itself, the way a bound ? does: the typed path
|
|
713
|
-
# would cast 99.5 against an integer column to 99 and quietly move
|
|
714
|
-
# the boundary. Everything else keeps the column's own
|
|
715
|
-
# serialization -- an enum's name, a time's zone, a custom type's
|
|
716
|
-
# scaling.
|
|
717
|
-
def quote_number(value)
|
|
718
|
-
case value
|
|
719
|
-
when ::Rational
|
|
720
|
-
raise ArgumentError,
|
|
721
|
-
"a Rational has no exact SQL spelling; to_d says the decimal meant"
|
|
722
|
-
when ::Integer, ::Float, ::BigDecimal
|
|
723
|
-
Arel::Nodes.build_quoted(value)
|
|
724
|
-
else value
|
|
725
|
-
end
|
|
726
|
-
end
|
|
727
|
-
|
|
728
|
-
# Resolves a function argument: a column or an expression as above,
|
|
729
|
-
# anything else a value to be quoted.
|
|
730
|
-
def to_arel_argument(arg, table, model)
|
|
731
|
-
case arg
|
|
732
|
-
when Node, Symbol, ::Rational then to_arel_operand(arg, table, model)
|
|
733
|
-
else Arel::Nodes.build_quoted(arg)
|
|
734
|
-
end
|
|
735
|
-
end
|
|
736
|
-
end
|
|
737
|
-
|
|
738
|
-
# `&`, `|` and `!` -- AND, OR and NOT, what joins conditions into one
|
|
739
|
-
# and negates them. A predicate carries them, and so do {Sql} and
|
|
740
|
-
# {Operation}, whose SQL is read as a condition the moment it is
|
|
741
|
-
# combined like one.
|
|
742
|
-
#
|
|
743
|
-
# Included in those three and never imported into a refinement, as
|
|
744
|
-
# {Predications} is: a bare symbol is a column, and a column is not a
|
|
745
|
-
# condition.
|
|
746
|
-
module Connectives
|
|
747
|
-
# `AND`.
|
|
748
|
-
# @return [AST::Predicate]
|
|
749
|
-
# @example
|
|
750
|
-
# Author.where { :age.between?(20, 40) & (:country == "JP") }
|
|
751
|
-
# Post.where { sql("score > 0") & (:published == true) }
|
|
752
|
-
def &(other)
|
|
753
|
-
And.new(self, other)
|
|
754
|
-
end
|
|
755
|
-
|
|
756
|
-
# `OR`.
|
|
757
|
-
# @return [AST::Predicate]
|
|
758
|
-
def |(other)
|
|
759
|
-
Or.new(self, other)
|
|
760
|
-
end
|
|
761
|
-
|
|
762
|
-
# `NOT (condition)`, negating anything; the negations SQL spells for
|
|
763
|
-
# itself, `IS NOT NULL` and its kin, have names of their own under
|
|
764
|
-
# {Predications}.
|
|
765
|
-
# @return [AST::Predicate]
|
|
766
|
-
# @example
|
|
767
|
-
# Author.where { !:name.like?("A%") }
|
|
768
|
-
#
|
|
769
|
-
# Being here rather than only on Predicate is what keeps `!` on Sql
|
|
770
|
-
# and Operation from falling through to Ruby's own, which would
|
|
771
|
-
# quietly answer false.
|
|
772
|
-
def !
|
|
773
|
-
Not.new(self)
|
|
774
|
-
end
|
|
775
|
-
end
|
|
776
|
-
|
|
777
|
-
# A condition: what a comparison or one of the tests gives back, and
|
|
778
|
-
# what `where`, `having` and a join's block hand the relation.
|
|
779
|
-
class Predicate < Node
|
|
780
|
-
include Connectives
|
|
781
|
-
end
|
|
782
|
-
|
|
783
|
-
# A literal standing where an expression would: `select { value(0).as(:depth) }`.
|
|
784
|
-
#
|
|
785
|
-
# Values reach the SQL quoted wherever they appear as an operand, but a
|
|
786
|
-
# bare Ruby literal at the top of a select list is refused as saying
|
|
787
|
-
# nothing. `value` is the spelling that quotes it there, and it carries
|
|
788
|
-
# the predications with it, so a literal can be compared and combined
|
|
789
|
-
# like anything else.
|
|
790
|
-
class Value < Node
|
|
791
|
-
include Predications
|
|
792
|
-
include Arithmetics
|
|
793
|
-
|
|
794
|
-
# @private
|
|
795
|
-
attr_reader :value
|
|
796
|
-
|
|
797
|
-
def initialize(value)
|
|
798
|
-
@value = value
|
|
799
|
-
end
|
|
800
|
-
|
|
801
|
-
# @private
|
|
802
|
-
def to_arel(_table, _model)
|
|
803
|
-
Arel::Nodes.build_quoted(value)
|
|
804
|
-
end
|
|
805
|
-
end
|
|
806
|
-
|
|
807
|
-
# SQL as written: `sql("length(name) > ?", 10)`. The ? and :name
|
|
808
|
-
# placeholders take quoted values through sanitize_sql_array, which
|
|
809
|
-
# needs the connection, so the binds wait here until the model is
|
|
810
|
-
# known. Without binds the statement passes untouched -- which is
|
|
811
|
-
# what leaves PostgreSQL's ? operators writable, since only the
|
|
812
|
-
# positional-bind rewrite reads ? as a placeholder.
|
|
813
|
-
#
|
|
814
|
-
# As an operand the statement is parenthesized: its precedence is
|
|
815
|
-
# whatever was written inside. The top of a select list gets it bare,
|
|
816
|
-
# through field_arel, where parentheses would refuse an alias written
|
|
817
|
-
# into the string.
|
|
818
|
-
class Sql < Node
|
|
819
|
-
include Predications
|
|
820
|
-
include Arithmetics
|
|
821
|
-
# After Arithmetics, whose & and | refuse: here they are AND and OR.
|
|
822
|
-
include Connectives
|
|
823
|
-
|
|
824
|
-
# @private
|
|
825
|
-
attr_reader :statement, :binds
|
|
826
|
-
|
|
827
|
-
def initialize(statement, binds)
|
|
828
|
-
unless statement.is_a?(::String)
|
|
829
|
-
raise ArgumentError,
|
|
830
|
-
"sql takes the statement as a string, not #{statement.inspect}"
|
|
831
|
-
end
|
|
832
|
-
@statement = statement
|
|
833
|
-
@binds = binds
|
|
834
|
-
end
|
|
835
|
-
|
|
836
|
-
# @private
|
|
837
|
-
def to_arel(_table, model)
|
|
838
|
-
Arel::Nodes::Grouping.new(field_arel(model))
|
|
839
|
-
end
|
|
840
|
-
|
|
841
|
-
# @private
|
|
842
|
-
def field_arel(model)
|
|
843
|
-
return Arel.sql(statement) if binds.empty?
|
|
844
|
-
|
|
845
|
-
Arel.sql(model.sanitize_sql_array([statement, *binds]))
|
|
846
|
-
end
|
|
847
|
-
end
|
|
848
|
-
|
|
849
|
-
# CASE, in both of the shapes SQL has for it. With an operand, each
|
|
850
|
-
# `when` is something to compare it against; without one, each `when` is
|
|
851
|
-
# a condition of its own.
|
|
852
|
-
#
|
|
853
|
-
# Every method returns a new node rather than adding to this one, so a
|
|
854
|
-
# case kept in a variable can be branched from more than once.
|
|
855
|
-
class Case < Node
|
|
856
|
-
include Predications
|
|
857
|
-
include Arithmetics
|
|
858
|
-
|
|
859
|
-
# Having no ELSE is not the same as an ELSE of nil, and nil is what an
|
|
860
|
-
# omitted argument looks like, so the absence needs a value of its own.
|
|
861
|
-
NOTHING = Object.new.freeze
|
|
862
|
-
private_constant :NOTHING
|
|
863
|
-
|
|
864
|
-
# @private
|
|
865
|
-
attr_reader :operand, :whens, :default
|
|
866
|
-
|
|
867
|
-
def initialize(operand = nil, whens = [], default = NOTHING)
|
|
868
|
-
@operand = operand
|
|
869
|
-
@whens = whens
|
|
870
|
-
@default = default
|
|
871
|
-
end
|
|
872
|
-
|
|
873
|
-
# The next `WHEN`: a value to compare the operand against, or a condition as a value or a block.
|
|
874
|
-
# @return [AST::Case::When]
|
|
875
|
-
def when(value = nil, &block)
|
|
876
|
-
When.new(self, Case.argument(:when, value, block))
|
|
877
|
-
end
|
|
878
|
-
|
|
879
|
-
# `THEN`, which belongs after a `when`; here it says so.
|
|
880
|
-
# @raise [ArgumentError]
|
|
881
|
-
#
|
|
882
|
-
# Kernel#then is on every object, so `then` in the wrong place would be
|
|
883
|
-
# answered by it -- with no block, silently, with an Enumerator.
|
|
884
|
-
def then(*)
|
|
885
|
-
raise ArgumentError, "then follows a when, and there is none to follow here"
|
|
886
|
-
end
|
|
887
|
-
|
|
888
|
-
# `ELSE value`, as a value or a block, closing the CASE. Without one the CASE gives NULL where no `when` matched.
|
|
889
|
-
# @return [AST::Case]
|
|
890
|
-
def else(value = nil, &block)
|
|
891
|
-
Case.new(operand, whens, Case.argument(:else, value, block))
|
|
892
|
-
end
|
|
893
|
-
|
|
894
|
-
# @private
|
|
895
|
-
def to_arel(table, model)
|
|
896
|
-
raise ArgumentError, "case needs a when before it means anything" if whens.empty?
|
|
897
|
-
|
|
898
|
-
node = operand ? Arel::Nodes::Case.new(to_arel_operand(operand, table, model))
|
|
899
|
-
: Arel::Nodes::Case.new
|
|
900
|
-
whens.each do |condition, result|
|
|
901
|
-
node.when(to_arel_argument(condition, table, model)).
|
|
902
|
-
then(to_arel_argument(result, table, model))
|
|
903
|
-
end
|
|
904
|
-
node.else(to_arel_argument(default, table, model)) unless default.equal?(NOTHING)
|
|
905
|
-
node
|
|
906
|
-
end
|
|
907
|
-
|
|
908
|
-
# A value or a block, and exactly one of them: the block is what makes
|
|
909
|
-
# `when { :age >= 60 }` read like the blocks around it, and the value is
|
|
910
|
-
# what makes `when(10)` possible at all.
|
|
911
|
-
# @private
|
|
912
|
-
def self.argument(name, value, block)
|
|
913
|
-
if block
|
|
914
|
-
raise ArgumentError, "#{name} takes a value or a block, not both" unless value.nil?
|
|
915
|
-
return block.call
|
|
916
|
-
end
|
|
917
|
-
raise ArgumentError, "#{name} needs a value or a block" if value.nil?
|
|
918
|
-
value
|
|
919
|
-
end
|
|
920
|
-
|
|
921
|
-
# What a `when` is until its `then` arrives. A Node so that using it
|
|
922
|
-
# as one says what is missing rather than reaching Active Record as
|
|
923
|
-
# something it cannot read.
|
|
924
|
-
class When < Node
|
|
925
|
-
def initialize(kase, condition)
|
|
926
|
-
@kase = kase
|
|
927
|
-
@condition = condition
|
|
928
|
-
end
|
|
929
|
-
|
|
930
|
-
# `THEN value`, as a value or a block, for the `when` before it.
|
|
931
|
-
# @return [AST::Case]
|
|
932
|
-
def then(value = nil, &block)
|
|
933
|
-
Case.new(@kase.operand,
|
|
934
|
-
@kase.whens + [[@condition, Case.argument(:then, value, block)]],
|
|
935
|
-
@kase.default)
|
|
936
|
-
end
|
|
937
|
-
|
|
938
|
-
# @private
|
|
939
|
-
def to_arel(_table, _model)
|
|
940
|
-
raise ArgumentError, "when needs a matching then"
|
|
941
|
-
end
|
|
942
|
-
end
|
|
943
|
-
end
|
|
944
|
-
|
|
945
|
-
# A path into a JSON document, spelled the two ways the adapters want it.
|
|
946
|
-
# Shared, because reading a value and setting one walk the same path.
|
|
947
|
-
# @private
|
|
948
|
-
module JsonSteps
|
|
949
|
-
def check_steps(path, called)
|
|
950
|
-
raise ArgumentError, "#{called} needs a key or an index" if path.empty?
|
|
951
|
-
path.each do |step|
|
|
952
|
-
next if step.is_a?(::Integer) || step.is_a?(::String) || step.is_a?(::Symbol)
|
|
953
|
-
raise ArgumentError, "a step is a key or an array index, not #{step.inspect}"
|
|
954
|
-
end
|
|
955
|
-
path
|
|
956
|
-
end
|
|
957
|
-
|
|
958
|
-
# PostgreSQL takes the steps as a text array, where every element is
|
|
959
|
-
# quoted so that a comma or a brace in a key is part of it. except
|
|
960
|
-
# writes its keys the same way, which are steps of no one path.
|
|
961
|
-
def steps_array(steps = path)
|
|
962
|
-
"{#{steps.map { |step| %("#{escape_step(step)}") }.join(',')}}"
|
|
963
|
-
end
|
|
964
|
-
|
|
965
|
-
# MySQL and SQLite take a path expression instead, where an integer is
|
|
966
|
-
# a subscript and a name that is not plain has to be quoted.
|
|
967
|
-
def dollar_path
|
|
968
|
-
path.inject(+"$") { |so_far, step| so_far << dollar_step(step) }
|
|
969
|
-
end
|
|
970
|
-
|
|
971
|
-
def dollar_step(step)
|
|
972
|
-
return "[#{step}]" if step.is_a?(::Integer)
|
|
973
|
-
name = step.to_s
|
|
974
|
-
"." + (name.match?(/\A[[:alpha:]_][[:alnum:]_]*\z/) ?
|
|
975
|
-
name : %("#{escape_step(step)}"))
|
|
976
|
-
end
|
|
977
|
-
|
|
978
|
-
def escape_step(step)
|
|
979
|
-
step.to_s.gsub("\\", "\\\\").gsub('"', '\\"')
|
|
980
|
-
end
|
|
981
|
-
end
|
|
982
|
-
|
|
983
|
-
# What a dug value may be compared with. dig_text gives text on every
|
|
984
|
-
# adapter, and what a text value compared with a number means is a
|
|
985
|
-
# question the three answer three ways: `dig_text(:n) == 5` is true on
|
|
986
|
-
# SQLite, an error on PostgreSQL and true on MySQL, while
|
|
987
|
-
# `dig_text(:flag) == true` is true, an error, and false. cast is what
|
|
988
|
-
# says which type was meant, and then all three agree.
|
|
989
|
-
#
|
|
990
|
-
# dig, bury and except give JSON, and a JSON comparison belongs to the
|
|
991
|
-
# JSON types -- jsonb and MySQL's -- where numbers compare as numbers
|
|
992
|
-
# and documents structurally, key order and spelling aside. The Ruby
|
|
993
|
-
# value goes in as a JSON literal, and the adapters without such a
|
|
994
|
-
# type refuse it from JsonLiteral when the SQL is written, which is
|
|
995
|
-
# when the adapter is known.
|
|
996
|
-
#
|
|
997
|
-
# A string against dig_text, and anything the block itself built -- a
|
|
998
|
-
# column, a function, another dug value -- go through untouched.
|
|
999
|
-
#
|
|
1000
|
-
# Arithmetic and the bit operators are refused outright on both sides:
|
|
1001
|
-
# `dig_text(:n) + 1` is 6 on SQLite, an error on PostgreSQL and 6.0 on
|
|
1002
|
-
# MariaDB, and an expression on the right does not change what the
|
|
1003
|
-
# dug side is.
|
|
1004
|
-
module JsonComparable
|
|
1005
|
-
%i[== != < <= > >=].each do |operator|
|
|
1006
|
-
define_method(operator) do |other|
|
|
1007
|
-
super(comparison_value(other))
|
|
1008
|
-
end
|
|
1009
|
-
end
|
|
1010
|
-
|
|
1011
|
-
def in?(values) = super(comparison_set(values))
|
|
1012
|
-
def not_in?(values) = super(comparison_set(values))
|
|
1013
|
-
def between?(min, max) = super(comparison_value(min), comparison_value(max))
|
|
1014
|
-
def not_between?(min, max) = super(comparison_value(min), comparison_value(max))
|
|
1015
|
-
|
|
1016
|
-
%i[+ - * / & | ^ << >> bitwise_and bitwise_or bitwise_xor].each do |operator|
|
|
1017
|
-
define_method(operator) do |_other|
|
|
1018
|
-
raise ArgumentError, arithmetic_refusal(operator)
|
|
1019
|
-
end
|
|
1020
|
-
end
|
|
1021
|
-
|
|
1022
|
-
def ~
|
|
1023
|
-
raise ArgumentError, arithmetic_refusal(:~)
|
|
1024
|
-
end
|
|
1025
|
-
|
|
1026
|
-
def bitwise_not
|
|
1027
|
-
raise ArgumentError, arithmetic_refusal(:bitwise_not)
|
|
1028
|
-
end
|
|
1029
|
-
|
|
1030
|
-
private
|
|
1031
|
-
# nil is left to the comparison itself, which says to use null?, and
|
|
1032
|
-
# so is anything the block built rather than wrote as a literal.
|
|
1033
|
-
def comparison_value(other)
|
|
1034
|
-
return other if other.nil? || other.is_a?(Node) || other.is_a?(::Symbol) ||
|
|
1035
|
-
other.is_a?(Arel::Nodes::Node) ||
|
|
1036
|
-
other.is_a?(Arel::Attributes::Attribute) ||
|
|
1037
|
-
other.is_a?(ActiveRecord::Relation)
|
|
1038
|
-
return json_literal(other) if json_value?
|
|
1039
|
-
return other if other.is_a?(::String)
|
|
1040
|
-
|
|
1041
|
-
raise ArgumentError,
|
|
1042
|
-
"dig_text gives text, and comparing it with #{other.inspect} means " \
|
|
1043
|
-
"something different on every adapter; cast it to the type meant"
|
|
1044
|
-
end
|
|
1045
|
-
|
|
1046
|
-
def json_literal(other)
|
|
1047
|
-
case other
|
|
1048
|
-
when ::String, ::Integer, ::Float, ::BigDecimal, true, false, ::Hash, ::Array
|
|
1049
|
-
JsonLiteral.new(other)
|
|
1050
|
-
when ::Rational
|
|
1051
|
-
raise ArgumentError,
|
|
1052
|
-
"a Rational has no exact SQL spelling; to_d says the decimal meant"
|
|
1053
|
-
else
|
|
1054
|
-
raise ArgumentError,
|
|
1055
|
-
"#{json_source} gives JSON, and #{other.inspect} has no JSON " \
|
|
1056
|
-
"spelling; dig_text gives the value"
|
|
1057
|
-
end
|
|
1058
|
-
end
|
|
1059
|
-
|
|
1060
|
-
def comparison_set(values)
|
|
1061
|
-
case values
|
|
1062
|
-
when ActiveRecord::Relation then values
|
|
1063
|
-
when ::Range
|
|
1064
|
-
In::QuotedRange.new(comparison_value(values.begin),
|
|
1065
|
-
comparison_value(values.end), values.exclude_end?)
|
|
1066
|
-
else values.map { |value| comparison_value(value) }
|
|
1067
|
-
end
|
|
1068
|
-
end
|
|
1069
|
-
|
|
1070
|
-
def arithmetic_refusal(operator)
|
|
1071
|
-
json_value? ?
|
|
1072
|
-
"#{json_source} gives JSON, and #{operator} on it means something " \
|
|
1073
|
-
"different on every adapter; cast dig_text to the type meant" :
|
|
1074
|
-
"dig_text gives text, and #{operator} on it means something " \
|
|
1075
|
-
"different on every adapter; cast it to the type meant"
|
|
1076
|
-
end
|
|
1077
|
-
end
|
|
1078
|
-
|
|
1079
|
-
# A Ruby value on the JSON side of a comparison, which jsonb and
|
|
1080
|
-
# MySQL's JSON type answer alike: numbers compare as numbers and
|
|
1081
|
-
# documents structurally. SQLite and MariaDB have only the text of
|
|
1082
|
-
# each -- spelling and key order deciding what equality means -- and
|
|
1083
|
-
# refuse here. PostgreSQL needs no cast, an untyped literal beside a
|
|
1084
|
-
# jsonb operand coercing to jsonb; MySQL is told CAST(... AS JSON),
|
|
1085
|
-
# since a bare string beside JSON would be a JSON string, and every
|
|
1086
|
-
# string outranks every number in its ordering.
|
|
1087
|
-
class JsonLiteral < Node
|
|
1088
|
-
# @private
|
|
1089
|
-
attr_reader :value
|
|
1090
|
-
|
|
1091
|
-
def initialize(value)
|
|
1092
|
-
@value = value
|
|
1093
|
-
end
|
|
1094
|
-
|
|
1095
|
-
# @private
|
|
1096
|
-
def to_arel(_table, model)
|
|
1097
|
-
Dialect.for(model).json_literal(Arel::Nodes.build_quoted(JSON.generate(value)), model)
|
|
1098
|
-
end
|
|
1099
|
-
end
|
|
1100
|
-
|
|
1101
|
-
# The JSON operations read a document, and what dig gives is one:
|
|
1102
|
-
# `dig(:author).key?(:email)` and `dig(:tags).contains?(...)` are the
|
|
1103
|
-
# same question asked of a part of it, and the adapters answer them
|
|
1104
|
-
# alike. What dig_text gives is text, and reading that as a document
|
|
1105
|
-
# again is where they part company: SQLite parses it back and MySQL
|
|
1106
|
-
# takes it as written, where PostgreSQL has no such function for text.
|
|
1107
|
-
module JsonDocument
|
|
1108
|
-
%i[dig dig_text key? keys contains? bury except].each do |name|
|
|
1109
|
-
define_method(name) do |*args|
|
|
1110
|
-
unless json_value?
|
|
1111
|
-
raise ArgumentError,
|
|
1112
|
-
"dig_text gives text, and #{name} reads JSON; dig keeps it"
|
|
1113
|
-
end
|
|
1114
|
-
super(*args)
|
|
1115
|
-
end
|
|
1116
|
-
end
|
|
1117
|
-
end
|
|
1118
|
-
|
|
1119
|
-
# JSON the query computes rather than reads out of a document: always
|
|
1120
|
-
# a JSON value, with no dig_text counterpart for JsonComparable's
|
|
1121
|
-
# advice to name. Included after JsonComparable, whose own
|
|
1122
|
-
# arithmetic_refusal it overrides.
|
|
1123
|
-
module ComputedJson
|
|
1124
|
-
# @private
|
|
1125
|
-
def json_value?
|
|
1126
|
-
true
|
|
1127
|
-
end
|
|
1128
|
-
|
|
1129
|
-
private
|
|
1130
|
-
def arithmetic_refusal(operator)
|
|
1131
|
-
"#{json_source} gives JSON, and #{operator} on it means " \
|
|
1132
|
-
"something different on every adapter"
|
|
1133
|
-
end
|
|
1134
|
-
end
|
|
1135
|
-
|
|
1136
|
-
# Reading inside a JSON document, which is what {Predications#dig} and
|
|
1137
|
-
# {Predications#dig_text} build. Every adapter can do it and no two
|
|
1138
|
-
# spell it alike: PostgreSQL walks an array of steps, SQLite has the
|
|
1139
|
-
# operators with a $ path, and MySQL has the functions -- which is what
|
|
1140
|
-
# this uses for that family, since MariaDB answers to the same adapter
|
|
1141
|
-
# and has no -> at all.
|
|
1142
|
-
#
|
|
1143
|
-
# The path is turned into a string either way, so a key with a space or
|
|
1144
|
-
# a quote in it travels as itself rather than having to be refused.
|
|
1145
|
-
# What a dug value compares with is {JsonComparable}'s to say.
|
|
1146
|
-
class JsonPath < Node
|
|
1147
|
-
include Predications
|
|
1148
|
-
include Arithmetics
|
|
1149
|
-
include JsonSteps
|
|
1150
|
-
include JsonComparable
|
|
1151
|
-
include JsonDocument
|
|
1152
|
-
|
|
1153
|
-
# @private
|
|
1154
|
-
attr_reader :operand, :path
|
|
1155
|
-
|
|
1156
|
-
def initialize(operand, path, json_value: true)
|
|
1157
|
-
@operand = operand
|
|
1158
|
-
@path = check_steps(path, "dig")
|
|
1159
|
-
@json_value = json_value
|
|
1160
|
-
end
|
|
1161
|
-
|
|
1162
|
-
# @private
|
|
1163
|
-
def json_value?
|
|
1164
|
-
@json_value
|
|
1165
|
-
end
|
|
1166
|
-
|
|
1167
|
-
# @private
|
|
1168
|
-
def to_arel(table, model)
|
|
1169
|
-
Dialect.for(model).json_path(
|
|
1170
|
-
to_arel_operand(operand, table, model),
|
|
1171
|
-
dollar_path, steps_array, json_value?, model)
|
|
1172
|
-
end
|
|
1173
|
-
|
|
1174
|
-
private
|
|
1175
|
-
def json_source
|
|
1176
|
-
"dig"
|
|
1177
|
-
end
|
|
1178
|
-
end
|
|
1179
|
-
|
|
1180
|
-
# Setting a value inside a JSON document, which is what bury does to what
|
|
1181
|
-
# dig reads. The document comes back changed rather than being written
|
|
1182
|
-
# anywhere; update_all is what writes it.
|
|
1183
|
-
class JsonSet < Node
|
|
1184
|
-
include Predications
|
|
1185
|
-
include JsonSteps
|
|
1186
|
-
include JsonComparable
|
|
1187
|
-
|
|
1188
|
-
# @private
|
|
1189
|
-
attr_reader :operand, :path, :value
|
|
1190
|
-
|
|
1191
|
-
def initialize(operand, path, value)
|
|
1192
|
-
@operand = operand
|
|
1193
|
-
@path = check_steps(path, "bury")
|
|
1194
|
-
@value = value
|
|
1195
|
-
end
|
|
1196
|
-
|
|
1197
|
-
# Always JSON, which is what the comparison guard asks.
|
|
1198
|
-
# @private
|
|
1199
|
-
def json_value?
|
|
1200
|
-
true
|
|
1201
|
-
end
|
|
1202
|
-
|
|
1203
|
-
# @private
|
|
1204
|
-
def to_arel(table, model)
|
|
1205
|
-
Dialect.for(model).json_set(
|
|
1206
|
-
to_arel_operand(operand, table, model),
|
|
1207
|
-
steps_array, dollar_path, value,
|
|
1208
|
-
(to_arel_operand(value, table, model) if expression?), model)
|
|
1209
|
-
end
|
|
1210
|
-
|
|
1211
|
-
private
|
|
1212
|
-
def expression?
|
|
1213
|
-
value.is_a?(Node) || value.is_a?(::Symbol)
|
|
1214
|
-
end
|
|
1215
|
-
|
|
1216
|
-
def json_source
|
|
1217
|
-
"bury"
|
|
1218
|
-
end
|
|
1219
|
-
end
|
|
1220
|
-
|
|
1221
|
-
# Keys taken out of a JSON document. PostgreSQL subtracts them, the
|
|
1222
|
-
# other two remove a path apiece.
|
|
1223
|
-
class JsonExcept < Node
|
|
1224
|
-
include Predications
|
|
1225
|
-
include JsonSteps
|
|
1226
|
-
include JsonComparable
|
|
1227
|
-
|
|
1228
|
-
# @private
|
|
1229
|
-
attr_reader :operand, :keys
|
|
1230
|
-
|
|
1231
|
-
def initialize(operand, keys)
|
|
1232
|
-
@operand = operand
|
|
1233
|
-
@keys = check_keys(keys)
|
|
1234
|
-
end
|
|
1235
|
-
|
|
1236
|
-
# @private
|
|
1237
|
-
def json_value?
|
|
1238
|
-
true
|
|
1239
|
-
end
|
|
1240
|
-
|
|
1241
|
-
# @private
|
|
1242
|
-
def to_arel(table, model)
|
|
1243
|
-
Dialect.for(model).json_remove(
|
|
1244
|
-
to_arel_operand(operand, table, model),
|
|
1245
|
-
keys.map { |key| "$#{dollar_step(key)}" },
|
|
1246
|
-
steps_array(keys), model)
|
|
1247
|
-
end
|
|
1248
|
-
|
|
1249
|
-
private
|
|
1250
|
-
# Keys, as Hash#except takes them: an index into an array is not what
|
|
1251
|
-
# the name says anywhere, and is bury's business through a path.
|
|
1252
|
-
def check_keys(keys)
|
|
1253
|
-
raise ArgumentError, "except needs a key" if keys.empty?
|
|
1254
|
-
keys.each do |key|
|
|
1255
|
-
next if key.is_a?(::String) || key.is_a?(::Symbol)
|
|
1256
|
-
raise ArgumentError,
|
|
1257
|
-
"except takes keys of the document, not #{key.inspect}"
|
|
1258
|
-
end
|
|
1259
|
-
keys
|
|
1260
|
-
end
|
|
1261
|
-
|
|
1262
|
-
def json_source
|
|
1263
|
-
"except"
|
|
1264
|
-
end
|
|
1265
|
-
end
|
|
1266
|
-
|
|
1267
|
-
# JSON containment: whether the document holds what is given.
|
|
1268
|
-
class JsonContains < Predicate
|
|
1269
|
-
# @private
|
|
1270
|
-
attr_reader :operand, :value
|
|
1271
|
-
|
|
1272
|
-
def initialize(operand, value)
|
|
1273
|
-
@operand = operand
|
|
1274
|
-
@value = value
|
|
1275
|
-
end
|
|
1276
|
-
|
|
1277
|
-
# @private
|
|
1278
|
-
def to_arel(table, model)
|
|
1279
|
-
Dialect.for(model).json_contains(
|
|
1280
|
-
to_arel_operand(operand, table, model),
|
|
1281
|
-
Arel::Nodes.build_quoted(JSON.generate(value)), model)
|
|
1282
|
-
end
|
|
1283
|
-
end
|
|
1284
|
-
|
|
1285
|
-
# Whether a key is in the document. PostgreSQL's spelling is the ?
|
|
1286
|
-
# operator rather than jsonb_exists, the function it is shorthand for,
|
|
1287
|
-
# because a GIN index matches the operator and never the function. A
|
|
1288
|
-
# ? is a bind placeholder only to sanitize_sql, which none of the SQL
|
|
1289
|
-
# written here passes through.
|
|
1290
|
-
class JsonHasKey < Predicate
|
|
1291
|
-
# @private
|
|
1292
|
-
attr_reader :operand, :key
|
|
1293
|
-
|
|
1294
|
-
def initialize(operand, key)
|
|
1295
|
-
@operand = operand
|
|
1296
|
-
@key = key
|
|
1297
|
-
end
|
|
1298
|
-
|
|
1299
|
-
# @private
|
|
1300
|
-
def to_arel(table, model)
|
|
1301
|
-
Dialect.for(model).json_has_key(
|
|
1302
|
-
to_arel_operand(operand, table, model),
|
|
1303
|
-
Arel::Nodes.build_quoted(key.to_s),
|
|
1304
|
-
Arel::Nodes.build_quoted("$.#{key}"), model)
|
|
1305
|
-
end
|
|
1306
|
-
end
|
|
1307
|
-
|
|
1308
|
-
# The keys of a JSON document, as Hash#keys gives them: a JSON array.
|
|
1309
|
-
# Only the MySQL family has a function for it; the other two reach the
|
|
1310
|
-
# same array through a subquery over their key-listing functions. The
|
|
1311
|
-
# type guard is what makes all four answer alike: the keys of what is
|
|
1312
|
-
# not an object are NULL rather than SQLite's array indices or
|
|
1313
|
-
# PostgreSQL's error, and the keys of {} are [] rather than
|
|
1314
|
-
# PostgreSQL's NULL, jsonb_agg over no rows.
|
|
1315
|
-
class JsonKeys < Node
|
|
1316
|
-
include Predications
|
|
1317
|
-
include JsonComparable
|
|
1318
|
-
include ComputedJson
|
|
1319
|
-
|
|
1320
|
-
# @private
|
|
1321
|
-
attr_reader :operand
|
|
1322
|
-
|
|
1323
|
-
def initialize(operand)
|
|
1324
|
-
@operand = operand
|
|
1325
|
-
end
|
|
1326
|
-
|
|
1327
|
-
# @private
|
|
1328
|
-
def to_arel(table, model)
|
|
1329
|
-
Dialect.for(model).json_keys(to_arel_operand(operand, table, model), model)
|
|
1330
|
-
end
|
|
1331
|
-
|
|
1332
|
-
private
|
|
1333
|
-
def json_source
|
|
1334
|
-
"keys"
|
|
1335
|
-
end
|
|
1336
|
-
end
|
|
1337
|
-
|
|
1338
|
-
# A JSON document built in the row: json_array from the values given,
|
|
1339
|
-
# json_object from a Ruby hash. SQLite and the MySQL family both say
|
|
1340
|
-
# the standard names; PostgreSQL is asked to build jsonb, whose
|
|
1341
|
-
# documents the other JSON operations here read.
|
|
1342
|
-
class JsonBuild < Node
|
|
1343
|
-
include Predications
|
|
1344
|
-
include JsonComparable
|
|
1345
|
-
include ComputedJson
|
|
1346
|
-
|
|
1347
|
-
# @private
|
|
1348
|
-
attr_reader :kind, :values
|
|
1349
|
-
|
|
1350
|
-
def initialize(kind, values)
|
|
1351
|
-
@kind = kind
|
|
1352
|
-
@values = kind == :object ? check_pairs(values) : values
|
|
1353
|
-
end
|
|
1354
|
-
|
|
1355
|
-
# @private
|
|
1356
|
-
def to_arel(table, model)
|
|
1357
|
-
dialect = Dialect.for(model)
|
|
1358
|
-
if kind == :array
|
|
1359
|
-
dialect.json_build(:array, nil,
|
|
1360
|
-
values.map { |value| build_argument(value, dialect, table, model) }, model)
|
|
1361
|
-
else
|
|
1362
|
-
dialect.json_build(:object, values.keys.map(&:to_s),
|
|
1363
|
-
values.values.map { |value| build_argument(value, dialect, table, model) }, model)
|
|
1364
|
-
end
|
|
1365
|
-
end
|
|
1366
|
-
|
|
1367
|
-
private
|
|
1368
|
-
# An expression is itself and a bare scalar is quoted; a document or
|
|
1369
|
-
# a boolean the dialect embeds as JSON, as bury takes it.
|
|
1370
|
-
def build_argument(value, dialect, table, model)
|
|
1371
|
-
case value
|
|
1372
|
-
when Node, ::Symbol then to_arel_operand(value, table, model)
|
|
1373
|
-
when ::Hash, ::Array, true, false then dialect.json_build_argument(value, model)
|
|
1374
|
-
when ::Rational then quote_number(value)
|
|
1375
|
-
else Arel::Nodes.build_quoted(value)
|
|
1376
|
-
end
|
|
1377
|
-
end
|
|
1378
|
-
|
|
1379
|
-
# The keys come from Ruby as Hash keys rather than alternating with
|
|
1380
|
-
# the values as SQL has them, which is what keeps a bare symbol
|
|
1381
|
-
# free to mean a column on the value side. Anything but a name is
|
|
1382
|
-
# refused here, before the adapters answer a NULL key three ways.
|
|
1383
|
-
def check_pairs(pairs)
|
|
1384
|
-
unless pairs.is_a?(::Hash)
|
|
1385
|
-
raise ArgumentError,
|
|
1386
|
-
"json_object takes a hash of keys to values, not #{pairs.inspect}"
|
|
1387
|
-
end
|
|
1388
|
-
pairs.each_key do |key|
|
|
1389
|
-
next if key.is_a?(::String) || key.is_a?(::Symbol)
|
|
1390
|
-
raise ArgumentError,
|
|
1391
|
-
"a key of json_object is a string or a symbol, not #{key.inspect}"
|
|
1392
|
-
end
|
|
1393
|
-
pairs
|
|
1394
|
-
end
|
|
1395
|
-
|
|
1396
|
-
def json_source
|
|
1397
|
-
"json_#{kind}"
|
|
1398
|
-
end
|
|
1399
|
-
end
|
|
1400
|
-
|
|
1401
|
-
# GROUP BY GROUPING SETS / ROLLUP / CUBE: several groupings asked for at
|
|
1402
|
-
# once, the totals of each coming back beside the rows. PostgreSQL has
|
|
1403
|
-
# all three and the MySQL family rollup alone; the block raises for the
|
|
1404
|
-
# rest before it gets this far.
|
|
1405
|
-
#
|
|
1406
|
-
# Each set is a list of its own, so grouping_sets takes lists and rollup
|
|
1407
|
-
# and cube take the columns themselves.
|
|
1408
|
-
class GroupingSets < Node
|
|
1409
|
-
# @private
|
|
1410
|
-
KINDS = {
|
|
1411
|
-
grouping_sets: Arel::Nodes::GroupingSet,
|
|
1412
|
-
rollup: Arel::Nodes::RollUp,
|
|
1413
|
-
cube: Arel::Nodes::Cube,
|
|
1414
|
-
}.freeze
|
|
1415
|
-
|
|
1416
|
-
# @private
|
|
1417
|
-
attr_reader :kind, :sets
|
|
1418
|
-
|
|
1419
|
-
def initialize(kind, sets)
|
|
1420
|
-
raise ArgumentError, "#{kind} needs something to group by" if sets.empty?
|
|
1421
|
-
@kind = kind
|
|
1422
|
-
@sets = sets
|
|
1423
|
-
end
|
|
1424
|
-
|
|
1425
|
-
# @private
|
|
1426
|
-
def to_arel(table, model)
|
|
1427
|
-
return with_rollup(table, model) if Dialect.for(model).grouping_by_with_rollup?
|
|
1428
|
-
|
|
1429
|
-
KINDS.fetch(kind).new(
|
|
1430
|
-
if kind == :grouping_sets
|
|
1431
|
-
sets.map do |set|
|
|
1432
|
-
Arel::Nodes::GroupingElement.new(
|
|
1433
|
-
Array(set).map { |column| to_arel_operand(column, table, model) })
|
|
1434
|
-
end
|
|
1435
|
-
else
|
|
1436
|
-
sets.map { |column| to_arel_operand(column, table, model) }
|
|
1437
|
-
end)
|
|
1438
|
-
end
|
|
1439
|
-
|
|
1440
|
-
private
|
|
1441
|
-
# The MySQL family spells rollup WITH ROLLUP, trailing the whole
|
|
1442
|
-
# group list rather than wrapping a list of its own -- which is also
|
|
1443
|
-
# why a rollup cannot stand beside other group entries there. The
|
|
1444
|
-
# columns are compiled by the connection's own visitor, so their
|
|
1445
|
-
# quoting is the adapter's.
|
|
1446
|
-
def with_rollup(table, model)
|
|
1447
|
-
columns = sets.map { |column| to_arel_operand(column, table, model) }
|
|
1448
|
-
sql = model.with_connection do |connection|
|
|
1449
|
-
columns.map { |column| connection.visitor.compile(column) }.join(", ")
|
|
1450
|
-
end
|
|
1451
|
-
Arel.sql("#{sql} WITH ROLLUP")
|
|
1452
|
-
end
|
|
1453
|
-
end
|
|
1454
|
-
|
|
1455
|
-
# A column of a named table, which is what `:posts[:author_id]` builds
|
|
1456
|
-
# (see {BlockSyntax#[]}): the spelling for another table's column in a
|
|
1457
|
-
# join's ON or a query over one.
|
|
1458
|
-
class Column < Node
|
|
1459
|
-
include Predications
|
|
1460
|
-
include Arithmetics
|
|
1461
|
-
|
|
1462
|
-
# @private
|
|
1463
|
-
attr_reader :table_name, :column_name
|
|
1464
|
-
|
|
1465
|
-
def initialize(table_name, column_name)
|
|
1466
|
-
@table_name = table_name
|
|
1467
|
-
@column_name = column_name
|
|
1468
|
-
end
|
|
1469
|
-
|
|
1470
|
-
# @private
|
|
1471
|
-
def to_arel(_table, _model)
|
|
1472
|
-
Arel::Table.new(table_name)[column_name]
|
|
1473
|
-
end
|
|
1474
|
-
end
|
|
1475
|
-
|
|
1476
|
-
# Arithmetic on columns and expressions. Ruby's precedence puts these
|
|
1477
|
-
# above the comparison operators, so :price * :quantity > 100 groups the
|
|
1478
|
-
# way it reads.
|
|
1479
|
-
#
|
|
1480
|
-
# A Duration on the right moves a date: `:due_on + 3.days`. No two
|
|
1481
|
-
# families spell the move alike, so the dialect writes it, a part of the
|
|
1482
|
-
# duration at a time.
|
|
1483
|
-
class Arithmetic < Node
|
|
1484
|
-
include Predications
|
|
1485
|
-
include Arithmetics
|
|
1486
|
-
|
|
1487
|
-
# Active Support's parts, as the units the SQL takes. A week is seven
|
|
1488
|
-
# days: SQLite and Oracle have no week.
|
|
1489
|
-
# @private
|
|
1490
|
-
UNITS = {
|
|
1491
|
-
years: :year, months: :month, weeks: :day, days: :day,
|
|
1492
|
-
hours: :hour, minutes: :minute, seconds: :second,
|
|
1493
|
-
}.freeze
|
|
1494
|
-
|
|
1495
|
-
# @private
|
|
1496
|
-
DATE_UNITS = %i[year month day].freeze
|
|
1497
|
-
|
|
1498
|
-
# @private
|
|
1499
|
-
attr_reader :left, :operator, :right
|
|
1500
|
-
|
|
1501
|
-
def initialize(left, operator, right)
|
|
1502
|
-
@left = left
|
|
1503
|
-
@operator = operator
|
|
1504
|
-
@right = right
|
|
1505
|
-
end
|
|
1506
|
-
|
|
1507
|
-
# @private
|
|
1508
|
-
def to_arel(table, model)
|
|
1509
|
-
arel_left = to_arel_operand(left, table, model)
|
|
1510
|
-
return move_date(arel_left, model) if right.is_a?(::ActiveSupport::Duration)
|
|
1511
|
-
# The operator dispatches Arel's Math, which a bare number carries
|
|
1512
|
-
# none of; quoted, it is a node with the same methods.
|
|
1513
|
-
arel_left = Arel::Nodes.build_quoted(arel_left) if arel_left.is_a?(::Numeric)
|
|
1514
|
-
arel_left.public_send(operator, to_arel_operand(right, table, model))
|
|
1515
|
-
end
|
|
1516
|
-
|
|
1517
|
-
# SQLite has no date type, and its datetime() gives whatever it is
|
|
1518
|
-
# handed a time of day, so a date column moved by a day would come
|
|
1519
|
-
# back a midnight and sort past the same day written bare. Its
|
|
1520
|
-
# dialect has date() for what is a date to begin with, and this is
|
|
1521
|
-
# what says so: a column the model declares a date, CURRENT_DATE, or
|
|
1522
|
-
# one of those already moved by a date's units. The other families
|
|
1523
|
-
# keep the type themselves and never ask.
|
|
1524
|
-
# @private
|
|
1525
|
-
def self.date_operand?(operand, model)
|
|
1526
|
-
case operand
|
|
1527
|
-
when ::Symbol then model.type_for_attribute(operand).type == :date
|
|
1528
|
-
when DatetimeValueFunction then operand.name == "CURRENT_DATE"
|
|
1529
|
-
when Arithmetic
|
|
1530
|
-
operand.right.is_a?(::ActiveSupport::Duration) &&
|
|
1531
|
-
date_operand?(operand.left, model) &&
|
|
1532
|
-
operand.right.parts.keys.all? { |part| DATE_UNITS.include?(UNITS[part]) }
|
|
1533
|
-
else false
|
|
1534
|
-
end
|
|
1535
|
-
end
|
|
1536
|
-
|
|
1537
|
-
private
|
|
1538
|
-
# Each amount is written into the SQL as a number, and a fraction
|
|
1539
|
-
# of a unit is not one every family takes, so it has to be a whole
|
|
1540
|
-
# one.
|
|
1541
|
-
def move_date(date, model)
|
|
1542
|
-
unless operator == :+ || operator == :-
|
|
1543
|
-
raise ArgumentError,
|
|
1544
|
-
"a duration is added to a date or subtracted from it, not #{operator}"
|
|
1545
|
-
end
|
|
1546
|
-
dialect = Dialect.for(model)
|
|
1547
|
-
date_only = Arithmetic.date_operand?(left, model)
|
|
1548
|
-
right.parts.reduce(date) do |arel, (part, amount)|
|
|
1549
|
-
unless amount.is_a?(::Integer)
|
|
1550
|
-
raise ArgumentError, "#{amount.inspect} #{part} is not a whole number of them"
|
|
1551
|
-
end
|
|
1552
|
-
unit = UNITS.fetch(part)
|
|
1553
|
-
amount *= 7 if part == :weeks
|
|
1554
|
-
dialect.add_interval(arel, amount, unit, operator == :-,
|
|
1555
|
-
date_only && DATE_UNITS.include?(unit))
|
|
1556
|
-
end
|
|
1557
|
-
end
|
|
1558
|
-
end
|
|
1559
|
-
|
|
1560
|
-
# What the bitwise operations refuse. Both refusals are there because
|
|
1561
|
-
# the same Ruby would otherwise mean different things per adapter: MySQL
|
|
1562
|
-
# and SQLite take a boolean for the one bit it is stored as, so
|
|
1563
|
-
# `published.bitwise_and(active)` would quietly be the AND it looks
|
|
1564
|
-
# like, while PostgreSQL has no such operator and would say so.
|
|
1565
|
-
# @private
|
|
1566
|
-
module BitwiseOperands
|
|
1567
|
-
private
|
|
1568
|
-
# Oracle is the family with no bitwise operators at all -- BITAND
|
|
1569
|
-
# is a function, with no OR, XOR, shift or NOT beside it -- so the
|
|
1570
|
-
# node refuses there before its server has to.
|
|
1571
|
-
def check_operators_exist(model)
|
|
1572
|
-
return if Dialect.for(model).bitwise_operators_supported?
|
|
1573
|
-
raise NotImplementedError,
|
|
1574
|
-
"the bitwise operations have no equivalent on " \
|
|
1575
|
-
"#{model.connection_db_config.adapter}"
|
|
1576
|
-
end
|
|
1577
|
-
|
|
1578
|
-
# A predicate alone is refused. The escape hatches are not: what
|
|
1579
|
-
# sql() or op() holds is as often an expression as a condition, and
|
|
1580
|
-
# here it is being read as the former.
|
|
1581
|
-
def check_operand(operand)
|
|
1582
|
-
return operand unless operand.is_a?(Predicate)
|
|
1583
|
-
raise ArgumentError,
|
|
1584
|
-
"a condition cannot be an operand of a bitwise operation; " \
|
|
1585
|
-
"& and | between conditions are AND and OR"
|
|
1586
|
-
end
|
|
1587
|
-
|
|
1588
|
-
# Only the unqualified column can be checked, since that is the one
|
|
1589
|
-
# the model is known to have.
|
|
1590
|
-
def check_not_boolean(operand, model)
|
|
1591
|
-
return unless operand.is_a?(::Symbol)
|
|
1592
|
-
return unless model.type_for_attribute(operand).type == :boolean
|
|
1593
|
-
raise ArgumentError,
|
|
1594
|
-
"#{operand.inspect} is a boolean column, which the bitwise " \
|
|
1595
|
-
"operations do not take; #{operand.inspect}.true? is the condition"
|
|
1596
|
-
end
|
|
1597
|
-
end
|
|
1598
|
-
|
|
1599
|
-
# SQL's bitwise operations. Each parenthesises itself, which is what
|
|
1600
|
-
# keeps the grouping the Ruby asked for: PostgreSQL gives & and | the
|
|
1601
|
-
# same precedence and reads a | b & c from the left, where
|
|
1602
|
-
# a.bitwise_or(b.bitwise_and(c)) means the other thing.
|
|
1603
|
-
class Bitwise < Node
|
|
1604
|
-
include Predications
|
|
1605
|
-
include Arithmetics
|
|
1606
|
-
include BitwiseOperands
|
|
1607
|
-
|
|
1608
|
-
# @private
|
|
1609
|
-
NODES = {
|
|
1610
|
-
:& => Arel::Nodes::BitwiseAnd,
|
|
1611
|
-
:| => Arel::Nodes::BitwiseOr,
|
|
1612
|
-
:<< => Arel::Nodes::BitwiseShiftLeft,
|
|
1613
|
-
:>> => Arel::Nodes::BitwiseShiftRight,
|
|
1614
|
-
}.freeze
|
|
1615
|
-
|
|
1616
|
-
# @private
|
|
1617
|
-
attr_reader :left, :operator, :right
|
|
1618
|
-
|
|
1619
|
-
def initialize(left, operator, right)
|
|
1620
|
-
@left = left
|
|
1621
|
-
@operator = operator
|
|
1622
|
-
@right = check_operand(right)
|
|
1623
|
-
end
|
|
1624
|
-
|
|
1625
|
-
# @private
|
|
1626
|
-
def to_arel(table, model)
|
|
1627
|
-
check_operators_exist(model)
|
|
1628
|
-
check_not_boolean(left, model)
|
|
1629
|
-
check_not_boolean(right, model)
|
|
1630
|
-
arel_left = to_arel_operand(left, table, model)
|
|
1631
|
-
arel_right = to_arel_argument(right, table, model)
|
|
1632
|
-
Arel::Nodes::Grouping.new(
|
|
1633
|
-
if operator == :^
|
|
1634
|
-
xor(arel_left, arel_right, model)
|
|
1635
|
-
else
|
|
1636
|
-
NODES.fetch(operator).new(arel_left, arel_right)
|
|
1637
|
-
end)
|
|
1638
|
-
end
|
|
1639
|
-
|
|
1640
|
-
private
|
|
1641
|
-
# Arel has a node for XOR, but it writes ^ on every adapter, and ^ is
|
|
1642
|
-
# exponentiation to PostgreSQL -- a wrong answer rather than an error.
|
|
1643
|
-
# PostgreSQL's own spelling, #, is where a comment starts on MySQL, so
|
|
1644
|
-
# it cannot be the portable one either. SQLite has no XOR at all;
|
|
1645
|
-
# (a | b) - (a & b) is it, at the cost of naming each operand twice.
|
|
1646
|
-
def xor(left, right, model)
|
|
1647
|
-
Dialect.for(model).bitwise_xor(left, right)
|
|
1648
|
-
end
|
|
1649
|
-
end
|
|
1650
|
-
|
|
1651
|
-
# ~, which every adapter has. MySQL answers with the unsigned 64-bit
|
|
1652
|
-
# number where the others answer with a negative one; the bits are the
|
|
1653
|
-
# same, and only reading the value back tells them apart.
|
|
1654
|
-
class BitwiseNot < Node
|
|
1655
|
-
include Predications
|
|
1656
|
-
include Arithmetics
|
|
1657
|
-
include BitwiseOperands
|
|
1658
|
-
|
|
1659
|
-
# @private
|
|
1660
|
-
attr_reader :operand
|
|
1661
|
-
|
|
1662
|
-
def initialize(operand)
|
|
1663
|
-
@operand = check_operand(operand)
|
|
1664
|
-
end
|
|
1665
|
-
|
|
1666
|
-
# @private
|
|
1667
|
-
def to_arel(table, model)
|
|
1668
|
-
check_operators_exist(model)
|
|
1669
|
-
check_not_boolean(operand, model)
|
|
1670
|
-
Arel::Nodes::Grouping.new(
|
|
1671
|
-
Arel::Nodes::BitwiseNot.new(to_arel_operand(operand, table, model)))
|
|
1672
|
-
end
|
|
1673
|
-
end
|
|
1674
|
-
|
|
1675
|
-
# OVER, on the two things that can carry a window: an aggregate, and a
|
|
1676
|
-
# function.
|
|
1677
|
-
module Windowing
|
|
1678
|
-
# `OVER ()`, an empty window to be filled by {Over#partition},
|
|
1679
|
-
# {Over#order}, {Over#rows} and {Over#range}.
|
|
1680
|
-
# @return [AST::Over]
|
|
1681
|
-
# @example
|
|
1682
|
-
# Author.select { avg(:age).over.partition(:country).as(:country_average) }
|
|
1683
|
-
# Post.select { sum(:likes).over.order(:created_at).rows(..0).as(:running) }
|
|
1684
|
-
def over
|
|
1685
|
-
Over.new(self)
|
|
1686
|
-
end
|
|
1687
|
-
end
|
|
1688
|
-
|
|
1689
|
-
# A function with a window. The window is built by chaining, the way
|
|
1690
|
-
# Arel's own is, and each method returns a new node rather than adding to
|
|
1691
|
-
# this one, so a window can be finished more than one way.
|
|
1692
|
-
class Over < Node
|
|
1693
|
-
include Predications
|
|
1694
|
-
include Arithmetics
|
|
1695
|
-
|
|
1696
|
-
# @private
|
|
1697
|
-
attr_reader :function, :partitions, :orders, :frame
|
|
1698
|
-
|
|
1699
|
-
def initialize(function, partitions = [], orders = [], frame = nil)
|
|
1700
|
-
@function = function
|
|
1701
|
-
@partitions = partitions
|
|
1702
|
-
@orders = orders
|
|
1703
|
-
@frame = frame
|
|
1704
|
-
end
|
|
1705
|
-
|
|
1706
|
-
# `PARTITION BY`, the columns or expressions given.
|
|
1707
|
-
# @return [AST::Over]
|
|
1708
|
-
def partition(*exprs)
|
|
1709
|
-
raise ArgumentError, "partition needs an expression" if exprs.empty?
|
|
1710
|
-
Over.new(function, partitions + exprs, orders, frame)
|
|
1711
|
-
end
|
|
1712
|
-
|
|
1713
|
-
# `ORDER BY` within the window: columns, or orderings such as `:age.desc`.
|
|
1714
|
-
# @return [AST::Over]
|
|
1715
|
-
def order(*exprs)
|
|
1716
|
-
raise ArgumentError, "order needs an expression" if exprs.empty?
|
|
1717
|
-
Over.new(function, partitions, orders + exprs, frame)
|
|
1718
|
-
end
|
|
1719
|
-
|
|
1720
|
-
# `ROWS BETWEEN`, as a range of rows counted from the current one: negative before it, positive after, `0` the row itself, an open end unbounded. `rows(..0)` is a running total, `rows(-1..1)` the row and its neighbours.
|
|
1721
|
-
# @param bounds [Range]
|
|
1722
|
-
# @return [AST::Over]
|
|
1723
|
-
def rows(bounds)
|
|
1724
|
-
Over.new(function, partitions, orders, framing(:rows, bounds))
|
|
1725
|
-
end
|
|
1726
|
-
|
|
1727
|
-
# `RANGE BETWEEN`, with the bounds as {#rows} takes them.
|
|
1728
|
-
# @param bounds [Range]
|
|
1729
|
-
# @return [AST::Over]
|
|
1730
|
-
def range(bounds)
|
|
1731
|
-
Over.new(function, partitions, orders, framing(:range, bounds))
|
|
1732
|
-
end
|
|
1733
|
-
|
|
1734
|
-
# @private
|
|
1735
|
-
def to_arel(table, model)
|
|
1736
|
-
window = Arel::Nodes::Window.new
|
|
1737
|
-
partitions.each { |expr| window.partition(to_arel_operand(expr, table, model)) }
|
|
1738
|
-
orders.each { |expr| window.order(to_arel_operand(expr, table, model)) }
|
|
1739
|
-
frame_arel(window) if frame
|
|
1740
|
-
|
|
1741
|
-
# Not every aggregate can ride a window everywhere; the node itself
|
|
1742
|
-
# says where, once the adapter is known.
|
|
1743
|
-
function.check_window(model) if function.respond_to?(:check_window)
|
|
1744
|
-
|
|
1745
|
-
# A window-only function refuses to build on its own; here is where
|
|
1746
|
-
# it is asked for the call itself.
|
|
1747
|
-
arel_function =
|
|
1748
|
-
function.is_a?(WindowFunction) ? function.call_arel(table, model)
|
|
1749
|
-
: function.to_arel(table, model)
|
|
1750
|
-
Arel::Nodes::Over.new(arel_function, window)
|
|
1751
|
-
end
|
|
1752
|
-
|
|
1753
|
-
private
|
|
1754
|
-
# The frame is a range of rows counted from the current one: negative
|
|
1755
|
-
# before it, positive after, 0 the row itself, and an open end for
|
|
1756
|
-
# unbounded. `rows(..0)` is what a running total wants.
|
|
1757
|
-
def framing(kind, bounds)
|
|
1758
|
-
raise ArgumentError, "a window has one frame" if frame
|
|
1759
|
-
unless bounds.is_a?(::Range)
|
|
1760
|
-
raise ArgumentError, "#{kind} takes a range of rows, as in rows(..0)"
|
|
1761
|
-
end
|
|
1762
|
-
if bounds.exclude_end?
|
|
1763
|
-
raise ArgumentError, "a frame ends on a row rather than before one; use .."
|
|
1764
|
-
end
|
|
1765
|
-
[bounds.begin, bounds.end].each do |bound|
|
|
1766
|
-
next if bound.nil? || bound.is_a?(::Integer)
|
|
1767
|
-
raise ArgumentError,
|
|
1768
|
-
"a frame bound is a number of rows, or nothing for unbounded"
|
|
1769
|
-
end
|
|
1770
|
-
[kind, bounds.begin, bounds.end]
|
|
1771
|
-
end
|
|
1772
|
-
|
|
1773
|
-
# Arel wants the keyword itself on the left of the BETWEEN, which is
|
|
1774
|
-
# what window.rows with no argument hands back.
|
|
1775
|
-
def frame_arel(window)
|
|
1776
|
-
kind, from, to = frame
|
|
1777
|
-
window.frame(
|
|
1778
|
-
Arel::Nodes::Between.new(
|
|
1779
|
-
window.public_send(kind),
|
|
1780
|
-
Arel::Nodes::And.new([bound(from, Arel::Nodes::Preceding.new),
|
|
1781
|
-
bound(to, Arel::Nodes::Following.new)])))
|
|
1782
|
-
end
|
|
1783
|
-
|
|
1784
|
-
def bound(rows, unbounded)
|
|
1785
|
-
return unbounded if rows.nil?
|
|
1786
|
-
return Arel::Nodes::CurrentRow.new if rows.zero?
|
|
1787
|
-
rows.negative? ? Arel::Nodes::Preceding.new(-rows)
|
|
1788
|
-
: Arel::Nodes::Following.new(rows)
|
|
1789
|
-
end
|
|
1790
|
-
end
|
|
1791
|
-
|
|
1792
|
-
# An aggregate -- `COUNT`, `SUM`, `AVG`, `MIN`, `MAX` -- over a group,
|
|
1793
|
-
# or, given {Windowing#over}, a window.
|
|
1794
|
-
class Aggregate < Node
|
|
1795
|
-
include Predications
|
|
1796
|
-
include Arithmetics
|
|
1797
|
-
include Windowing
|
|
1798
|
-
|
|
1799
|
-
# The aggregates DISTINCT changes: over each value once, count counts
|
|
1800
|
-
# fewer and sum and avg reckon less. min and max give the same
|
|
1801
|
-
# either way, so a DISTINCT there is refused as saying nothing.
|
|
1802
|
-
# @private
|
|
1803
|
-
DISTINCT_FUNCTIONS = %i[count sum average].freeze
|
|
1804
|
-
|
|
1805
|
-
# @private
|
|
1806
|
-
attr_reader :operand, :function, :distinct, :condition
|
|
1807
|
-
|
|
1808
|
-
def initialize(operand, function, distinct: false, condition: nil)
|
|
1809
|
-
if distinct && !DISTINCT_FUNCTIONS.include?(function)
|
|
1810
|
-
raise ArgumentError, "#{function} does not take distinct; it would give the same"
|
|
1811
|
-
end
|
|
1812
|
-
if distinct && operand == :*
|
|
1813
|
-
raise ArgumentError, "count(:*) does not take distinct; name a column"
|
|
1814
|
-
end
|
|
1815
|
-
@operand = operand
|
|
1816
|
-
@function = function
|
|
1817
|
-
@distinct = distinct
|
|
1818
|
-
@condition = condition
|
|
1819
|
-
end
|
|
1820
|
-
|
|
1821
|
-
# `FILTER (WHERE condition)`: the aggregate taken over the rows the
|
|
1822
|
-
# condition holds for, as a value or a block. Where there is no
|
|
1823
|
-
# FILTER clause -- MySQL, SQL Server -- the CASE that means the same.
|
|
1824
|
-
# @return [AST::Aggregate]
|
|
1825
|
-
# @example
|
|
1826
|
-
# Author.select { [count(:*).as(:all), count(:*).filter { :age < 50 }.as(:young)] }
|
|
1827
|
-
def filter(condition = nil, &block)
|
|
1828
|
-
Aggregate.new(operand, function, distinct: distinct,
|
|
1829
|
-
condition: Case.argument(:filter, condition, block))
|
|
1830
|
-
end
|
|
1831
|
-
|
|
1832
|
-
# @private
|
|
1833
|
-
def to_arel(table, model)
|
|
1834
|
-
return aggregate(operand, table, model) unless condition
|
|
1835
|
-
|
|
1836
|
-
# A family without a FILTER clause gets the CASE that means the same.
|
|
1837
|
-
# An aggregate passes over a NULL, so the case that yields nothing for
|
|
1838
|
-
# the rows the condition misses is the same aggregate over the same
|
|
1839
|
-
# rows -- count(*) has no operand to keep, and counts a 1 instead.
|
|
1840
|
-
unless Dialect.for(model).filter_supported?
|
|
1841
|
-
kept = Case.new.when(condition).then(operand == :* ? 1 : operand)
|
|
1842
|
-
return aggregate(kept, table, model)
|
|
1843
|
-
end
|
|
1844
|
-
|
|
1845
|
-
aggregate(operand, table, model).filter(condition.to_arel(table, model))
|
|
1846
|
-
end
|
|
1847
|
-
|
|
1848
|
-
private
|
|
1849
|
-
def aggregate(over, table, model)
|
|
1850
|
-
arel_operand = to_arel_operand(over, table, model)
|
|
1851
|
-
return arel_operand.count(distinct) if function == :count
|
|
1852
|
-
call = arel_operand.public_send(function)
|
|
1853
|
-
call.distinct = distinct
|
|
1854
|
-
call
|
|
1855
|
-
end
|
|
1856
|
-
end
|
|
1857
|
-
|
|
1858
|
-
# Rows gathered into one JSON document: json_arrayagg collects a value
|
|
1859
|
-
# from each row into an array, json_objectagg a key and a value into an
|
|
1860
|
-
# object. Every adapter has the pair under a name of its own; what
|
|
1861
|
-
# PostgreSQL gets is the jsonb one, whose documents the other JSON
|
|
1862
|
-
# operations here read.
|
|
1863
|
-
class JsonAggregate < Node
|
|
1864
|
-
include Predications
|
|
1865
|
-
include JsonComparable
|
|
1866
|
-
include ComputedJson
|
|
1867
|
-
include Windowing
|
|
1868
|
-
|
|
1869
|
-
# @private
|
|
1870
|
-
attr_reader :kind, :operands, :condition
|
|
1871
|
-
|
|
1872
|
-
def initialize(kind, operands, condition: nil)
|
|
1873
|
-
@kind = kind
|
|
1874
|
-
@operands = operands
|
|
1875
|
-
@condition = condition
|
|
1876
|
-
end
|
|
1877
|
-
|
|
1878
|
-
# `FILTER (WHERE condition)`, as {Aggregate#filter}; refused on the
|
|
1879
|
-
# MySQL family, where the CASE that stands in would leave a JSON null
|
|
1880
|
-
# for every row it drops.
|
|
1881
|
-
# @return [AST::JsonAggregate]
|
|
1882
|
-
def filter(condition = nil, &block)
|
|
1883
|
-
JsonAggregate.new(kind, operands,
|
|
1884
|
-
condition: Case.argument(:filter, condition, block))
|
|
1885
|
-
end
|
|
1886
|
-
|
|
1887
|
-
# Over asks here before writing a window, since a family may take
|
|
1888
|
-
# every other aggregate as one but not these two.
|
|
1889
|
-
# @private
|
|
1890
|
-
def check_window(model)
|
|
1891
|
-
Dialect.for(model).check_json_aggregate_window(json_source, model)
|
|
1892
|
-
end
|
|
1893
|
-
|
|
1894
|
-
# @private
|
|
1895
|
-
def to_arel(table, model)
|
|
1896
|
-
dialect = Dialect.for(model)
|
|
1897
|
-
call = Arel::Nodes::NamedFunction.new(
|
|
1898
|
-
dialect.json_aggregate_name(kind),
|
|
1899
|
-
operands.map { |operand| to_arel_argument(operand, table, model) })
|
|
1900
|
-
return call unless condition
|
|
1901
|
-
|
|
1902
|
-
# The CASE that stands in for FILTER elsewhere hands the aggregate a
|
|
1903
|
-
# NULL for every row the condition misses, and these two keep a NULL
|
|
1904
|
-
# -- as JSON null -- rather than passing over it, so it is refused.
|
|
1905
|
-
unless dialect.json_aggregate_filter_supported?
|
|
1906
|
-
raise NotImplementedError,
|
|
1907
|
-
"#{json_source}.filter has no equivalent on " \
|
|
1908
|
-
"#{model.connection_db_config.adapter}; a CASE would leave a " \
|
|
1909
|
-
"null in the document for every row it drops"
|
|
1910
|
-
end
|
|
1911
|
-
call.filter(condition.to_arel(table, model))
|
|
1912
|
-
end
|
|
1913
|
-
|
|
1914
|
-
private
|
|
1915
|
-
def json_source
|
|
1916
|
-
"json_#{kind}"
|
|
1917
|
-
end
|
|
1918
|
-
end
|
|
1919
|
-
|
|
1920
|
-
# The strings of a group joined into one, a separator between:
|
|
1921
|
-
# `string_agg(:title, ", ")`, with `.order` for the order they are
|
|
1922
|
-
# joined in. Every family has it under a name of its own with the
|
|
1923
|
-
# ORDER BY in a place of its own, and Arel has no node for an ORDER BY
|
|
1924
|
-
# inside a call, so the dialect writes the call. A NULL is passed
|
|
1925
|
-
# over as by any aggregate, so the CASE that stands in for FILTER
|
|
1926
|
-
# means the same here and is not refused as the JSON aggregates' is.
|
|
1927
|
-
class StringAggregate < Node
|
|
1928
|
-
include Predications
|
|
1929
|
-
include Windowing
|
|
1930
|
-
|
|
1931
|
-
# @private
|
|
1932
|
-
attr_reader :operand, :separator, :orders, :condition
|
|
1933
|
-
|
|
1934
|
-
def initialize(operand, separator, orders: [], condition: nil)
|
|
1935
|
-
unless separator.is_a?(::String)
|
|
1936
|
-
raise ArgumentError, "#{separator.inspect} is not a String separator"
|
|
1937
|
-
end
|
|
1938
|
-
@operand = operand
|
|
1939
|
-
@separator = separator
|
|
1940
|
-
@orders = orders
|
|
1941
|
-
@condition = condition
|
|
1942
|
-
end
|
|
1943
|
-
|
|
1944
|
-
# The order the strings are joined in: columns, or orderings such as
|
|
1945
|
-
# `:title.desc`.
|
|
1946
|
-
# @return [AST::StringAggregate]
|
|
1947
|
-
def order(*exprs)
|
|
1948
|
-
raise ArgumentError, "order needs an expression" if exprs.empty?
|
|
1949
|
-
StringAggregate.new(operand, separator, orders: orders + exprs, condition: condition)
|
|
1950
|
-
end
|
|
1951
|
-
|
|
1952
|
-
# `FILTER (WHERE condition)`, as {Aggregate#filter}.
|
|
1953
|
-
# @return [AST::StringAggregate]
|
|
1954
|
-
def filter(condition = nil, &block)
|
|
1955
|
-
StringAggregate.new(operand, separator, orders: orders,
|
|
1956
|
-
condition: Case.argument(:filter, condition, block))
|
|
1957
|
-
end
|
|
1958
|
-
|
|
1959
|
-
# @private
|
|
1960
|
-
def check_window(model)
|
|
1961
|
-
Dialect.for(model).check_string_aggregate_window(model)
|
|
1962
|
-
end
|
|
1963
|
-
|
|
1964
|
-
# @private
|
|
1965
|
-
def to_arel(table, model)
|
|
1966
|
-
dialect = Dialect.for(model)
|
|
1967
|
-
kept = condition && !dialect.filter_supported? ?
|
|
1968
|
-
Case.new.when(condition).then(operand) : operand
|
|
1969
|
-
call = dialect.string_agg(
|
|
1970
|
-
to_arel_argument(kept, table, model), separator,
|
|
1971
|
-
orders.map { |expr| to_arel_operand(expr, table, model) },
|
|
1972
|
-
string_operand?(model), model)
|
|
1973
|
-
return call unless condition && dialect.filter_supported?
|
|
1974
|
-
Arel::Nodes::Filter.new(call, condition.to_arel(table, model))
|
|
1975
|
-
end
|
|
1976
|
-
|
|
1977
|
-
private
|
|
1978
|
-
# Whether the operand is a column the model declares a string.
|
|
1979
|
-
# PostgreSQL asks, its STRING_AGG taking text and nothing else; the
|
|
1980
|
-
# others convert for themselves.
|
|
1981
|
-
def string_operand?(model)
|
|
1982
|
-
operand.is_a?(::Symbol) &&
|
|
1983
|
-
%i[string text].include?(model.type_for_attribute(operand).type)
|
|
1984
|
-
end
|
|
1985
|
-
end
|
|
1986
|
-
|
|
1987
|
-
# A column alias, quoted by the adapter, so that the name asked for is
|
|
1988
|
-
# the name that comes back: unquoted, PostgreSQL folds a capital away
|
|
1989
|
-
# and the other two keep it, which is one block meaning two things.
|
|
1990
|
-
# Quoting also leaves nothing to refuse -- a name that would have been
|
|
1991
|
-
# SQL is an identifier with a strange name instead.
|
|
1992
|
-
#
|
|
1993
|
-
# `quote: false` asks for the name as written, for a schema that wants
|
|
1994
|
-
# the folding.
|
|
1995
|
-
class As < Node
|
|
1996
|
-
# @private
|
|
1997
|
-
attr_reader :operand, :alias_name, :quote
|
|
1998
|
-
|
|
1999
|
-
def initialize(operand, alias_name, quote: true)
|
|
2000
|
-
# Checked here rather than where the SQL is built, so that a name
|
|
2001
|
-
# the adapter is not being asked to quote is refused where it was
|
|
2002
|
-
# written.
|
|
2003
|
-
AST.check_name(alias_name, ALIAS_NAME, "column alias") unless quote
|
|
2004
|
-
@operand = operand
|
|
2005
|
-
@alias_name = alias_name
|
|
2006
|
-
@quote = quote
|
|
2007
|
-
end
|
|
2008
|
-
|
|
2009
|
-
# @private
|
|
2010
|
-
def to_arel(table, model)
|
|
2011
|
-
to_arel_operand(operand, table, model).as(alias_sql(model))
|
|
2012
|
-
end
|
|
2013
|
-
|
|
2014
|
-
private
|
|
2015
|
-
def alias_sql(model)
|
|
2016
|
-
name = alias_name.to_s
|
|
2017
|
-
return name unless quote
|
|
2018
|
-
model.with_connection { |connection| connection.quote_column_name(name) }
|
|
2019
|
-
end
|
|
2020
|
-
end
|
|
2021
|
-
|
|
2022
|
-
# A collation named for a comparison or an ordering: `:name.collate(:ci)`.
|
|
2023
|
-
# It stands as an expression -- compared, ordered by, selected -- and
|
|
2024
|
-
# gives back one of its own, so the collation carries through.
|
|
2025
|
-
#
|
|
2026
|
-
# The name follows COLLATE as a bare identifier with no Arel node of its
|
|
2027
|
-
# own. What names are safe turns on whether the family quotes it -- only
|
|
2028
|
-
# PostgreSQL does -- so the dialect checks the name as it builds the
|
|
2029
|
-
# clause, rather than this node holding one rule for all of them.
|
|
2030
|
-
class Collate < Node
|
|
2031
|
-
include Predications
|
|
2032
|
-
|
|
2033
|
-
# @private
|
|
2034
|
-
attr_reader :operand, :name
|
|
2035
|
-
|
|
2036
|
-
def initialize(operand, name)
|
|
2037
|
-
@name = name.to_s
|
|
2038
|
-
@operand = operand
|
|
2039
|
-
end
|
|
2040
|
-
|
|
2041
|
-
# @private
|
|
2042
|
-
def to_arel(table, model)
|
|
2043
|
-
Dialect.for(model).collate(to_arel_operand(operand, table, model), name, model)
|
|
2044
|
-
end
|
|
2045
|
-
end
|
|
2046
|
-
|
|
2047
|
-
# An ordering, `"age" DESC`: a direction on a column or an expression,
|
|
2048
|
-
# and through {#nulls_first} and {#nulls_last} a place for the NULLs.
|
|
2049
|
-
class Ordering < Node
|
|
2050
|
-
# @private
|
|
2051
|
-
attr_reader :operand, :direction, :nulls
|
|
2052
|
-
|
|
2053
|
-
def initialize(operand, direction, nulls = nil)
|
|
2054
|
-
@operand = operand
|
|
2055
|
-
@direction = direction
|
|
2056
|
-
@nulls = nulls
|
|
2057
|
-
end
|
|
2058
|
-
|
|
2059
|
-
# `NULLS FIRST`; portable, since Arel emulates it where MySQL has
|
|
2060
|
-
# none.
|
|
2061
|
-
# @return [AST::Ordering]
|
|
2062
|
-
#
|
|
2063
|
-
# MySQL has no NULLS FIRST/LAST, but Arel emulates it there with a
|
|
2064
|
-
# leading IS NULL ordering, so these are portable.
|
|
2065
|
-
def nulls_first
|
|
2066
|
-
Ordering.new(operand, direction, :nulls_first)
|
|
2067
|
-
end
|
|
2068
|
-
|
|
2069
|
-
# `NULLS LAST`.
|
|
2070
|
-
# @return [AST::Ordering]
|
|
2071
|
-
def nulls_last
|
|
2072
|
-
Ordering.new(operand, direction, :nulls_last)
|
|
2073
|
-
end
|
|
2074
|
-
|
|
2075
|
-
# @private
|
|
2076
|
-
def to_arel(table, model)
|
|
2077
|
-
ordering = to_arel_operand(operand, table, model).public_send(direction)
|
|
2078
|
-
nulls ? ordering.public_send(nulls) : ordering
|
|
2079
|
-
end
|
|
2080
|
-
end
|
|
2081
|
-
|
|
2082
|
-
# A function call, `UPPER(name)`: what the scalar functions of
|
|
2083
|
-
# {BlockContext} build, and {BlockContext#fn} for a function the list
|
|
2084
|
-
# does not name.
|
|
2085
|
-
class Function < Node
|
|
2086
|
-
include Predications
|
|
2087
|
-
include Arithmetics
|
|
2088
|
-
include Windowing
|
|
2089
|
-
|
|
2090
|
-
# @private
|
|
2091
|
-
attr_reader :name, :args
|
|
2092
|
-
|
|
2093
|
-
def initialize(name, args)
|
|
2094
|
-
@name = name
|
|
2095
|
-
@args = args
|
|
2096
|
-
end
|
|
2097
|
-
|
|
2098
|
-
# @private
|
|
2099
|
-
def to_arel(table, model)
|
|
2100
|
-
arel_args = args.map { |arg| to_arel_argument(arg, table, model) }
|
|
2101
|
-
Arel::Nodes::NamedFunction.new(name, arel_args)
|
|
2102
|
-
end
|
|
2103
|
-
end
|
|
2104
|
-
|
|
2105
|
-
# Escape hatch for operators without a spelling of their own, the way
|
|
2106
|
-
# fn is for functions. The operator is emitted as written -- whether
|
|
2107
|
-
# the adapter has it is the caller's assertion, as fn's names are --
|
|
2108
|
-
# and the values ride as quoted literals, so on PostgreSQL an untyped
|
|
2109
|
-
# one takes the type of the operand beside it.
|
|
2110
|
-
class Operation < Node
|
|
2111
|
-
include Predications
|
|
2112
|
-
include Arithmetics
|
|
2113
|
-
# After Arithmetics, whose & and | refuse: here they are AND and OR.
|
|
2114
|
-
include Connectives
|
|
2115
|
-
|
|
2116
|
-
# @private
|
|
2117
|
-
attr_reader :operator, :left, :right
|
|
2118
|
-
|
|
2119
|
-
def initialize(operator, left, right)
|
|
2120
|
-
@operator = AST.check_name(operator, OPERATOR, "operator").to_s
|
|
2121
|
-
@left = check_side(left)
|
|
2122
|
-
@right = check_side(right)
|
|
2123
|
-
end
|
|
2124
|
-
|
|
2125
|
-
# @private
|
|
2126
|
-
def to_arel(table, model)
|
|
2127
|
-
Arel::Nodes::Grouping.new(
|
|
2128
|
-
Arel::Nodes::InfixOperation.new(
|
|
2129
|
-
operator, side(left, table, model), side(right, table, model)))
|
|
2130
|
-
end
|
|
2131
|
-
|
|
2132
|
-
private
|
|
2133
|
-
# An expression operand is parenthesized: an unknown operator's
|
|
2134
|
-
# precedence is unknown too, and PostgreSQL reads its named
|
|
2135
|
-
# operators from the left, so a bare infix on the right would take
|
|
2136
|
-
# the new operator's left side into its own.
|
|
2137
|
-
def side(operand, table, model)
|
|
2138
|
-
arel = to_arel_argument(operand, table, model)
|
|
2139
|
-
operand.is_a?(Node) ? Arel::Nodes::Grouping.new(arel) : arel
|
|
2140
|
-
end
|
|
2141
|
-
|
|
2142
|
-
def check_side(operand)
|
|
2143
|
-
if operand.is_a?(::Hash) || operand.is_a?(::Array) || operand.is_a?(::Set)
|
|
2144
|
-
raise ArgumentError,
|
|
2145
|
-
"#{operand.inspect} has no one SQL spelling; a string says it " \
|
|
2146
|
-
"in the adapter's own, to_json for a document"
|
|
2147
|
-
end
|
|
2148
|
-
operand
|
|
2149
|
-
end
|
|
2150
|
-
end
|
|
2151
|
-
|
|
2152
|
-
# ROW_NUMBER and its kind: functions that say nothing without a window.
|
|
2153
|
-
# On its own this refuses rather than reaching the database as an error
|
|
2154
|
-
# there; over asks it for call_arel instead.
|
|
2155
|
-
class WindowFunction < Function
|
|
2156
|
-
# @private
|
|
2157
|
-
alias_method :call_arel, :to_arel
|
|
2158
|
-
|
|
2159
|
-
# @private
|
|
2160
|
-
def to_arel(_table, _model)
|
|
2161
|
-
raise ArgumentError, "#{name.downcase} is a window function; it needs over"
|
|
2162
|
-
end
|
|
2163
|
-
end
|
|
2164
|
-
|
|
2165
|
-
# EXTRACT(field FROM expr). The field is grammar rather than a value --
|
|
2166
|
-
# a keyword the adapter reads bare -- so it has to be a plain name,
|
|
2167
|
-
# which Arel upcases on the way out.
|
|
2168
|
-
class Extract < Node
|
|
2169
|
-
include Predications
|
|
2170
|
-
include Arithmetics
|
|
2171
|
-
|
|
2172
|
-
# @private
|
|
2173
|
-
attr_reader :field, :operand
|
|
2174
|
-
|
|
2175
|
-
def initialize(field, operand)
|
|
2176
|
-
@field = AST.check_name(field, ALIAS_NAME, "extract field")
|
|
2177
|
-
@operand = operand
|
|
2178
|
-
end
|
|
2179
|
-
|
|
2180
|
-
# @private
|
|
2181
|
-
def to_arel(table, model)
|
|
2182
|
-
Arel::Nodes::Extract.new(to_arel_argument(operand, table, model), field.to_s)
|
|
2183
|
-
end
|
|
2184
|
-
end
|
|
2185
|
-
|
|
2186
|
-
# CAST(expr AS type). The type is grammar too, written into the SQL as
|
|
2187
|
-
# given -- it is the adapter's own name for the type, and whether it
|
|
2188
|
-
# exists is the database's to say -- so it has to look like one:
|
|
2189
|
-
# a plain name, at most parenthesized with lengths.
|
|
2190
|
-
class Cast < Node
|
|
2191
|
-
include Predications
|
|
2192
|
-
include Arithmetics
|
|
2193
|
-
|
|
2194
|
-
# @private
|
|
2195
|
-
attr_reader :operand, :sql_type
|
|
2196
|
-
|
|
2197
|
-
def initialize(operand, sql_type)
|
|
2198
|
-
@operand = operand
|
|
2199
|
-
@sql_type = AST.check_name(sql_type, TYPE_NAME, "SQL type")
|
|
2200
|
-
end
|
|
2201
|
-
|
|
2202
|
-
# @private
|
|
2203
|
-
def to_arel(table, model)
|
|
2204
|
-
Arel::Nodes::NamedFunction.new(
|
|
2205
|
-
"CAST",
|
|
2206
|
-
[Arel::Nodes::As.new(to_arel_argument(operand, table, model),
|
|
2207
|
-
Arel::Nodes::SqlLiteral.new(sql_type.to_s))])
|
|
2208
|
-
end
|
|
2209
|
-
end
|
|
2210
|
-
|
|
2211
|
-
# CURRENT_TIMESTAMP and its relatives, what the SQL grammar calls a
|
|
2212
|
-
# datetime value function. The grammar has them bare, and PostgreSQL
|
|
2213
|
-
# and SQLite reject them written as calls, so unlike Function the name
|
|
2214
|
-
# is emitted without parentheses. A precision is the one thing that
|
|
2215
|
-
# does go into parentheses, and it is written into the SQL as given, so
|
|
2216
|
-
# only an Integer is accepted.
|
|
2217
|
-
class DatetimeValueFunction < Node
|
|
2218
|
-
include Predications
|
|
2219
|
-
include Arithmetics
|
|
2220
|
-
|
|
2221
|
-
# @private
|
|
2222
|
-
attr_reader :name, :precision
|
|
2223
|
-
|
|
2224
|
-
def initialize(name, precision = nil)
|
|
2225
|
-
unless precision.nil? || precision.is_a?(Integer)
|
|
2226
|
-
raise ArgumentError,
|
|
2227
|
-
"#{precision.inspect} is not an Integer precision"
|
|
2228
|
-
end
|
|
2229
|
-
@name = name
|
|
2230
|
-
@precision = precision
|
|
2231
|
-
end
|
|
2232
|
-
|
|
2233
|
-
# @private
|
|
2234
|
-
def to_arel(_table, _model)
|
|
2235
|
-
Arel::Nodes::SqlLiteral.new(
|
|
2236
|
-
precision ? "#{name}(#{precision})" : name)
|
|
2237
|
-
end
|
|
2238
|
-
end
|
|
2239
|
-
|
|
2240
|
-
# A plain SQL comparison. The value is passed through as it is, so a Range
|
|
2241
|
-
# or an Array compares against a PostgreSQL range or array column, the way
|
|
2242
|
-
# Active Record's own force_equality? types do.
|
|
2243
|
-
class Comparison < Predicate
|
|
2244
|
-
# @private
|
|
2245
|
-
OPERATOR_MAP = {
|
|
2246
|
-
:== => :eq, :!= => :not_eq,
|
|
2247
|
-
:> => :gt, :>= => :gteq, :< => :lt, :<= => :lteq
|
|
2248
|
-
}.freeze
|
|
2249
|
-
|
|
2250
|
-
# @private
|
|
2251
|
-
attr_reader :column, :operator, :value
|
|
2252
|
-
|
|
2253
|
-
def initialize(column, operator, value)
|
|
2254
|
-
@column = column
|
|
2255
|
-
@operator = operator
|
|
2256
|
-
@value = value
|
|
2257
|
-
end
|
|
2258
|
-
|
|
2259
|
-
# @private
|
|
2260
|
-
def to_arel(table, model)
|
|
2261
|
-
arel_column = to_arel_operand(column, table, model)
|
|
2262
|
-
arel_value =
|
|
2263
|
-
case value
|
|
2264
|
-
when Node then value.to_arel(table, model)
|
|
2265
|
-
when ::Symbol then column_operand(value, table, model)
|
|
2266
|
-
when ActiveRecord::Relation then scalar_subquery(value)
|
|
2267
|
-
else quote_number(value)
|
|
2268
|
-
end
|
|
2269
|
-
arel_column.public_send(OPERATOR_MAP.fetch(operator), arel_value)
|
|
2270
|
-
end
|
|
2271
|
-
|
|
2272
|
-
private
|
|
2273
|
-
# A relation compared against a column has to yield a single value, so
|
|
2274
|
-
# unlike In there is no sensible default select list to fall back on.
|
|
2275
|
-
def scalar_subquery(relation)
|
|
2276
|
-
if relation.select_values.empty?
|
|
2277
|
-
raise ArgumentError,
|
|
2278
|
-
"#{operator} needs a subquery selecting one value; add a select"
|
|
2279
|
-
end
|
|
2280
|
-
relation = relation.send(:apply_join_dependency) if relation.eager_loading?
|
|
2281
|
-
relation.arel
|
|
2282
|
-
end
|
|
2283
|
-
end
|
|
2284
|
-
|
|
2285
|
-
# IS TRUE, IS FALSE and their negations, which every adapter spells the
|
|
2286
|
-
# same way and answers alike, NULL included.
|
|
2287
|
-
class TruthValue < Predicate
|
|
2288
|
-
# @private
|
|
2289
|
-
attr_reader :operand, :value, :negated
|
|
2290
|
-
|
|
2291
|
-
def initialize(operand, value, negated: false)
|
|
2292
|
-
@operand = operand
|
|
2293
|
-
@value = value
|
|
2294
|
-
@negated = negated
|
|
2295
|
-
end
|
|
2296
|
-
|
|
2297
|
-
# @private
|
|
2298
|
-
def to_arel(table, model)
|
|
2299
|
-
Dialect.for(model).truth_value(
|
|
2300
|
-
to_arel_operand(operand, table, model), value, negated, model)
|
|
2301
|
-
end
|
|
2302
|
-
end
|
|
2303
|
-
|
|
2304
|
-
# A relation standing for a set of values, which is what IN and the
|
|
2305
|
-
# quantifiers each take. The treatment is Active Record's own
|
|
2306
|
-
# RelationHandler's: without an explicit select list the subquery
|
|
2307
|
-
# selects the model's primary key.
|
|
2308
|
-
# @private
|
|
2309
|
-
module SetSubquery
|
|
2310
|
-
private
|
|
2311
|
-
def set_subquery(relation, spelling)
|
|
2312
|
-
relation = relation.send(:apply_join_dependency) if relation.eager_loading?
|
|
2313
|
-
if relation.select_values.empty?
|
|
2314
|
-
model = relation.model
|
|
2315
|
-
if model.composite_primary_key?
|
|
2316
|
-
raise ArgumentError,
|
|
2317
|
-
"Cannot map composite primary key #{model.primary_key} to #{spelling}"
|
|
2318
|
-
end
|
|
2319
|
-
relation = relation.select(relation.table[model.primary_key])
|
|
2320
|
-
end
|
|
2321
|
-
relation.arel
|
|
2322
|
-
end
|
|
2323
|
-
end
|
|
2324
|
-
|
|
2325
|
-
# IN for a list of values, BETWEEN for a range, IN (SELECT ...) for a
|
|
2326
|
-
# relation.
|
|
2327
|
-
class In < Predicate
|
|
2328
|
-
include SetSubquery
|
|
2329
|
-
|
|
2330
|
-
# Range holds its endpoints to Comparable, which a quoted node is
|
|
2331
|
-
# not, so this quacks the three methods Arel's between reads.
|
|
2332
|
-
# @private
|
|
2333
|
-
QuotedRange = Struct.new(:begin, :end, :exclude_end) do
|
|
2334
|
-
def exclude_end? = exclude_end
|
|
2335
|
-
end
|
|
2336
|
-
|
|
2337
|
-
# @private
|
|
2338
|
-
attr_reader :operand, :values, :negated
|
|
2339
|
-
|
|
2340
|
-
def initialize(operand, values, negated: false)
|
|
2341
|
-
@operand = operand
|
|
2342
|
-
@values = values
|
|
2343
|
-
@negated = negated
|
|
2344
|
-
end
|
|
2345
|
-
|
|
2346
|
-
# @private
|
|
2347
|
-
def to_arel(table, model)
|
|
2348
|
-
arel_operand = to_arel_operand(operand, table, model)
|
|
2349
|
-
case values
|
|
2350
|
-
when Range, QuotedRange
|
|
2351
|
-
lower = quote_value(values.begin, table, model)
|
|
2352
|
-
upper = quote_value(values.end, table, model)
|
|
2353
|
-
if json_between?(model) && lower && upper && !negated
|
|
2354
|
-
return arel_operand.gteq(lower).and(
|
|
2355
|
-
values.exclude_end? ? arel_operand.lt(upper) : arel_operand.lteq(upper))
|
|
2356
|
-
end
|
|
2357
|
-
range = QuotedRange.new(lower, upper, values.exclude_end?)
|
|
2358
|
-
arel_operand.public_send(negated ? :not_between : :between, range)
|
|
2359
|
-
when ActiveRecord::Relation
|
|
2360
|
-
arel_operand.public_send(negated ? :not_in : :in, set_subquery(values, "IN"))
|
|
2361
|
-
else
|
|
2362
|
-
arg = values
|
|
2363
|
-
if arg.is_a?(::Array)
|
|
2364
|
-
arg = arg.map { |value| quote_value(value, table, model) }
|
|
2365
|
-
return json_list(arel_operand, arg) if json_list?(model)
|
|
2366
|
-
end
|
|
2367
|
-
arel_operand.public_send(negated ? :not_in : :in, arg)
|
|
2368
|
-
end
|
|
2369
|
-
end
|
|
2370
|
-
|
|
2371
|
-
private
|
|
2372
|
-
# An element that is already an expression resolves, a symbol is a
|
|
2373
|
-
# column here as everywhere, a number is quoted as itself, and the
|
|
2374
|
-
# rest ride for Arel to cast by the column.
|
|
2375
|
-
def quote_value(value, table, model)
|
|
2376
|
-
case value
|
|
2377
|
-
when Node then value.to_arel(table, model)
|
|
2378
|
-
when ::Symbol then column_operand(value, table, model)
|
|
2379
|
-
else quote_number(value)
|
|
2380
|
-
end
|
|
2381
|
-
end
|
|
2382
|
-
|
|
2383
|
-
# MySQL leaves IN and BETWEEN out of its JSON comparisons -- they
|
|
2384
|
-
# fall back to another comparison entirely -- so on it a JSON set is
|
|
2385
|
-
# spelled as the comparisons it means: the closed range as its two
|
|
2386
|
-
# bounds, the list as one equality per element. That names the dug
|
|
2387
|
-
# value once per element, the price SQLite's XOR pays per operand;
|
|
2388
|
-
# a negated range needs nothing, Arel writing it as two comparisons
|
|
2389
|
-
# everywhere. MariaDB never gets this far: the endpoints refuse as
|
|
2390
|
-
# they resolve.
|
|
2391
|
-
def json_between?(model)
|
|
2392
|
-
(values.begin.is_a?(JsonLiteral) || values.end.is_a?(JsonLiteral)) &&
|
|
2393
|
-
Dialect.for(model).json_list_by_element?
|
|
2394
|
-
end
|
|
2395
|
-
|
|
2396
|
-
def json_list?(model)
|
|
2397
|
-
values.any? { |value| value.is_a?(JsonLiteral) } &&
|
|
2398
|
-
Dialect.for(model).json_list_by_element?
|
|
2399
|
-
end
|
|
2400
|
-
|
|
2401
|
-
def json_list(arel_operand, elements)
|
|
2402
|
-
comparisons = elements.map do |element|
|
|
2403
|
-
negated ? arel_operand.not_eq(element) : arel_operand.eq(element)
|
|
2404
|
-
end
|
|
2405
|
-
joined = comparisons.inject do |so_far, piece|
|
|
2406
|
-
negated ? so_far.and(piece) : so_far.or(piece)
|
|
2407
|
-
end
|
|
2408
|
-
negated ? Arel::Nodes::Grouping.new(joined) : joined
|
|
2409
|
-
end
|
|
2410
|
-
end
|
|
2411
|
-
|
|
2412
|
-
# ANY and ALL, which stand on the right of a comparison and say how many
|
|
2413
|
-
# of the subquery's rows have to satisfy it. Where a scalar subquery
|
|
2414
|
-
# has to return one row, these take as many as come.
|
|
2415
|
-
class Quantified < Node
|
|
2416
|
-
include SetSubquery
|
|
2417
|
-
|
|
2418
|
-
# @private
|
|
2419
|
-
attr_reader :kind, :relation
|
|
2420
|
-
|
|
2421
|
-
def initialize(kind, relation)
|
|
2422
|
-
unless relation.is_a?(ActiveRecord::Relation)
|
|
2423
|
-
raise ArgumentError,
|
|
2424
|
-
"#{kind} takes a relation as its subquery; a list is what in? takes"
|
|
2425
|
-
end
|
|
2426
|
-
@kind = kind
|
|
2427
|
-
@relation = relation
|
|
2428
|
-
end
|
|
2429
|
-
|
|
2430
|
-
# The subquery goes in as its own AST rather than as the manager,
|
|
2431
|
-
# which would parenthesise it a second time -- and to PostgreSQL
|
|
2432
|
-
# `ANY ((SELECT ...))` is ANY of one scalar, which it refuses.
|
|
2433
|
-
# @private
|
|
2434
|
-
def to_arel(_table, _model)
|
|
2435
|
-
Arel::Nodes::NamedFunction.new(kind, [set_subquery(relation, kind).ast])
|
|
2436
|
-
end
|
|
2437
|
-
end
|
|
2438
|
-
|
|
2439
|
-
# EXISTS (SELECT ...) for a relation. Correlate the subquery with the
|
|
2440
|
-
# outer table through qualified columns. EXISTS only asks whether a row
|
|
2441
|
-
# comes back, so unlike In there is no select list to fix up.
|
|
2442
|
-
class Exists < Predicate
|
|
2443
|
-
# @private
|
|
2444
|
-
attr_reader :relation
|
|
2445
|
-
|
|
2446
|
-
def initialize(relation)
|
|
2447
|
-
@relation = relation
|
|
2448
|
-
end
|
|
2449
|
-
|
|
2450
|
-
# @private
|
|
2451
|
-
def to_arel(_table, _model)
|
|
2452
|
-
subquery = relation
|
|
2453
|
-
if subquery.eager_loading?
|
|
2454
|
-
subquery = subquery.send(:apply_join_dependency)
|
|
2455
|
-
end
|
|
2456
|
-
subquery.arel.exists
|
|
2457
|
-
end
|
|
2458
|
-
end
|
|
2459
|
-
|
|
2460
|
-
# `LIKE`, negated or case-insensitive as asked. The pattern of
|
|
2461
|
-
# {Predications#like?} goes as written, `%` and `_` its wildcards; the
|
|
2462
|
-
# shortcuts, {Predications#start_with?} and its kin, escape theirs and
|
|
2463
|
-
# say so with `ESCAPE`, since SQLite reads no escape character unless
|
|
2464
|
-
# told one.
|
|
2465
|
-
class Like < Predicate
|
|
2466
|
-
# @private
|
|
2467
|
-
ESCAPE = "\\"
|
|
2468
|
-
|
|
2469
|
-
# Escapes % and _ so that they match literally. The pattern built from
|
|
2470
|
-
# the result must be used with ESCAPE, since SQLite has no default
|
|
2471
|
-
# escape character.
|
|
2472
|
-
# @private
|
|
2473
|
-
def self.escape(string)
|
|
2474
|
-
ActiveRecord::Base.sanitize_sql_like(string, ESCAPE)
|
|
2475
|
-
end
|
|
2476
|
-
|
|
2477
|
-
# ORs one LIKE per pattern, for the shortcuts that accept several
|
|
2478
|
-
# literals the way String#start_with? does.
|
|
2479
|
-
# @private
|
|
2480
|
-
def self.any(operand, patterns)
|
|
2481
|
-
patterns.map { |pattern| new(operand, pattern, ESCAPE) }.
|
|
2482
|
-
inject { |left, right| Or.new(left, right) }
|
|
2483
|
-
end
|
|
2484
|
-
|
|
2485
|
-
# @private
|
|
2486
|
-
attr_reader :operand, :pattern, :escape, :case_sensitive, :negated
|
|
2487
|
-
|
|
2488
|
-
def initialize(operand, pattern, escape = nil, case_sensitive: true,
|
|
2489
|
-
negated: false)
|
|
2490
|
-
@operand = operand
|
|
2491
|
-
@pattern = pattern
|
|
2492
|
-
@escape = escape
|
|
2493
|
-
@case_sensitive = case_sensitive
|
|
2494
|
-
@negated = negated
|
|
2495
|
-
end
|
|
2496
|
-
|
|
2497
|
-
# @private
|
|
2498
|
-
def to_arel(table, model)
|
|
2499
|
-
# Arel matches case-insensitively unless told otherwise, which is
|
|
2500
|
-
# what picks ILIKE over LIKE on PostgreSQL.
|
|
2501
|
-
to_arel_operand(operand, table, model).
|
|
2502
|
-
public_send(negated ? :does_not_match : :matches,
|
|
2503
|
-
pattern, escape, case_sensitive)
|
|
2504
|
-
end
|
|
2505
|
-
end
|
|
2506
|
-
|
|
2507
|
-
# IS [NOT] DISTINCT FROM, spelled IS / IS NOT on SQLite and <=> on
|
|
2508
|
-
# MySQL. NULL compares as a value here, which is what separates these
|
|
2509
|
-
# from = and <>.
|
|
2510
|
-
class DistinctFrom < Predicate
|
|
2511
|
-
# @private
|
|
2512
|
-
attr_reader :operand, :value, :negated
|
|
2513
|
-
|
|
2514
|
-
def initialize(operand, value, negated: false)
|
|
2515
|
-
@operand = operand
|
|
2516
|
-
@value = value
|
|
2517
|
-
@negated = negated
|
|
2518
|
-
end
|
|
2519
|
-
|
|
2520
|
-
# @private
|
|
2521
|
-
def to_arel(table, model)
|
|
2522
|
-
arel_operand = to_arel_operand(operand, table, model)
|
|
2523
|
-
arel_value = value.is_a?(Node) ? value.to_arel(table, model) : value
|
|
2524
|
-
if negated
|
|
2525
|
-
arel_operand.is_distinct_from(arel_value)
|
|
2526
|
-
else
|
|
2527
|
-
arel_operand.is_not_distinct_from(arel_value)
|
|
2528
|
-
end
|
|
2529
|
-
end
|
|
2530
|
-
end
|
|
2531
|
-
|
|
2532
|
-
# Comparisons against a PostgreSQL array column, named after the Ruby
|
|
2533
|
-
# methods that mean the same thing: member? is Enumerable's element
|
|
2534
|
-
# test, superset? and subset? are Set's whole-array containment, and
|
|
2535
|
-
# intersect? is Array's "any element in common". Each name maps to one
|
|
2536
|
-
# operator; the elements are rendered as an array literal, which
|
|
2537
|
-
# PostgreSQL coerces to the column's element type, so any expression
|
|
2538
|
-
# works as the operand and no schema lookup is needed.
|
|
2539
|
-
class ArrayPredicate < Predicate
|
|
2540
|
-
# The whole-array comparisons take the collection kinds their
|
|
2541
|
-
# namesakes compare against: an Array, or a Set for the Set methods.
|
|
2542
|
-
# @private
|
|
2543
|
-
def self.elements(arg, method_name)
|
|
2544
|
-
case arg
|
|
2545
|
-
when ::Array then arg
|
|
2546
|
-
when ::Set then arg.to_a
|
|
2547
|
-
else
|
|
2548
|
-
raise ArgumentError, "#{method_name} takes an Array or Set of elements"
|
|
2549
|
-
end
|
|
2550
|
-
end
|
|
2551
|
-
|
|
2552
|
-
# @private
|
|
2553
|
-
attr_reader :operand, :operator, :elements
|
|
2554
|
-
|
|
2555
|
-
def initialize(operand, operator, elements)
|
|
2556
|
-
@operand = operand
|
|
2557
|
-
@operator = operator
|
|
2558
|
-
@elements = elements
|
|
2559
|
-
end
|
|
2560
|
-
|
|
2561
|
-
# @private
|
|
2562
|
-
#
|
|
2563
|
-
# The refusal is the gem's rather than Arel's: the visitor stopped
|
|
2564
|
-
# @> and && off PostgreSQL, but <@ rode an InfixOperation and
|
|
2565
|
-
# rendered anywhere, so subset? alone reached the other servers.
|
|
2566
|
-
def to_arel(table, model)
|
|
2567
|
-
unless Dialect.for(model).array_comparisons_supported?
|
|
2568
|
-
raise NotImplementedError,
|
|
2569
|
-
"the array comparisons have no equivalent on " \
|
|
2570
|
-
"#{model.connection_db_config.adapter}"
|
|
2571
|
-
end
|
|
2572
|
-
arel_operand = to_arel_operand(operand, table, model)
|
|
2573
|
-
quoted = Arel::Nodes.build_quoted(array_literal)
|
|
2574
|
-
case operator
|
|
2575
|
-
when :"@>" then Arel::Nodes::Contains.new(arel_operand, quoted)
|
|
2576
|
-
when :"&&" then Arel::Nodes::Overlaps.new(arel_operand, quoted)
|
|
2577
|
-
else Arel::Nodes::InfixOperation.new(operator, arel_operand, quoted)
|
|
2578
|
-
end
|
|
2579
|
-
end
|
|
2580
|
-
|
|
2581
|
-
private
|
|
2582
|
-
# PostgreSQL array input syntax: elements joined by commas inside
|
|
2583
|
-
# braces, and an element is double-quoted whenever it is empty, spells
|
|
2584
|
-
# NULL, or contains a character the parser treats specially.
|
|
2585
|
-
def array_literal
|
|
2586
|
-
encoded = elements.map do |value|
|
|
2587
|
-
s = value.to_s
|
|
2588
|
-
if s.empty? || s.casecmp?("null") || s.match?(/[\s{},"\\]/)
|
|
2589
|
-
"\"#{s.gsub(/["\\]/) { |c| "\\#{c}" }}\""
|
|
2590
|
-
else
|
|
2591
|
-
s
|
|
2592
|
-
end
|
|
2593
|
-
end
|
|
2594
|
-
"{#{encoded.join(',')}}"
|
|
2595
|
-
end
|
|
2596
|
-
end
|
|
2597
|
-
|
|
2598
|
-
# Regular expression match: REGEXP on MySQL, ~ on PostgreSQL. SQLite has
|
|
2599
|
-
# no regexp operator built in, so Arel raises NotImplementedError there.
|
|
2600
|
-
class Match < Predicate
|
|
2601
|
-
# @private
|
|
2602
|
-
attr_reader :operand, :pattern, :negated
|
|
2603
|
-
|
|
2604
|
-
def initialize(operand, pattern, negated: false)
|
|
2605
|
-
@operand = operand
|
|
2606
|
-
@pattern = pattern.is_a?(Regexp) ? regexp_source(pattern) : pattern
|
|
2607
|
-
@negated = negated
|
|
2608
|
-
end
|
|
2609
|
-
|
|
2610
|
-
# @private
|
|
2611
|
-
def to_arel(table, model)
|
|
2612
|
-
arel_operand = to_arel_operand(operand, table, model)
|
|
2613
|
-
if negated
|
|
2614
|
-
arel_operand.does_not_match_regexp(pattern)
|
|
2615
|
-
else
|
|
2616
|
-
arel_operand.matches_regexp(pattern)
|
|
2617
|
-
end
|
|
2618
|
-
end
|
|
2619
|
-
|
|
2620
|
-
private
|
|
2621
|
-
# A Regexp literal reads naturally with =~, but only its source crosses
|
|
2622
|
-
# over; the database has its own dialect and no notion of Ruby's flags.
|
|
2623
|
-
# Dropping a flag would silently change what the query matches, so
|
|
2624
|
-
# anything beyond a plain literal is refused rather than ignored.
|
|
2625
|
-
def regexp_source(regexp)
|
|
2626
|
-
unless regexp.options.zero?
|
|
2627
|
-
raise ArgumentError,
|
|
2628
|
-
"#{regexp.inspect} has options that SQL cannot express; " \
|
|
2629
|
-
"pass the pattern as a string instead"
|
|
2630
|
-
end
|
|
2631
|
-
regexp.source
|
|
2632
|
-
end
|
|
2633
|
-
end
|
|
2634
|
-
|
|
2635
|
-
# `AND`, which `&` between two conditions builds.
|
|
2636
|
-
class And < Predicate
|
|
2637
|
-
# @private
|
|
2638
|
-
attr_reader :left, :right
|
|
2639
|
-
|
|
2640
|
-
def initialize(left, right)
|
|
2641
|
-
@left = AST.check_condition(left, :&)
|
|
2642
|
-
@right = AST.check_condition(right, :&)
|
|
2643
|
-
end
|
|
2644
|
-
|
|
2645
|
-
# @private
|
|
2646
|
-
def to_arel(table, model)
|
|
2647
|
-
left.to_arel(table, model).and(right.to_arel(table, model))
|
|
2648
|
-
end
|
|
2649
|
-
end
|
|
2650
|
-
|
|
2651
|
-
# `OR`, which `|` between two conditions builds.
|
|
2652
|
-
class Or < Predicate
|
|
2653
|
-
# @private
|
|
2654
|
-
attr_reader :left, :right
|
|
2655
|
-
|
|
2656
|
-
def initialize(left, right)
|
|
2657
|
-
@left = AST.check_condition(left, :|)
|
|
2658
|
-
@right = AST.check_condition(right, :|)
|
|
2659
|
-
end
|
|
2660
|
-
|
|
2661
|
-
# @private
|
|
2662
|
-
def to_arel(table, model)
|
|
2663
|
-
left.to_arel(table, model).or(right.to_arel(table, model))
|
|
2664
|
-
end
|
|
2665
|
-
end
|
|
2666
|
-
|
|
2667
|
-
# `NOT`, which `!` on a condition builds.
|
|
2668
|
-
class Not < Predicate
|
|
2669
|
-
# @private
|
|
2670
|
-
attr_reader :operand
|
|
2671
|
-
|
|
2672
|
-
def initialize(operand)
|
|
2673
|
-
@operand = AST.check_condition(operand, :!)
|
|
2674
|
-
end
|
|
2675
|
-
|
|
2676
|
-
# @private
|
|
2677
|
-
def to_arel(table, model)
|
|
2678
|
-
Arel::Nodes::Not.new(operand.to_arel(table, model))
|
|
2679
|
-
end
|
|
2680
|
-
end
|
|
2681
81
|
end
|
|
2682
82
|
end
|
|
2683
83
|
end
|