arel_extensions 1.2.18 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +183 -0
  3. data/README.md +42 -0
  4. data/arel_extensions.gemspec +3 -3
  5. data/gemfiles/rails5_2.gemfile +6 -5
  6. data/gemfiles/rails6.gemfile +6 -6
  7. data/gemfiles/rails6_1.gemfile +30 -0
  8. data/gemfiles/rails7.gemfile +30 -0
  9. data/gemspecs/arel_extensions-v1.gemspec +28 -0
  10. data/{gemspec_v2 → gemspecs}/arel_extensions-v2.gemspec +1 -1
  11. data/generate_gems.sh +4 -3
  12. data/lib/arel_extensions/aliases.rb +14 -0
  13. data/lib/arel_extensions/attributes.rb +2 -0
  14. data/lib/arel_extensions/insert_manager.rb +19 -17
  15. data/lib/arel_extensions/math.rb +16 -16
  16. data/lib/arel_extensions/math_functions.rb +7 -3
  17. data/lib/arel_extensions/nodes/case.rb +2 -1
  18. data/lib/arel_extensions/nodes/cast.rb +1 -1
  19. data/lib/arel_extensions/nodes/concat.rb +1 -1
  20. data/lib/arel_extensions/nodes/function.rb +9 -3
  21. data/lib/arel_extensions/nodes/json.rb +3 -1
  22. data/lib/arel_extensions/nodes/length.rb +6 -0
  23. data/lib/arel_extensions/nodes/replace.rb +0 -8
  24. data/lib/arel_extensions/nodes/std.rb +4 -4
  25. data/lib/arel_extensions/nodes/union.rb +1 -1
  26. data/lib/arel_extensions/nodes/union_all.rb +1 -1
  27. data/lib/arel_extensions/string_functions.rb +10 -2
  28. data/lib/arel_extensions/version.rb +1 -1
  29. data/lib/arel_extensions/visitors/mssql.rb +1 -1
  30. data/lib/arel_extensions/visitors/mysql.rb +9 -2
  31. data/lib/arel_extensions/visitors/oracle.rb +2 -2
  32. data/lib/arel_extensions/visitors/oracle12.rb +0 -12
  33. data/lib/arel_extensions/visitors/postgresql.rb +14 -8
  34. data/lib/arel_extensions/visitors/to_sql.rb +26 -9
  35. data/lib/arel_extensions/visitors.rb +9 -1
  36. data/lib/arel_extensions.rb +64 -14
  37. data/test/database.yml +2 -0
  38. data/test/real_db_test.rb +5 -1
  39. data/test/visitors/test_to_sql.rb +18 -11
  40. data/test/with_ar/all_agnostic_test.rb +20 -4
  41. data/test/with_ar/insert_agnostic_test.rb +6 -2
  42. data/test/with_ar/test_bulk_sqlite.rb +6 -2
  43. data/test/with_ar/test_math_sqlite.rb +6 -2
  44. data/test/with_ar/test_string_mysql.rb +6 -2
  45. data/test/with_ar/test_string_sqlite.rb +6 -2
  46. data/version_v1.rb +1 -1
  47. data/version_v2.rb +1 -1
  48. metadata +14 -9
@@ -21,7 +21,11 @@ module ArelExtensions
21
21
  end
22
22
 
23
23
  def as other
24
- Arel::Nodes::As.new(self, Arel.sql(other))
24
+ res = Arel::Nodes::As.new(self.clone, Arel.sql(other))
25
+ if Gem::Version.new(Arel::VERSION) >= Gem::Version.new("9.0.0")
26
+ self.alias = Arel.sql(other)
27
+ end
28
+ res
25
29
  end
26
30
 
27
31
  def expr
@@ -48,7 +52,9 @@ module ArelExtensions
48
52
  case att
49
53
  when Arel::Attributes::Attribute
50
54
  begin
51
- Arel::Table.engine.connection.schema_cache.columns_hash(att.relation.table_name)[att.name.to_s].type
55
+ if Arel::Table.engine.connection.tables.include? att.relation.table_name
56
+ Arel::Table.engine.connection.schema_cache.columns_hash(att.relation.table_name)[att.name.to_s].type
57
+ end
52
58
  rescue
