activerecord-refined 0.5.1 → 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}"
@@ -75,6 +89,27 @@ module ActiveRecord
75
89
  Comparison.new(self, :!=, nil)
76
90
  end
77
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
+
78
113
  def in?(values)
79
114
  In.new(self, values)
80
115
  end
@@ -91,6 +126,13 @@ module ActiveRecord
91
126
  In.new(self, min..max, negated: true)
92
127
  end
93
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
+
94
136
  def like?(pattern)
95
137
  Like.new(self, pattern)
96
138
  end
@@ -168,6 +210,39 @@ module ActiveRecord
168
210
  def intersect?(elements)
169
211
  ArrayPredicate.new(self, :"&&", ArrayPredicate.elements(elements, "intersect?"))
170
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
171
246
  end
172
247
 
173
248
  # Arithmetic builders shared by symbols, qualified columns and
@@ -189,41 +264,48 @@ module ActiveRecord
189
264
  def /(other)
190
265
  Arithmetic.new(self, :/, other)
191
266
  end
192
- end
193
267
 
194
- # Aggregate builders shared by symbols, qualified columns and
195
- # expressions. Imported into the Symbol refinement like Predications,
196
- # so every method must be defined with def.
197
- module Aggregations
198
- # DISTINCT is Arel's only aggregate modifier, and only for count.
199
- def count(distinct: false)
200
- 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)
201
278
  end
202
279
 
203
- def sum
204
- Aggregate.new(self, :sum)
280
+ def ^(other)
281
+ Bitwise.new(self, :^, other)
205
282
  end
206
283
 
207
- def average
208
- Aggregate.new(self, :average)
284
+ def <<(other)
285
+ Bitwise.new(self, :<<, other)
209
286
  end
210
287
 
211
- def maximum
212
- Aggregate.new(self, :maximum)
288
+ def >>(other)
289
+ Bitwise.new(self, :>>, other)
213
290
  end
214
291
 
215
- def minimum
216
- Aggregate.new(self, :minimum)
292
+ def ~
293
+ BitwiseNot.new(self)
217
294
  end
218
295
  end
219
296
 
220
297
  class Node
221
- 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)
222
304
  raise ScriptError, "subclass must override this method"
223
305
  end
224
306
 
225
- def as(alias_name)
226
- As.new(self, alias_name)
307
+ def as(alias_name, quote: true)
308
+ As.new(self, alias_name, quote: quote)
227
309
  end
228
310
 
229
311
  def asc
@@ -237,9 +319,9 @@ module ActiveRecord
237
319
  private
238
320
 
239
321
  # Resolves an operand denoting a column or an expression.
240
- def to_arel_operand(operand, table)
322
+ def to_arel_operand(operand, table, model)
241
323
  case operand
242
- when Node then operand.to_arel(table)
324
+ when Node then operand.to_arel(table, model)
243
325
  when :* then Arel.star
244
326
  when Symbol then table[operand]
245
327
  else operand
@@ -248,9 +330,9 @@ module ActiveRecord
248
330
 
249
331
  # Resolves a function argument: a column or an expression as above,
250
332
  # anything else a value to be quoted.
251
- def to_arel_argument(arg, table)
333
+ def to_arel_argument(arg, table, model)
252
334
  case arg
253
- when Node, Symbol then to_arel_operand(arg, table)
335
+ when Node, Symbol then to_arel_operand(arg, table, model)
254
336
  else Arel::Nodes.build_quoted(arg)
255
337
  end
256
338
  end
@@ -280,7 +362,6 @@ module ActiveRecord
280
362
  class Value < Node
281
363
  include Predications
282
364
  include Arithmetics
283
- include Aggregations
284
365
 
285
366
  attr_reader :value
286
367
 
@@ -288,15 +369,316 @@ module ActiveRecord
288
369
  @value = value
289
370
  end
290
371
 
