activerecord-refined 0.5.0 → 0.6.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.
@@ -1,11 +1,9 @@
1
+ # JSON.generate, for the document a containment test is given.
2
+ require 'json'
3
+
1
4
  module ActiveRecord
2
5
  module Refined
3
6
  module AST
4
- # Column aliases and function names are written into the SQL as given,
5
- # where a value would have been quoted, so anything that is not a plain
6
- # name is refused. Quoting them instead would need the model's adapter,
7
- # which does not reach this far, and quoting with the wrong one would be
8
- # a bug of its own -- MySQL does not read "x" as an identifier.
9
7
  NAME = /[[:alpha:]_][[:alnum:]_$]*/
10
8
  ALIAS_NAME = /\A#{NAME}\z/
11
9
  FUNCTION_NAME = /\A#{NAME}(\.#{NAME})?\z/
@@ -13,6 +11,22 @@ module ActiveRecord
13
11
  # lengths -- double precision, decimal(10,2).
14
12
  TYPE_NAME = /\A[[:alpha:]_][[:alnum:]_ ]*(\(\d+(, ?\d+)?\))?\z/
15
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
+ ADAPTER_FAMILIES = {
19
+ 'sqlite3' => :sqlite,
20
+ 'postgresql' => :postgresql,
21
+ 'postgis' => :postgresql,
22
+ 'mysql2' => :mysql,
23
+ 'trilogy' => :mysql,
24
+ }.freeze
25
+
26
+ def self.adapter_family(model)
27
+ ADAPTER_FAMILIES[model.connection_db_config.adapter] || :unknown
28
+ end
29
+
16
30
  def self.check_name(name, pattern, what)
17
31
  return name if pattern.match?(name.to_s)
18
32
  raise ArgumentError, "#{name.inspect} is not a plain #{what}"
@@ -63,26 +77,78 @@ module ActiveRecord
63
77
  Match.new(self, pattern, negated: true)
64
78
  end
65
79
 
80
+ # `!` negates any predicate, so these are here for the four that SQL
81
+ # spells for itself: IS NOT NULL rather than NOT (... IS NULL), and
82
+ # likewise NOT IN and NOT LIKE. They mean the same thing either way,
83
+ # including when the column is NULL; what they save is the reading.
66
84
  def null?
67
85
  Comparison.new(self, :==, nil)
68
86
  end
69
87
 
88
+ def not_null?
89
+ Comparison.new(self, :!=, nil)
90
+ end
91
+
92
+ # IS TRUE and IS FALSE differ from a comparison against the literal in
93
+ # what they make of NULL: `flag = TRUE` is itself NULL there, and a
94
+ # NULL predicate selects nothing, while these two answer false. So the
95
+ # difference shows in the negations: `not_true?` keeps the NULL rows
96
+ # that `!(:flag == true)` drops.
97
+ def true?
98
+ TruthValue.new(self, true)
99
+ end
100
+
101
+ def not_true?
102
+ TruthValue.new(self, true, negated: true)
103
+ end
104
+
105
+ def false?
106
+ TruthValue.new(self, false)
107
+ end
108
+
109
+ def not_false?
110
+ TruthValue.new(self, false, negated: true)
111
+ end
112
+
70
113
  def in?(values)
71
114
  In.new(self, values)
72
115
  end
73
116
 
117
+ def not_in?(values)
118
+ In.new(self, values, negated: true)
119
+ end
120
+
74
121
  def between?(min, max)
75
122
  In.new(self, min..max)
76
123
  end
77
124
 
125
+ def not_between?(min, max)
126
+ In.new(self, min..max, negated: true)
127
+ end
128
+
129
+ # CASE with this as the operand, compared against each `when`:
130
+ # `:age.when(10).then(1).else(0)`. The other shape, where each `when`
131
+ # carries its own condition, starts at `case_when`.
132
+ def when(value = nil, &block)
133
+ Case.new(self).when(value, &block)
134
+ end
135
+
78
136
  def like?(pattern)
79
137
  Like.new(self, pattern)
80
138
  end
81
139
 
140
+ def not_like?(pattern)
141
+ Like.new(self, pattern, negated: true)
142
+ end
143
+
82
144
  def ilike?(pattern)
83
145
  Like.new(self, pattern, nil, case_sensitive: false)
84
146
  end
85
147
 
148
+ def not_ilike?(pattern)
149
+ Like.new(self, pattern, nil, case_sensitive: false, negated: true)
150
+ end
151
+
86
152
  # Case-insensitive equality, folded on both sides rather than left to
87
153
  # the collation, so it means the same thing on every adapter.
88
154
  def casecmp?(value)
@@ -144,6 +210,39 @@ module ActiveRecord
144
210
  def intersect?(elements)
145
211
  ArrayPredicate.new(self, :"&&", ArrayPredicate.elements(elements, "intersect?"))
146
212
  end
213
+
214
+ # Reading inside a JSON document, by the name of what Hash does. A
215
+ # string or symbol steps into an object, an integer into an array, and
216
+ # what comes back is the value rather than the JSON, since that is what
217
+ # a comparison wants. dig_json keeps it JSON, for a document to be dug
218
+ # into further or compared whole.
219
+ def dig(*path)
220
+ JsonPath.new(self, path)
221
+ end
222
+
223
+ def dig_json(*path)
224
+ JsonPath.new(self, path, as_json: true)
225
+ end
226
+
227
+ # What dig reads, bury sets: the last argument is the value and the
228
+ # rest are the path to it. The document comes back changed rather
229
+ # than being written anywhere, which update_all is for.
230
+ def bury(*path, value)
231
+ JsonSet.new(self, path, value)
232
+ end
233
+
234
+ # Whether the document holds what is given, which SQL calls
235
+ # containment. SQLite has no equivalent.
236
+ def contains?(value)
237
+ JsonContains.new(self, value)
238
+ end
239
+
240
+ # Whether the key is there at all, as Hash#key? asks. Hash has
241
+ # has_key? too; one name is enough, and this is the one Ruby's own
242
+ # style prefers.
243
+ def key?(key)
244
+ JsonHasKey.new(self, key)
245
+ end
147
246
  end
148
247
 
149
248
  # Arithmetic builders shared by symbols, qualified columns and