53
59
  att
54
60
  end
@@ -76,7 +82,7 @@ module ArelExtensions
76
82
  when ActiveSupport::Duration
77
83
  Arel.sql(object.to_i)
78
84
  when Array
79
- Arel::Nodes::Grouping.new(object.map{|r| convert_to_node(e)})
85
+ Arel.grouping(object.map{|e| convert_to_node(e)})
80
86
  else
81
87
  raise(ArgumentError, "#{object.class} cannot be converted to CONCAT arg")
82
88
  end
@@ -46,7 +46,9 @@ module ArelExtensions
46
46
  when DateTime, Time
47
47
  convert_to_node(n.strftime("%Y-%m-%dT%H:%M:%S.%L%:z"))
48
48
  when NilClass
49
- Arel.sql('null')
49
+ Arel.null
50
+ when Arel::SelectManager
51
+ Arel.grouping(n)
50
52
  else
51
53
  convert_to_node(n)
52
54
  end
@@ -2,6 +2,12 @@ module ArelExtensions
2
2
  module Nodes
3
3
  class Length < Function
4
4
  RETURN_TYPE = :integer
5
+ attr_accessor :bytewise
6
+
7
+ def initialize(node, bytewise = true)
8
+ @bytewise = bytewise
9
+ super([node])
10
+ end
5
11
  end
6
12
  end
7
13
  end
@@ -10,10 +10,6 @@ module ArelExtensions
10
10
  @substitute = convert_to_node(substitute)
11
11
  super([@left,@pattern,@substitute])
12
12
  end
13
-
14
- def +(other)
15
- return ArelExtensions::Nodes::Concat.new(self.expressions + [other])
16
- end
17
13
  end
18
14
 
19
15
  class RegexpReplace < Function
@@ -26,10 +22,6 @@ module ArelExtensions
26
22
  @substitute = convert_to_node(substitute)
27
23
  super([@left,@pattern,@substitute])
28
24
  end
29
-
30
- def +(other)
31
- return ArelExtensions::Nodes::Concat.new(self.expressions + [other])
32
- end
33
25
  end
34
26
  end
35
27
  end
@@ -4,9 +4,9 @@ module ArelExtensions
4
4
  RETURN_TYPE = :number
5
5
  attr_accessor :unbiased_estimator
6
6
 
7
- def initialize node, opts = {}
7
+ def initialize node, **opts
8
8
  @unbiased_estimator = opts[:unbiased] ? true : false
9
- super node, opts
9
+ super node, **opts
10
10
  end
11
11
  end
12
12
 
@@ -14,9 +14,9 @@ module ArelExtensions
14
14
  RETURN_TYPE = :number
15
15
  attr_accessor :unbiased_estimator
16
16
 
17
- def initialize node, opts = {}
17
+ def initialize node, **opts
18
18
  @unbiased_estimator = opts[:unbiased] ? true : false
19
- super node, opts
19
+ super node, **opts
20
20
  end
21
21
  end
22
22
  end
@@ -14,7 +14,7 @@ module ArelExtensions
14
14
  end
15
15
 
16
16
  def as other
17
- Arel::Nodes::TableAlias.new Arel::Nodes::Grouping.new(self), Arel::Nodes::SqlLiteral.new(other.to_s)
17
+ Arel::Nodes::TableAlias.new Arel.grouping(self), Arel::Nodes::SqlLiteral.new(other.to_s)
18
18
  end
19
19
  end
20
20
  end
@@ -10,7 +10,7 @@ module ArelExtensions
10
10
  end
11
11
 
12
12
  def as other
13
- Arel::Nodes::TableAlias.new Arel::Nodes::Grouping.new(self), Arel::Nodes::SqlLiteral.new(other.to_s)
13
+ Arel::Nodes::TableAlias.new Arel.grouping(self), Arel::Nodes::SqlLiteral.new(other.to_s)
14
14
  end
15
15
  end
16
16
  end
@@ -24,9 +24,17 @@ module ArelExtensions
24
24
  ArelExtensions::Nodes::FindInSet.new [other, self]