291
- def to_arel(_table)
372
+ def to_arel(_table, _model)
292
373
  Arel::Nodes.build_quoted(value)
293
374
  end
294
375
  end
295
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
+
296
679
  class Column < Node
297
680
  include Predications
298
681
  include Arithmetics
299
- include Aggregations
300
682
 
301
683
  attr_reader :table_name, :column_name
302
684
 
@@ -305,7 +687,7 @@ module ActiveRecord
305
687
  @column_name = column_name
306
688
  end
307
689
 
308
- def to_arel(_table)
690
+ def to_arel(_table, _model)
309
691
  Arel::Table.new(table_name)[column_name]
310
692
  end
311
693
  end
@@ -316,7 +698,6 @@ module ActiveRecord
316
698
  class Arithmetic < Node
317
699
  include Predications
318
700
  include Arithmetics
319
- include Aggregations
320
701
 
321
702
  attr_reader :left, :operator, :right
322
703
 
@@ -326,19 +707,218 @@ module ActiveRecord
326
707
  @right = right
327
708
  end
328
709
 
329
- def to_arel(table)
330
- to_arel_operand(left, table).
331
- 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)
332
911
  end
333
912
  end
334
913
 
335
914
  class Aggregate < Node
336
915
  include Predications
337
916
  include Arithmetics
917
+ include Windowing
338
918
 
339
- attr_reader :operand, :function, :distinct
919
+ attr_reader :operand, :function, :distinct, :condition
340
920
 
341
- def initialize(operand, function, distinct: false)
921
+ def initialize(operand, function, distinct: false, condition: nil)
342
922
  if distinct && function != :count
343
923
  raise ArgumentError, "#{function} does not take distinct"
344
924
  end
@@ -348,10 +928,35 @@ module ActiveRecord
348
928
  @operand = operand
349
929
  @function = function
350
930
  @distinct = distinct
931
+ @condition = condition
932
+ end
933
+
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))
351
939
  end
352
940
 
353
- def to_arel(table)
354
- arel_operand = to_arel_operand(operand, table)
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)
355
960
  if function == :count
356
961
  arel_operand.count(distinct)
357
962
  else
@@ -360,16 +965,37 @@ module ActiveRecord
360
965
  end
361
966
  end
362
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.
363
976
  class As < Node
364
- attr_reader :operand, :alias_name
977
+ attr_reader :operand, :alias_name, :quote
365
978
 
366
- 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
367
984
  @operand = operand
368
- @alias_name = AST.check_name(alias_name, ALIAS_NAME, "column alias")
985
+ @alias_name = alias_name
986
+ @quote = quote
369
987
  end
370
988
 
371
- def to_arel(table)
372
- 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) }
373
999
  end
374
1000
  end
375
1001
 
@@ -392,8 +1018,8 @@ module ActiveRecord
392
1018
  Ordering.new(operand, direction, :nulls_last)
393
1019
  end
394
1020
 
395
- def to_arel(table)
396
- 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)
397
1023
  nulls ? ordering.public_send(nulls) : ordering
398
1024
  end
399
1025
  end
@@ -401,6 +1027,7 @@ module ActiveRecord
401
1027
  class Function < Node
402
1028
  include Predications
403
1029
  include Arithmetics
1030
+ include Windowing
404
1031
 
405
1032
  attr_reader :name, :args
406
1033
 
@@ -409,12 +1036,23 @@ module ActiveRecord
409
1036
  @args = args
410
1037
  end
411
1038
 
412
- def to_arel(table)
413
- 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) }
414
1041
  Arel::Nodes::NamedFunction.new(name, arel_args)
415
1042
  end
416
1043
  end
417
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
+
418
1056
  # EXTRACT(field FROM expr). The field is grammar rather than a value --
419
1057
  # a keyword the adapter reads bare -- so it has to be a plain name,
420
1058
  # which Arel upcases on the way out.
@@ -429,8 +1067,8 @@ module ActiveRecord
429
1067
  @operand = operand