@@ -165,41 +264,48 @@ module ActiveRecord
165
264
  def /(other)
166
265
  Arithmetic.new(self, :/, other)
167
266
  end
168
- end
169
267
 
170
- # Aggregate builders shared by symbols, qualified columns and
171
- # expressions. Imported into the Symbol refinement like Predications,
172
- # so every method must be defined with def.
173
- module Aggregations
174
- # DISTINCT is Arel's only aggregate modifier, and only for count.
175
- def count(distinct: false)
176
- Aggregate.new(self, :count, distinct: distinct)
268
+ # SQL's bitwise operators. & and | are AND and OR between conditions
269
+ # and are defined there, which is what leaves them free to mean here
270
+ # what SQL means by them. Ruby's precedence puts all six above the
271
+ # comparisons, so `:flags & 4 > 0` groups the way it reads.
272
+ def &(other)
273
+ Bitwise.new(self, :&, other)
274
+ end
275
+
276
+ def |(other)
277
+ Bitwise.new(self, :|, other)
177
278
  end
178
279
 
179
- def sum
180
- Aggregate.new(self, :sum)
280
+ def ^(other)
281
+ Bitwise.new(self, :^, other)
181
282
  end
182
283
 
183
- def average
184
- Aggregate.new(self, :average)
284
+ def <<(other)
285
+ Bitwise.new(self, :<<, other)
185
286
  end
186
287
 
187
- def maximum
188
- Aggregate.new(self, :maximum)
288
+ def >>(other)
289
+ Bitwise.new(self, :>>, other)
189
290
  end
190
291
 
191
- def minimum
192
- Aggregate.new(self, :minimum)
292
+ def ~
293
+ BitwiseNot.new(self)
193
294
  end
194
295
  end
195
296
 
196
297
  class Node
197
- def to_arel(table)
298
+ # The model travels with the table because some SQL cannot be written
299
+ # without knowing the adapter, and a node is built before anything
300
+ # knows which one it will be rendered for -- a symbol becomes a node
301
+ # inside a refinement, where there is no model to ask. Most nodes
302
+ # never look at it and only pass it on.
303
+ def to_arel(table, model)
198
304
  raise ScriptError, "subclass must override this method"
199
305
  end
200
306
 
201
- def as(alias_name)
202
- As.new(self, alias_name)
307
+ def as(alias_name, quote: true)
308
+ As.new(self, alias_name, quote: quote)
203
309
  end
204
310
 
205
311
  def asc
@@ -213,9 +319,9 @@ module ActiveRecord
213
319
  private
214
320
 
215
321
  # Resolves an operand denoting a column or an expression.
216
- def to_arel_operand(operand, table)
322
+ def to_arel_operand(operand, table, model)
217
323
  case operand
218
- when Node then operand.to_arel(table)
324
+ when Node then operand.to_arel(table, model)
219
325
  when :* then Arel.star
220
326
  when Symbol then table[operand]
221
327
  else operand
@@ -224,9 +330,9 @@ module ActiveRecord
224
330
 
225
331
  # Resolves a function argument: a column or an expression as above,
226
332
  # anything else a value to be quoted.
227
- def to_arel_argument(arg, table)
333
+ def to_arel_argument(arg, table, model)
228
334
  case arg
229
- when Node, Symbol then to_arel_operand(arg, table)
335
+ when Node, Symbol then to_arel_operand(arg, table, model)
230
336
  else Arel::Nodes.build_quoted(arg)
231
337
  end
232
338
  end
@@ -246,10 +352,333 @@ module ActiveRecord
246
352
  end
247
353
  end
248
354
 