25
25
  end
26
26
 
27
- # LENGTH function returns the length of the value in a text field.
27
+ # LENGTH function returns the length (bytewise) of the value in a text field.
28
28
  def length
29
- ArelExtensions::Nodes::Length.new [self]
29
+ ArelExtensions::Nodes::Length.new self, true
30
+ end
31
+
32
+ def byte_length
33
+ ArelExtensions::Nodes::Length.new self, true
34
+ end
35
+
36
+ def char_length
37
+ ArelExtensions::Nodes::Length.new self, false
30
38
  end
31
39
 
32
40
  # LOCATE function returns the first starting position of a string in another string.
@@ -1,3 +1,3 @@
1
1
  module ArelExtensions
2
- VERSION = "1.2.18".freeze
2
+ VERSION = "1.3.0".freeze
3
3
  end
@@ -155,7 +155,7 @@ module ArelExtensions
155
155
  end
156
156
 
157
157
  def visit_ArelExtensions_Nodes_Length o, collector
158
- collector << "LEN("
158
+ collector << "#{o.bytewise ? 'DATALENGTH' : 'LEN'}("
159
159
  collector = visit o.expr, collector
160
160
  collector << ")"
161
161
  collector
@@ -354,9 +354,16 @@ module ArelExtensions
354
354
  else
355
355
  collector = visit o.left, collector
356
356
  end
357
- collector << " AS `"
357
+ collector << " AS "
358
+
359
+ # sometimes these values are already quoted, if they are, don't double quote it
360
+ quote = o.right.is_a?(Arel::Nodes::SqlLiteral) && o.right[0] != '`' && o.right[-1] != '`'
361
+
362
+ collector << '`' if quote
358
363
  collector = visit o.right, collector
359
- collector << "`"
364
+ collector << '`' if quote
365
+
366
+ collector
360
367
  collector
361
368
  end
362
369
 
@@ -5,7 +5,7 @@ module ArelExtensions
5
5
  SPECIAL_CHARS = {"\t" => 'CHR(9)', "\n" => 'CHR(10)', "\r" => 'CHR(13)'}
6
6
  DATE_MAPPING = {'d' => 'DAY', 'm' => 'MONTH', 'w' => 'IW', 'y' => 'YEAR', 'wd' => 'D', 'h' => 'HOUR', 'mn' => 'MINUTE', 's' => 'SECOND'}
