arel_extensions 2.0.4 → 2.0.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +54 -86
  3. data/README.md +1 -1
  4. data/Rakefile +13 -2
  5. data/arel_extensions.gemspec +1 -1
  6. data/gemfiles/rails4.gemfile +1 -1
  7. data/gemfiles/rails6.gemfile +1 -1
  8. data/generate_gems.sh +4 -3
  9. data/lib/arel_extensions.rb +33 -19
  10. data/lib/arel_extensions/attributes.rb +0 -1
  11. data/lib/arel_extensions/boolean_functions.rb +39 -12
  12. data/lib/arel_extensions/insert_manager.rb +7 -5
  13. data/lib/arel_extensions/nodes/abs.rb +0 -0
  14. data/lib/arel_extensions/nodes/case.rb +1 -1
  15. data/lib/arel_extensions/nodes/ceil.rb +0 -0
  16. data/lib/arel_extensions/nodes/coalesce.rb +0 -0
  17. data/lib/arel_extensions/nodes/concat.rb +0 -0
  18. data/lib/arel_extensions/nodes/duration.rb +0 -0
  19. data/lib/arel_extensions/nodes/find_in_set.rb +0 -0
  20. data/lib/arel_extensions/nodes/floor.rb +0 -0
  21. data/lib/arel_extensions/nodes/function.rb +0 -0
  22. data/lib/arel_extensions/nodes/is_null.rb +0 -0
  23. data/lib/arel_extensions/nodes/length.rb +0 -0
  24. data/lib/arel_extensions/nodes/locate.rb +0 -0
  25. data/lib/arel_extensions/nodes/rand.rb +0 -0
  26. data/lib/arel_extensions/nodes/replace.rb +0 -0
  27. data/lib/arel_extensions/nodes/round.rb +0 -0
  28. data/lib/arel_extensions/nodes/soundex.rb +0 -0
  29. data/lib/arel_extensions/nodes/substring.rb +0 -0
  30. data/lib/arel_extensions/nodes/trim.rb +0 -0
  31. data/lib/arel_extensions/nodes/union.rb +0 -0
  32. data/lib/arel_extensions/nodes/wday.rb +0 -0
  33. data/lib/arel_extensions/predications.rb +22 -19
  34. data/lib/arel_extensions/set_functions.rb +2 -2
  35. data/lib/arel_extensions/string_functions.rb +13 -0
  36. data/lib/arel_extensions/version.rb +1 -1
  37. data/lib/arel_extensions/visitors.rb +1 -1
  38. data/lib/arel_extensions/visitors/ibm_db.rb +1 -1
  39. data/lib/arel_extensions/visitors/mysql.rb +24 -9
  40. data/lib/arel_extensions/visitors/oracle.rb +7 -8
  41. data/lib/arel_extensions/visitors/postgresql.rb +1 -1
  42. data/lib/arel_extensions/visitors/sqlite.rb +11 -7
  43. data/lib/arel_extensions/visitors/to_sql.rb +31 -38
  44. data/test/arelx_test_helper.rb +28 -0
  45. data/test/support/fake_record.rb +4 -0
  46. data/test/test_comparators.rb +8 -7
  47. data/test/visitors/test_bulk_insert_oracle.rb +4 -3
  48. data/test/visitors/test_bulk_insert_sqlite.rb +4 -3
  49. data/test/visitors/test_bulk_insert_to_sql.rb +3 -3
  50. data/test/visitors/test_oracle.rb +41 -41
  51. data/test/visitors/test_to_sql.rb +359 -206
  52. data/test/with_ar/all_agnostic_test.rb +18 -8
  53. data/test/with_ar/insert_agnostic_test.rb +1 -1
  54. data/test/with_ar/test_bulk_sqlite.rb +4 -3
  55. data/test/with_ar/test_math_sqlite.rb +1 -1
  56. data/test/with_ar/test_string_mysql.rb +1 -1
  57. data/test/with_ar/test_string_sqlite.rb +1 -1
  58. data/version_v1.rb +1 -1
  59. data/version_v2.rb +1 -1
  60. metadata +3 -3
  61. data/test/helper.rb +0 -18