355
+ # A literal standing where an expression would: `select { value(0).as(:depth) }`.
356
+ #
357
+ # Values reach the SQL quoted wherever they appear as an operand, but the
358
+ # top of a select list is ActiveRecord's, and a bare string there is SQL
359
+ # rather than a string. Saying `value` is how you ask for the other
360
+ # meaning, and it carries the predications with it, so a literal can be
361
+ # compared and combined like anything else.
362
+ class Value < Node
363
+ include Predications
364
+ include Arithmetics
365
+
366
+ attr_reader :value
367
+
368
+ def initialize(value)
369
+ @value = value
370
+ end
371
+
372
+ def to_arel(_table, _model)
373
+ Arel::Nodes.build_quoted(value)
374
+ end
375
+ end
376
+
377
+ # CASE, in both of the shapes SQL has for it. With an operand, each
378
+ # `when` is something to compare it against; without one, each `when` is
379
+ # a condition of its own.
380
+ #
381
+ # Every method returns a new node rather than adding to this one, so a
382
+ # case kept in a variable can be branched from more than once.
383
+ class Case < Node
384
+ include Predications
385
+ include Arithmetics
386
+
387
+ # Having no ELSE is not the same as an ELSE of nil, and nil is what an
388
+ # omitted argument looks like, so the absence needs a value of its own.
389
+ NOTHING = Object.new.freeze
390
+ private_constant :NOTHING
391
+
392
+ attr_reader :operand, :whens, :default
393
+
394
+ def initialize(operand = nil, whens = [], default = NOTHING)
395
+ @operand = operand
396
+ @whens = whens
397
+ @default = default
398
+ end
399
+
400
+ def when(value = nil, &block)
401
+ Pending.new(self, Case.argument(:when, value, block))
402
+ end
403
+
404
+ # Kernel#then is on every object, so `then` in the wrong place would be
405
+ # answered by it -- with no block, silently, with an Enumerator.
406
+ def then(*)
407
+ raise ArgumentError, "then follows a when, and there is none to follow here"
408
+ end
409
+
410
+ def else(value = nil, &block)
411
+ Case.new(operand, whens, Case.argument(:else, value, block))
412
+ end
413
+
414
+ def to_arel(table, model)
415
+ raise ArgumentError, "case needs a when before it means anything" if whens.empty?
416
+
417
+ node = operand ? Arel::Nodes::Case.new(to_arel_operand(operand, table, model))
418
+ : Arel::Nodes::Case.new
419
+ whens.each do |condition, result|
420
+ node.when(to_arel_argument(condition, table, model)).
421
+ then(to_arel_argument(result, table, model))
422
+ end
423
+ node.else(to_arel_argument(default, table, model)) unless default.equal?(NOTHING)
424
+ node
425
+ end
426
+
427
+ # A value or a block, and exactly one of them: the block is what makes
428
+ # `when { :age >= 60 }` read like the blocks around it, and the value is
429
+ # what makes `when(10)` possible at all.
430
+ def self.argument(name, value, block)
431
+ if block
432
+ raise ArgumentError, "#{name} takes a value or a block, not both" unless value.nil?
433
+ return block.call
434
+ end
435
+ raise ArgumentError, "#{name} needs a value or a block" if value.nil?
436
+ value
437
+ end
438
+
439
+ # What a `when` is until its `then` arrives. A Node so that using it
440
+ # as one says what is missing rather than reaching ActiveRecord as
441
+ # something it cannot read.
442
+ class Pending < Node
443
+ def initialize(kase, condition)
444
+ @kase = kase
445
+ @condition = condition
446
+ end
447
+
448
+ def then(value = nil, &block)
449
+ Case.new(@kase.operand,
450
+ @kase.whens + [[@condition, Case.argument(:then, value, block)]],
451
+ @kase.default)
452
+ end
453
+
454
+ def to_arel(_table, _model)
455
+ raise ArgumentError, "when needs a matching then"
456
+ end
457
+ end
458
+ end
459
+
460
+ # A path into a JSON document, spelled the two ways the adapters want it.
461
+ # Shared, because reading a value and setting one walk the same path.
462
+ module JsonSteps
463
+ def check_steps(path, called)
464
+ raise ArgumentError, "#{called} needs a key or an index" if path.empty?
465
+ path.each do |step|
466
+ next if step.is_a?(::Integer) || step.is_a?(::String) || step.is_a?(::Symbol)
467
+ raise ArgumentError, "a step is a key or an array index, not #{step.inspect}"
468
+ end
469
+ path
470
+ end
471
+
472
+ # PostgreSQL takes the steps as a text array, where every element is
473
+ # quoted so that a comma or a brace in a key is part of it.
474
+ def steps_array
475
+ "{#{path.map {|step| %("#{escape_step(step)}") }.join(',')}}"
476
+ end
477
+
478
+ # MySQL and SQLite take a path expression instead, where an integer is
479
+ # a subscript and a name that is not plain has to be quoted.
480
+ def dollar_path
481
+ path.inject(+'$') do |so_far, step|
482
+ next so_far << "[#{step}]" if step.is_a?(::Integer)
483
+ name = step.to_s
484
+ so_far << '.' << (name.match?(/\A[[:alpha:]_][[:alnum:]_]*\z/) ?
485
+ name : %("#{escape_step(step)}"))
486
+ end
487
+ end
488
+
489
+ def escape_step(step)
490
+ step.to_s.gsub('\\', '\\\\').gsub('"', '\\"')
491
+ end
492
+ end
493
+
494
+ # Reading inside a JSON document. Every adapter can do it and no two
495
+ # spell it alike: PostgreSQL walks an array of steps, SQLite has the
496
+ # operators with a $ path, and MySQL has the functions -- which is what
497
+ # this uses for that family, since MariaDB answers to the same adapter
498
+ # and has no -> at all.
499
+ #
500
+ # The path is turned into a string either way, so a key with a space or
501
+ # a quote in it travels as itself rather than having to be refused.
502
+ class JsonPath < Node
503
+ include Predications
504
+ include Arithmetics
505
+ include JsonSteps
506
+
507
+ attr_reader :operand, :path, :as_json
508
+
509
+ def initialize(operand, path, as_json: false)
510
+ @operand = operand
511
+ @path = check_steps(path, 'dig')
512
+ @as_json = as_json
513
+ end
514
+
515
+ def to_arel(table, model)
516
+ document = to_arel_operand(operand, table, model)
517
+ case AST.adapter_family(model)
518
+ when :postgresql
519
+ Arel::Nodes::InfixOperation.new(
520
+ as_json ? :"#>" : :"#>>", document, Arel::Nodes.build_quoted(steps_array))
521
+ when :mysql
522
+ extracted = Arel::Nodes::NamedFunction.new(
523
+ 'JSON_EXTRACT', [document, Arel::Nodes.build_quoted(dollar_path)])
524
+ as_json ? extracted : Arel::Nodes::NamedFunction.new('JSON_UNQUOTE', [extracted])
525
+ else
526
+ extracted = Arel::Nodes::InfixOperation.new(
527
+ as_json ? :"->" : :"->>", document, Arel::Nodes.build_quoted(dollar_path))
528
+ # SQLite's ->> gives back the value with its type, where the other
529
+ # two give text. Cast so that `dig(:n) == '5'` means the same
530
+ # thing everywhere, and a number wants a cast everywhere too.
531
+ as_json ? extracted : Arel::Nodes::NamedFunction.new(
532
+ 'CAST', [Arel::Nodes::As.new(extracted, Arel::Nodes::SqlLiteral.new('text'))])
533
+ end
534
+ end
535
+
536
+ end
537
+
538
+ # Setting a value inside a JSON document, which is what bury does to what
539
+ # dig reads. The document comes back changed rather than being written
540
+ # anywhere; update_all is what writes it.
541
+ class JsonSet < Node
542
+ include Predications
543
+ include JsonSteps
544
+
545
+ attr_reader :operand, :path, :value
546
+
547
+ def initialize(operand, path, value)
548
+ @operand = operand
549
+ @path = check_steps(path, 'bury')
550
+ @value = value
551
+ end
552
+
553
+ def to_arel(table, model)
554
+ document = to_arel_operand(operand, table, model)
555
+ if AST.adapter_family(model) == :postgresql
556
+ Arel::Nodes::NamedFunction.new(
557
+ 'jsonb_set',
558
+ [document, Arel::Nodes.build_quoted(steps_array), postgresql_value(table, model)])
559
+ else
560
+ Arel::Nodes::NamedFunction.new(
561
+ 'JSON_SET',
562
+ [document, Arel::Nodes.build_quoted(dollar_path), other_value(table, model)])
563
+ end
564
+ end
565
+
566
+ private
567
+
568
+ # jsonb_set takes jsonb, so an expression is turned into it and a Ruby
569
+ # value goes in as the JSON that says it -- '"x"' rather than 'x',
570
+ # which is not a document at all.
571
+ def postgresql_value(table, model)
572
+ return Arel::Nodes::NamedFunction.new(
573
+ 'to_jsonb', [to_arel_operand(value, table, model)]) if expression?
574
+ Arel::Nodes.build_quoted(JSON.generate(value))
575
+ end
576
+
577
+ # The others take the value as it is, except a whole document, which
578
+ # they read out of a literal rather than take as a string. MySQL casts
579
+ # to JSON where MariaDB, which answers to the same adapter, does not.
580
+ def other_value(table, model)
581
+ return to_arel_operand(value, table, model) if expression?
582
+ return Arel::Nodes.build_quoted(value) unless value.is_a?(::Hash) || value.is_a?(::Array)
583
+
584
+ Arel::Nodes::NamedFunction.new(
585
+ 'JSON_EXTRACT',
586
+ [Arel::Nodes.build_quoted(JSON.generate(value)), Arel::Nodes.build_quoted('$')])
587
+ end
588
+
589
+ def expression?
590
+ value.is_a?(Node) || value.is_a?(::Symbol)
591
+ end
592
+ end
593
+
594
+ # JSON containment: whether the document holds what is given.
595
+ class JsonContains < Predicate
596
+ attr_reader :operand, :value
597
+
598
+ def initialize(operand, value)
599
+ @operand = operand
600
+ @value = value
601
+ end
602
+
603
+ def to_arel(table, model)
604
+ document = to_arel_operand(operand, table, model)
605
+ json = Arel::Nodes.build_quoted(JSON.generate(value))
606
+ case AST.adapter_family(model)
607
+ when :postgresql then Arel::Nodes::Contains.new(document, json)
608
+ when :mysql
609
+ Arel::Nodes::NamedFunction.new('JSON_CONTAINS', [document, json])
610
+ else
611
+ # Later than the others, since the adapter is only known here.
612
+ raise NotImplementedError,
613
+ "contains? has no equivalent on #{model.connection_db_config.adapter}"
614
+ end
615
+ end
616
+ end
617
+
618
+ # Whether a key is in the document. PostgreSQL has an operator for it,
619
+ # ?, which is also what a bind parameter looks like to several drivers;
620
+ # the function it is shorthand for says the same thing and survives.
621
+ class JsonHasKey < Predicate
622
+ attr_reader :operand, :key
623
+
624
+ def initialize(operand, key)
625
+ @operand = operand
626
+ @key = key
627
+ end
628
+
629
+ def to_arel(table, model)
630
+ document = to_arel_operand(operand, table, model)
631
+ name = Arel::Nodes.build_quoted(key.to_s)
632
+ path = Arel::Nodes.build_quoted("$.#{key}")
633
+ case AST.adapter_family(model)
634
+ when :postgresql
635
+ Arel::Nodes::NamedFunction.new('jsonb_exists', [document, name])
636
+ when :mysql
637
+ Arel::Nodes::NamedFunction.new(
638
+ 'JSON_CONTAINS_PATH', [document, Arel::Nodes.build_quoted('one'), path])
639
+ else
640
+ Arel::Nodes::NamedFunction.new('json_type', [document, path]).not_eq(nil)
641
+ end
642
+ end
643
+ end
644
+
645
+ # GROUP BY GROUPING SETS / ROLLUP / CUBE: several groupings asked for at
646
+ # once, the totals of each coming back beside the rows. PostgreSQL has
647
+ # all three; the block raises for the others before it gets this far.
648
+ #
649
+ # Each set is a list of its own, so grouping_sets takes lists and rollup
650
+ # and cube take the columns themselves.
651
+ class GroupingSets < Node
652
+ KINDS = {
653
+ grouping_sets: Arel::Nodes::GroupingSet,
654
+ rollup: Arel::Nodes::RollUp,
655
+ cube: Arel::Nodes::Cube,
656
+ }.freeze
657
+
658
+ attr_reader :kind, :sets
659
+
660
+ def initialize(kind, sets)
661
+ raise ArgumentError, "#{kind} needs something to group by" if sets.empty?
662
+ @kind = kind
663
+ @sets = sets
664
+ end
665
+
666
+ def to_arel(table, model)
667
+ KINDS.fetch(kind).new(
668
+ if kind == :grouping_sets
669
+ sets.map do |set|
670
+ Arel::Nodes::GroupingElement.new(
671
+ Array(set).map {|column| to_arel_operand(column, table, model) })
672
+ end
673
+ else
674
+ sets.map {|column| to_arel_operand(column, table, model) }
675
+ end)
676
+ end
677
+ end
678
+
249
679
  class Column < Node
