activerecord-refined 0.8.1 → 0.10.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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +17 -0
  3. data/README.md +111 -815
  4. data/activerecord-refined.gemspec +36 -17
  5. data/docs/conditions.md +206 -0
  6. data/docs/ctes.md +65 -0
  7. data/docs/expressions.md +125 -0
  8. data/docs/functions.md +219 -0
  9. data/docs/grouping.md +55 -0
  10. data/docs/joins.md +73 -0
  11. data/docs/json.md +230 -0
  12. data/docs/ordering.md +55 -0
  13. data/docs/time_zones.md +30 -0
  14. data/docs/windows.md +41 -0
  15. data/docs/writing.md +33 -0
  16. data/examples/aggregations.rb +31 -11
  17. data/examples/complex_joins.rb +12 -10
  18. data/examples/ctes.rb +22 -20
  19. data/examples/expressions.rb +110 -45
  20. data/examples/json.rb +77 -38
  21. data/examples/postgresql.rb +64 -53
  22. data/examples/predicates.rb +35 -33
  23. data/examples/subqueries.rb +20 -18
  24. data/examples/windows.rb +23 -21
  25. data/examples/writes.rb +26 -24
  26. data/lib/active_record/refined/ast.rb +1117 -373
  27. data/lib/active_record/refined/dialect/mariadb.rb +25 -0
  28. data/lib/active_record/refined/dialect/mysql.rb +18 -0
  29. data/lib/active_record/refined/dialect/mysql_compat.rb +67 -0
  30. data/lib/active_record/refined/dialect/oracle.rb +110 -0
  31. data/lib/active_record/refined/dialect/postgresql.rb +120 -0
  32. data/lib/active_record/refined/dialect/sql_server.rb +115 -0
  33. data/lib/active_record/refined/dialect/sqlite.rb +57 -0
  34. data/lib/active_record/refined/dialect.rb +340 -0
  35. data/lib/active_record/refined.rb +877 -307
  36. data/lib/activerecord-refined/version.rb +3 -1
  37. data/lib/activerecord-refined.rb +9 -5
  38. metadata +186 -15
  39. data/.github/workflows/push_gem.yml +0 -45
  40. data/.github/workflows/sandbox.yml +0 -295
  41. data/.github/workflows/test.yml +0 -90
  42. data/.gitignore +0 -19
  43. data/Gemfile +0 -12
  44. data/Rakefile +0 -25
  45. data/benchmark/query_building.rb +0 -129
  46. data/test/test_block_syntax.rb +0 -2493
  47. data/test/test_helper.rb +0 -221
@@ -1,45 +1,66 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # JSON.generate, for the document a containment test is given.
2
- require 'json'
4
+ require "json"
3
5
 
4
6
  module ActiveRecord
5
7
  module Refined
6
8
  module AST
9
+ # @private
7
10
  NAME = /[[:alpha:]_][[:alnum:]_$]*/
11
+ # @private
8
12
  ALIAS_NAME = /\A#{NAME}\z/
13
+ # @private
9
14
  FUNCTION_NAME = /\A#{NAME}(\.#{NAME})?\z/
15
+ # A collation name where the family writes it bare, as all but PostgreSQL
16
+ # do. A plain identifier: a hyphen unquoted would read as the collation
17
+ # minus a number, `x COLLATE nocase-1` as `(x COLLATE nocase) - 1`, valid
18
+ # and wrong. PostgreSQL quotes the name and widens this; see its dialect.
19
+ # @private
20
+ COLLATION_NAME = /\A#{NAME}\z/
10
21
  # A SQL type as cast writes it: words, at most parenthesized with
11
22
  # lengths -- double precision, decimal(10,2).
23
+ # @private
12
24
  TYPE_NAME = /\A[[:alpha:]_][[:alnum:]_ ]*(\(\d+(, ?\d+)?\))?\z/