@@ -23,6 +23,5 @@ module ArelExtensions
23
23
  def !=(other)
24
24
  Arel::Nodes::NotEqual.new self, Arel::Nodes.build_quoted(other, self)
25
25
  end
26
-
27
26
  end
28
27
  end
@@ -1,3 +1,4 @@
1
+ # coding: utf-8
1
2
  require 'arel_extensions/nodes/then'
2
3
 
3
4
  module ArelExtensions
@@ -8,7 +9,7 @@ module ArelExtensions
8
9
  end
9
10
 
10
11
  def and *others
11
- Arel::Nodes::And.new([self]+ others.flatten)
12
+ Arel::Nodes::And.new self, others
12
13
  end
13
14
 
14
15
  def ⋁(other)
@@ -16,28 +17,54 @@ module ArelExtensions
16
17
  end
17
18
 
18
19
  def or *others
19
- args = others.flatten
20
- if args.length == 1
21
- Arel::Nodes::Or.new(self, args.first)
22
- else
23
- ArelExtensions::Nodes::Or.new([self]+ args)
24
- end
20
+ Arel::Nodes::Or.new self, others
25
21
  end
26
22
 
27
23
  def then(t, f = nil)
28
24
  ArelExtensions::Nodes::Then.new [self, t, f]
29
25
  end
26
+
30
27
  end
31
28
  end
32
29
 
33
- Arel::Nodes::And.class_eval do
30
+ class Arel::Nodes::And
34
31
  include ArelExtensions::BooleanFunctions
35
- end
36
32
 
37
- Arel::Nodes::Or.class_eval do
38
- include ArelExtensions::BooleanFunctions
33
+ def self.new *children
34
+ children =
35
+ children.flatten.map { |c|
36
+ c.is_a?(self) ? c.children : c
37
+ }.flatten
38
+ super(children)
39
+ end
40
+
39
41
  end
40
42
 
41
- ArelExtensions::Nodes.const_set('Or',Class.new(Arel::Nodes::And)).class_eval do
43
+ # For some reason, Arel's And is properly defined as variadic (it
44
+ # stores @children, and hashes it all). However Arel's Or is defined
45
+ # as binary, with only @left and @right, and hashing only @left and @right.
46
+ #
47
+ # So reimplement its ctor and accessors.
48
+
49
+ class Arel::Nodes::Or
42
50
  include ArelExtensions::BooleanFunctions
51
+
52
+ attr_reader :children
53
+
54
+ def self.new *children
55
+ children =
56
+ children.flatten.map { |c|
57
+ c.is_a?(self) ? c.children : c
58
+ }.flatten
59
+ super(*children)
60
+ end
61
+
62
+ def initialize *children
63
+ @children = children
64
+ end
65
+
66
+ def hash
67
+ children.hash
68
+ end
69
+
43
70
  end
@@ -4,21 +4,23 @@ module ArelExtensions
4
4
  module InsertManager
5
5
 
6
6
  def bulk_insert(cols, data)
7
+ res_columns = []
7
8
  case cols.first
8
9
  when String, Symbol
9
10
  cols.each { |c|
10
- @ast.columns << @ast.relation[c]
11
+ res_columns << @ast.relation[c]
11
12
  }
12
13
  when Array
13
14
  if String === cols.first.first
14
- @ast.columns = cols.map {|c| [@ast.relation[c.first]] }
15
+ res_columns = cols.map {|c| [@ast.relation[c.first]] }
15
16
  elsif Arel::Attributes::Attribute == cols.first.first
16
- @ast.columns = cols
17
+ res_columns = cols
17
18
  end
18
19
  when NilClass
19
- @ast.columns = @ast.relation.columns
20
+ res_columns = @ast.relation.columns
20
21
  end
21
- self.values = BulkValues.new(@ast.columns, data)
22
+ self.values = BulkValues.new(res_columns, data)
23
+ @ast.columns = res_columns
22
24
  end
23
25
 
24
26
  class BulkValues < Arel::Nodes::Node
File without changes
@@ -26,7 +26,7 @@ module ArelExtensions
26
26
  end