250
680
  include Predications
251
681
  include Arithmetics
252
- include Aggregations
253
682
 
254
683
  attr_reader :table_name, :column_name
255
684
 
@@ -258,7 +687,7 @@ module ActiveRecord
258
687
  @column_name = column_name
259
688
  end
260
689
 
261
- def to_arel(_table)
690
+ def to_arel(_table, _model)
262
691
  Arel::Table.new(table_name)[column_name]
263
692
  end
264
693
  end
@@ -269,7 +698,6 @@ module ActiveRecord
269
698
  class Arithmetic < Node
270
699
  include Predications
271
700
  include Arithmetics
272
- include Aggregations
273
701
 
274
702
  attr_reader :left, :operator, :right
275
703
 
@@ -279,19 +707,218 @@ module ActiveRecord
279
707
  @right = right
280
708
  end
281
709
 
282
- def to_arel(table)
283
- to_arel_operand(left, table).
284
- public_send(operator, to_arel_operand(right, table))
710
+ def to_arel(table, model)
711
+ to_arel_operand(left, table, model).
712
+ public_send(operator, to_arel_operand(right, table, model))
713
+ end
714
+ end
715
+
716
+ # What the bitwise operators refuse. Both refusals are there because
717
+ # the same Ruby would otherwise mean different things per adapter: MySQL
718
+ # and SQLite take a boolean for the one bit it is stored as, so
719
+ # `published & active` would quietly be the AND it looks like, while
720
+ # PostgreSQL has no such operator and would say so.
721
+ module BitwiseOperands
722
+ private
723
+
724
+ def check_operand(operand, operator)
725
+ return operand unless operand.is_a?(Predicate)
726
+ raise ArgumentError,
727
+ "a condition cannot be an operand of #{operator}; " \
728
+ "& and | between conditions are AND and OR"
729
+ end
730
+
731
+ # Only the unqualified column can be checked, since that is the one
732
+ # the model is known to have.
733
+ def check_not_boolean(operand, operator, model)
734
+ return unless operand.is_a?(::Symbol)
735
+ return unless model.type_for_attribute(operand).type == :boolean
736
+ raise ArgumentError,
737
+ "#{operand.inspect} is a boolean column, which #{operator} does " \
738
+ "not take; #{operand.inspect}.true? is the condition"
739
+ end
740
+ end
741
+
742
+ # SQL's bitwise operators. Each parenthesises itself, which is what
743
+ # keeps Ruby's grouping: PostgreSQL gives & and | the same precedence
744
+ # and reads a | b & c from the left, where Ruby reads the & first.
745
+ class Bitwise < Node
746
+ include Predications
747
+ include Arithmetics
748
+ include BitwiseOperands
749
+
750
+ NODES = {
751
+ :& => Arel::Nodes::BitwiseAnd,
752
+ :| => Arel::Nodes::BitwiseOr,
753
+ :<< => Arel::Nodes::BitwiseShiftLeft,
754
+ :>> => Arel::Nodes::BitwiseShiftRight,
755
+ }.freeze
756
+
757
+ attr_reader :left, :operator, :right
758
+
759
+ def initialize(left, operator, right)
760
+ @left = left
761
+ @operator = operator
762
+ @right = check_operand(right, operator)
763
+ end
764
+
765
+ def to_arel(table, model)
766
+ check_not_boolean(left, operator, model)
767
+ check_not_boolean(right, operator, model)
768
+ arel_left = to_arel_operand(left, table, model)
769
+ arel_right = to_arel_argument(right, table, model)
770
+ Arel::Nodes::Grouping.new(
771
+ if operator == :^
772
+ xor(arel_left, arel_right, model)
773
+ else
774
+ NODES.fetch(operator).new(arel_left, arel_right)
775
+ end)
776
+ end
777
+
778
+ private
779
+
780
+ # Arel has a node for XOR, but it writes ^ on every adapter, and ^ is
781
+ # exponentiation to PostgreSQL -- a wrong answer rather than an error.
782
+ # PostgreSQL's own spelling, #, is where a comment starts on MySQL, so
783
+ # it cannot be the portable one either. SQLite has no XOR at all;
784
+ # (a | b) - (a & b) is it, at the cost of naming each operand twice.
785
+ def xor(left, right, model)
786
+ case AST.adapter_family(model)
787
+ when :postgresql then Arel::Nodes::InfixOperation.new('#', left, right)
788
+ when :mysql then Arel::Nodes::BitwiseXor.new(left, right)
789
+ else
790
+ Arel::Nodes::Subtraction.new(
791
+ Arel::Nodes::Grouping.new(Arel::Nodes::BitwiseOr.new(left, right)),
792
+ Arel::Nodes::Grouping.new(Arel::Nodes::BitwiseAnd.new(left, right)))
793
+ end
794
+ end
795
+ end
796
+
797
+ # ~, which every adapter has. MySQL answers with the unsigned 64-bit
798
+ # number where the others answer with a negative one; the bits are the
799
+ # same, and only reading the value back tells them apart.
800
+ class BitwiseNot < Node
801
+ include Predications
802
+ include Arithmetics
803
+ include BitwiseOperands
804
+
805
+ attr_reader :operand
806
+
807
+ def initialize(operand)
808
+ @operand = check_operand(operand, :~)
809
+ end
810
+
811
+ def to_arel(table, model)
812
+ check_not_boolean(operand, :~, model)
813
+ Arel::Nodes::Grouping.new(
814
+ Arel::Nodes::BitwiseNot.new(to_arel_operand(operand, table, model)))
815
+ end
816
+ end
817
+
818
+ # OVER, on the two things that can carry a window: an aggregate, and a
819
+ # function.
820
+ module Windowing
821
+ def over
822
+ Over.new(self)
823
+ end
824
+ end
825
+
826
+ # A function with a window. The window is built by chaining, the way
827
+ # Arel's own is, and each method returns a new node rather than adding to
828
+ # this one, so a window can be finished more than one way.
829
+ class Over < Node
830
+ include Predications
831
+ include Arithmetics
832
+
833
+ attr_reader :function, :partitions, :orders, :frame
834
+
835
+ def initialize(function, partitions = [], orders = [], frame = nil)
836
+ @function = function
837
+ @partitions = partitions
838
+ @orders = orders
839
+ @frame = frame
840
+ end
841
+
842
+ def partition(*exprs)
843
+ raise ArgumentError, "partition needs an expression" if exprs.empty?
844
+ Over.new(function, partitions + exprs, orders, frame)
845
+ end
846
+
847
+ def order(*exprs)
848
+ raise ArgumentError, "order needs an expression" if exprs.empty?
849
+ Over.new(function, partitions, orders + exprs, frame)
850
+ end
851
+
852
+ def rows(bounds)
853
+ Over.new(function, partitions, orders, framing(:rows, bounds))
854
+ end
855
+
856
+ def range(bounds)
857
+ Over.new(function, partitions, orders, framing(:range, bounds))
858
+ end
859
+
860
+ def to_arel(table, model)
861
+ window = Arel::Nodes::Window.new
862
+ partitions.each {|expr| window.partition(to_arel_operand(expr, table, model)) }
863
+ orders.each {|expr| window.order(to_arel_operand(expr, table, model)) }
864
+ frame_arel(window) if frame
865
+
866
+ # A window-only function refuses to build on its own; here is where
867
+ # it is asked for the call itself.
868
+ arel_function =
869
+ function.is_a?(WindowFunction) ? function.call_arel(table, model)
870
+ : function.to_arel(table, model)
871
+ Arel::Nodes::Over.new(arel_function, window)
872
+ end
873
+
874
+ private
875
+
876
+ # The frame is a range of rows counted from the current one: negative
877
+ # before it, positive after, 0 the row itself, and an open end for
878
+ # unbounded. `rows(..0)` is what a running total wants.
879
+ def framing(kind, bounds)
880
+ raise ArgumentError, "a window has one frame" if frame
881
+ unless bounds.is_a?(::Range)
882
+ raise ArgumentError, "#{kind} takes a range of rows, as in rows(..0)"
883
+ end
884
+ if bounds.exclude_end?
885
+ raise ArgumentError, "a frame ends on a row rather than before one; use .."
886
+ end
887
+ [bounds.begin, bounds.end].each do |bound|
888
+ next if bound.nil? || bound.is_a?(::Integer)
889
+ raise ArgumentError,
890
+ "a frame bound is a number of rows, or nothing for unbounded"
891
+ end
892
+ [kind, bounds.begin, bounds.end]
893
+ end
894
+
895
+ # Arel wants the keyword itself on the left of the BETWEEN, which is
896
+ # what window.rows with no argument hands back.
897
+ def frame_arel(window)
898
+ kind, from, to = frame
899
+ window.frame(
900
+ Arel::Nodes::Between.new(
901
+ window.public_send(kind),
902
+ Arel::Nodes::And.new([bound(from, Arel::Nodes::Preceding.new),
903
+ bound(to, Arel::Nodes::Following.new)])))
904
+ end
905
+
906
+ def bound(rows, unbounded)
907
+ return unbounded if rows.nil?
908
+ return Arel::Nodes::CurrentRow.new if rows.zero?
909
+ rows.negative? ? Arel::Nodes::Preceding.new(-rows)
910
+ : Arel::Nodes::Following.new(rows)
285
911
  end
