arel 6.0.4 → 7.1.4

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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/History.txt +31 -12
  3. data/MIT-LICENSE.txt +2 -1
  4. data/{README.markdown → README.md} +88 -31
  5. data/lib/arel.rb +1 -1
  6. data/lib/arel/attributes/attribute.rb +8 -0
  7. data/lib/arel/crud.rb +4 -3
  8. data/lib/arel/delete_manager.rb +6 -1
  9. data/lib/arel/insert_manager.rb +1 -1
  10. data/lib/arel/math.rb +24 -0
  11. data/lib/arel/nodes.rb +6 -38
  12. data/lib/arel/nodes/binary.rb +0 -2
  13. data/lib/arel/nodes/bind_param.rb +3 -0
  14. data/lib/arel/nodes/case.rb +57 -0
  15. data/lib/arel/nodes/casted.rb +44 -0
  16. data/lib/arel/nodes/delete_statement.rb +2 -0
  17. data/lib/arel/nodes/infix_operation.rb +36 -1
  18. data/lib/arel/nodes/matches.rb +3 -1
  19. data/lib/arel/nodes/regexp.rb +14 -0
  20. data/lib/arel/nodes/select_core.rb +5 -5
  21. data/lib/arel/nodes/table_alias.rb +6 -2
  22. data/lib/arel/nodes/unary.rb +6 -3
  23. data/lib/arel/nodes/unary_operation.rb +25 -0
  24. data/lib/arel/predications.rb +38 -14
  25. data/lib/arel/select_manager.rb +6 -6
  26. data/lib/arel/table.rb +34 -59
  27. data/lib/arel/tree_manager.rb +3 -8
  28. data/lib/arel/update_manager.rb +1 -1
  29. data/lib/arel/visitors.rb +1 -23
  30. data/lib/arel/visitors/depth_first.rb +14 -2
  31. data/lib/arel/visitors/dot.rb +12 -1
  32. data/lib/arel/visitors/informix.rb +6 -1
  33. data/lib/arel/visitors/mssql.rb +35 -3
  34. data/lib/arel/visitors/mysql.rb +8 -0
  35. data/lib/arel/visitors/oracle.rb +13 -2
  36. data/lib/arel/visitors/oracle12.rb +59 -0
  37. data/lib/arel/visitors/postgresql.rb +56 -4
  38. data/lib/arel/visitors/sqlite.rb +9 -0
  39. data/lib/arel/visitors/to_sql.rb +94 -52
  40. data/lib/arel/visitors/where_sql.rb +10 -1
  41. metadata +11 -6