27
27
  end
28
28
 
29
- ArelExtensions::Nodes::Case.class_eval do
29
+ class ArelExtensions::Nodes::Case
30
30
  include Arel::Expressions
31
31
  include Arel::OrderPredications
32
32
  include ArelExtensions::Math
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -1,5 +1,6 @@
1
1
  module ArelExtensions
2
2
  module Predications
3
+
3
4
  def when right, expression = nil
4
5
  ArelExtensions::Nodes::Case.new(self).when(right,expression)
5
6
  end
@@ -20,23 +21,25 @@ module ArelExtensions
20
21
  ArelExtensions::Nodes::Cast.new([self,right])
21
22
  end
22
23
 
23
- def in(other) #In should handle nil element in the Array
24
+ def in(*other) #In should handle nil element in the Array
25
+ other = other.first if other.length <= 1
24
26
  case other
25
27
  when Range
26
28
  self.between(other)
29
+ when Arel::Nodes::Grouping
30
+ Arel::Nodes::In.new(self, quoted_node(other))
27
31
  when Enumerable
28
32
  nils, values = other.partition{ |v| v.nil? }
29
33
  ranges, values = values.partition{ |v| v.is_a?(Range) || v.is_a?(Arel::SelectManager)}
30
-
31
34
  # In order of (imagined) decreasing efficiency: nil, values, and then more complex.
32
35
  clauses =
33
36
  nils.uniq.map { |r| self.in(r) } \
34
37
  + (case values.uniq.size
35
38
  when 0 then []
36
- when 1 then [self == values[0]]
39
+ when 1 then [values[0].is_a?(Arel::Nodes::Grouping) ? self.in(values[0]) : self.eq(values[0])]
37
40
  else [Arel::Nodes::In.new(self, quoted_array(values))] end) \
38
41
  + ranges.uniq.map { |r| self.in(r) }
39
- clauses.empty? ? self.is_null : clauses.reduce(&:or)
42
+ clauses.empty? ? Arel.false : clauses.reduce(&:or)
40
43
  when nil
41
44
  self.is_null
42
45
  when Arel::SelectManager
@@ -46,24 +49,25 @@ module ArelExtensions
46
49
  end
47
50
  end
48
51
 
49
- def not_in(other) #In should handle nil element in the Array
52
+ def not_in(*other) #In should handle nil element in the Array
53
+ other = other.first if other.length <= 1
50
54
  case other
51
55
  when Range
52
56
  Arel::Nodes::Not.new(self.between(other))
57
+ when Arel::Nodes::Grouping
58
+ Arel::Nodes::NotIn.new(self, quoted_node(other))
53
59
  when Enumerable
54
- if other.include?(nil)
55
- other.delete(nil)
56
- case other.length
57
- when 0
58
- self.is_not_null
59
- when 1
60
- self.is_not_null.and(self!=other[0])
61
- else
62
- self.is_not_null.and(Arel::Nodes::NotIn.new(self,quoted_array(other)))
63
- end
64
- else
65
- Arel::Nodes::NotIn.new(self,quoted_array(other))
66
- end
60
+ nils, values = other.partition{ |v| v.nil? }
61
+ ranges, values = values.partition{ |v| v.is_a?(Range) || v.is_a?(Arel::SelectManager)}
62
+ # In order of (imagined) decreasing efficiency: nil, values, and then more complex.
63
+ clauses =
64
+ nils.uniq.map { |r| self.not_in(r) } \
65
+ + (case values.uniq.size
66
+ when 0 then []
67
+ when 1 then [values[0].is_a?(Arel::Nodes::Grouping) ? self.not_in(values[0]) : self.not_eq(values[0])]
68
+ else [Arel::Nodes::NotIn.new(self, quoted_array(values))] end) \
69
+ + ranges.uniq.map { |r| self.not_in(r) }
70
+ Arel::Nodes::And.new clauses
67
71
  when nil
68
72
  self.is_not_null
69
73
  when Arel::SelectManager
@@ -93,6 +97,5 @@ module ArelExtensions
93
97
  raise(ArgumentError, "#{object.class} can not be converted to CONCAT arg")