286
912
  end
287
913
 
288
914
  class Aggregate < Node
289
915
  include Predications
290
916
  include Arithmetics
917
+ include Windowing
291
918
 
292
- attr_reader :operand, :function, :distinct
919
+ attr_reader :operand, :function, :distinct, :condition
293
920
 
294
- def initialize(operand, function, distinct: false)
921
+ def initialize(operand, function, distinct: false, condition: nil)
295
922
  if distinct && function != :count
296
923
  raise ArgumentError, "#{function} does not take distinct"
297
924
  end
@@ -301,10 +928,35 @@ module ActiveRecord
301
928
  @operand = operand
302
929
  @function = function
303
930
  @distinct = distinct
931
+ @condition = condition
304
932
  end
305
933
 
306
- def to_arel(table)
307
- arel_operand = to_arel_operand(operand, table)
934
+ # FILTER (WHERE ...): the aggregate is taken over the rows the
935
+ # condition holds for. A value or a block, as `when` takes them.
936
+ def filter(condition = nil, &block)
937
+ Aggregate.new(operand, function, distinct: distinct,
938
+ condition: Case.argument(:filter, condition, block))
939
+ end
940
+
941
+ def to_arel(table, model)
942
+ return aggregate(operand, table, model) unless condition
943
+
944
+ # MySQL has no FILTER clause. An aggregate passes over a NULL, so
945
+ # the case that yields nothing for the rows the condition misses is
946
+ # the same aggregate over the same rows -- count(*) has no operand to
947
+ # keep, and counts a 1 instead.
948
+ if AST.adapter_family(model) == :mysql
949
+ kept = Case.new.when(condition).then(operand == :* ? 1 : operand)
950
+ return aggregate(kept, table, model)
951
+ end
952
+
953
+ aggregate(operand, table, model).filter(condition.to_arel(table, model))
954
+ end
955
+
956
+ private
957
+
958
+ def aggregate(over, table, model)
959
+ arel_operand = to_arel_operand(over, table, model)
308
960
  if function == :count