13
-
14
- # Which family of spellings an adapter belongs to. MariaDB answers to
15
- # the mysql2 adapter and is counted with MySQL, though the two part
16
- # company over JSON. An adapter nobody has classified keeps the
17
- # standard spellings and is left to say for itself what it cannot do.
18
- #
19
- # pglite is PostgreSQL itself compiled to WebAssembly, reached through
20
- # wasmify-rails' adapter; the server it answers for is the same one.
21
- ADAPTER_FAMILIES = {
22
- 'sqlite3' => :sqlite,
23
- 'postgresql' => :postgresql,
24
- 'postgis' => :postgresql,
25
- 'pglite' => :postgresql,
26
- 'mysql2' => :mysql,
27
- 'trilogy' => :mysql,
28
- }.freeze
29
-
30
- def self.adapter_family(model)
31
- ADAPTER_FAMILIES[model.connection_db_config.adapter] || :unknown
32
- end
25
+ # The characters PostgreSQL allows an operator to be made of, the
26
+ # widest operator alphabet of the three; op admits nothing else, so a
27
+ # letter, a space or a quote never reaches the SQL as an operator.
28
+ # @private
29
+ OPERATOR = %r{\A[+\-*/<>=~!@\#%^&|`?]+\z}
33
30
 
34
31
  def self.check_name(name, pattern, what)
35
32
  return name if pattern.match?(name.to_s)
36
33
  raise ArgumentError, "#{name.inspect} is not a plain #{what}"
37
34
  end
38
35
 
36
+ # A Ruby document or boolean written where JSON is wanted. Taken as it
37
+ # is, a document would be the string that spells it and a boolean
38
+ # SQLite's own 1. SQLite's json() marks the literal for the JSON
39
+ # functions; the MySQL family, which has no json(), reads it with
40
+ # JSON_EXTRACT.
41
+ def self.json_argument(value, model)
42
+ Dialect.for(model).json_argument(value, model)
43
+ end
44
+
45
+ # The conditions a column or an expression can be put in. Every one
46
+ # gives back a condition that combines with `&`, `|` and `!`, and the
47
+ # comparisons quote a Ruby value on the right the way Active Record
48
+ # does, or take a column, an expression or a subquery there.
49
+ #
50
+ # @example
51
+ # Author.where { :age.between?(20, 40) & :name.like?("A%") }
52
+ # Author.where { :id.in?(Post.select(:author_id)) }
53
+ #
39
54
  # Predicate builders shared by symbols, qualified columns and
40
55
  # expressions. Imported into the Symbol refinement with
41
56
  # Refinement#import_methods, so every method must be defined with def.
42
57
  module Predications
58
+ # `=`. 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.
59
+ # @return [AST::Predicate]
60
+ # @example
61
+ # Author.where { :name == "alice" }
62
+ # Author.where { :age == Author.select { max(:age) } }
63
+ #
43
64
  # == and != mean SQL = and <>, and = NULL is never true there, so nil
44
65
  # is rejected rather than silently rewritten to IS NULL. null? builds
45
66
  # its node directly and stays clear of this check.
@@ -50,6 +71,8 @@ module ActiveRecord
50
71
  Comparison.new(self, :==, other)
51
72
  end
52
73
 
74
+ # `!=`; `nil` is refused, as with `==`.
75
+ # @return [AST::Predicate]
53
76
  def !=(other)
54
77
  if other.nil?
55
78
  raise ArgumentError, "!= does not take nil; use !null? instead"
@@ -57,30 +80,50 @@ module ActiveRecord
57
80
  Comparison.new(self, :!=, other)
58
81
  end
59
82
 
83
+ # `>`.
84
+ # @return [AST::Predicate]
60
85
  def >(other)
61
86
  Comparison.new(self, :>, other)
62
87
  end
63
88
 
89
+ # `>=`.
90
+ # @return [AST::Predicate]
64
91
  def >=(other)
65
92
  Comparison.new(self, :>=, other)
66
93
  end
67
94
 
95
+ # `<`.
96
+ # @return [AST::Predicate]
68
97
  def <(other)
69
98
  Comparison.new(self, :<, other)
70
99
  end
71
100
 
101
+ # `<=`.
102
+ # @return [AST::Predicate]
72
103
  def <=(other)
73
104
  Comparison.new(self, :<=, other)
74
105
  end
75
106
 
107
+ # A regular expression match: `~` on PostgreSQL, `REGEXP` on MySQL, and what the adapter has elsewhere. A Ruby Regexp's source is the pattern.
108
+ # @param pattern [Regexp, String]
109
+ # @return [AST::Predicate]
110
+ # @example
111
+ # Author.where { :name =~ /^A/ }
76
112
  def =~(pattern)
77
113
  Match.new(self, pattern)
78
114
  end
79
115
 
116
+ # The negated regular expression match.
117
+ # @return [AST::Predicate]
80
118
  def !~(pattern)
81
119
  Match.new(self, pattern, negated: true)
82
120
  end
83
121
 
122
+ # `IS NULL`.
123
+ # @return [AST::Predicate]
124
+ # @example
125
+ # Author.where { :country.null? }
126
+ #
84
127
  # `!` negates any predicate, so these are here for the four that SQL
85
128
  # spells for itself: IS NOT NULL rather than NOT (... IS NULL), and
86
129
  # likewise NOT IN and NOT LIKE. They mean the same thing either way,
@@ -89,10 +132,17 @@ module ActiveRecord
89
132
  Comparison.new(self, :==, nil)
90
133
  end
91
134
 
135
+ # `IS NOT NULL`.
136
+ # @return [AST::Predicate]
92
137
  def not_null?
93
138
  Comparison.new(self, :!=, nil)
94
139
  end
95
140
 
141
+ # `IS TRUE`: true of the rows where the boolean is true, false where it is false or NULL -- where `== true` would be NULL.
142
+ # @return [AST::Predicate]
143
+ # @example
144
+ # Post.where { :published.true? }
145
+ #
96
146
  # IS TRUE and IS FALSE differ from a comparison against the literal in
97
147
  # what they make of NULL: `flag = TRUE` is itself NULL there, and a
98
148
  # NULL predicate selects nothing, while these two answer false. So the
@@ -102,34 +152,62 @@ module ActiveRecord
102
152
  TruthValue.new(self, true)
103
153
  end
104
154
 
155
+ # `IS NOT TRUE`: keeps the NULL rows that `!(:flag == true)` drops.
156
+ # @return [AST::Predicate]
105
157
  def not_true?
106
158
  TruthValue.new(self, true, negated: true)
107
159
  end
108
160
 
161
+ # `IS FALSE`.
162
+ # @return [AST::Predicate]
109
163
  def false?
110
164
  TruthValue.new(self, false)
111
165
  end
112
166
 
167
+ # `IS NOT FALSE`.
168
+ # @return [AST::Predicate]
113
169
  def not_false?
114
170
  TruthValue.new(self, false, negated: true)
115
171
  end
116
172
 
173
+ # `IN (...)`: an array of values, a range, or a relation as a subquery.
174
+ # @param values [Array, Range, ActiveRecord::Relation]
175
+ # @return [AST::Predicate]
176
+ # @example
177
+ # Author.where { :country.in?(%w[JP US]) }
178
+ # Author.where { :id.in?(Post.select(:author_id)) }
117
179
  def in?(values)
118
180
  In.new(self, values)
119
181
  end
120
182
 
183
+ # `NOT IN (...)`.
184
+ # @return [AST::Predicate]
121
185
  def not_in?(values)
122
186
  In.new(self, values, negated: true)
123
187
  end
124
188
 
189
+ # `BETWEEN min AND max`, with either end a value, a column or an expression.
190
+ # @return [AST::Predicate]
191
+ # @example
192
+ # Author.where { :age.between?(20, 40) }
193
+ #
194
+ # Not min..max: an endpoint may be an expression, which Range would
195
+ # refuse to hold, since expressions do not compare among themselves.
125
196
  def between?(min, max)
126
- In.new(self, min..max)
197
+ In.new(self, In::QuotedRange.new(min, max, false))
127
198
  end
128
199
 
200
+ # `NOT BETWEEN min AND max`.
201
+ # @return [AST::Predicate]
129
202
  def not_between?(min, max)
130
- In.new(self, min..max, negated: true)
203
+ In.new(self, In::QuotedRange.new(min, max, false), negated: true)
131
204
  end
132
205
 
206
+ # `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`.
207
+ # @return [AST::Case::When]
208
+ # @example
209
+ # Author.select { :country.when("JP").then("Japan").else("elsewhere").as(:where) }
210
+ #
133
211
  # CASE with this as the operand, compared against each `when`:
134
212
  # `:age.when(10).then(1).else(0)`. The other shape, where each `when`
135
213
  # carries its own condition, starts at `case_when`.
@@ -137,22 +215,38 @@ module ActiveRecord
137
215
  Case.new(self).when(value, &block)
138
216
  end
139
217
 
218
+ # `LIKE pattern`, the pattern as written: `%` and `_` are its wildcards.
219
+ # @param pattern [String]
220
+ # @return [AST::Predicate]
221
+ # @example
222
+ # Author.where { :name.like?("A%") }
140
223
  def like?(pattern)
141
224
  Like.new(self, pattern)
142
225
  end
143
226
 
227
+ # `NOT LIKE pattern`.
228
+ # @return [AST::Predicate]
144
229
  def not_like?(pattern)
145
230
  Like.new(self, pattern, negated: true)
146
231
  end
147
232
 
233
+ # A case-insensitive `LIKE`: `ILIKE` on PostgreSQL, and `LIKE` over both sides lower-cased elsewhere.
234
+ # @return [AST::Predicate]
148
235
  def ilike?(pattern)
149
236
  Like.new(self, pattern, nil, case_sensitive: false)
150
237
  end
151
238
 
239
+ # The negated case-insensitive `LIKE`.
240
+ # @return [AST::Predicate]
152
241
  def not_ilike?(pattern)
153
242
  Like.new(self, pattern, nil, case_sensitive: false, negated: true)
154
243
  end
155
244
 
245
+ # Case-insensitive equality: `LOWER(column) = LOWER(value)`.
246
+ # @return [AST::Predicate]
247
+ # @example
248
+ # Author.where { :name.casecmp?("Alice") }
249
+ #
156
250
  # Case-insensitive equality, folded on both sides rather than left to
157
251
  # the collation, so it means the same thing on every adapter.
158
252
  def casecmp?(value)
@@ -163,34 +257,56 @@ module ActiveRecord
163
257
  Function.new("LOWER", [value]))
164
258
  end
165
259
 
260
+ # `IS DISTINCT FROM`: `!=` that treats NULL as a value. `IS NOT` on SQLite, `NOT <=>` on MySQL.
261
+ # @return [AST::Predicate]
262
+ #
166
263
  # Null-safe comparison: unlike = and <>, these treat NULL as a value,
167
264
  # so not_distinct_from? is the one equality that may take nil.
168
265
  def distinct_from?(value)
169
266
  DistinctFrom.new(self, value, negated: true)
170
267
  end
171
268
 
269
+ # `IS NOT DISTINCT FROM`: `=` that treats NULL as a value, so this is the one equality that takes `nil`.
270
+ # @return [AST::Predicate]
271
+ # @example
272
+ # Author.where { :country.not_distinct_from?(nil) }
172
273
  def not_distinct_from?(value)
173
274
  DistinctFrom.new(self, value)
174
275
  end
175
276
 
277
+ # `LIKE 'prefix%'`, the prefix escaped so that a `%` or `_` in it is itself; several prefixes are `OR`ed.
278
+ # @return [AST::Predicate]
279
+ # @example
280
+ # Author.where { :name.start_with?("A", "B") }
176
281
  def start_with?(*prefixes)
177
282
  if prefixes.empty?
178
283
  raise ArgumentError, "start_with? needs at least one prefix"
179
284
  end
180
- Like.any(self, prefixes.map {|prefix| "#{Like.escape(prefix)}%" })
285
+ Like.any(self, prefixes.map { |prefix| "#{Like.escape(prefix)}%" })
181
286
  end
182
287
 
288
+ # `LIKE '%suffix'`, escaped as {#start_with?} escapes.
289
+ # @return [AST::Predicate]
183
290
  def end_with?(*suffixes)
184
291
  if suffixes.empty?
185
292
  raise ArgumentError, "end_with? needs at least one suffix"
186
293
  end
187
- Like.any(self, suffixes.map {|suffix| "%#{Like.escape(suffix)}" })
294
+ Like.any(self, suffixes.map { |suffix| "%#{Like.escape(suffix)}" })
188
295
  end
189
296
 
297
+ # `LIKE '%substring%'`, escaped as {#start_with?} escapes.
298
+ # @return [AST::Predicate]
299
+ # @example
300
+ # Post.where { :title.include?("ruby") }
190
301
  def include?(substring)
191
302
  Like.new(self, "%#{Like.escape(substring)}%", Like::ESCAPE)
192
303
  end
193
304
 
305
+ # Whether a PostgreSQL array column holds the element: `@> ARRAY[element]`.
306
+ # @return [AST::Predicate]
307
+ # @example
308
+ # Post.where { :tags.member?("ruby") }
309
+ #
194
310
  # The array comparisons carry the meaning of their Ruby namesakes.
195
311
  # member? is Enumerable's element test, so an Array argument is
196
312
  # rejected rather than quietly meaning something Array#member? does
@@ -203,18 +319,32 @@ module ActiveRecord
203
319
  ArrayPredicate.new(self, :"@>", [element])
204
320
  end
205
321
 
322
+ # Whether an array column holds every element given: `@>`.
323
+ # @return [AST::Predicate]
206
324
  def superset?(elements)
207
325
  ArrayPredicate.new(self, :"@>", ArrayPredicate.elements(elements, "superset?"))
208
326
  end
209
327
 
328
+ # Whether every element of an array column is among those given: `<@`.
329
+ # @return [AST::Predicate]
210
330
  def subset?(elements)
211
331
  ArrayPredicate.new(self, :"<@", ArrayPredicate.elements(elements, "subset?"))
212
332
  end
213
333
 
334
+ # Whether an array column and the elements given share any: `&&`.
335
+ # @return [AST::Predicate]
336
+ # @example
337
+ # Post.where { :tags.intersect?(%w[ruby sql]) }
214
338
  def intersect?(elements)
215
339
  ArrayPredicate.new(self, :"&&", ArrayPredicate.elements(elements, "intersect?"))
216
340
  end
217
341
 
342
+ # 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.
343
+ # @return [AST::JsonPath]
344
+ # @example
345
+ # Doc.select { :meta.dig(:author).as(:author) }
346
+ # Doc.where { :meta.dig(:author).key?(:name) }
347
+ #
218
348
  # Reading inside a JSON document, by the name of what Hash does. A
219
349
  # string or symbol steps into an object, an integer into an array, and
220
350
  # what comes back is still JSON, the way Hash#dig hands back the
@@ -222,13 +352,22 @@ module ActiveRecord
222
352
  # the JSON questions. dig_text gives the value as text instead,
223
353
  # which is what a comparison wants.
224
354
  def dig(*path)
225
- JsonPath.new(self, path, as_json: true)
355
+ JsonPath.new(self, path)
226
356
  end
227
357
 
358
+ # 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.
359
+ # @return [AST::JsonPath]
360
+ # @example
361
+ # Doc.where { :meta.dig_text(:author, :name) == "alice" }
228
362
  def dig_text(*path)
229
- JsonPath.new(self, path)
363
+ JsonPath.new(self, path, json_value: false)
230
364
  end
231
365
 
366
+ # The document without the keys given, as Hash#except gives it; an expression, for `update_all` to write back.
367
+ # @return [AST::JsonExcept]
368
+ # @example
369
+ # Doc.update_all { { meta: :meta.except(:draft) } }
370
+ #
232
371
  # Keys taken out of a JSON document, by the name of what Hash does,
233
372
  # and taking keys as Hash#except takes them. Like bury it gives back
234
373
  # the document changed rather than writing it anywhere.
@@ -236,6 +375,11 @@ module ActiveRecord
236
375
  JsonExcept.new(self, keys)
237
376
  end
238
377
 
378
+ # The document with a value set at a path, as {#dig} reads one; an expression, for `update_all` to write back.
379
+ # @return [AST::JsonSet]
380
+ # @example
381
+ # Doc.update_all { { meta: :meta.bury(:author, :name, "alice") } }
382
+ #
239
383
  # What dig reads, bury sets: the last argument is the value and the
240
384
  # rest are the path to it. The document comes back changed rather
241
385
  # than being written anywhere, which update_all is for.
@@ -243,40 +387,78 @@ module ActiveRecord
243
387
  JsonSet.new(self, path, value)
244
388
  end
245
389
 
390
+ # Whether the document contains the Ruby document given, which SQL calls containment: `@>` on PostgreSQL, `JSON_CONTAINS` on MySQL. SQLite and MariaDB have none.
391
+ # @return [AST::Predicate]
392
+ # @example
393
+ # Doc.where { :meta.contains?(author: { name: "alice" }) }
394
+ #
246
395
  # Whether the document holds what is given, which SQL calls
247
396
  # containment. SQLite has no equivalent.
248
397
  def contains?(value)
249
398
  JsonContains.new(self, value)
250
399
  end
251
400
 
401
+ # Whether the object has the key, as Hash#key? asks.
402
+ # @return [AST::Predicate]
403
+ # @example
404
+ # Doc.where { :meta.key?(:author) }
405
+ #
252
406
  # Whether the key is there at all, as Hash#key? asks. Hash has
253
407
  # has_key? too; one name is enough, and this is the one Ruby's own
254
408
  # style prefers.
255
409
  def key?(key)
256
410
  JsonHasKey.new(self, key)
257
411
  end
412
+
413
+ # The keys of the object as a JSON array, as Hash#keys gives them. Oracle has none.
414
+ # @return [AST::JsonKeys]
415
+ #
416
+ # The keys of the document, as Hash#keys gives them: a JSON array.
417
+ def keys
418
+ JsonKeys.new(self)
419
+ end
258
420
  end
259
421
 
422
+ # The arithmetic and the bitwise operators on a column or an
423
+ # expression. Ruby puts all of them above the comparisons, so
424
+ # `:price * :quantity > 100` groups the way it reads, and a number on
425
+ # the left -- `20 - :quantity` -- builds the same expression.
426
+ #
427
+ # @example
428
+ # LineItem.where { :price * :quantity > 1000 }
429
+ # LineItem.select { (:flags & 4).as(:featured) }
430
+ #
260
431
  # Arithmetic builders shared by symbols, qualified columns and
261
432
  # expressions. Imported into the Symbol refinement like Predications,
262
433
  # so every method must be defined with def.
263
434
  module Arithmetics
435
+ # `+`; with an Active Support duration on the right, a date moved: `:due_on + 3.days`.
436
+ # @return [AST::Arithmetic]
264
437
  def +(other)
265
438
  Arithmetic.new(self, :+, other)
266
439
  end
267
440
 
441
+ # `-`; with a duration on the right, a date moved back.
442
+ # @return [AST::Arithmetic]
268
443
  def -(other)
269
444
  Arithmetic.new(self, :-, other)
270
445
  end
271
446
 
447
+ # `*`.
448
+ # @return [AST::Arithmetic]
272
449
  def *(other)
273
450
  Arithmetic.new(self, :*, other)
274
451
  end
275
452
 
453
+ # `/`.
454
+ # @return [AST::Arithmetic]
276
455
  def /(other)
277
456
  Arithmetic.new(self, :/, other)
278
457
  end
279
458
 
459
+ # Bitwise AND. `&` between two conditions is AND, which leaves this free to mean the SQL operator.
460
+ # @return [AST::Bitwise]
461
+ #
280
462
  # SQL's bitwise operators. & and | are AND and OR between conditions
281
463
  # and are defined there, which is what leaves them free to mean here
282
464
  # what SQL means by them. Ruby's precedence puts all six above the
@@ -285,27 +467,90 @@ module ActiveRecord
285
467
  Bitwise.new(self, :&, other)
286
468
  end
287
469
 
470
+ # Bitwise OR.
471
+ # @return [AST::Bitwise]
288
472
  def |(other)
289
473
  Bitwise.new(self, :|, other)
290
474
  end
291
475
 
476
+ # Bitwise XOR: `#` on PostgreSQL, `^` on MySQL, and the two operations it is made of on SQLite.
477
+ # @return [AST::Bitwise]
292
478
  def ^(other)
293
479
  Bitwise.new(self, :^, other)
294
480
  end
295
481
 
482
+ # A shift left.
483
+ # @return [AST::Bitwise]
296
484
  def <<(other)
297
485
  Bitwise.new(self, :<<, other)
298
486
  end
299
487
 
488
+ # A shift right.
489
+ # @return [AST::Bitwise]
300
490
  def >>(other)
301
491
  Bitwise.new(self, :>>, other)
302
492
  end
303
493
 
494
+ # Bitwise NOT.
495
+ # @return [AST::BitwiseNot]
304
496
  def ~
305
497
  BitwiseNot.new(self)
306
498
  end
307
499
  end
308
500
 
501
+ # Arithmetic with the number on the left, imported into the numeric
502
+ # refinements: 20 - :quantity builds what :quantity + 20 builds. Only
503
+ # a column or an expression on the right means a query; anything else
504
+ # goes back to the number through super, so 1 + 2 is 3 inside a block
505
+ # too.
506
+ # @private
507
+ module NumericArithmetics
508
+ def +(other)
509
+ return super unless other.is_a?(::Symbol) || other.is_a?(Node)
510
+ Arithmetic.new(self, :+, other)
511
+ end
512
+
513
+ def -(other)
514
+ return super unless other.is_a?(::Symbol) || other.is_a?(Node)
515
+ Arithmetic.new(self, :-, other)
516
+ end
517
+
518
+ def *(other)
519
+ return super unless other.is_a?(::Symbol) || other.is_a?(Node)
520
+ Arithmetic.new(self, :*, other)
521
+ end
522
+
523
+ def /(other)
524
+ return super unless other.is_a?(::Symbol) || other.is_a?(Node)
525
+ Arithmetic.new(self, :/, other)
526
+ end
527
+
528
+ def &(other)
529
+ return super unless other.is_a?(::Symbol) || other.is_a?(Node)
530
+ Bitwise.new(self, :&, other)
531
+ end
532
+
533
+ def |(other)
534
+ return super unless other.is_a?(::Symbol) || other.is_a?(Node)
535
+ Bitwise.new(self, :|, other)
536
+ end
537
+
538
+ def ^(other)
539
+ return super unless other.is_a?(::Symbol) || other.is_a?(Node)
540
+ Bitwise.new(self, :^, other)
541
+ end
542
+
543
+ def <<(other)
544
+ return super unless other.is_a?(::Symbol) || other.is_a?(Node)
545
+ Bitwise.new(self, :<<, other)
546
+ end
547
+
548
+ def >>(other)
549
+ return super unless other.is_a?(::Symbol) || other.is_a?(Node)
550
+ Bitwise.new(self, :>>, other)
551
+ end
552
+ end
553
+
309
554
  class Node
310
555
  # The model travels with the table because some SQL cannot be written
311
556
  # without knowing the adapter, and a node is built before anything
@@ -316,38 +561,86 @@ module ActiveRecord
316
561
  raise ScriptError, "subclass must override this method"
317
562
  end
318
563
 
564
+ # The expression under an alias, as {BlockSyntax#as} gives a column
565
+ # one.
566
+ # @return [AST::As]
319
567
  def as(alias_name, quote: true)
320
568
  As.new(self, alias_name, quote: quote)
321
569
  end
322
570
 
571
+ # An ascending ordering by the expression.
572
+ # @return [AST::Ordering]
323
573
  def asc
324
574
  Ordering.new(self, :asc)
325
575
  end
326
576
 
577
+ # A descending ordering by the expression.
578
+ # @return [AST::Ordering]
327
579
  def desc
328
580
  Ordering.new(self, :desc)
329
581
  end
330
582
 
583
+ # The expression under a collation, as {BlockSyntax#collate}.
584
+ # @return [AST::Collate]
585
+ def collate(name)
586
+ Collate.new(self, name)
587
+ end
588
+
331
589
  private
590
+ # Resolves an operand denoting a column or an expression. A number
591
+ # rides along for Arel to write out, which it can do for Integer and
592
+ # Float alone: a BigDecimal is quoted, which the adapter spells as
593
+ # the exact decimal, and a Rational, which no decimal spells exactly,
594
+ # is refused.
595
+ def to_arel_operand(operand, table, model)
596
+ case operand
597
+ when Node then operand.to_arel(table, model)
598
+ when :* then Arel.star
599
+ when Symbol then table[operand]
600
+ when ::BigDecimal, ::Rational then quote_number(operand)
601
+ else operand
602
+ end
603
+ end
332
604
 
333
- # Resolves an operand denoting a column or an expression.
334
- def to_arel_operand(operand, table, model)
335
- case operand
336
- when Node then operand.to_arel(table, model)
337
- when :* then Arel.star
338
- when Symbol then table[operand]
339
- else operand
605
+ # A bare symbol is a column in every position, the value side of a
606
+ # comparison included. The name is checked against the model, since
607
+ # a name it has no column for is almost always an enum value spelled
608
+ # as a symbol -- which, taken as a column, would quietly compare
609
+ # against nothing anyone meant.
610
+ def column_operand(name, table, model)
611
+ unless model.column_names.include?(name.to_s)
612
+ raise ArgumentError,
613
+ "#{name.inspect} is no column of #{model.table_name}; an enum " \
614
+ "value is written as its string, a column of another table " \
615
+ "qualified"
616
+ end
617
+ table[name]
340
618
  end
341
- end
342
619
 
343
- # Resolves a function argument: a column or an expression as above,
344
- # anything else a value to be quoted.
345
- def to_arel_argument(arg, table, model)
346
- case arg
347
- when Node, Symbol then to_arel_operand(arg, table, model)
348
- else Arel::Nodes.build_quoted(arg)
620
+ # A number compares as itself, the way a bound ? does: the typed path
621
+ # would cast 99.5 against an integer column to 99 and quietly move
622
+ # the boundary. Everything else keeps the column's own
623
+ # serialization -- an enum's name, a time's zone, a custom type's
624
+ # scaling.
625
+ def quote_number(value)
626
+ case value
627
+ when ::Rational
628
+ raise ArgumentError,
629
+ "a Rational has no exact SQL spelling; to_d says the decimal meant"
630
+ when ::Integer, ::Float, ::BigDecimal
631
+ Arel::Nodes.build_quoted(value)
632
+ else value
633
+ end
634
+ end
635
+
636
+ # Resolves a function argument: a column or an expression as above,
637
+ # anything else a value to be quoted.
638
+ def to_arel_argument(arg, table, model)
639
+ case arg
640
+ when Node, Symbol, ::Rational then to_arel_operand(arg, table, model)
641
+ else Arel::Nodes.build_quoted(arg)
642
+ end
349
643
  end
350
- end
351
644
  end
352
645
 
353
646
  class Predicate < Node
@@ -366,11 +659,11 @@ module ActiveRecord
366
659
 
367
660
  # A literal standing where an expression would: `select { value(0).as(:depth) }`.
368
661
  #
369
- # Values reach the SQL quoted wherever they appear as an operand, but the
370
- # top of a select list is Active Record's, and a bare string there is SQL
371
- # rather than a string. Saying `value` is how you ask for the other
372
- # meaning, and it carries the predications with it, so a literal can be
373
- # compared and combined like anything else.
662
+ # Values reach the SQL quoted wherever they appear as an operand, but a
663
+ # bare Ruby literal at the top of a select list is refused as saying
664
+ # nothing. `value` is the spelling that quotes it there, and it carries
665
+ # the predications with it, so a literal can be compared and combined
666
+ # like anything else.
374
667
  class Value < Node
375
668
  include Predications
376
669
  include Arithmetics
@@ -386,6 +679,43 @@ module ActiveRecord
386
679
  end
387
680
  end
388
681
 
682
+ # SQL as written: `sql("length(name) > ?", 10)`. The ? and :name
683
+ # placeholders take quoted values through sanitize_sql_array, which
684
+ # needs the connection, so the binds wait here until the model is
685
+ # known. Without binds the statement passes untouched -- which is
686
+ # what leaves PostgreSQL's ? operators writable, since only the
687
+ # positional-bind rewrite reads ? as a placeholder.
688
+ #
689
+ # As an operand the statement is parenthesized: its precedence is
690
+ # whatever was written inside. The top of a select list gets it bare,
691
+ # through field_arel, where parentheses would refuse an alias written
692
+ # into the string.
693
+ class Sql < Node
694
+ include Predications
695
+ include Arithmetics
696
+
697
+ attr_reader :statement, :binds
698
+
699
+ def initialize(statement, binds)
700
+ unless statement.is_a?(::String)
701
+ raise ArgumentError,
702
+ "sql takes the statement as a string, not #{statement.inspect}"
703
+ end
704
+ @statement = statement
705
+ @binds = binds
706
+ end
707
+
708
+ def to_arel(_table, model)
709
+ Arel::Nodes::Grouping.new(field_arel(model))
710
+ end
711
+
712
+ def field_arel(model)
713
+ return Arel.sql(statement) if binds.empty?
714
+
715
+ Arel.sql(model.sanitize_sql_array([statement, *binds]))
716
+ end
717
+ end
718
+
389
719
  # CASE, in both of the shapes SQL has for it. With an operand, each
390
720
  # `when` is something to compare it against; without one, each `when` is
391
721
  # a condition of its own.
@@ -409,16 +739,23 @@ module ActiveRecord
409
739
  @default = default
410
740
  end
411
741
 
742
+ # The next `WHEN`: a value to compare the operand against, or a condition as a value or a block.
743
+ # @return [AST::Case::When]
412
744
  def when(value = nil, &block)
413
745
  Pending.new(self, Case.argument(:when, value, block))
414
746
  end
415
747
 
748
+ # `THEN`, which belongs after a `when`; here it says so.
749
+ # @raise [ArgumentError]
750
+ #
416
751
  # Kernel#then is on every object, so `then` in the wrong place would be
417
752
  # answered by it -- with no block, silently, with an Enumerator.
418
753
  def then(*)
419
754
  raise ArgumentError, "then follows a when, and there is none to follow here"
420
755
  end
421
756
 
757
+ # `ELSE value`, as a value or a block, closing the CASE. Without one the CASE gives NULL where no `when` matched.
758
+ # @return [AST::Case]
422
759
  def else(value = nil, &block)
423
760
  Case.new(operand, whens, Case.argument(:else, value, block))
424
761
  end
@@ -457,6 +794,8 @@ module ActiveRecord
457
794
  @condition = condition
458
795
  end
459
796
 
797
+ # `THEN value`, as a value or a block, for the `when` before it.
798
+ # @return [AST::Case]
460
799
  def then(value = nil, &block)
461
800
  Case.new(@kase.operand,
462
801
  @kase.whens + [[@condition, Case.argument(:then, value, block)]],
@@ -485,24 +824,24 @@ module ActiveRecord
485
824
  # quoted so that a comma or a brace in a key is part of it. except
486
825
  # writes its keys the same way, which are steps of no one path.
487
826
  def steps_array(steps = path)
488
- "{#{steps.map {|step| %("#{escape_step(step)}") }.join(',')}}"
827
+ "{#{steps.map { |step| %("#{escape_step(step)}") }.join(',')}}"
489
828
  end
490
829
 
491
830
  # MySQL and SQLite take a path expression instead, where an integer is
492
831
  # a subscript and a name that is not plain has to be quoted.
493
832
  def dollar_path
494
- path.inject(+'$') {|so_far, step| so_far << dollar_step(step) }
833
+ path.inject(+"$") { |so_far, step| so_far << dollar_step(step) }
495
834
  end
496
835
 
497
836
  def dollar_step(step)
498
837
  return "[#{step}]" if step.is_a?(::Integer)
499
838
  name = step.to_s
500
- '.' + (name.match?(/\A[[:alpha:]_][[:alnum:]_]*\z/) ?
839
+ "." + (name.match?(/\A[[:alpha:]_][[:alnum:]_]*\z/) ?
501
840
  name : %("#{escape_step(step)}"))
502
841
  end
503
842
 
504
843
  def escape_step(step)
505
- step.to_s.gsub('\\', '\\\\').gsub('"', '\\"')
844
+ step.to_s.gsub("\\", "\\\\").gsub('"', '\\"')
506
845
  end
507
846
  end
508
847
 
@@ -521,11 +860,12 @@ module ActiveRecord
521
860
  # `dig_text(:flag) == true` is true, an error, and false. cast is what
522
861
  # says which type was meant, and then all three agree.
523
862
  #
524
- # dig is refused the other way about: the JSON for a string carries
525
- # its quotes, so `dig(:name) == 'alice'` is false on SQLite, an
526
- # error on PostgreSQL and true on MySQL. dig_text is the one that
527
- # gives the value. What bury and except give back is JSON as dig's
528
- # is, and is refused the same way.
863
+ # dig, bury and except give JSON, and a JSON comparison belongs to the
864
+ # JSON types -- jsonb and MySQL's -- where numbers compare as numbers
865
+ # and documents structurally, key order and spelling aside. The Ruby
866
+ # value goes in as a JSON literal, and the adapters without such a
867
+ # type refuse it from JsonLiteral when the SQL is written, which is
868
+ # when the adapter is known.
529
869
  #
530
870
  # A string against dig_text, and anything the block itself built -- a
531
871
  # column, a function, another dug value -- go through untouched.
@@ -537,15 +877,14 @@ module ActiveRecord
537
877
  module JsonComparable
538
878
  %i[== != < <= > >=].each do |operator|
539
879
  define_method(operator) do |other|
540
- check_comparable(other)
541
- super(other)
880
+ super(comparison_value(other))
542
881
  end
543
882
  end
544
883
 
545
- def in?(values) = super(check_each(values))
546
- def not_in?(values) = super(check_each(values))
547
- def between?(min, max) = super(*check_each([min, max]))
548
- def not_between?(min, max) = super(*check_each([min, max]))
884
+ def in?(values) = super(comparison_set(values))
885
+ def not_in?(values) = super(comparison_set(values))
886
+ def between?(min, max) = super(comparison_value(min), comparison_value(max))
887
+ def not_between?(min, max) = super(comparison_value(min), comparison_value(max))
549
888
 
550
889
  %i[+ - * / & | ^ << >>].each do |operator|
551
890
  define_method(operator) do |_other|
@@ -558,38 +897,71 @@ module ActiveRecord
558
897
  end
559
898
 
560
899
  private
900
+ # nil is left to the comparison itself, which says to use null?, and
901
+ # so is anything the block built rather than wrote as a literal.
902
+ def comparison_value(other)
903
+ return other if other.nil? || other.is_a?(Node) || other.is_a?(::Symbol) ||
904
+ other.is_a?(Arel::Nodes::Node) ||
905
+ other.is_a?(Arel::Attributes::Attribute) ||
906
+ other.is_a?(ActiveRecord::Relation)
907
+ return json_literal(other) if json_value?
908
+ return other if other.is_a?(::String)
909
+
910
+ raise ArgumentError,
911
+ "dig_text gives text, and comparing it with #{other.inspect} means " \
912
+ "something different on every adapter; cast it to the type meant"
913
+ end
561
914
 
562
- # nil is left to the comparison itself, which says to use null?, and
563
- # so is anything the block built rather than wrote as a literal.
564
- def check_comparable(other)
565
- return if other.nil? || other.is_a?(Node) || other.is_a?(::Symbol) ||
566
- other.is_a?(Arel::Nodes::Node) ||
567
- other.is_a?(Arel::Attributes::Attribute) ||
568
- other.is_a?(ActiveRecord::Relation)
569
- return if other.is_a?(::String) && !as_json
915
+ def json_literal(other)
916
+ case other
917
+ when ::String, ::Integer, ::Float, ::BigDecimal, true, false, ::Hash, ::Array
918
+ JsonLiteral.new(other)
919
+ when ::Rational
920
+ raise ArgumentError,
921
+ "a Rational has no exact SQL spelling; to_d says the decimal meant"
922
+ else
923
+ raise ArgumentError,
924
+ "#{json_source} gives JSON, and #{other.inspect} has no JSON " \
925
+ "spelling; dig_text gives the value"
926
+ end
927
+ end
570
928
 
571
- raise ArgumentError, as_json ?
572
- "#{json_source} gives JSON, and comparing it with #{other.inspect} " \
573
- "means something different on every adapter; dig_text gives the value" :
574
- "dig_text gives text, and comparing it with #{other.inspect} means " \
575
- "something different on every adapter; cast it to the type meant"
576
- end
929
+ def comparison_set(values)
930
+ case values
931
+ when ActiveRecord::Relation then values
932
+ when ::Range
933
+ In::QuotedRange.new(comparison_value(values.begin),
934
+ comparison_value(values.end), values.exclude_end?)
935
+ else values.map { |value| comparison_value(value) }
936
+ end
937
+ end
577
938
 
578
- def check_each(values)
579
- case values
580
- when ActiveRecord::Relation then values
581
- when ::Range then [values.begin, values.end].each {|v| check_comparable(v) }
582
- else values.each {|value| check_comparable(value) }
939
+ def arithmetic_refusal(operator)
940
+ json_value? ?
941
+ "#{json_source} gives JSON, and #{operator} on it means something " \
942
+ "different on every adapter; cast dig_text to the type meant" :
943
+ "dig_text gives text, and #{operator} on it means something " \
944
+ "different on every adapter; cast it to the type meant"
583
945
  end
584
- values
946
+ end
947
+
948
+ # A Ruby value on the JSON side of a comparison, which jsonb and
949
+ # MySQL's JSON type answer alike: numbers compare as numbers and
950
+ # documents structurally. SQLite and MariaDB have only the text of
951
+ # each -- spelling and key order deciding what equality means -- and
952
+ # refuse here. PostgreSQL needs no cast, an untyped literal beside a
953
+ # jsonb operand coercing to jsonb; MySQL is told CAST(... AS JSON),
954
+ # since a bare string beside JSON would be a JSON string, and every
955
+ # string outranks every number in its ordering.
956
+ class JsonLiteral < Node
957
+ attr_reader :value
958
+
959
+ def initialize(value)
960
+ @value = value
585
961
  end
586
962
 
587
- def arithmetic_refusal(operator)
588
- as_json ?
589
- "#{json_source} gives JSON, and #{operator} on it means something " \
590
- "different on every adapter; cast dig_text to the type meant" :
591
- "dig_text gives text, and #{operator} on it means something " \
592
- "different on every adapter; cast it to the type meant"
963
+ def to_arel(_table, model)
964
+ Dialect.for(model).json_literal(Arel::Nodes.build_quoted(JSON.generate(value)), model)
593
965
  end
594
966
  end
595
967
 
@@ -600,9 +972,9 @@ module ActiveRecord
600
972
  # again is where they part company: SQLite parses it back and MySQL
601
973
  # takes it as written, where PostgreSQL has no such function for text.
602
974
  module JsonDocument
603
- %i[dig dig_text key? contains? bury except].each do |name|
975
+ %i[dig dig_text key? keys contains? bury except].each do |name|
604
976
  define_method(name) do |*args|
605
- unless as_json
977
+ unless json_value?
606
978
  raise ArgumentError,
607
979
  "dig_text gives text, and #{name} reads JSON; dig keeps it"
608
980
  end
@@ -611,6 +983,22 @@ module ActiveRecord
611
983
  end
612
984
  end
613
985
 
986
+ # JSON the query computes rather than reads out of a document: always
987
+ # a JSON value, with no dig_text counterpart for JsonComparable's
988
+ # advice to name. Included after JsonComparable, whose own
989
+ # arithmetic_refusal it overrides.
990
+ module ComputedJson
991
+ def json_value?
992
+ true
993
+ end
994
+
995
+ private
996
+ def arithmetic_refusal(operator)
997
+ "#{json_source} gives JSON, and #{operator} on it means " \
998
+ "something different on every adapter"
999
+ end
1000
+ end
1001
+
614
1002
  class JsonPath < Node
615
1003
  include Predications
616
1004
  include Arithmetics
@@ -618,40 +1006,28 @@ module ActiveRecord
618
1006
  include JsonComparable
619
1007
  include JsonDocument
620
1008
 
621
- attr_reader :operand, :path, :as_json
1009
+ attr_reader :operand, :path
622
1010
 
623
- def initialize(operand, path, as_json: false)
1011
+ def initialize(operand, path, json_value: true)
624
1012
  @operand = operand
625
- @path = check_steps(path, 'dig')
626
- @as_json = as_json
1013
+ @path = check_steps(path, "dig")
1014
+ @json_value = json_value
1015
+ end
1016
+
1017
+ def json_value?
1018
+ @json_value
627
1019
  end
628
1020
 
629
1021
  def to_arel(table, model)
630
- document = to_arel_operand(operand, table, model)
631
- case AST.adapter_family(model)
632
- when :postgresql
633
- Arel::Nodes::InfixOperation.new(
634
- as_json ? :"#>" : :"#>>", document, Arel::Nodes.build_quoted(steps_array))
635
- when :mysql
636
- extracted = Arel::Nodes::NamedFunction.new(
637
- 'JSON_EXTRACT', [document, Arel::Nodes.build_quoted(dollar_path)])
638
- as_json ? extracted : Arel::Nodes::NamedFunction.new('JSON_UNQUOTE', [extracted])
639
- else
640
- extracted = Arel::Nodes::InfixOperation.new(
641
- as_json ? :"->" : :"->>", document, Arel::Nodes.build_quoted(dollar_path))
642
- # SQLite's ->> gives back the value with its type, where the other
643
- # two give text. Cast so that `dig_text(:n) == '5'` means the
644
- # same thing everywhere, and a number wants a cast everywhere too.
645
- as_json ? extracted : Arel::Nodes::NamedFunction.new(
646
- 'CAST', [Arel::Nodes::As.new(extracted, Arel::Nodes::SqlLiteral.new('text'))])
647
- end
1022
+ Dialect.for(model).json_path(
1023
+ to_arel_operand(operand, table, model),
1024
+ dollar_path, steps_array, json_value?, model)
648
1025
  end
649
1026
 
650
1027
  private
651
-
652
- def json_source
653
- 'dig'
654
- end
1028
+ def json_source
1029
+ "dig"
1030
+ end
655
1031
  end
656
1032
 
657
1033
  # Setting a value inside a JSON document, which is what bury does to what
@@ -666,67 +1042,30 @@ module ActiveRecord
666
1042
 
667
1043
  def initialize(operand, path, value)
668
1044
  @operand = operand
669
- @path = check_steps(path, 'bury')
1045
+ @path = check_steps(path, "bury")
670
1046
  @value = value
671
1047
  end
672
1048
 
673
1049
  # Always JSON, which is what the comparison guard asks.
674
- def as_json
1050
+ def json_value?
675
1051
  true
676
1052
  end
677
1053
 
678
1054
  def to_arel(table, model)
679
- document = to_arel_operand(operand, table, model)
680
- if AST.adapter_family(model) == :postgresql
681
- Arel::Nodes::NamedFunction.new(
682
- 'jsonb_set',
683
- [document, Arel::Nodes.build_quoted(steps_array), postgresql_value(table, model)])
684
- else
685
- Arel::Nodes::NamedFunction.new(
686
- 'JSON_SET',
687
- [document, Arel::Nodes.build_quoted(dollar_path), other_value(table, model)])
688
- end
1055
+ Dialect.for(model).json_set(
1056
+ to_arel_operand(operand, table, model),
1057
+ steps_array, dollar_path, value,
1058
+ (to_arel_operand(value, table, model) if expression?), model)
689
1059
  end
690
1060
 
691
1061
  private
692
-
693
- # jsonb_set takes jsonb, so an expression is turned into it and a Ruby
694
- # value goes in as the JSON that says it -- '"x"' rather than 'x',
695
- # which is not a document at all.
696
- def postgresql_value(table, model)
697
- return Arel::Nodes::NamedFunction.new(
698
- 'to_jsonb', [to_arel_operand(value, table, model)]) if expression?
699
- Arel::Nodes.build_quoted(JSON.generate(value))
700
- end
701
-
702
- # The others take the value as it is, except a whole document or a
703
- # boolean, which go in as JSON: taken as they are, a document would
704
- # be the string that spells it, and a boolean SQLite's own 1.
705
- # SQLite's json() marks the literal for JSON_SET; the MySQL family,
706
- # which has no json(), reads it with JSON_EXTRACT.
707
- def other_value(table, model)
708
- return to_arel_operand(value, table, model) if expression?
709
- unless value.is_a?(::Hash) || value.is_a?(::Array) ||
710
- value == true || value == false
711
- return Arel::Nodes.build_quoted(value)
712
- end
713
-
714
- json = Arel::Nodes.build_quoted(JSON.generate(value))
715
- if AST.adapter_family(model) == :sqlite
716
- Arel::Nodes::NamedFunction.new('json', [json])
717
- else
718
- Arel::Nodes::NamedFunction.new(
719
- 'JSON_EXTRACT', [json, Arel::Nodes.build_quoted('$')])
1062
+ def expression?
1063
+ value.is_a?(Node) || value.is_a?(::Symbol)
720
1064
  end
721
- end
722
-
723
- def expression?
724
- value.is_a?(Node) || value.is_a?(::Symbol)
725
- end
726
1065
 
727
- def json_source
728
- 'bury'
729
- end
1066
+ def json_source
1067
+ "bury"
1068
+ end
730
1069
  end
731
1070
 
732
1071
  # Keys taken out of a JSON document. PostgreSQL subtracts them, the
@@ -743,52 +1082,33 @@ module ActiveRecord
743
1082
  @keys = check_keys(keys)
744
1083
  end
745
1084
 
746
- def as_json
1085
+ def json_value?
747
1086
  true
748
1087
  end
749
1088
 
750
1089
  def to_arel(table, model)
751
- document = to_arel_operand(operand, table, model)
752
- if AST.adapter_family(model) == :postgresql
753
- # Grouped because - binds tighter than #>: dug out of a document,
754
- # the subtraction would otherwise take the path literal first.
755
- return Arel::Nodes::InfixOperation.new(
756
- :-, Arel::Nodes::Grouping.new(document), key_array)
757
- end
758
-
759
- Arel::Nodes::NamedFunction.new(
760
- 'JSON_REMOVE',
761
- [document, *keys.map {|key| Arel::Nodes.build_quoted("$#{dollar_step(key)}") }])
1090
+ Dialect.for(model).json_remove(
1091
+ to_arel_operand(operand, table, model),
1092
+ keys.map { |key| "$#{dollar_step(key)}" },
1093
+ steps_array(keys), model)
762
1094
  end
763
1095
 
764
1096
  private
765
-
766
- # jsonb has three subtractions -- a key, an array of keys, an element
767
- # by index -- and an array literal written without a type is read as
768
- # the first of them: `meta - '{draft}'` takes out the key spelled
769
- # {draft}, which is nothing, and says nothing about it.
770
- def key_array
771
- Arel::Nodes::NamedFunction.new(
772
- 'CAST',
773
- [Arel::Nodes::As.new(Arel::Nodes.build_quoted(steps_array(keys)),
774
- Arel::Nodes::SqlLiteral.new('text[]'))])
775
- end
776
-
777
- # Keys, as Hash#except takes them: an index into an array is not what
778
- # the name says anywhere, and is bury's business through a path.
779
- def check_keys(keys)
780
- raise ArgumentError, 'except needs a key' if keys.empty?
781
- keys.each do |key|
782
- next if key.is_a?(::String) || key.is_a?(::Symbol)
783
- raise ArgumentError,
784
- "except takes keys of the document, not #{key.inspect}"
1097
+ # Keys, as Hash#except takes them: an index into an array is not what
1098
+ # the name says anywhere, and is bury's business through a path.
1099
+ def check_keys(keys)
1100
+ raise ArgumentError, "except needs a key" if keys.empty?
1101
+ keys.each do |key|
1102
+ next if key.is_a?(::String) || key.is_a?(::Symbol)
1103
+ raise ArgumentError,
1104
+ "except takes keys of the document, not #{key.inspect}"
1105
+ end
1106
+ keys
785
1107
  end
786
- keys
787
- end
788
1108
 
789
- def json_source
790
- 'except'
791
- end
1109
+ def json_source
1110
+ "except"
1111
+ end
792
1112
  end
793
1113
 
794
1114
  # JSON containment: whether the document holds what is given.
@@ -801,23 +1121,17 @@ module ActiveRecord
801
1121
  end
802
1122
 
803
1123
  def to_arel(table, model)
804
- document = to_arel_operand(operand, table, model)
805
- json = Arel::Nodes.build_quoted(JSON.generate(value))
806
- case AST.adapter_family(model)
807
- when :postgresql then Arel::Nodes::Contains.new(document, json)
808
- when :mysql
809
- Arel::Nodes::NamedFunction.new('JSON_CONTAINS', [document, json])
810
- else
811
- # Later than the others, since the adapter is only known here.
812
- raise NotImplementedError,
813
- "contains? has no equivalent on #{model.connection_db_config.adapter}"
814
- end
1124
+ Dialect.for(model).json_contains(
1125
+ to_arel_operand(operand, table, model),
1126
+ Arel::Nodes.build_quoted(JSON.generate(value)), model)
815
1127
  end
816
1128
  end
817
1129
 
818
- # Whether a key is in the document. PostgreSQL has an operator for it,
819
- # ?, which is also what a bind parameter looks like to several drivers;
820
- # the function it is shorthand for says the same thing and survives.
1130
+ # Whether a key is in the document. PostgreSQL's spelling is the ?
1131
+ # operator rather than jsonb_exists, the function it is shorthand for,
1132
+ # because a GIN index matches the operator and never the function. A
1133
+ # ? is a bind placeholder only to sanitize_sql, which none of the SQL
1134
+ # written here passes through.
821
1135
  class JsonHasKey < Predicate
822
1136
  attr_reader :operand, :key
823
1137
 
@@ -827,28 +1141,111 @@ module ActiveRecord
827
1141
  end
828
1142
 
829
1143
  def to_arel(table, model)
830
- document = to_arel_operand(operand, table, model)
831
- name = Arel::Nodes.build_quoted(key.to_s)
832
- path = Arel::Nodes.build_quoted("$.#{key}")
833
- case AST.adapter_family(model)
834
- when :postgresql
835
- Arel::Nodes::NamedFunction.new('jsonb_exists', [document, name])
836
- when :mysql
837
- Arel::Nodes::NamedFunction.new(
838
- 'JSON_CONTAINS_PATH', [document, Arel::Nodes.build_quoted('one'), path])
1144
+ Dialect.for(model).json_has_key(
1145
+ to_arel_operand(operand, table, model),
1146
+ Arel::Nodes.build_quoted(key.to_s),
1147
+ Arel::Nodes.build_quoted("$.#{key}"), model)
1148
+ end
1149
+ end
1150
+
1151
+ # The keys of a JSON document, as Hash#keys gives them: a JSON array.
1152
+ # Only the MySQL family has a function for it; the other two reach the
1153
+ # same array through a subquery over their key-listing functions. The
1154
+ # type guard is what makes all four answer alike: the keys of what is
1155
+ # not an object are NULL rather than SQLite's array indices or
1156
+ # PostgreSQL's error, and the keys of {} are [] rather than
1157
+ # PostgreSQL's NULL, jsonb_agg over no rows.
1158
+ class JsonKeys < Node
1159
+ include Predications
1160
+ include JsonComparable
1161
+ include ComputedJson
1162
+
1163
+ attr_reader :operand
1164
+
1165
+ def initialize(operand)
1166
+ @operand = operand
1167
+ end
1168
+
1169
+ def to_arel(table, model)
1170
+ Dialect.for(model).json_keys(to_arel_operand(operand, table, model), model)
1171
+ end
1172
+
1173
+ private
1174
+ def json_source
1175
+ "keys"
1176
+ end
1177
+ end
1178
+
1179
+ # A JSON document built in the row: json_array from the values given,
1180
+ # json_object from a Ruby hash. SQLite and the MySQL family both say
1181
+ # the standard names; PostgreSQL is asked to build jsonb, whose
1182
+ # documents the other JSON operations here read.
1183
+ class JsonBuild < Node
1184
+ include Predications
1185
+ include JsonComparable
1186
+ include ComputedJson
1187
+
1188
+ attr_reader :kind, :values
1189
+
1190
+ def initialize(kind, values)
1191
+ @kind = kind
1192
+ @values = kind == :object ? check_pairs(values) : values
1193
+ end
1194
+
1195
+ def to_arel(table, model)
1196
+ dialect = Dialect.for(model)
1197
+ if kind == :array
1198
+ dialect.json_build(:array, nil,
1199
+ values.map { |value| build_argument(value, dialect, table, model) }, model)
839
1200
  else
840
- Arel::Nodes::NamedFunction.new('json_type', [document, path]).not_eq(nil)
1201
+ dialect.json_build(:object, values.keys.map(&:to_s),
1202
+ values.values.map { |value| build_argument(value, dialect, table, model) }, model)
841
1203
  end
842
1204
  end
1205
+
1206
+ private
1207
+ # An expression is itself and a bare scalar is quoted; a document or
1208
+ # a boolean the dialect embeds as JSON, as bury takes it.
1209
+ def build_argument(value, dialect, table, model)
1210
+ case value
1211
+ when Node, ::Symbol then to_arel_operand(value, table, model)
1212
+ when ::Hash, ::Array, true, false then dialect.json_build_argument(value, model)
1213
+ when ::Rational then quote_number(value)
1214
+ else Arel::Nodes.build_quoted(value)
1215
+ end
1216
+ end
1217
+
1218
+ # The keys come from Ruby as Hash keys rather than alternating with
1219
+ # the values as SQL has them, which is what keeps a bare symbol
1220
+ # free to mean a column on the value side. Anything but a name is
1221
+ # refused here, before the adapters answer a NULL key three ways.
1222
+ def check_pairs(pairs)
1223
+ unless pairs.is_a?(::Hash)
1224
+ raise ArgumentError,
1225
+ "json_object takes a hash of keys to values, not #{pairs.inspect}"
1226
+ end
1227
+ pairs.each_key do |key|
1228
+ next if key.is_a?(::String) || key.is_a?(::Symbol)
1229
+ raise ArgumentError,
1230
+ "a key of json_object is a string or a symbol, not #{key.inspect}"
1231
+ end
1232
+ pairs
1233
+ end
1234
+
1235
+ def json_source
1236
+ "json_#{kind}"
1237
+ end
843
1238
  end
844
1239
 
845
1240
  # GROUP BY GROUPING SETS / ROLLUP / CUBE: several groupings asked for at
846
1241
  # once, the totals of each coming back beside the rows. PostgreSQL has
847
- # all three; the block raises for the others before it gets this far.
1242
+ # all three and the MySQL family rollup alone; the block raises for the
1243
+ # rest before it gets this far.
848
1244
  #
849
1245
  # Each set is a list of its own, so grouping_sets takes lists and rollup
850
1246
  # and cube take the columns themselves.
851
1247
  class GroupingSets < Node
1248
+ # @private
852
1249
  KINDS = {
853
1250
  grouping_sets: Arel::Nodes::GroupingSet,
854
1251
  rollup: Arel::Nodes::RollUp,
@@ -864,16 +1261,32 @@ module ActiveRecord
864
1261
  end
865
1262
 
866
1263
  def to_arel(table, model)
1264
+ return with_rollup(table, model) if Dialect.for(model).grouping_by_with_rollup?
1265
+
867
1266
  KINDS.fetch(kind).new(
868
1267
  if kind == :grouping_sets
869
1268
  sets.map do |set|
870
1269
  Arel::Nodes::GroupingElement.new(
871
- Array(set).map {|column| to_arel_operand(column, table, model) })
1270
+ Array(set).map { |column| to_arel_operand(column, table, model) })
872
1271
  end
873
1272
  else
874
- sets.map {|column| to_arel_operand(column, table, model) }
1273
+ sets.map { |column| to_arel_operand(column, table, model) }
875
1274
  end)
876
1275
  end
1276
+
1277
+ private
1278
+ # The MySQL family spells rollup WITH ROLLUP, trailing the whole
1279
+ # group list rather than wrapping a list of its own -- which is also
1280
+ # why a rollup cannot stand beside other group entries there. The
1281
+ # columns are compiled by the connection's own visitor, so their
1282
+ # quoting is the adapter's.
1283
+ def with_rollup(table, model)
1284
+ columns = sets.map { |column| to_arel_operand(column, table, model) }
1285
+ sql = model.with_connection do |connection|
1286
+ columns.map { |column| connection.visitor.compile(column) }.join(", ")
1287
+ end
1288
+ Arel.sql("#{sql} WITH ROLLUP")
1289
+ end
877
1290
  end
878
1291
 
879
1292
  class Column < Node
@@ -895,10 +1308,25 @@ module ActiveRecord
895
1308
  # Arithmetic on columns and expressions. Ruby's precedence puts these
896
1309
  # above the comparison operators, so :price * :quantity > 100 groups the
897
1310
  # way it reads.
1311
+ #
1312
+ # A Duration on the right moves a date: `:due_on + 3.days`. No two
1313
+ # families spell the move alike, so the dialect writes it, a part of the
1314
+ # duration at a time.
898
1315
  class Arithmetic < Node
899
1316
  include Predications
900
1317
  include Arithmetics
901
1318
 
1319
+ # Active Support's parts, as the units the SQL takes. A week is seven
1320
+ # days: SQLite and Oracle have no week.
1321
+ # @private
1322
+ UNITS = {
1323
+ years: :year, months: :month, weeks: :day, days: :day,
1324
+ hours: :hour, minutes: :minute, seconds: :second,
1325
+ }.freeze
1326
+
1327
+ # @private
1328
+ DATE_UNITS = %i[year month day].freeze
1329
+
902
1330
  attr_reader :left, :operator, :right
903
1331
 
904
1332
  def initialize(left, operator, right)
@@ -908,9 +1336,54 @@ module ActiveRecord
908
1336
  end
909
1337
 
910
1338
  def to_arel(table, model)
911
- to_arel_operand(left, table, model).
912
- public_send(operator, to_arel_operand(right, table, model))
1339
+ arel_left = to_arel_operand(left, table, model)
1340
+ return move_date(arel_left, model) if right.is_a?(::ActiveSupport::Duration)
1341
+ # The operator dispatches Arel's Math, which a bare number carries
1342
+ # none of; quoted, it is a node with the same methods.
1343
+ arel_left = Arel::Nodes.build_quoted(arel_left) if arel_left.is_a?(::Numeric)
1344
+ arel_left.public_send(operator, to_arel_operand(right, table, model))
1345
+ end
1346
+
1347
+ # SQLite has no date type, and its datetime() gives whatever it is
1348
+ # handed a time of day, so a date column moved by a day would come
1349
+ # back a midnight and sort past the same day written bare. Its
1350
+ # dialect has date() for what is a date to begin with, and this is
1351
+ # what says so: a column the model declares a date, CURRENT_DATE, or
1352
+ # one of those already moved by a date's units. The other families
1353
+ # keep the type themselves and never ask.
1354
+ def self.date_operand?(operand, model)
1355
+ case operand
1356
+ when ::Symbol then model.type_for_attribute(operand).type == :date
1357
+ when DatetimeValueFunction then operand.name == "CURRENT_DATE"
1358
+ when Arithmetic
1359
+ operand.right.is_a?(::ActiveSupport::Duration) &&
1360
+ date_operand?(operand.left, model) &&
1361
+ operand.right.parts.keys.all? { |part| DATE_UNITS.include?(UNITS[part]) }
1362
+ else false
1363
+ end
913
1364
  end
1365
+
1366
+ private
1367
+ # Each amount is written into the SQL as a number, and a fraction
1368
+ # of a unit is not one every family takes, so it has to be a whole
1369
+ # one.
1370
+ def move_date(date, model)
1371
+ unless operator == :+ || operator == :-
1372
+ raise ArgumentError,
1373
+ "a duration is added to a date or subtracted from it, not #{operator}"
1374
+ end
1375
+ dialect = Dialect.for(model)
1376
+ date_only = Arithmetic.date_operand?(left, model)
1377
+ right.parts.reduce(date) do |arel, (part, amount)|
1378
+ unless amount.is_a?(::Integer)
1379
+ raise ArgumentError, "#{amount.inspect} #{part} is not a whole number of them"
1380
+ end
1381
+ unit = UNITS.fetch(part)
1382
+ amount *= 7 if part == :weeks
1383
+ dialect.add_interval(arel, amount, unit, operator == :-,
1384
+ date_only && DATE_UNITS.include?(unit))
1385
+ end
1386
+ end
914
1387
  end
915
1388
 
916
1389
  # What the bitwise operators refuse. Both refusals are there because
@@ -920,23 +1393,22 @@ module ActiveRecord
920
1393
  # PostgreSQL has no such operator and would say so.
921
1394
  module BitwiseOperands
922
1395
  private
1396
+ def check_operand(operand, operator)
1397
+ return operand unless operand.is_a?(Predicate)
1398
+ raise ArgumentError,
1399
+ "a condition cannot be an operand of #{operator}; " \
1400
+ "& and | between conditions are AND and OR"
1401
+ end
923
1402
 
924
- def check_operand(operand, operator)
925
- return operand unless operand.is_a?(Predicate)
926
- raise ArgumentError,
927
- "a condition cannot be an operand of #{operator}; " \
928
- "& and | between conditions are AND and OR"
929
- end
930
-
931
- # Only the unqualified column can be checked, since that is the one
932
- # the model is known to have.
933
- def check_not_boolean(operand, operator, model)
934
- return unless operand.is_a?(::Symbol)
935
- return unless model.type_for_attribute(operand).type == :boolean
936
- raise ArgumentError,
937
- "#{operand.inspect} is a boolean column, which #{operator} does " \
938
- "not take; #{operand.inspect}.true? is the condition"
939
- end
1403
+ # Only the unqualified column can be checked, since that is the one
1404
+ # the model is known to have.
1405
+ def check_not_boolean(operand, operator, model)
1406
+ return unless operand.is_a?(::Symbol)
1407
+ return unless model.type_for_attribute(operand).type == :boolean
1408
+ raise ArgumentError,
1409
+ "#{operand.inspect} is a boolean column, which #{operator} does " \
1410
+ "not take; #{operand.inspect}.true? is the condition"
1411
+ end
940
1412
  end
941
1413
 
942
1414
  # SQL's bitwise operators. Each parenthesises itself, which is what
@@ -947,6 +1419,7 @@ module ActiveRecord
947
1419
  include Arithmetics
948
1420
  include BitwiseOperands
949
1421
 
1422
+ # @private
950
1423
  NODES = {
951
1424
  :& => Arel::Nodes::BitwiseAnd,
952
1425
  :| => Arel::Nodes::BitwiseOr,
@@ -976,22 +1449,14 @@ module ActiveRecord
976
1449
  end
977
1450
 
978
1451
  private
979
-
980
- # Arel has a node for XOR, but it writes ^ on every adapter, and ^ is
981
- # exponentiation to PostgreSQL -- a wrong answer rather than an error.
982
- # PostgreSQL's own spelling, #, is where a comment starts on MySQL, so
983
- # it cannot be the portable one either. SQLite has no XOR at all;
984
- # (a | b) - (a & b) is it, at the cost of naming each operand twice.
985
- def xor(left, right, model)
986
- case AST.adapter_family(model)
987
- when :postgresql then Arel::Nodes::InfixOperation.new('#', left, right)
988
- when :mysql then Arel::Nodes::BitwiseXor.new(left, right)
989
- else
990
- Arel::Nodes::Subtraction.new(
991
- Arel::Nodes::Grouping.new(Arel::Nodes::BitwiseOr.new(left, right)),
992
- Arel::Nodes::Grouping.new(Arel::Nodes::BitwiseAnd.new(left, right)))
1452
+ # Arel has a node for XOR, but it writes ^ on every adapter, and ^ is
1453
+ # exponentiation to PostgreSQL -- a wrong answer rather than an error.
1454
+ # PostgreSQL's own spelling, #, is where a comment starts on MySQL, so
1455
+ # it cannot be the portable one either. SQLite has no XOR at all;
1456
+ # (a | b) - (a & b) is it, at the cost of naming each operand twice.
1457
+ def xor(left, right, model)
1458
+ Dialect.for(model).bitwise_xor(left, right)
993
1459
  end
994
- end
995
1460
  end
996
1461
 
997
1462
  # ~, which every adapter has. MySQL answers with the unsigned 64-bit
@@ -1018,6 +1483,12 @@ module ActiveRecord
1018
1483
  # OVER, on the two things that can carry a window: an aggregate, and a
1019
1484
  # function.
1020
1485
  module Windowing
1486
+ # `OVER ()`, an empty window to be filled by {Over#partition},
1487
+ # {Over#order}, {Over#rows} and {Over#range}.
1488
+ # @return [AST::Over]
1489
+ # @example
1490
+ # Author.select { avg(:age).over.partition(:country).as(:country_average) }
1491
+ # Post.select { sum(:likes).over.order(:created_at).rows(..0).as(:running) }
1021
1492
  def over
1022
1493
  Over.new(self)
1023
1494
  end
@@ -1039,30 +1510,44 @@ module ActiveRecord
1039
1510
  @frame = frame
1040
1511
  end
1041
1512
 
1513
+ # `PARTITION BY`, the columns or expressions given.
1514
+ # @return [AST::Over]
1042
1515
  def partition(*exprs)
1043
1516
  raise ArgumentError, "partition needs an expression" if exprs.empty?
1044
1517
  Over.new(function, partitions + exprs, orders, frame)
1045
1518
  end
1046
1519
 
1520
+ # `ORDER BY` within the window: columns, or orderings such as `:age.desc`.
1521
+ # @return [AST::Over]
1047
1522
  def order(*exprs)
1048
1523
  raise ArgumentError, "order needs an expression" if exprs.empty?
1049
1524
  Over.new(function, partitions, orders + exprs, frame)
1050
1525
  end
1051
1526
 
1527
+ # `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.
1528
+ # @param bounds [Range]
1529
+ # @return [AST::Over]
1052
1530
  def rows(bounds)
1053
1531
  Over.new(function, partitions, orders, framing(:rows, bounds))
1054
1532
  end
1055
1533
 
1534
+ # `RANGE BETWEEN`, with the bounds as {#rows} takes them.
1535
+ # @param bounds [Range]
1536
+ # @return [AST::Over]
1056
1537
  def range(bounds)
1057
1538
  Over.new(function, partitions, orders, framing(:range, bounds))
1058
1539
  end
1059
1540
 
1060
1541
  def to_arel(table, model)
1061
1542
  window = Arel::Nodes::Window.new
1062
- partitions.each {|expr| window.partition(to_arel_operand(expr, table, model)) }
1063
- orders.each {|expr| window.order(to_arel_operand(expr, table, model)) }
1543
+ partitions.each { |expr| window.partition(to_arel_operand(expr, table, model)) }
1544
+ orders.each { |expr| window.order(to_arel_operand(expr, table, model)) }
1064
1545
  frame_arel(window) if frame
1065
1546
 
1547
+ # Not every aggregate can ride a window everywhere; the node itself
1548
+ # says where, once the adapter is known.
1549
+ function.check_window(model) if function.respond_to?(:check_window)
1550
+
1066
1551
  # A window-only function refuses to build on its own; here is where
1067
1552
  # it is asked for the call itself.
1068
1553
  arel_function =
@@ -1072,43 +1557,42 @@ module ActiveRecord
1072
1557
  end
1073
1558
 
1074
1559
  private
1075
-
1076
- # The frame is a range of rows counted from the current one: negative
1077
- # before it, positive after, 0 the row itself, and an open end for
1078
- # unbounded. `rows(..0)` is what a running total wants.
1079
- def framing(kind, bounds)
1080
- raise ArgumentError, "a window has one frame" if frame
1081
- unless bounds.is_a?(::Range)
1082
- raise ArgumentError, "#{kind} takes a range of rows, as in rows(..0)"
1083
- end
1084
- if bounds.exclude_end?
1085
- raise ArgumentError, "a frame ends on a row rather than before one; use .."
1086
- end
1087
- [bounds.begin, bounds.end].each do |bound|
1088
- next if bound.nil? || bound.is_a?(::Integer)
1089
- raise ArgumentError,
1090
- "a frame bound is a number of rows, or nothing for unbounded"
1560
+ # The frame is a range of rows counted from the current one: negative
1561
+ # before it, positive after, 0 the row itself, and an open end for
1562
+ # unbounded. `rows(..0)` is what a running total wants.
1563
+ def framing(kind, bounds)
1564
+ raise ArgumentError, "a window has one frame" if frame
1565
+ unless bounds.is_a?(::Range)
1566
+ raise ArgumentError, "#{kind} takes a range of rows, as in rows(..0)"
1567
+ end
1568
+ if bounds.exclude_end?
1569
+ raise ArgumentError, "a frame ends on a row rather than before one; use .."
1570
+ end
1571
+ [bounds.begin, bounds.end].each do |bound|
1572
+ next if bound.nil? || bound.is_a?(::Integer)
1573
+ raise ArgumentError,
1574
+ "a frame bound is a number of rows, or nothing for unbounded"
1575
+ end
1576
+ [kind, bounds.begin, bounds.end]
1091
1577
  end
1092
- [kind, bounds.begin, bounds.end]
1093
- end
1094
1578
 
1095
- # Arel wants the keyword itself on the left of the BETWEEN, which is
1096
- # what window.rows with no argument hands back.
1097
- def frame_arel(window)
1098
- kind, from, to = frame
1099
- window.frame(
1100
- Arel::Nodes::Between.new(
1101
- window.public_send(kind),
1102
- Arel::Nodes::And.new([bound(from, Arel::Nodes::Preceding.new),
1103
- bound(to, Arel::Nodes::Following.new)])))
1104
- end
1579
+ # Arel wants the keyword itself on the left of the BETWEEN, which is
1580
+ # what window.rows with no argument hands back.
1581
+ def frame_arel(window)
1582
+ kind, from, to = frame
1583
+ window.frame(
1584
+ Arel::Nodes::Between.new(
1585
+ window.public_send(kind),
1586
+ Arel::Nodes::And.new([bound(from, Arel::Nodes::Preceding.new),
1587
+ bound(to, Arel::Nodes::Following.new)])))
1588
+ end
1105
1589
 
1106
- def bound(rows, unbounded)
1107
- return unbounded if rows.nil?
1108
- return Arel::Nodes::CurrentRow.new if rows.zero?
1109
- rows.negative? ? Arel::Nodes::Preceding.new(-rows)
1110
- : Arel::Nodes::Following.new(rows)
1111
- end
1590
+ def bound(rows, unbounded)
1591
+ return unbounded if rows.nil?
1592
+ return Arel::Nodes::CurrentRow.new if rows.zero?
1593
+ rows.negative? ? Arel::Nodes::Preceding.new(-rows)
1594
+ : Arel::Nodes::Following.new(rows)
1595
+ end
1112
1596
  end
1113
1597
 
1114
1598
  class Aggregate < Node
@@ -1116,11 +1600,17 @@ module ActiveRecord
1116
1600
  include Arithmetics
1117
1601
  include Windowing
1118
1602
 
1603
+ # The aggregates DISTINCT changes: over each value once, count counts
1604
+ # fewer and sum and avg reckon less. min and max give the same
1605
+ # either way, so a DISTINCT there is refused as saying nothing.
1606
+ # @private
1607
+ DISTINCT_FUNCTIONS = %i[count sum average].freeze
1608
+
1119
1609
  attr_reader :operand, :function, :distinct, :condition
1120
1610
 
1121
1611
  def initialize(operand, function, distinct: false, condition: nil)
1122
- if distinct && function != :count
1123
- raise ArgumentError, "#{function} does not take distinct"
1612
+ if distinct && !DISTINCT_FUNCTIONS.include?(function)
1613
+ raise ArgumentError, "#{function} does not take distinct; it would give the same"
1124
1614
  end
1125
1615
  if distinct && operand == :*
1126
1616
  raise ArgumentError, "count(:*) does not take distinct; name a column"
@@ -1131,8 +1621,12 @@ module ActiveRecord
1131
1621
  @condition = condition
1132
1622
  end
1133
1623
 
1134
- # FILTER (WHERE ...): the aggregate is taken over the rows the
1135
- # condition holds for. A value or a block, as `when` takes them.
1624
+ # `FILTER (WHERE condition)`: the aggregate taken over the rows the
1625
+ # condition holds for, as a value or a block. Where there is no
1626
+ # FILTER clause -- MySQL, SQL Server -- the CASE that means the same.
1627
+ # @return [AST::Aggregate]
1628
+ # @example
1629
+ # Author.select { [count(:*).as(:all), count(:*).filter { :age < 50 }.as(:young)] }
1136
1630
  def filter(condition = nil, &block)
1137
1631
  Aggregate.new(operand, function, distinct: distinct,
1138
1632
  condition: Case.argument(:filter, condition, block))
@@ -1141,11 +1635,11 @@ module ActiveRecord
1141
1635
  def to_arel(table, model)
1142
1636
  return aggregate(operand, table, model) unless condition
1143
1637
 
1144
- # MySQL has no FILTER clause. An aggregate passes over a NULL, so
1145
- # the case that yields nothing for the rows the condition misses is
1146
- # the same aggregate over the same rows -- count(*) has no operand to
1147
- # keep, and counts a 1 instead.
1148
- if AST.adapter_family(model) == :mysql
1638
+ # A family without a FILTER clause gets the CASE that means the same.
1639
+ # An aggregate passes over a NULL, so the case that yields nothing for
1640
+ # the rows the condition misses is the same aggregate over the same
1641
+ # rows -- count(*) has no operand to keep, and counts a 1 instead.
1642
+ unless Dialect.for(model).filter_supported?
1149
1643
  kept = Case.new.when(condition).then(operand == :* ? 1 : operand)
1150
1644
  return aggregate(kept, table, model)
1151
1645
  end
@@ -1154,15 +1648,136 @@ module ActiveRecord
1154
1648
  end
1155
1649
 
1156
1650
  private
1651
+ def aggregate(over, table, model)
1652
+ arel_operand = to_arel_operand(over, table, model)
1653
+ return arel_operand.count(distinct) if function == :count
1654
+ call = arel_operand.public_send(function)
1655
+ call.distinct = distinct
1656
+ call
1657
+ end
1658
+ end
1157
1659
 
1158
- def aggregate(over, table, model)
1159
- arel_operand = to_arel_operand(over, table, model)
1160
- if function == :count
1161
- arel_operand.count(distinct)
1162
- else
1163
- arel_operand.public_send(function)
1660
+ # Rows gathered into one JSON document: json_arrayagg collects a value
1661
+ # from each row into an array, json_objectagg a key and a value into an
1662
+ # object. Every adapter has the pair under a name of its own; what
1663
+ # PostgreSQL gets is the jsonb one, whose documents the other JSON
1664
+ # operations here read.
1665
+ class JsonAggregate < Node
1666
+ include Predications
1667
+ include JsonComparable
1668
+ include ComputedJson
1669
+ include Windowing
1670
+
1671
+ attr_reader :kind, :operands, :condition
1672
+
1673
+ def initialize(kind, operands, condition: nil)
1674
+ @kind = kind
1675
+ @operands = operands
1676
+ @condition = condition
1677
+ end
1678
+
1679
+ # `FILTER (WHERE condition)`, as {Aggregate#filter}; refused on the
1680
+ # MySQL family, where the CASE that stands in would leave a JSON null
1681
+ # for every row it drops.
1682
+ # @return [AST::JsonAggregate]
1683
+ def filter(condition = nil, &block)
1684
+ JsonAggregate.new(kind, operands,
1685
+ condition: Case.argument(:filter, condition, block))
1686
+ end
1687
+
1688
+ # Over asks here before writing a window, since a family may take
1689
+ # every other aggregate as one but not these two.
1690
+ def check_window(model)
1691
+ Dialect.for(model).check_json_aggregate_window(json_source, model)
1692
+ end
1693
+
1694
+ def to_arel(table, model)
1695
+ dialect = Dialect.for(model)
1696
+ call = Arel::Nodes::NamedFunction.new(
1697
+ dialect.json_aggregate_name(kind),
1698
+ operands.map { |operand| to_arel_argument(operand, table, model) })
1699
+ return call unless condition
1700
+
1701
+ # The CASE that stands in for FILTER elsewhere hands the aggregate a
1702
+ # NULL for every row the condition misses, and these two keep a NULL
1703
+ # -- as JSON null -- rather than passing over it, so it is refused.
1704
+ unless dialect.json_aggregate_filter_supported?
1705
+ raise NotImplementedError,
1706
+ "#{json_source}.filter has no equivalent on " \
1707
+ "#{model.connection_db_config.adapter}; a CASE would leave a " \
1708
+ "null in the document for every row it drops"
1709
+ end
1710
+ call.filter(condition.to_arel(table, model))
1711
+ end
1712
+
1713
+ private
1714
+ def json_source
1715
+ "json_#{kind}"
1716
+ end
1717
+ end
1718
+
1719
+ # The strings of a group joined into one, a separator between:
1720
+ # `string_agg(:title, ", ")`, with `.order` for the order they are
1721
+ # joined in. Every family has it under a name of its own with the
1722
+ # ORDER BY in a place of its own, and Arel has no node for an ORDER BY
1723
+ # inside a call, so the dialect writes the call. A NULL is passed
1724
+ # over as by any aggregate, so the CASE that stands in for FILTER
1725
+ # means the same here and is not refused as the JSON aggregates' is.
1726
+ class StringAggregate < Node
1727
+ include Predications
1728
+ include Windowing
1729
+
1730
+ attr_reader :operand, :separator, :orders, :condition
1731
+
1732
+ def initialize(operand, separator, orders: [], condition: nil)
1733
+ unless separator.is_a?(::String)
1734
+ raise ArgumentError, "#{separator.inspect} is not a String separator"
1164
1735
  end
1736
+ @operand = operand
1737
+ @separator = separator
1738
+ @orders = orders
1739
+ @condition = condition
1740
+ end
1741
+
1742
+ # The order the strings are joined in: columns, or orderings such as
1743
+ # `:title.desc`.
1744
+ # @return [AST::StringAggregate]
1745
+ def order(*exprs)
1746
+ raise ArgumentError, "order needs an expression" if exprs.empty?
1747
+ StringAggregate.new(operand, separator, orders: orders + exprs, condition: condition)
1748
+ end
1749
+
1750
+ # `FILTER (WHERE condition)`, as {Aggregate#filter}.
1751
+ # @return [AST::StringAggregate]
1752
+ def filter(condition = nil, &block)
1753
+ StringAggregate.new(operand, separator, orders: orders,
1754
+ condition: Case.argument(:filter, condition, block))
1755
+ end
1756
+
1757
+ def check_window(model)
1758
+ Dialect.for(model).check_string_aggregate_window(model)
1759
+ end
1760
+
1761
+ def to_arel(table, model)
1762
+ dialect = Dialect.for(model)
1763
+ kept = condition && !dialect.filter_supported? ?
1764
+ Case.new.when(condition).then(operand) : operand
1765
+ call = dialect.string_agg(
1766
+ to_arel_argument(kept, table, model), separator,
1767
+ orders.map { |expr| to_arel_operand(expr, table, model) },
1768
+ string_operand?(model), model)
1769
+ return call unless condition && dialect.filter_supported?
1770
+ Arel::Nodes::Filter.new(call, condition.to_arel(table, model))
1165
1771
  end
1772
+
1773
+ private
1774
+ # Whether the operand is a column the model declares a string.
1775
+ # PostgreSQL asks, its STRING_AGG taking text and nothing else; the
1776
+ # others convert for themselves.
1777
+ def string_operand?(model)
1778
+ operand.is_a?(::Symbol) &&
1779
+ %i[string text].include?(model.type_for_attribute(operand).type)
1780
+ end
1166
1781
  end
1167
1782
 
1168
1783
  # A column alias, quoted by the adapter, so that the name asked for is
@@ -1191,11 +1806,33 @@ module ActiveRecord
1191
1806
  end
1192
1807
 
1193
1808
  private
1809
+ def alias_sql(model)
1810
+ name = alias_name.to_s
1811
+ return name unless quote
1812
+ model.with_connection { |connection| connection.quote_column_name(name) }
1813
+ end
1814
+ end
1194
1815
 
1195
- def alias_sql(model)
1196
- name = alias_name.to_s
1197
- return name unless quote
1198
- model.with_connection {|connection| connection.quote_column_name(name) }
1816
+ # A collation named for a comparison or an ordering: `:name.collate(:ci)`.
1817
+ # It stands as an expression -- compared, ordered by, selected -- and
1818
+ # gives back one of its own, so the collation carries through.
1819
+ #
1820
+ # The name follows COLLATE as a bare identifier with no Arel node of its
1821
+ # own. What names are safe turns on whether the family quotes it -- only
1822
+ # PostgreSQL does -- so the dialect checks the name as it builds the
1823
+ # clause, rather than this node holding one rule for all of them.
1824
+ class Collate < Node
1825
+ include Predications
1826
+
1827
+ attr_reader :operand, :name
1828
+
1829
+ def initialize(operand, name)
1830
+ @name = name.to_s
1831
+ @operand = operand
1832
+ end
1833
+
1834
+ def to_arel(table, model)
1835
+ Dialect.for(model).collate(to_arel_operand(operand, table, model), name, model)
1199
1836
  end
1200
1837
  end
1201
1838
 
@@ -1208,12 +1845,18 @@ module ActiveRecord
1208
1845
  @nulls = nulls
1209
1846
  end
1210
1847
 
1848
+ # `NULLS FIRST`; portable, since Arel emulates it where MySQL has
1849
+ # none.
1850
+ # @return [AST::Ordering]
1851
+ #
1211
1852
  # MySQL has no NULLS FIRST/LAST, but Arel emulates it there with a
1212
1853
  # leading IS NULL ordering, so these are portable.
1213
1854
  def nulls_first
1214
1855
  Ordering.new(operand, direction, :nulls_first)
1215
1856
  end
1216
1857
 
1858
+ # `NULLS LAST`.
1859
+ # @return [AST::Ordering]
1217
1860
  def nulls_last
1218
1861
  Ordering.new(operand, direction, :nulls_last)
1219
1862
  end
@@ -1237,11 +1880,54 @@ module ActiveRecord
1237
1880
  end
1238
1881
 
1239
1882
  def to_arel(table, model)
1240
- arel_args = args.map {|arg| to_arel_argument(arg, table, model) }
1883
+ arel_args = args.map { |arg| to_arel_argument(arg, table, model) }
1241
1884
  Arel::Nodes::NamedFunction.new(name, arel_args)
1242
1885
  end
1243
1886
  end
1244
1887
 
1888
+ # Escape hatch for operators without a spelling of their own, the way
1889
+ # fn is for functions. The operator is emitted as written -- whether
1890
+ # the adapter has it is the caller's assertion, as fn's names are --
1891
+ # and the values ride as quoted literals, so on PostgreSQL an untyped
1892
+ # one takes the type of the operand beside it.
1893
+ class Operation < Node
1894
+ include Predications
1895
+ include Arithmetics
1896
+
1897
+ attr_reader :operator, :left, :right
1898
+
1899
+ def initialize(operator, left, right)
1900
+ @operator = AST.check_name(operator, OPERATOR, "operator").to_s
1901
+ @left = check_side(left)
1902
+ @right = check_side(right)
1903
+ end
1904
+
1905
+ def to_arel(table, model)
1906
+ Arel::Nodes::Grouping.new(
1907
+ Arel::Nodes::InfixOperation.new(
1908
+ operator, side(left, table, model), side(right, table, model)))
1909
+ end
1910
+
1911
+ private
1912
+ # An expression operand is parenthesized: an unknown operator's
1913
+ # precedence is unknown too, and PostgreSQL reads its named
1914
+ # operators from the left, so a bare infix on the right would take
1915
+ # the new operator's left side into its own.
1916
+ def side(operand, table, model)
1917
+ arel = to_arel_argument(operand, table, model)
1918
+ operand.is_a?(Node) ? Arel::Nodes::Grouping.new(arel) : arel
1919
+ end
1920
+
1921
+ def check_side(operand)
1922
+ if operand.is_a?(::Hash) || operand.is_a?(::Array) || operand.is_a?(::Set)
1923
+ raise ArgumentError,
1924
+ "#{operand.inspect} has no one SQL spelling; a string says it " \
1925
+ "in the adapter's own, to_json for a document"
1926
+ end
1927
+ operand
1928
+ end
1929
+ end
1930
+
1245
1931
  # ROW_NUMBER and its kind: functions that say nothing without a window.
1246
1932
  # On its own this refuses rather than reaching the database as an error
1247
1933
  # there; over asks it for call_arel instead.
@@ -1326,6 +2012,7 @@ module ActiveRecord
1326
2012
  # or an Array compares against a PostgreSQL range or array column, the way
1327
2013
  # Active Record's own force_equality? types do.
1328
2014
  class Comparison < Predicate
2015
+ # @private
1329
2016
  OPERATOR_MAP = {
1330
2017
  :== => :eq, :!= => :not_eq,
1331
2018
  :> => :gt, :>= => :gteq, :< => :lt, :<= => :lteq
@@ -1344,24 +2031,24 @@ module ActiveRecord
1344
2031
  arel_value =
1345
2032
  case value
1346
2033
  when Node then value.to_arel(table, model)
2034
+ when ::Symbol then column_operand(value, table, model)
1347
2035
  when ActiveRecord::Relation then scalar_subquery(value)
1348
- else value
2036
+ else quote_number(value)
1349
2037
  end
1350
2038
  arel_column.public_send(OPERATOR_MAP.fetch(operator), arel_value)
1351
2039
  end
1352
2040
 
1353
2041
  private
1354
-
1355
- # A relation compared against a column has to yield a single value, so
1356
- # unlike In there is no sensible default select list to fall back on.
1357
- def scalar_subquery(relation)
1358
- if relation.select_values.empty?
1359
- raise ArgumentError,
1360
- "#{operator} needs a subquery selecting one value; add a select"
2042
+ # A relation compared against a column has to yield a single value, so
2043
+ # unlike In there is no sensible default select list to fall back on.
2044
+ def scalar_subquery(relation)
2045
+ if relation.select_values.empty?
2046
+ raise ArgumentError,
2047
+ "#{operator} needs a subquery selecting one value; add a select"
2048
+ end
2049
+ relation = relation.send(:apply_join_dependency) if relation.eager_loading?
2050
+ relation.arel
1361
2051
  end
1362
- relation = relation.send(:apply_join_dependency) if relation.eager_loading?
1363
- relation.arel
1364
- end
1365
2052
  end
1366
2053
 
1367
2054
  # IS TRUE, IS FALSE and their negations, which every adapter spells the
@@ -1376,9 +2063,8 @@ module ActiveRecord
1376
2063
  end
1377
2064
 
1378
2065
  def to_arel(table, model)
1379
- literal = value ? Arel::Nodes::True.new : Arel::Nodes::False.new
1380
- Arel::Nodes::InfixOperation.new(negated ? 'IS NOT' : 'IS',
1381
- to_arel_operand(operand, table, model), literal)
2066
+ Dialect.for(model).truth_value(
2067
+ to_arel_operand(operand, table, model), value, negated, model)
1382
2068
  end
1383
2069
  end
1384
2070
 
@@ -1388,19 +2074,18 @@ module ActiveRecord
1388
2074
  # selects the model's primary key.
1389
2075
  module SetSubquery
1390
2076
  private
1391
-
1392
- def set_subquery(relation, spelling)
1393
- relation = relation.send(:apply_join_dependency) if relation.eager_loading?
1394
- if relation.select_values.empty?
1395
- model = relation.model
1396
- if model.composite_primary_key?
1397
- raise ArgumentError,
1398
- "Cannot map composite primary key #{model.primary_key} to #{spelling}"
2077
+ def set_subquery(relation, spelling)
2078
+ relation = relation.send(:apply_join_dependency) if relation.eager_loading?
2079
+ if relation.select_values.empty?
2080
+ model = relation.model
2081
+ if model.composite_primary_key?
2082
+ raise ArgumentError,
2083
+ "Cannot map composite primary key #{model.primary_key} to #{spelling}"
2084
+ end
2085
+ relation = relation.select(relation.table[model.primary_key])
1399
2086
  end
1400
- relation = relation.select(relation.table[model.primary_key])
2087
+ relation.arel
1401
2088
  end
1402
- relation.arel
1403
- end
1404
2089
  end
1405
2090
 
1406
2091
  # IN for a list of values, BETWEEN for a range, IN (SELECT ...) for a
@@ -1408,6 +2093,12 @@ module ActiveRecord
1408
2093
  class In < Predicate
1409
2094
  include SetSubquery
1410
2095
 
2096
+ # Range holds its endpoints to Comparable, which a quoted node is
2097
+ # not, so this quacks the three methods Arel's between reads.
2098
+ QuotedRange = Struct.new(:begin, :end, :exclude_end) do
2099
+ def exclude_end? = exclude_end
2100
+ end
2101
+
1411
2102
  attr_reader :operand, :values, :negated
1412
2103
 
1413
2104
  def initialize(operand, values, negated: false)
@@ -1418,13 +2109,67 @@ module ActiveRecord
1418
2109
 
1419
2110
  def to_arel(table, model)
1420
2111
  arel_operand = to_arel_operand(operand, table, model)
1421
- if values.is_a?(Range)
1422
- arel_operand.public_send(negated ? :not_between : :between, values)
2112
+ case values
2113
+ when Range, QuotedRange
2114
+ lower = quote_value(values.begin, table, model)
2115
+ upper = quote_value(values.end, table, model)
2116
+ if json_between?(model) && lower && upper && !negated
2117
+ return arel_operand.gteq(lower).and(
2118
+ values.exclude_end? ? arel_operand.lt(upper) : arel_operand.lteq(upper))
2119
+ end
2120
+ range = QuotedRange.new(lower, upper, values.exclude_end?)
2121
+ arel_operand.public_send(negated ? :not_between : :between, range)
2122
+ when ActiveRecord::Relation
2123
+ arel_operand.public_send(negated ? :not_in : :in, set_subquery(values, "IN"))
1423
2124
  else
1424
- arg = values.is_a?(ActiveRecord::Relation) ? set_subquery(values, 'IN') : values
2125
+ arg = values
2126
+ if arg.is_a?(::Array)
2127
+ arg = arg.map { |value| quote_value(value, table, model) }
2128
+ return json_list(arel_operand, arg) if json_list?(model)
2129
+ end
1425
2130
  arel_operand.public_send(negated ? :not_in : :in, arg)
1426
2131
  end
1427
2132
  end
2133
+
2134
+ private
2135
+ # An element that is already an expression resolves, a symbol is a
2136
+ # column here as everywhere, a number is quoted as itself, and the
2137
+ # rest ride for Arel to cast by the column.
2138
+ def quote_value(value, table, model)
2139
+ case value
2140
+ when Node then value.to_arel(table, model)
2141
+ when ::Symbol then column_operand(value, table, model)
2142
+ else quote_number(value)
2143
+ end
2144
+ end
2145
+
2146
+ # MySQL leaves IN and BETWEEN out of its JSON comparisons -- they
2147
+ # fall back to another comparison entirely -- so on it a JSON set is
2148
+ # spelled as the comparisons it means: the closed range as its two
2149
+ # bounds, the list as one equality per element. That names the dug
2150
+ # value once per element, the price SQLite's XOR pays per operand;
2151
+ # a negated range needs nothing, Arel writing it as two comparisons
2152
+ # everywhere. MariaDB never gets this far: the endpoints refuse as
2153
+ # they resolve.
2154
+ def json_between?(model)
2155
+ (values.begin.is_a?(JsonLiteral) || values.end.is_a?(JsonLiteral)) &&
2156
+ Dialect.for(model).json_list_by_element?
2157
+ end
2158
+
2159
+ def json_list?(model)
2160
+ values.any? { |value| value.is_a?(JsonLiteral) } &&
2161
+ Dialect.for(model).json_list_by_element?
2162
+ end
2163
+
2164
+ def json_list(arel_operand, elements)
2165
+ comparisons = elements.map do |element|
2166
+ negated ? arel_operand.not_eq(element) : arel_operand.eq(element)
2167
+ end
2168
+ joined = comparisons.inject do |so_far, piece|
2169
+ negated ? so_far.and(piece) : so_far.or(piece)
2170
+ end
2171
+ negated ? Arel::Nodes::Grouping.new(joined) : joined
2172
+ end
1428
2173
  end
1429
2174
 
1430
2175
  # ANY and ALL, which stand on the right of a comparison and say how many
@@ -1472,7 +2217,8 @@ module ActiveRecord
1472
2217
  end
1473
2218
 
1474
2219
  class Like < Predicate
1475
- ESCAPE = "\\".freeze
2220
+ # @private
2221
+ ESCAPE = "\\"
1476
2222
 
1477
2223
  # Escapes % and _ so that they match literally. The pattern built from
1478
2224
  # the result must be used with ESCAPE, since SQLite has no default
@@ -1484,8 +2230,8 @@ module ActiveRecord
1484
2230
  # ORs one LIKE per pattern, for the shortcuts that accept several
1485
2231
  # literals the way String#start_with? does.
1486
2232
  def self.any(operand, patterns)
1487
- patterns.map {|pattern| new(operand, pattern, ESCAPE) }.
1488
- inject {|left, right| Or.new(left, right) }
2233
+ patterns.map { |pattern| new(operand, pattern, ESCAPE) }.
2234
+ inject { |left, right| Or.new(left, right) }
1489
2235
  end
1490
2236
 
1491
2237
  attr_reader :operand, :pattern, :escape, :case_sensitive, :negated
@@ -1569,21 +2315,20 @@ module ActiveRecord
1569
2315
  end
1570
2316
 
1571
2317
  private
1572
-
1573
- # PostgreSQL array input syntax: elements joined by commas inside
1574
- # braces, and an element is double-quoted whenever it is empty, spells
1575
- # NULL, or contains a character the parser treats specially.
1576
- def array_literal
1577
- encoded = elements.map do |value|
1578
- s = value.to_s
1579
- if s.empty? || s.casecmp?("null") || s.match?(/[\s{},"\\]/)
1580
- "\"#{s.gsub(/["\\]/) {|c| "\\#{c}" }}\""
1581
- else
1582
- s
2318
+ # PostgreSQL array input syntax: elements joined by commas inside
2319
+ # braces, and an element is double-quoted whenever it is empty, spells
2320
+ # NULL, or contains a character the parser treats specially.
2321
+ def array_literal
2322
+ encoded = elements.map do |value|
2323
+ s = value.to_s
2324
+ if s.empty? || s.casecmp?("null") || s.match?(/[\s{},"\\]/)
2325
+ "\"#{s.gsub(/["\\]/) { |c| "\\#{c}" }}\""
2326
+ else
2327
+ s
2328
+ end
1583
2329
  end
2330
+ "{#{encoded.join(',')}}"
1584
2331
  end
1585
- "{#{encoded.join(',')}}"
1586
- end
1587
2332
  end
1588
2333
 
1589
2334
  # Regular expression match: REGEXP on MySQL, ~ on PostgreSQL. SQLite has
@@ -1607,19 +2352,18 @@ module ActiveRecord
1607
2352
  end
1608
2353
 
1609
2354
  private
1610
-
1611
- # A Regexp literal reads naturally with =~, but only its source crosses
1612
- # over; the database has its own dialect and no notion of Ruby's flags.
1613
- # Dropping a flag would silently change what the query matches, so
1614
- # anything beyond a plain literal is refused rather than ignored.
1615
- def regexp_source(regexp)
1616
- unless regexp.options.zero?
1617
- raise ArgumentError,
1618
- "#{regexp.inspect} has options that SQL cannot express; " \
1619
- "pass the pattern as a string instead"
2355
+ # A Regexp literal reads naturally with =~, but only its source crosses
2356
+ # over; the database has its own dialect and no notion of Ruby's flags.
2357
+ # Dropping a flag would silently change what the query matches, so
2358
+ # anything beyond a plain literal is refused rather than ignored.
2359
+ def regexp_source(regexp)
2360
+ unless regexp.options.zero?
2361
+ raise ArgumentError,
2362
+ "#{regexp.inspect} has options that SQL cannot express; " \
2363
+ "pass the pattern as a string instead"
2364
+ end
2365
+ regexp.source
1620
2366
  end
1621
- regexp.source
1622
- end
1623
2367
  end
1624
2368
 
1625
2369
  class And < Predicate