94
98
  end
95
99
  end
96
-
97
100
  end
98
101
  end
@@ -23,10 +23,10 @@ module ArelExtensions
23
23
  end
24
24
  end
25
25
 
26
- Arel::Nodes::Union.class_eval do
26
+ class Arel::Nodes::Union
27
27
  include ArelExtensions::SetFunctions
28
28
  end
29
29
 
30
- Arel::Nodes::UnionAll.class_eval do
30
+ class Arel::Nodes::UnionAll
31
31
  include ArelExtensions::SetFunctions
32
32
  end
@@ -64,6 +64,19 @@ module ArelExtensions
64
64
  grouping_any :imatches, others, escape
65
65
  end
66
66
 
67
+ # def grouping_any method, others, *extra
68
+ # puts "*******************"
69
+ # puts method
70
+ # puts others.inspect
71
+ # puts extra.inspect
72
+ # puts "-------------------"
73
+ # res = super(method,others,*extra)
74
+ # puts res.to_sql
75
+ # puts res.inspect
76
+ # puts "*******************"
77
+ # res
78
+ # end
79
+
67
80
  def imatches_all others, escape = nil
68
81
  grouping_all :imatches, others, escape, escape
69
82
  end
@@ -1,3 +1,3 @@
1
1
  module ArelExtensions
2
- VERSION = "2.0.4".freeze
2
+ VERSION = "2.0.8".freeze
3
3
  end
@@ -6,7 +6,7 @@ require 'arel_extensions/visitors/postgresql'
6
6
  require 'arel_extensions/visitors/sqlite'
7
7
  require 'arel_extensions/visitors/mssql'
8
8
 
9
- Arel::Visitors::MSSQL.class_eval do
9
+ class Arel::Visitors::MSSQL
10
10
  include ArelExtensions::Visitors::MSSQL
11
11
 
12
12
  alias_method :old_visit_Arel_Nodes_As, :visit_Arel_Nodes_As
@@ -1,6 +1,6 @@
1
1
  module ArelExtensions
2
2
  module Visitors
3
- Arel::Visitors::IBM_DB.class_eval do
3
+ class Arel::Visitors::IBM_DB
4
4
 
5
5
  def visit_ArelExtensions_Nodes_Ceil o, collector
6
6
  collector << "CEILING("
@@ -1,6 +1,6 @@
1
1
  module ArelExtensions
2
2
  module Visitors
3
- Arel::Visitors::MySQL.class_eval do
3
+ class Arel::Visitors::MySQL
4
4
  Arel::Visitors::MySQL::DATE_MAPPING = {'d' => 'DAY', 'm' => 'MONTH', 'w' => 'WEEK', 'y' => 'YEAR', 'wd' => 'WEEKDAY', 'h' => 'HOUR', 'mn' => 'MINUTE', 's' => 'SECOND'}