309
961
  arel_operand.count(distinct)
310
962
  else
@@ -313,16 +965,37 @@ module ActiveRecord
313
965
  end
314
966
  end
315
967
 
968
+ # A column alias, quoted by the adapter, so that the name asked for is
969
+ # the name that comes back: unquoted, PostgreSQL folds a capital away
970
+ # and the other two keep it, which is one block meaning two things.
971
+ # Quoting also leaves nothing to refuse -- a name that would have been
972
+ # SQL is an identifier with a strange name instead.
973
+ #
974
+ # `quote: false` asks for the name as written, for a schema that wants
975
+ # the folding.
316
976
  class As < Node
317
- attr_reader :operand, :alias_name
977
+ attr_reader :operand, :alias_name, :quote
318
978
 
319
- def initialize(operand, alias_name)
979
+ def initialize(operand, alias_name, quote: true)
980
+ # Checked here rather than where the SQL is built, so that a name
981
+ # the adapter is not being asked to quote is refused where it was
982
+ # written.
983
+ AST.check_name(alias_name, ALIAS_NAME, "column alias") unless quote
320
984
  @operand = operand
321
- @alias_name = AST.check_name(alias_name, ALIAS_NAME, "column alias")
985
+ @alias_name = alias_name
986
+ @quote = quote
322
987
  end
323
988
 
324
- def to_arel(table)
325
- to_arel_operand(operand, table).as(alias_name.to_s)
989
+ def to_arel(table, model)
990
+ to_arel_operand(operand, table, model).as(alias_sql(model))
991
+ end
992
+
993
+ private
994
+
995
+ def alias_sql(model)
996
+ name = alias_name.to_s
997
+ return name unless quote
998
+ model.with_connection {|connection| connection.quote_column_name(name) }
326
999
  end
327
1000
  end
328
1001
 
@@ -345,8 +1018,8 @@ module ActiveRecord
345
1018
  Ordering.new(operand, direction, :nulls_last)
346
1019
  end
347
1020
 
348
- def to_arel(table)
349
- ordering = to_arel_operand(operand, table).public_send(direction)
1021
+ def to_arel(table, model)
1022
+ ordering = to_arel_operand(operand, table, model).public_send(direction)
350
1023
  nulls ? ordering.public_send(nulls) : ordering
351
1024
  end
352
1025
  end
@@ -354,6 +1027,7 @@ module ActiveRecord
354
1027
  class Function < Node
355
1028
  include Predications
356
1029
  include Arithmetics
1030
+ include Windowing
357
1031
 
358
1032
  attr_reader :name, :args
359
1033
 
@@ -362,12 +1036,23 @@ module ActiveRecord
362
1036
  @args = args
363
1037
  end
364
1038
 