430
1068
  end
431
1069
 
432
- def to_arel(table)
433
- 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)
434
1072
  end
435
1073
  end
436
1074
 
@@ -449,10 +1087,10 @@ module ActiveRecord
449
1087
  @sql_type = AST.check_name(sql_type, TYPE_NAME, "SQL type")
450
1088
  end
451
1089
 
452
- def to_arel(table)
1090
+ def to_arel(table, model)
453
1091
  Arel::Nodes::NamedFunction.new(
454
1092
  "CAST",
455
- [Arel::Nodes::As.new(to_arel_argument(operand, table),
1093
+ [Arel::Nodes::As.new(to_arel_argument(operand, table, model),
456
1094
  Arel::Nodes::SqlLiteral.new(sql_type.to_s))])
457
1095
  end
458
1096
  end
@@ -478,7 +1116,7 @@ module ActiveRecord
478
1116
  @precision = precision
479
1117
  end
480
1118
 
481
- def to_arel(_table)
1119
+ def to_arel(_table, _model)
482
1120
  Arel::Nodes::SqlLiteral.new(
483
1121
  precision ? "#{name}(#{precision})" : name)
484
1122
  end
@@ -501,11 +1139,11 @@ module ActiveRecord
501
1139
  @value = value
502
1140
  end
503
1141
 
504
- def to_arel(table)
505
- arel_column = to_arel_operand(column, table)
1142
+ def to_arel(table, model)
1143
+ arel_column = to_arel_operand(column, table, model)
506
1144
  arel_value =
507
1145
  case value
508
- when Node then value.to_arel(table)
1146
+ when Node then value.to_arel(table, model)
509
1147
  when ActiveRecord::Relation then scalar_subquery(value)
510
1148
  else value
511
1149
  end
@@ -526,9 +1164,50 @@ module ActiveRecord
526
1164
  end
527
1165
  end
528
1166
 
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
1171
+
1172
+ def initialize(operand, value, negated: false)
1173
+ @operand = operand
1174
+ @value = value
1175
+ @negated = negated
1176
+ end
1177
+
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)
1182
+ end
1183
+ end
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
1190
+ private
1191
+
1192
+ def set_subquery(relation, spelling)
1193
+ relation = relation.send(:apply_join_dependency) if relation.eager_loading?
1194
+ if relation.select_values.empty?
1195
+ model = relation.model
1196
+ if model.composite_primary_key?
1197
+ raise ArgumentError,
1198
+ "Cannot map composite primary key #{model.primary_key} to #{spelling}"
1199
+ end
1200
+ relation = relation.select(relation.table[model.primary_key])
1201
+ end
1202
+ relation.arel
1203
+ end
1204
+ end
1205
+
529
1206
  # IN for a list of values, BETWEEN for a range, IN (SELECT ...) for a
530
1207
  # relation.
531
1208
  class In < Predicate
1209
+ include SetSubquery
1210
+
532
1211
  attr_reader :operand, :values, :negated
533
1212
 
534
1213
  def initialize(operand, values, negated: false)
@@ -537,34 +1216,39 @@ module ActiveRecord
537
1216
  @negated = negated
538
1217
  end
539
1218
 
540
- def to_arel(table)
541
- arel_operand = to_arel_operand(operand, table)
1219
+ def to_arel(table, model)
1220
+ arel_operand = to_arel_operand(operand, table, model)
542
1221
  if values.is_a?(Range)
543
1222
  arel_operand.public_send(negated ? :not_between : :between, values)
544
1223
  else
545
- arg = values.is_a?(ActiveRecord::Relation) ? subquery(values) : values
1224
+ arg = values.is_a?(ActiveRecord::Relation) ? set_subquery(values, 'IN') : values
546
1225
  arel_operand.public_send(negated ? :not_in : :in, arg)
547
1226
  end
548
1227
  end
1228
+ end
549
1229
 
550
- private
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
551
1235
 
552
- # The same treatment ActiveRecord's own RelationHandler gives a
553
- # relation used as a value: without an explicit select list the
554
- # subquery selects the model's primary key.
555
- def subquery(relation)
556
- if relation.eager_loading?
557
- relation = relation.send(:apply_join_dependency)
558
- end
559
- if relation.select_values.empty?
560
- model = relation.model
561
- if model.composite_primary_key?
562
- raise ArgumentError,
563
- "Cannot map composite primary key #{model.primary_key} to IN"
564
- end
565
- relation = relation.select(relation.table[model.primary_key])
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"
566
1242
  end
567
- relation.arel
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])
568
1252
  end
