arel_extensions 1.2.18 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
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]]))