365
- def to_arel(table)
366
- arel_args = args.map {|arg| to_arel_argument(arg, table) }
1039
+ def to_arel(table, model)
1040
+ arel_args = args.map {|arg| to_arel_argument(arg, table, model) }
367
1041
  Arel::Nodes::NamedFunction.new(name, arel_args)
368
1042
  end
369
1043
  end
370
1044
 
1045
+ # ROW_NUMBER and its kind: functions that say nothing without a window.
1046
+ # On its own this refuses rather than reaching the database as an error
1047
+ # there; over asks it for call_arel instead.
1048
+ class WindowFunction < Function
1049
+ alias_method :call_arel, :to_arel
1050
+
1051
+ def to_arel(_table, _model)
1052
+ raise ArgumentError, "#{name.downcase} is a window function; it needs over"
1053
+ end
1054
+ end
1055
+
371
1056
  # EXTRACT(field FROM expr). The field is grammar rather than a value --
372
1057
  # a keyword the adapter reads bare -- so it has to be a plain name,
373
1058
  # which Arel upcases on the way out.
@@ -382,8 +1067,8 @@ module ActiveRecord
382
1067
  @operand = operand
383
1068
  end
384
1069
 
385
- def to_arel(table)
386
- Arel::Nodes::Extract.new(to_arel_argument(operand, table), field.to_s)
1070
+ def to_arel(table, model)
1071
+ Arel::Nodes::Extract.new(to_arel_argument(operand, table, model), field.to_s)
387
1072
  end
388
1073
  end
389
1074
 
@@ -402,10 +1087,10 @@ module ActiveRecord
402
1087
  @sql_type = AST.check_name(sql_type, TYPE_NAME, "SQL type")
403
1088
  end
404
1089
 