569
1253
  end
570
1254
 
@@ -578,7 +1262,7 @@ module ActiveRecord
578
1262
  @relation = relation
579
1263
  end
580
1264
 
581
- def to_arel(_table)
1265
+ def to_arel(_table, _model)
582
1266
  subquery = relation
583
1267
  if subquery.eager_loading?
584
1268
  subquery = subquery.send(:apply_join_dependency)
@@ -615,10 +1299,10 @@ module ActiveRecord
615
1299
  @negated = negated
616
1300
  end
617
1301
 
618
- def to_arel(table)
1302
+ def to_arel(table, model)
619
1303
  # Arel matches case-insensitively unless told otherwise, which is
620
1304
  # what picks ILIKE over LIKE on PostgreSQL.
621
- to_arel_operand(operand, table).
1305
+ to_arel_operand(operand, table, model).
622
1306
  public_send(negated ? :does_not_match : :matches,
623
1307
  pattern, escape, case_sensitive)
624
1308
  end
@@ -636,9 +1320,9 @@ module ActiveRecord
636
1320
  @negated = negated
637
1321
  end
638
1322
 
639
- def to_arel(table)
640
- arel_operand = to_arel_operand(operand, table)
641
- 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
642
1326
  if negated
643
1327
  arel_operand.is_distinct_from(arel_value)
644
1328
  else
@@ -674,8 +1358,8 @@ module ActiveRecord
674
1358
  @elements = elements
675
1359
  end
676
1360
 
677
- def to_arel(table)
678
- arel_operand = to_arel_operand(operand, table)
1361
+ def to_arel(table, model)
1362
+ arel_operand = to_arel_operand(operand, table, model)
679
1363
  quoted = Arel::Nodes.build_quoted(array_literal)
680
1364
  case operator
681
1365
  when :"@>" then Arel::Nodes::Contains.new(arel_operand, quoted)
@@ -713,8 +1397,8 @@ module ActiveRecord
713
1397
  @negated = negated
714
1398
  end
715
1399
 
716
- def to_arel(table)
717
- arel_operand = to_arel_operand(operand, table)
1400
+ def to_arel(table, model)
1401
+ arel_operand = to_arel_operand(operand, table, model)
718
1402
  if negated
719
1403
  arel_operand.does_not_match_regexp(pattern)
720
1404
  else
@@ -746,8 +1430,8 @@ module ActiveRecord
746
1430
  @right = right
747
1431
  end
748
1432
 
749
- def to_arel(table)
750
- 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))
751
1435
  end
752
1436
  end
753
1437
 
@@ -759,8 +1443,8 @@ module ActiveRecord
759
1443
  @right = right
760
1444
  end
761
1445
 
762
- def to_arel(table)
763
- 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))
764
1448
  end
765
1449
  end
766
1450
 
@@ -771,8 +1455,8 @@ module ActiveRecord
771
1455
  @operand = operand
772
1456
  end
773
1457
 
774
- def to_arel(table)
775
- Arel::Nodes::Not.new(operand.to_arel(table))
1458
+ def to_arel(table, model)
1459
+ Arel::Nodes::Not.new(operand.to_arel(table, model))
776
1460
  end
777
1461
  end
778
1462
  end