@@ -0,0 +1,59 @@
1
+ module Arel
2
+ module Visitors
3
+ class Oracle12 < Arel::Visitors::ToSql
4
+ private
5
+
6
+ def visit_Arel_Nodes_SelectStatement o, collector
7
+ # Oracle does not allow LIMIT clause with select for update
8
+ if o.limit && o.lock
9
+ raise ArgumentError, <<-MSG
10
+ 'Combination of limit and lock is not supported.
11
+ because generated SQL statements
12
+ `SELECT FOR UPDATE and FETCH FIRST n ROWS` generates ORA-02014.`
13
+ MSG
14
+ end
15
+ super
16
+ end
17
+
18
+ def visit_Arel_Nodes_SelectOptions o, collector
19
+ collector = maybe_visit o.offset, collector
20
+ collector = maybe_visit o.limit, collector
21
+ collector = maybe_visit o.lock, collector
22
+ end
23
+
24
+ def visit_Arel_Nodes_Limit o, collector
25
+ collector << "FETCH FIRST "
26
+ collector = visit o.expr, collector
27
+ collector << " ROWS ONLY"
28
+ end
29
+
30
+ def visit_Arel_Nodes_Offset o, collector
31
+ collector << "OFFSET "
32
+ visit o.expr, collector
33
+ collector << " ROWS"
34
+ end
35
+
36
+ def visit_Arel_Nodes_Except o, collector
37
+ collector << "( "
38
+ collector = infix_value o, collector, " MINUS "
39
+ collector << " )"
40
+ end
41
+
42
+ def visit_Arel_Nodes_UpdateStatement o, collector
43
+ # Oracle does not allow ORDER BY/LIMIT in UPDATEs.
44
+ if o.orders.any? && o.limit.nil?
45
+ # However, there is no harm in silently eating the ORDER BY clause if no LIMIT has been provided,
46
+ # otherwise let the user deal with the error
47
+ o = o.dup
48
+ o.orders = []
49
+ end
50
+
51
+ super
52
+ end
53
+
54
+ def visit_Arel_Nodes_BindParam o, collector
55
+ collector.add_bind(o) { |i| ":a#{i}" }
56
+ end
57
+ end
58
+ end
59
+ end
@@ -1,22 +1,42 @@
1
1
  module Arel
2
2
  module Visitors
3
3
  class PostgreSQL < Arel::Visitors::ToSql
4
+ CUBE = 'CUBE'
5
+ ROLLUP = 'ROLLUP'
6
+ GROUPING_SET = 'GROUPING SET'
7
+
4
8
  private
5
9
 
6
10
  def visit_Arel_Nodes_Matches o, collector
7
- infix_value o, collector, ' ILIKE '
11
+ op = o.case_sensitive ? ' LIKE ' : ' ILIKE '
12
+ collector = infix_value o, collector, op
13
+ if o.escape
14
+ collector << ' ESCAPE '
15
+ visit o.escape, collector
16
+ else
17
+ collector
18
+ end
8
19
  end
9
20
 
10
21
  def visit_Arel_Nodes_DoesNotMatch o, collector
11
- infix_value o, collector, ' NOT ILIKE '
22
+ op = o.case_sensitive ? ' NOT LIKE ' : ' NOT ILIKE '
23
+ collector = infix_value o, collector, op
24
+ if o.escape
25
+ collector << ' ESCAPE '
26
+ visit o.escape, collector
27
+ else
28
+ collector
29
+ end
12
30
  end
13
31
 
14
32
  def visit_Arel_Nodes_Regexp o, collector
15
- infix_value o, collector, ' ~ '
33
+ op = o.case_sensitive ? ' ~ ' : ' ~* '
34
+ infix_value o, collector, op
16
35
  end
17
36
 
18
37
  def visit_Arel_Nodes_NotRegexp o, collector
19
- infix_value o, collector, ' !~ '
38
+ op = o.case_sensitive ? ' !~ ' : ' !~* '
39
+ infix_value o, collector, op
20
40
  end
21
41
 
22
42
  def visit_Arel_Nodes_DistinctOn o, collector
@@ -27,6 +47,38 @@ module Arel
27
47
  def visit_Arel_Nodes_BindParam o, collector
28
48
  collector.add_bind(o) { |i| "$#{i}" }
29
49
  end
50
+
51
+ def visit_Arel_Nodes_GroupingElement o, collector
52
+ collector << "( "
53
+ visit(o.expr, collector) << " )"
54
+ end
55
+
56
+ def visit_Arel_Nodes_Cube o, collector
57
+ collector << CUBE
58
+ grouping_array_or_grouping_element o, collector
59
+ end
60
+
61
+ def visit_Arel_Nodes_RollUp o, collector
62
+ collector << ROLLUP
63
+ grouping_array_or_grouping_element o, collector
64
+ end
65
+
66
+ def visit_Arel_Nodes_GroupingSet o, collector
67
+ collector << GROUPING_SET
68
+ grouping_array_or_grouping_element o, collector
69
+ end
70
+
71
+ # Utilized by GroupingSet, Cube & RollUp visitors to
72
+ # handle grouping aggregation semantics
73
+ def grouping_array_or_grouping_element o, collector
74
+ if o.expr.is_a? Array
75
+ collector << "( "
76
+ visit o.expr, collector
77
+ collector << " )"
78
+ else
79
+ visit o.expr, collector
80
+ end
81
+ end
30
82
  end
31
83
  end
32
84
  end
@@ -12,6 +12,15 @@ module Arel
12
12
  o.limit = Arel::Nodes::Limit.new(-1) if o.offset && !o.limit
13
13
  super
14
14
  end
15
+
16
+ def visit_Arel_Nodes_True o, collector
17
+ collector << "1"
18
+ end
19
+
20
+ def visit_Arel_Nodes_False o, collector
21
+ collector << "0"
22
+ end
23
+
15
24
  end
16
25
  end
17
26
  end
@@ -4,6 +4,12 @@ require 'arel/visitors/reduce'
4
4
 
5
5
  module Arel
6
6
  module Visitors
7
+ class UnsupportedVisitError < StandardError
8
+ def initialize(object)
9
+ super "Unsupported argument type: #{object.class.name}. Construct an Arel node instead."
10
+ end
11
+ end
12
+
7
13
  class ToSql < Arel::Visitors::Reduce
8
14
  ##
9
15
  # This is some roflscale crazy stuff. I'm roflscaling this because
@@ -74,14 +80,14 @@ module Arel
74
80
  end
75
81
 
76
82
  def visit_Arel_Nodes_DeleteStatement o, collector
77
- collector << "DELETE FROM "
83
+ collector << 'DELETE FROM '
78
84
  collector = visit o.relation, collector
79
85
  if o.wheres.any?
80
- collector << " WHERE "
81
- inject_join o.wheres, collector, AND
82
- else
83
- collector
86
+ collector << ' WHERE '
87
+ collector = inject_join o.wheres, collector, AND
84
88
  end
89
+
90
+ maybe_visit o.limit, collector
85
91
  end
86
92
 
87
93
  # FIXME: we should probably have a 2-pass visitor for this
@@ -164,7 +170,7 @@ module Arel
164
170
  end
165
171
 
166
172
  def table_exists? name
167
- schema_cache.table_exists? name
173
+ schema_cache.data_source_exists?(name)
168
174
  end
169
175
 
170
176
  def column_for attr
@@ -193,7 +199,7 @@ module Arel
193
199
  collector << quote(value, attr && column_for(attr)).to_s
194
200
  end
195
201
  unless i == len
196
- collector << ', '
202
+ collector << COMMA
197
203
  end
198
204
  }
199
205
 
@@ -211,7 +217,6 @@ module Arel
211
217
  }
212
218
 
213
219
  unless o.orders.empty?
214
- collector << SPACE
215
220
  collector << ORDER_BY
216
221
  len = o.orders.length - 1
217
222
  o.orders.each_with_index { |x, i|
@@ -220,11 +225,15 @@ module Arel
220
225
  }
221
226
  end
222
227
 
228
+ visit_Arel_Nodes_SelectOptions(o, collector)
229
+
230
+ collector
231
+ end
232
+
233
+ def visit_Arel_Nodes_SelectOptions o, collector
223
234
  collector = maybe_visit o.limit, collector
224
235
  collector = maybe_visit o.offset, collector
225
236
  collector = maybe_visit o.lock, collector
226
-
227
- collector
228
237
  end
229
238
 
230
239
  def visit_Arel_Nodes_SelectCore o, collector
@@ -234,50 +243,33 @@ module Arel
234
243
 
235
244
  collector = maybe_visit o.set_quantifier, collector
236
245
 
237
- unless o.projections.empty?
238
- collector << SPACE
239
- len = o.projections.length - 1
240
- o.projections.each_with_index do |x, i|
241
- collector = visit(x, collector)
242
- collector << COMMA unless len == i
243
- end
244
- end
246
+ collect_nodes_for o.projections, collector, SPACE
245
247
 
246
248
  if o.source && !o.source.empty?
247
249
  collector << " FROM "
248
250
  collector = visit o.source, collector
249
251
  end
250
252
 
251
- unless o.wheres.empty?
252
- collector << WHERE
253
- len = o.wheres.length - 1
254
- o.wheres.each_with_index do |x, i|
255
- collector = visit(x, collector)
256
- collector << AND unless len == i
257
- end
253
+ collect_nodes_for o.wheres, collector, WHERE, AND
254
+ collect_nodes_for o.groups, collector, GROUP_BY
255
+ unless o.havings.empty?
256
+ collector << " HAVING "
257
+ inject_join o.havings, collector, AND
258
258
  end
259
+ collect_nodes_for o.windows, collector, WINDOW
259
260
 
260
- unless o.groups.empty?
261
- collector << GROUP_BY
262
- len = o.groups.length - 1
263
- o.groups.each_with_index do |x, i|
264
- collector = visit(x, collector)
265
- collector << COMMA unless len == i
266
- end
267
- end
268
-
269
- collector = maybe_visit o.having, collector
261
+ collector
262
+ end
270
263
 
271
- unless o.windows.empty?
272
- collector << WINDOW
273
- len = o.windows.length - 1
274
- o.windows.each_with_index do |x, i|
264
+ def collect_nodes_for nodes, collector, spacer, connector = COMMA
265
+ unless nodes.empty?
266
+ collector << spacer
267
+ len = nodes.length - 1
268
+ nodes.each_with_index do |x, i|
275
269
  collector = visit(x, collector)
276
- collector << COMMA unless len == i
270
+ collector << connector unless len == i
277
271
  end
278
272
  end
279
-
280
- collector
281
273
  end
282
274
 
283
275
  def visit_Arel_Nodes_Bin o, collector
@@ -337,13 +329,13 @@ module Arel
337
329
  end
338
330
 
339
331
  if o.orders.any?
340
- collector << ' ' if o.partitions.any?
332
+ collector << SPACE if o.partitions.any?
341
333
  collector << "ORDER BY "
342
334
  collector = inject_join o.orders, collector, ", "
343
335
  end
344
336
 
345
337
  if o.framing
346
- collector << ' ' if o.partitions.any? or o.orders.any?
338
+ collector << SPACE if o.partitions.any? or o.orders.any?
347
339
  collector = visit o.framing, collector
348
340
  end
349
341
 
@@ -405,11 +397,6 @@ module Arel
405
397
  end
406
398
  end
407
399
 
408
- def visit_Arel_Nodes_Having o, collector
409
- collector << "HAVING "
410
- visit o.expr, collector
411
- end
412
-
413
400
  def visit_Arel_Nodes_Offset o, collector
414
401
  collector << "OFFSET "
415
402
  visit o.expr, collector
@@ -557,7 +544,7 @@ module Arel
557
544
  collector = visit o.left, collector
558
545
  end
559
546
  if o.right.any?
560
- collector << " " if o.left
547
+ collector << SPACE if o.left
561
548
  collector = inject_join o.right, collector, ' '
562
549
  end
563
550
  collector
@@ -701,6 +688,35 @@ module Arel
701
688
  visit o.right, collector
702
689
  end
703
690
 
691
+ def visit_Arel_Nodes_Case o, collector
692
+ collector << "CASE "
693
+ if o.case
694
+ visit o.case, collector
695
+ collector << " "
696
+ end
697
+ o.conditions.each do |condition|
698
+ visit condition, collector
699
+ collector << " "
700
+ end
701
+ if o.default
702
+ visit o.default, collector
703
+ collector << " "
704
+ end
705
+ collector << "END"
706
+ end
707
+
708
+ def visit_Arel_Nodes_When o, collector
709
+ collector << "WHEN "
710
+ visit o.left, collector
711
+ collector << " THEN "
712
+ visit o.right, collector
713
+ end
714
+
715
+ def visit_Arel_Nodes_Else o, collector
716
+ collector << "ELSE "
717
+ visit o.expr, collector
718
+ end
719
+
704
720
  def visit_Arel_Nodes_UnqualifiedColumn o, collector
705
721
  collector << "#{quote_column_name o.name}"
706
722
  collector
@@ -729,11 +745,15 @@ module Arel
729
745
  alias :visit_Integer :literal
730
746
 
731
747
  def quoted o, a
732
- quote(o, column_for(a))
748
+ if a && a.able_to_type_cast?
749
+ quote(a.type_cast_for_database(o))
750
+ else
751
+ quote(o, column_for(a))
752
+ end
733
753
  end
734
754
 
735
755
  def unsupported o, collector
736
- raise "unsupported: #{o.class.name}"
756
+ raise UnsupportedVisitError.new(o)
737
757
  end
738
758
 
739
759
  alias :visit_ActiveSupport_Multibyte_Chars :unsupported
@@ -762,6 +782,11 @@ module Arel
762
782
  alias :visit_Arel_Nodes_Multiplication :visit_Arel_Nodes_InfixOperation
763
783
  alias :visit_Arel_Nodes_Division :visit_Arel_Nodes_InfixOperation
764
784
 
785
+ def visit_Arel_Nodes_UnaryOperation o, collector
786
+ collector << " #{o.operator} "
787
+ visit o.expr, collector
788
+ end
789
+
765
790
  def visit_Array o, collector
766
791
  inject_join o, collector, ", "
767
792
  end
@@ -769,6 +794,9 @@ module Arel
769
794
 
770
795
  def quote value, column = nil
771
796
  return value if Arel::Nodes::SqlLiteral === value
797
+ if column
798
+ print_type_cast_deprecation
799
+ end
772
800
  @connection.quote value, column
773
801
  end
774
802
 
@@ -818,6 +846,20 @@ module Arel
818
846
  collector
819
847
  end
820
848
  end
849
+
850
+ def print_type_cast_deprecation
851
+ unless defined?($arel_silence_type_casting_deprecation) && $arel_silence_type_casting_deprecation
852
+ warn <<-eowarn
853
+ Arel performing automatic type casting is deprecated, and will be removed in Arel 8.0. If you are seeing this, it is because you are manually passing a value to an Arel predicate, and the `Arel::Table` object was constructed manually. The easiest way to remove this warning is to use an `Arel::Table` object returned from calling `arel_table` on an ActiveRecord::Base subclass.
854
+
855
+ If you're certain the value is already of the right type, change `attribute.eq(value)` to `attribute.eq(Arel::Nodes::Quoted.new(value))` (you will be able to remove that in Arel 8.0, it is only required to silence this deprecation warning).
856
+
857
+ You can also silence this warning globally by setting `$arel_silence_type_casting_deprecation` to `true`. (Do NOT do this if you are a library author)
858
+
859
+ If you are passing user input to a predicate, you must either give an appropriate type caster object to the `Arel::Table`, or manually cast the value before passing it to Arel.
860
+ eowarn
861
+ end
862
+ end
821
863
  end
822
864
  end
823
865
  end
@@ -1,11 +1,20 @@
1
1
  module Arel
2
2
  module Visitors
3
3
  class WhereSql < Arel::Visitors::ToSql
4
+ def initialize(inner_visitor, *args, &block)
5
+ @inner_visitor = inner_visitor
6
+ super(*args, &block)
7
+ end
8
+
4
9
  private
5
10
 
6
11
  def visit_Arel_Nodes_SelectCore o, collector
7
12
  collector << "WHERE "
8
- inject_join o.wheres, collector, ' AND '
13
+ wheres = o.wheres.map do |where|
14
+ Nodes::SqlLiteral.new(@inner_visitor.accept(where, collector.class.new).value)
15
+ end
16
+
17
+ inject_join wheres, collector, ' AND '
9
18
  end
10
19
  end
11
20
  end