405
- def to_arel(table)
1090
+ def to_arel(table, model)
406
1091
  Arel::Nodes::NamedFunction.new(
407
1092
  "CAST",
408
- [Arel::Nodes::As.new(to_arel_argument(operand, table),
1093
+ [Arel::Nodes::As.new(to_arel_argument(operand, table, model),
409
1094
  Arel::Nodes::SqlLiteral.new(sql_type.to_s))])
410
1095
  end
411
1096
  end
@@ -431,7 +1116,7 @@ module ActiveRecord
431
1116
  @precision = precision
432
1117
  end
433
1118
 
434
- def to_arel(_table)
1119
+ def to_arel(_table, _model)
435
1120
  Arel::Nodes::SqlLiteral.new(
436
1121
  precision ? "#{name}(#{precision})" : name)
437
1122
  end
@@ -454,11 +1139,11 @@ module ActiveRecord
454
1139
  @value = value
455
1140
  end
456
1141
 
457
- def to_arel(table)
458
- arel_column = to_arel_operand(column, table)
1142
+ def to_arel(table, model)
1143
+ arel_column = to_arel_operand(column, table, model)
459
1144
  arel_value =
460
1145
  case value
461
- when Node then value.to_arel(table)
1146
+ when Node then value.to_arel(table, model)
462
1147
  when ActiveRecord::Relation then scalar_subquery(value)
463
1148
  else value
464
1149
  end
@@ -479,39 +1164,38 @@ module ActiveRecord
479
1164
  end
480
1165
  end
481
1166
 
482
- # IN for a list of values, BETWEEN for a range, IN (SELECT ...) for a
483
- # relation.
484
- class In < Predicate
485
- attr_reader :operand, :values
1167
+ # IS TRUE, IS FALSE and their negations, which every adapter spells the
1168
+ # same way and answers alike, NULL included.
1169
+ class TruthValue < Predicate
1170
+ attr_reader :operand, :value, :negated
486
1171
 
487
- def initialize(operand, values)
1172
+ def initialize(operand, value, negated: false)
488
1173
  @operand = operand
489
- @values = values
1174
+ @value = value
1175
+ @negated = negated
490
1176
  end
491
1177
 
492
- def to_arel(table)
493
- arel_operand = to_arel_operand(operand, table)
494
- case values
495
- when Range then arel_operand.between(values)
496
- when ActiveRecord::Relation then arel_operand.in(subquery(values))
497
- else arel_operand.in(values)
498
- end
1178
+ def to_arel(table, model)
1179
+ literal = value ? Arel::Nodes::True.new : Arel::Nodes::False.new
1180
+ Arel::Nodes::InfixOperation.new(negated ? 'IS NOT' : 'IS',
1181
+ to_arel_operand(operand, table, model), literal)
499
1182
  end
1183
+ end
500
1184
 
1185
+ # A relation standing for a set of values, which is what IN and the
1186
+ # quantifiers each take. The treatment is ActiveRecord's own
1187
+ # RelationHandler's: without an explicit select list the subquery
1188
+ # selects the model's primary key.
1189
+ module SetSubquery
501
1190
  private
502
1191
 
503
- # The same treatment ActiveRecord's own RelationHandler gives a
504
- # relation used as a value: without an explicit select list the
505
- # subquery selects the model's primary key.
506
- def subquery(relation)
507
- if relation.eager_loading?
508
- relation = relation.send(:apply_join_dependency)
509
- end
1192
+ def set_subquery(relation, spelling)
1193
+ relation = relation.send(:apply_join_dependency) if relation.eager_loading?
510
1194
  if relation.select_values.empty?
511
1195
  model = relation.model
512
1196
  if model.composite_primary_key?
513
1197
  raise ArgumentError,
514
- "Cannot map composite primary key #{model.primary_key} to IN"
1198
+ "Cannot map composite primary key #{model.primary_key} to #{spelling}"
515
1199
  end
516
1200
  relation = relation.select(relation.table[model.primary_key])
517
1201
  end
@@ -519,6 +1203,55 @@ module ActiveRecord
519
1203
  end
520
1204
  end
521
1205
 
1206
+ # IN for a list of values, BETWEEN for a range, IN (SELECT ...) for a
1207
+ # relation.
1208
+ class In < Predicate
1209
+ include SetSubquery
1210
+
1211
+ attr_reader :operand, :values, :negated
1212
+
1213
+ def initialize(operand, values, negated: false)
1214
+ @operand = operand
1215
+ @values = values
1216
+ @negated = negated
1217
+ end
1218
+
1219
+ def to_arel(table, model)
1220
+ arel_operand = to_arel_operand(operand, table, model)
1221
+ if values.is_a?(Range)
1222
+ arel_operand.public_send(negated ? :not_between : :between, values)
1223
+ else
1224
+ arg = values.is_a?(ActiveRecord::Relation) ? set_subquery(values, 'IN') : values
1225
+ arel_operand.public_send(negated ? :not_in : :in, arg)
1226
+ end
1227
+ end
1228
+ end
1229
+
1230
+ # ANY and ALL, which stand on the right of a comparison and say how many
1231
+ # of the subquery's rows have to satisfy it. Where a scalar subquery
1232
+ # has to return one row, these take as many as come.
1233
+ class Quantified < Node
1234
+ include SetSubquery
1235
+
1236
+ attr_reader :kind, :relation
1237
+
1238
+ def initialize(kind, relation)
1239
+ unless relation.is_a?(ActiveRecord::Relation)
1240
+ raise ArgumentError,
1241
+ "#{kind} takes a relation as its subquery; a list is what in? takes"
1242
+ end
1243
+ @kind = kind
1244
+ @relation = relation
1245
+ end
1246
+
1247
+ # The subquery goes in as its own AST rather than as the manager,
1248
+ # which would parenthesise it a second time -- and to PostgreSQL
1249
+ # `ANY ((SELECT ...))` is ANY of one scalar, which it refuses.
1250
+ def to_arel(_table, _model)
1251
+ Arel::Nodes::NamedFunction.new(kind, [set_subquery(relation, kind).ast])
1252
+ end
1253
+ end
1254
+
522
1255
  # EXISTS (SELECT ...) for a relation. Correlate the subquery with the
523
1256
  # outer table through qualified columns. EXISTS only asks whether a row
524
1257
  # comes back, so unlike In there is no select list to fix up.
@@ -529,7 +1262,7 @@ module ActiveRecord
529
1262
  @relation = relation
530
1263
  end
531
1264
 
532
- def to_arel(_table)
1265
+ def to_arel(_table, _model)
533
1266
  subquery = relation
534
1267
  if subquery.eager_loading?
535
1268
  subquery = subquery.send(:apply_join_dependency)
@@ -555,19 +1288,23 @@ module ActiveRecord
555
1288
  inject {|left, right| Or.new(left, right) }
556
1289
  end
557
1290
 
558
- attr_reader :operand, :pattern, :escape, :case_sensitive
1291
+ attr_reader :operand, :pattern, :escape, :case_sensitive, :negated
559
1292
 
560
- def initialize(operand, pattern, escape = nil, case_sensitive: true)
1293
+ def initialize(operand, pattern, escape = nil, case_sensitive: true,
1294
+ negated: false)
561
1295
  @operand = operand
562
1296
  @pattern = pattern
563
1297
  @escape = escape
564
1298
  @case_sensitive = case_sensitive
1299
+ @negated = negated
565
1300
  end
566
1301
 
567
- def to_arel(table)
1302
+ def to_arel(table, model)
568
1303
  # Arel matches case-insensitively unless told otherwise, which is
569
1304
  # what picks ILIKE over LIKE on PostgreSQL.
570
- to_arel_operand(operand, table).matches(pattern, escape, case_sensitive)
1305
+ to_arel_operand(operand, table, model).
1306
+ public_send(negated ? :does_not_match : :matches,
1307
+ pattern, escape, case_sensitive)
571
1308
  end
572
1309
  end
573
1310
 
@@ -583,9 +1320,9 @@ module ActiveRecord
583
1320
  @negated = negated
584
1321
  end
585
1322
 
586
- def to_arel(table)
587
- arel_operand = to_arel_operand(operand, table)
588
- arel_value = value.is_a?(Node) ? value.to_arel(table) : value
1323
+ def to_arel(table, model)
1324
+ arel_operand = to_arel_operand(operand, table, model)
1325
+ arel_value = value.is_a?(Node) ? value.to_arel(table, model) : value
589
1326
  if negated
590
1327
  arel_operand.is_distinct_from(arel_value)
591
1328
  else
@@ -621,8 +1358,8 @@ module ActiveRecord
621
1358
  @elements = elements
622
1359
  end
623
1360
 
624
- def to_arel(table)
625
- arel_operand = to_arel_operand(operand, table)
1361
+ def to_arel(table, model)
1362
+ arel_operand = to_arel_operand(operand, table, model)
626
1363
  quoted = Arel::Nodes.build_quoted(array_literal)
627
1364
  case operator
628
1365
  when :"@>" then Arel::Nodes::Contains.new(arel_operand, quoted)
@@ -660,8 +1397,8 @@ module ActiveRecord
660
1397
  @negated = negated
661
1398
  end
662
1399
 
663
- def to_arel(table)
664
- arel_operand = to_arel_operand(operand, table)
1400
+ def to_arel(table, model)
1401
+ arel_operand = to_arel_operand(operand, table, model)
665
1402
  if negated
666
1403
  arel_operand.does_not_match_regexp(pattern)
667
1404
  else
@@ -693,8 +1430,8 @@ module ActiveRecord
693
1430
  @right = right
694
1431
  end
695
1432
 
696
- def to_arel(table)
697
- left.to_arel(table).and(right.to_arel(table))
1433
+ def to_arel(table, model)
1434
+ left.to_arel(table, model).and(right.to_arel(table, model))
698
1435
  end
699
1436
  end
700
1437
 
@@ -706,8 +1443,8 @@ module ActiveRecord
706
1443
  @right = right
707
1444
  end
708
1445
 
709
- def to_arel(table)
710
- left.to_arel(table).or(right.to_arel(table))
1446
+ def to_arel(table, model)
1447
+ left.to_arel(table, model).or(right.to_arel(table, model))
711
1448
  end
712
1449
  end
713
1450
 
@@ -718,8 +1455,8 @@ module ActiveRecord
718
1455
  @operand = operand
719
1456
  end
720
1457
 
721
- def to_arel(table)
722
- Arel::Nodes::Not.new(operand.to_arel(table))
1458
+ def to_arel(table, model)
1459
+ Arel::Nodes::Not.new(operand.to_arel(table, model))
723
1460
  end
724
1461
  end
725
1462
  end