5
5
  Arel::Visitors::MySQL::DATE_FORMAT_DIRECTIVES = { # ISO C / POSIX
6
6
  '%Y' => '%Y', '%C' => '', '%y' => '%y', '%m' => '%m', '%B' => '%M', '%b' => '%b', '%^b' => '%b', # year, month
@@ -193,6 +193,13 @@ module ArelExtensions
193
193
  collector
194
194
  end
195
195
 
196
+ def visit_ArelExtensions_Nodes_RegexpReplace o, collector
197
+ if !regexp_replace_supported?
198
+ warn("Warning : ArelExtensions: REGEXP_REPLACE does not seem to be available in the current version of the DBMS, it might crash")
199
+ end
200
+ super(o,collector)
201
+ end
202
+
196
203
  def visit_ArelExtensions_Nodes_Format o, collector
197
204
  case o.col_type
198
205
  when :date, :datetime
@@ -403,7 +410,7 @@ module ArelExtensions
403
410
 
404
411
  def visit_Aggregate_For_AggregateFunction o, collector
405
412
  if !window_supported?
406
- warn("Warning : ArelExtensions: Window Functions are not available in the current version on the DBMS.")
413
+ warn("Warning : ArelExtensions: Window Functions are not available in the current version of the DBMS.")
407
414
  return collector
408
415
  end
409
416
 
@@ -449,14 +456,22 @@ module ArelExtensions
449
456
  version_supported?('10.2.3', '8.0')
450
457
  end
451
458
 
452
- def version_supported?(mysql_v = '10.2.3',mariadb_v = '5.7.0')
459
+ def regexp_replace_supported?
460
+ version_supported?('10.0.5', '8.0')
461
+ end
462
+
463
+ def version_supported?(mariadb_v = '10.2.3', mysql_v = '5.7.0')
453
464
  conn = Arel::Table.engine.connection
454
- conn.send(:mariadb?) &&
455
- (conn.respond_to?(:get_database_version) && conn.send(:get_database_version) >= mysql_v ||
456
- conn.respond_to?(:version) && conn.send(:version) >= mysql_v) ||
457
- !Arel::Table.engine.connection.send(:mariadb?) &&
458
- (conn.respond_to?(:get_database_version) && conn.send(:get_database_version) >= mariadb_v ||
459
- conn.respond_to?(:version) && conn.send(:version) >= mariadb_v)
465
+ conn.send(:mariadb?) && \
466
+ (conn.respond_to?(:get_database_version) && conn.send(:get_database_version) >= mariadb_v || \
467
+ conn.respond_to?(:version) && conn.send(:version) >= mariadb_v || \
468
+ conn.instance_variable_get(:"@version") && conn.instance_variable_get(:"@version") >= mariadb_v) || \
469
+ !conn.send(:mariadb?) && \
470
+ (conn.respond_to?(:get_database_version) && conn.send(:get_database_version) >= mysql_v || \
471
+ conn.respond_to?(:version) && conn.send(:version) >= mysql_v || \
472
+ conn.instance_variable_get(:"@version") && conn.instance_variable_get(:"@version") >= mysql_v)
473
+ # ideally we should parse the instance_variable @full_version because @version contains only the supposedly
474
+ # corresponding mysql version of the current mariadb version (which is not very helpful most of the time)
460
475
  end
461
476
 
462
477
  def visit_ArelExtensions_Nodes_Json o,collector
@@ -1,7 +1,7 @@
1
1
  #require 'oracle_visitor'
2
2
  module ArelExtensions
3
3
  module Visitors
4
- Arel::Visitors::Oracle.class_eval do
4
+ class Arel::Visitors::Oracle
5
5
 
6
6
  SPECIAL_CHARS = {"\t" => 'CHR(9)', "\n" => 'CHR(10)', "\r" => 'CHR(13)'}
7
7
  Arel::Visitors::Oracle::DATE_MAPPING = {'d' => 'DAY', 'm' => 'MONTH', 'w' => 'IW', 'y' => 'YEAR', 'wd' => 'D', 'h' => 'HOUR', 'mn' => 'MINUTE', 's' => 'SECOND'}
@@ -467,14 +467,12 @@ module ArelExtensions
467
467
  o.left.each_with_index do |row, idx| # values
468
468
  collector << " UNION ALL " if idx != 0
469
469
  collector << "(SELECT "
470
- v = Arel::Nodes::Values.new(row, o.cols)
471
- len = v.expressions.length - 1
472
- v.expressions.each_with_index { |value, i|
470
+ len = row.length - 1
471
+ row.zip(o.cols).each_with_index { |(value, attr), i|
473
472
  case value
474
473
  when Arel::Nodes::SqlLiteral, Arel::Nodes::BindParam
475
474
  collector = visit value, collector
476
475
  else
477
- attr = v.columns[i]
478
476
  collector << quote(value, attr && column_for(attr)).to_s
479
477
  end
480
478
  collector << Arel::Visitors::Oracle::COMMA unless i == len
@@ -490,12 +488,13 @@ module ArelExtensions
490
488
  o.left.each_with_index do |row, idx|
491
489
  collector << " UNION ALL " if idx != 0
492
490
  collector << "(SELECT "
493
- v = Arel::Nodes::Values.new(row, o.cols)
494
- len = v.expressions.length - 1
495
- v.expressions.zip(v.columns).each_with_index { |(value, attr), i|
491
+ len = row.length - 1
492
+ row.zip(o.cols).each_with_index { |(value, attr), i|
496
493
  case value
497
494
  when Arel::Nodes::SqlLiteral, Arel::Nodes::BindParam
498
495
  collector = visit value, collector
496
+ when Integer
497
+ collector << value.to_s
499
498
  else
500
499
  collector << (attr && attr.able_to_type_cast? ? quote(attr.type_cast_for_database(value)) : quote(value).to_s)
501
500
  end
@@ -1,6 +1,6 @@
1
1
  module ArelExtensions
2
2
  module Visitors
3
- Arel::Visitors::PostgreSQL.class_eval do
3
+ class Arel::Visitors::PostgreSQL
4
4
  Arel::Visitors::PostgreSQL::DATE_MAPPING = {'d' => 'DAY', 'm' => 'MONTH', 'w' => 'WEEK', 'y' => 'YEAR', 'wd' => 'DOW', 'h' => 'HOUR', 'mn' => 'MINUTE', 's' => 'SECOND'}
5
5
  Arel::Visitors::PostgreSQL::DATE_FORMAT_DIRECTIVES = {
6
6
  '%Y' => 'IYYY', '%C' => 'CC', '%y' => 'YY', '%m' => 'MM', '%B' => 'Month', '%^B' => 'MONTH', '%b' => 'Mon', '%^b' => 'MON',
@@ -1,6 +1,6 @@
1
1
  module ArelExtensions
2
2
  module Visitors
3
- Arel::Visitors::SQLite.class_eval do
3
+ class Arel::Visitors::SQLite
4
4
  Arel::Visitors::SQLite::DATE_MAPPING = {'d' => '%d', 'm' => '%m', 'w' => '%W', 'y' => '%Y', 'wd' => '%w', 'M' => '%M', 'h' => '%H', 'mn' => '%M', 's' => '%S'}
5
5
  Arel::Visitors::SQLite::DATE_FORMAT_DIRECTIVES = { # ISO C / POSIX
6
6
  '%Y' => '%Y', '%C' => '', '%y' => '%y', '%m' => '%m', '%B' => '%M', '%b' => '%b', '%^b' => '%b', # year, month
@@ -235,9 +235,8 @@ module ArelExtensions
235
235
  def visit_ArelExtensions_InsertManager_BulkValues o, collector
236
236
  o.left.each_with_index do |row, idx|
237
237
  collector << 'SELECT '
238
- v = Arel::Nodes::Values.new(row, o.cols)
239
- len = v.expressions.length - 1
240
- v.expressions.zip(v.columns).each_with_index { |(value, attr), i|
238
+ len = row.length - 1
239
+ row.zip(o.cols).each_with_index { |(value, attr), i|
241
240
  case value
242
241
  when Arel::Nodes::SqlLiteral, Arel::Nodes::BindParam
243
242
  collector = visit value.as(attr.name), collector
@@ -258,12 +257,17 @@ module ArelExtensions
258
257
  def visit_ArelExtensions_InsertManager_BulkValues o, collector
259
258
  o.left.each_with_index do |row, idx|
260
259
  collector << 'SELECT '
261
- v = Arel::Nodes::Values.new(row, o.cols)
262
- len = v.expressions.length - 1
263
- v.expressions.zip(v.columns).each_with_index { |(value, attr), i|
260
+ len = row.length - 1
261
+ row.zip(o.cols).each_with_index { |(value, attr), i|
264
262
  case value
265
263
  when Arel::Nodes::SqlLiteral, Arel::Nodes::BindParam
266
264
  collector = visit value.as(attr.name), collector
265
+ when Integer
266
+ collector << value.to_s
267
+ if idx == 0
268
+ collector << " AS "
269
+ collector << quote(attr.name)
270
+ end
267
271
  else
268
272
  collector << (attr && attr.able_to_type_cast? ? quote(attr.type_cast_for_database(value)) : quote(value).to_s)
269
273
  if idx == 0