7
7
  DATE_FORMAT_DIRECTIVES = {
8
- '%Y' => 'IYYY', '%C' => 'CC', '%y' => 'YY', '%m' => 'MM', '%B' => 'Month', '%^B' => 'MONTH', '%b' => 'Mon', '%^b' => 'MON',
8
+ '%Y' => 'YYYY', '%C' => 'CC', '%y' => 'YY', '%m' => 'MM', '%B' => 'Month', '%^B' => 'MONTH', '%b' => 'Mon', '%^b' => 'MON',
9
9
  '%d' => 'DD', '%e' => 'FMDD', '%j' => 'DDD', '%w' => '', '%A' => 'Day', # day, weekday
10
10
  '%H' => 'HH24', '%k' => '', '%I' => 'HH', '%l' => '', '%P' => 'am', '%p' => 'AM', # hours
11
11
  '%M' => 'MI', '%S' => 'SS', '%L' => 'MS', '%N' => 'US', '%z' => 'tz' # seconds, subseconds
@@ -308,7 +308,7 @@ module ArelExtensions
308
308
  end
309
309
 
310
310
  def visit_ArelExtensions_Nodes_Length o, collector
311
- collector << "LENGTH("
311
+ collector << "LENGTH#{o.bytewise ? 'B' : ''}("
312
312
  collector = visit o.expr, collector
313
313
  collector << ")"
314
314
  collector
@@ -2,18 +2,6 @@ module ArelExtensions
2
2
  module Visitors
3
3
  Arel::Visitors.send(:remove_const,'Oracle12') if Arel::Visitors.const_defined?('Oracle12')
4
4
  Arel::Visitors.const_set('Oracle12',Class.new(Arel::Visitors::Oracle)).class_eval do
5
- def visit_Arel_Nodes_SelectStatement(o, collector)
6
- # Oracle does not allow LIMIT clause with select for update
7
- if o.limit && o.lock
8
- raise ArgumentError, <<-MSG
9
- 'Combination of limit and lock is not supported.
10
- because generated SQL statements
11
- `SELECT FOR UPDATE and FETCH FIRST n ROWS` generates ORA-02014.`
12
- MSG
13
- end
14
-
15
- super
16
- end
17
5
 
18
6
  def visit_Arel_Nodes_SelectOptions(o, collector)
19
7
  collector = maybe_visit o.offset, collector
@@ -7,7 +7,7 @@ module ArelExtensions
7
7
  }.freeze
8
8
 
9
9
  DATE_FORMAT_DIRECTIVES = {
10
- '%Y' => 'IYYY', '%C' => 'CC', '%y' => 'YY',
10
+ '%Y' => 'YYYY', '%C' => 'CC', '%y' => 'YY',
11
11
  '%m' => 'MM', '%B' => 'Month', '%^B' => 'MONTH', '%b' => 'Mon', '%^b' => 'MON',
12
12
  '%d' => 'DD', '%e' => 'FMDD', '%j' => 'DDD', '%w' => '', '%A' => 'Day', # day, weekday
13
13
  '%H' => 'HH24', '%k' => '', '%I' => 'HH', '%l' => '', '%P' => 'am', '%p' => 'AM', # hours
@@ -86,9 +86,15 @@ module ArelExtensions
86
86
  else
87
87
  collector = visit o.left, collector
88
88
  end
89
- collector << " AS \""
89
+ collector << " AS "
90
+
91
+ # sometimes these values are already quoted, if they are, don't double quote it
92
+ quote = o.right.is_a?(Arel::Nodes::SqlLiteral) && o.right[0] != '"' && o.right[-1] != '"'
93
+
94
+ collector << '"' if quote
90
95
  collector = visit o.right, collector
91
- collector << "\""
96
+ collector << '"' if quote
97
+
92
98
  collector
93
99
  end
94
100
 
@@ -373,6 +379,8 @@ module ArelExtensions
373
379
  Arel::Nodes::SqlLiteral.new('date')
374
380
  when :binary
375
381
  Arel::Nodes::SqlLiteral.new('binary')
382
+ when :jsonb
383
+ Arel::Nodes::SqlLiteral.new('jsonb')
376
384
  else
377
385
  Arel::Nodes::SqlLiteral.new(o.as_attr.to_s)
378
386
  end
@@ -401,13 +409,13 @@ module ArelExtensions
401
409
  ArelExtensions::Nodes::Concat.new([
402
410
  Arel::Nodes::NamedFunction.new('TRIM',[
403
411
  Arel::Nodes::NamedFunction.new('TO_CHAR',[
404
- col.abs/Arel::Nodes.build_quoted(10).pow(col.abs.log10.floor),
412
+ Arel.when(col.not_eq 0).then(col.abs/Arel::Nodes.build_quoted(10).pow(col.abs.log10.floor)).else(1),
405
413
  Arel::Nodes.build_quoted('FM'+nines_before+'"'+comma+'"V'+nines_after)
406
414
  ])]),
407
415
  o.type,
408
416
  Arel::Nodes::NamedFunction.new('TRIM',[
409
417
  Arel::Nodes::NamedFunction.new('TO_CHAR',[
410
- col.abs.log10.floor,
418
+ Arel.when(col.not_eq 0).then(col.abs.log10.floor).else(0),
411
419
  Arel::Nodes.build_quoted('FM'+nines_before)
412
420
  ])])
413
421
  ])
@@ -511,8 +519,6 @@ module ArelExtensions
511
519
  collector << '::jsonb'
512
520
  when NilClass
513
521
  collector << %Q['null'::jsonb]
514
- when Arel::Attributes::Attribute
515
- collector = visit o.dict.cast(:jsonb), collector
516
522
  else
517
523
  collector = visit o.dict, collector
518
524
  collector << '::jsonb'
@@ -532,7 +538,7 @@ module ArelExtensions
532
538
 
533
539
  def visit_ArelExtensions_Nodes_JsonGet o,collector
534
540
  collector = visit o.dict, collector
535
- collector << ' -> '
541
+ collector << ' ->> '
536
542
  collector = visit o.key, collector
537
543
  collector
538
544
  end
@@ -3,6 +3,20 @@ module ArelExtensions
3
3
  class Arel::Visitors::ToSql
4
4
  COMMA = ', ' unless defined?(COMMA)
5
5
 
6
+ # Escape properly the string expression expr.
7
+ # Take care of escaping.
8
+ def make_json_string expr
9
+ Arel::Nodes.build_quoted('"') \
10
+ + expr
11
+ .coalesce('')
12
+ .replace('\\','\\\\').replace('"','\"').replace("\n", '\n') \
13
+ + '"'
14
+ end
15
+
16
+ def make_json_null
17
+ Arel::Nodes.build_quoted("null")
18
+ end
19
+
6
20
  # Math Functions
7
21
  def visit_ArelExtensions_Nodes_Abs o, collector
8
22
  collector << "ABS("
@@ -108,7 +122,7 @@ module ArelExtensions
108
122
  end
109
123
 
110
124
  def visit_ArelExtensions_Nodes_Length o, collector
111
- collector << "LENGTH("
125
+ collector << "#{o.bytewise ? '' : 'CHAR_'}LENGTH("
112
126
  collector = visit o.left, collector
113
127
  collector << ")"
114
128
  collector
@@ -339,6 +353,9 @@ module ArelExtensions
339
353
  # override
340
354
  remove_method(:visit_Arel_Nodes_As) rescue nil # if Arel::Visitors::ToSql.method_defined?(:visit_Arel_Nodes_As)
341
355
  def visit_Arel_Nodes_As o, collector
356
+ if o.left.respond_to?(:alias)
357
+ o.left.alias = nil
358
+ end
342
359
  if o.left.is_a?(Arel::Nodes::Binary)
343
360
  collector << '('
344
361
  collector = visit o.left, collector
@@ -589,20 +606,20 @@ module ArelExtensions
589
606
  def json_value(o,v)
590
607
  case o.type_of_node(v)
591
608
  when :string
592
- Arel.when(v.is_null).then(Arel::Nodes.build_quoted("null")).else(Arel::Nodes.build_quoted('"') + v.replace('\\','\\\\').replace('"','\"') + '"')
609
+ Arel.when(v.is_null).then(make_json_null).else(make_json_string(v))
593
610
  when :date
594
611
  s = v.format('%Y-%m-%d')
595
- Arel.when(s.is_null).then(Arel::Nodes.build_quoted("null")).else(Arel::Nodes.build_quoted('"') + s + '"')
612
+ Arel.when(s.is_null).then(make_json_null).else(make_json_string(s))
596
613
  when :datetime
597
614
  s = v.format('%Y-%m-%dT%H:%M:%S')
598
- Arel.when(s.is_null).then(Arel::Nodes.build_quoted("null")).else(Arel::Nodes.build_quoted('"') + s + '"')
615
+ Arel.when(s.is_null).then(make_json_null).else(make_json_string(s))
599
616
  when :time
600
617
  s = v.format('%H:%M:%S')
601
- Arel.when(s.is_null).then(Arel::Nodes.build_quoted("null")).else(Arel::Nodes.build_quoted('"') + s + '"')
618
+ Arel.when(s.is_null).then(make_json_null).else(make_json_string(s))
602
619
  when :nil
603
- Arel::Nodes.build_quoted("null")
620
+ make_json_null
604
621
  else
605
- ArelExtensions::Nodes::Cast.new([v, :string]).coalesce("null")
622
+ ArelExtensions::Nodes::Cast.new([v, :string]).coalesce(make_json_null)
606
623
  end
607
624
  end
608
625
 
@@ -624,7 +641,7 @@ module ArelExtensions
624
641
  if i != 0
625
642
  res += ', '
626
643
  end
627
- res += Arel::Nodes.build_quoted('"') + ArelExtensions::Nodes::Cast.new([k, :string]).coalesce("").replace('\\','\\\\').replace('"','\"') + '": '
644
+ res += make_json_string(ArelExtensions::Nodes::Cast.new([k, :string])) + ': '
628
645
  res += json_value(o,v)
629
646
  end
630
647
  res += '}'
@@ -646,7 +663,7 @@ module ArelExtensions
646
663
  if i != 0
647
664
  res = res + ', '
648
665
  end
649
- kv = Arel::Nodes.build_quoted('"') + ArelExtensions::Nodes::Cast.new([k, :string]).coalesce("").replace('\\','\\\\').replace('"','\"') + '": '
666
+ kv = make_json_string(ArelExtensions::Nodes::Cast.new([k, :string])) + ': '
650
667
  kv += json_value(o,v)
651
668
  res = res + kv.group_concat(', ', order: Array(orders)).coalesce('')
652
669
  end
@@ -9,9 +9,17 @@ if defined?(Arel::Visitors::Oracle)
9
9
  require 'arel_extensions/visitors/oracle12'
10
10
  end
11
11
 
12
- if defined?(Arel::Visitors::MSSQL)
12
+ if defined?(Arel::Visitors::SQLServer) || defined?(Arel::Visitors::MSSQL)
13
13
  require 'arel_extensions/visitors/mssql'
14
+ end
14
15
 
16
+ if defined?(Arel::Visitors::SQLServer)
17
+ class Arel::Visitors::SQLServer
18
+ include ArelExtensions::Visitors::MSSQL
19
+ end
20
+ end
21
+
22
+ if defined?(Arel::Visitors::MSSQL)
15
23
  class Arel::Visitors::MSSQL
16
24
  include ArelExtensions::Visitors::MSSQL
17
25
 
@@ -16,8 +16,10 @@ class Arel::Nodes::Casted
16
16
  include Arel::AliasPredication
17
17
 
18
18
  # They forget to define hash.
19
- def hash
20
- [self.class, self.val, self.attribute].hash
19
+ if Gem::Version.new(Arel::VERSION) < Gem::Version.new("10.0.0")
20
+ def hash
21
+ [self.class, self.val, self.attribute].hash
22
+ end
21
23
  end
22
24
  end
23
25
 
@@ -31,6 +33,12 @@ class Arel::Nodes::Grouping
31
33
  include Arel::OrderPredications
32
34
  end
33
35
 
36
+ class Arel::Nodes::Ordering
37
+ def eql? other
38
+ self.hash.eql? other.hash
39
+ end
40
+ end
41
+
34
42
  class Arel::Nodes::Function
35
43
  include Arel::Math
36
44
  include Arel::Expressions
@@ -44,6 +52,7 @@ if Gem::Version.new(Arel::VERSION) >= Gem::Version.new("7.1.0")
44
52
  end
45
53
 
46
54
  require 'arel_extensions/version'
55
+ require 'arel_extensions/aliases'
47
56
  require 'arel_extensions/attributes'
48
57
  require 'arel_extensions/visitors'
49
58
  require 'arel_extensions/nodes'
@@ -80,11 +89,13 @@ module Arel
80
89
  end
81
90
 
82
91
  def self.json *expr
83
- if expr.length == 1
84
- ArelExtensions::Nodes::Json.new(expr.first)
85
- else
86
- ArelExtensions::Nodes::Json.new(expr)
87
- end
92
+ ArelExtensions::Nodes::Json.new(
93
+ if expr.length == 1
94
+ expr.first
95
+ else
96
+ expr
97
+ end
98
+ )
88
99
  end
89
100
 
90
101
  def self.when condition
@@ -92,20 +103,31 @@ module Arel
92
103
  end
93
104
 
94
105
  def self.duration s, expr
95
- ArelExtensions::Nodes::Duration.new(s.to_s+'i',expr)
106
+ ArelExtensions::Nodes::Duration.new("#{s}i", expr)
96
107
  end
97
108
 
109
+ def self.grouping *v
110
+ Arel::Nodes::Grouping.new(*v)
111
+ end
112
+
113
+ # The TRUE pseudo literal.
98
114
  def self.true
99
- Arel::Nodes::Equality.new(1,1)
115
+ Arel::Nodes::Equality.new(1, 1)
100
116
  end
101
117
 
118
+ # The FALSE pseudo literal.
102
119
  def self.false
103
- Arel::Nodes::Equality.new(1,0)
120
+ Arel::Nodes::Equality.new(1, 0)
121
+ end
122
+
123
+ # The NULL literal.
124
+ def self.null
125
+ Arel::Nodes.build_quoted(nil)
104
126
  end
105
127
 
106
128
  def self.tuple *v
107
- tmp = Arel::Nodes::Grouping.new(nil)
108
- Arel::Nodes::Grouping.new(v.map{|e| tmp.convert_to_node(e)})
129
+ tmp = Arel.grouping(nil)
130
+ Arel.grouping v.map{|e| tmp.convert_to_node(e)}
109
131
  end
110
132
  end
111
133
 
@@ -115,6 +137,7 @@ class Arel::Attributes::Attribute
115
137
  end
116
138
 
117
139
  class Arel::Nodes::Function
140
+ include ArelExtensions::Aliases
118
141
  include ArelExtensions::Math
119
142
  include ArelExtensions::Comparators
120
143
  include ArelExtensions::DateDuration
@@ -126,7 +149,11 @@ class Arel::Nodes::Function
126
149
 
127
150
  alias_method(:old_as, :as) rescue nil
128
151
  def as other
129
- Arel::Nodes::As.new(self, Arel.sql(other))
152
+ res = Arel::Nodes::As.new(self.clone, Arel.sql(other))
153
+ if Gem::Version.new(Arel::VERSION) >= Gem::Version.new("9.0.0")
154
+ self.alias = Arel.sql(other)
155
+ end
156
+ res
130
157
  end
131
158
  end
132
159
 
@@ -146,6 +173,9 @@ class Arel::Nodes::Unary
146
173
  include ArelExtensions::MathFunctions
147
174
  include ArelExtensions::Comparators
148
175
  include ArelExtensions::Predications
176
+ def eql? other
177
+ hash == other.hash
178
+ end
149
179
  end
150
180
 
151
181
  class Arel::Nodes::Binary
@@ -155,6 +185,9 @@ class Arel::Nodes::Binary
155
185
  include ArelExtensions::Comparators
156
186
  include ArelExtensions::BooleanFunctions
157
187
  include ArelExtensions::Predications
188
+ def eql? other
189
+ hash == other.hash
190
+ end
158
191
  end
159
192
 
160
193
  class Arel::Nodes::Equality
@@ -171,6 +204,19 @@ end
171
204
  class Arel::SelectManager
172
205
  include ArelExtensions::SetFunctions
173
206
  include ArelExtensions::Nodes
207
+
208
+ def as table_name
209
+ Arel::Nodes::TableAlias.new(self, table_name)
210
+ end
211
+
212
+ # Install an alias, if present.
213
+ def xas table_name
214
+ if table_name.present?
215
+ as table_name
216
+ else
217
+ self
218
+ end
219
+ end
174
220
  end
175
221
 
176
222
  class Arel::Nodes::As
@@ -180,7 +226,11 @@ end
180
226
  class Arel::Table
181
227
  alias_method(:old_alias, :alias) rescue nil
182
228
  def alias(name = "#{self.name}_2")
183
- name.blank? ? self : Arel::Nodes::TableAlias.new(self,name)
229
+ if name.present?
230
+ Arel::Nodes::TableAlias.new(self, name)
231
+ else
232
+ self
233
+ end
184
234
  end
185
235
  end
186
236
 
data/test/database.yml CHANGED
@@ -10,6 +10,8 @@ mysql:
10
10
  adapter: mysql2
11
11
  database: arext_test
12
12
  username: travis
13
+ host: 127.0.0.1
14
+ port: 3306
13
15
  encoding: utf8
14
16
  jdbc-mysql:
15
17
  adapter: jdbcmysql
data/test/real_db_test.rb CHANGED
@@ -8,7 +8,11 @@ require 'arel_extensions'
8
8
  def setup_db
9
9
  ActiveRecord::Base.configurations = YAML.load_file('test/database.yml')
10
10
  ActiveRecord::Base.establish_connection(ENV['DB'].try(:to_sym) || (RUBY_PLATFORM == 'java' ? :"jdbc-sqlite" : :sqlite))
11
- ActiveRecord::Base.default_timezone = :utc
11
+ if ActiveRecord::VERSION::MAJOR >= 7
12
+ ActiveRecord.default_timezone = :utc
13
+ else
14
+ ActiveRecord::Base.default_timezone = :utc
15
+ end
12
16
  @cnx = ActiveRecord::Base.connection
13
17
  if ActiveRecord::Base.connection.adapter_name =~ /sqlite/i
14
18
  $sqlite = true
@@ -282,16 +282,23 @@ module ArelExtensions
282
282
  .must_be_like %{CASE "users"."name" WHEN 'smith' THEN 'cool' ELSE 'uncool' END ILIKE 'value'}
283
283
  end
284
284
 
285
- it "should be possible to use as on anything" do
286
- _(compile(@table[:name].as('alias'))).must_be_like %{"users"."name" AS alias}
287
- _(compile(@table[:name].concat(' test').as('alias'))).must_be_like %{CONCAT("users"."name", ' test') AS alias}
288
- _(compile((@table[:name] + ' test').as('alias'))).must_be_like %{CONCAT("users"."name", ' test') AS alias}
289
- _(compile((@table[:age] + 42).as('alias'))).must_be_like %{("users"."age" + 42) AS alias}
290
- _(compile(@table[:name].coalesce('').as('alias'))).must_be_like %{COALESCE("users"."name", '') AS alias}
291
- _(compile(Arel::Nodes.build_quoted('test').as('alias'))).must_be_like %{'test' AS alias}
292
- _(compile(@table.project(@table[:name]).as('alias'))).must_be_like %{(SELECT "users"."name" FROM "users") alias}
293
- _(compile(@table[:name].when("smith").then("cool").else("uncool").as('alias')))
294
- .must_be_like %{CASE "users"."name" WHEN 'smith' THEN 'cool' ELSE 'uncool' END AS alias}
285
+ it "should be possible to use as/xas on anything" do
286
+ {
287
+ @table[:name] => %{"users"."name" AS alias},
288
+ @table[:name].concat(' test') => %{CONCAT("users"."name", ' test') AS alias},
289
+ (@table[:name] + ' test') => %{CONCAT("users"."name", ' test') AS alias},
290
+ (@table[:age] + 42) => %{("users"."age" + 42) AS alias},
291
+ @table[:name].coalesce('') => %{COALESCE("users"."name", '') AS alias},
292
+ Arel::Nodes.build_quoted('test') => %{'test' AS alias},
293
+ @table.project(@table[:name]) => %{(SELECT "users"."name" FROM "users") "alias"},
294
+ @table[:name].when("smith").then("cool").else("uncool") => %{CASE "users"."name" WHEN 'smith' THEN 'cool' ELSE 'uncool' END AS alias},
295
+ }.each do |exp, res|
296
+ _(compile(exp.as('alias'))).must_be_like res
297
+ _(compile(exp.xas('alias'))).must_be_like res
298
+
299
+ res_no_alias = res.gsub(/\s*(?:AS alias|"alias")\s*\z/, '')
300
+ _(compile(exp.xas(nil))).must_be_like res_no_alias
301
+ end
295
302
  end
296
303
 
297
304
  it "should accept comparators on functions" do
@@ -373,7 +380,7 @@ module ArelExtensions
373
380
  end
374
381
 
375
382
  it "should respecting Grouping" do
376
- g = ->(*v) { Arel::Nodes::Grouping.new(v) }
383
+ g = ->(*v) { Arel.grouping(v) }
377
384
  _(compile(g[@table[:id], @table[:age]].in [g[1, 42]]))
378
385
  .must_be_like %{("users"."id", "users"."age") IN ((1, 42))}
379
386
  _(compile(g[@table[:id], @table[:age]].in [g[1, 42], g[2, 51]]))