arel_extensions 2.0.21 → 2.2.2

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 (138) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +1 -2
  3. data/.github/workflows/publish.yml +29 -0
  4. data/.github/workflows/release.yml +30 -0
  5. data/.github/workflows/ruby.yml +377 -80
  6. data/.gitignore +7 -6
  7. data/.rubocop.yml +62 -1
  8. data/CONTRIBUTING.md +102 -0
  9. data/Gemfile +2 -23
  10. data/NEWS.md +89 -0
  11. data/README.md +228 -84
  12. data/Rakefile +11 -4
  13. data/TODO +0 -1
  14. data/appveyor.yml +60 -22
  15. data/arel_extensions.gemspec +11 -12
  16. data/bin/build +15 -0
  17. data/bin/compose +6 -0
  18. data/bin/publish +8 -0
  19. data/dev/arelx.dockerfile +44 -0
  20. data/dev/compose.yaml +71 -0
  21. data/dev/postgres.dockerfile +5 -0
  22. data/dev/rbenv +189 -0
  23. data/gemfiles/rails3.gemfile +10 -10
  24. data/gemfiles/rails4_2.gemfile +38 -0
  25. data/gemfiles/rails5.gemfile +29 -0
  26. data/gemfiles/rails5_1_4.gemfile +13 -13
  27. data/gemfiles/rails5_2.gemfile +16 -14
  28. data/gemfiles/rails6.gemfile +18 -15
  29. data/gemfiles/rails6_1.gemfile +18 -15
  30. data/gemfiles/rails7.gemfile +33 -0
  31. data/gemfiles/rails7_1.gemfile +33 -0
  32. data/gemfiles/rails7_2.gemfile +33 -0
  33. data/gemspecs/arel_extensions-v1.gemspec +12 -13
  34. data/gemspecs/arel_extensions-v2.gemspec +11 -12
  35. data/init/mssql.sql +0 -0
  36. data/init/mysql.sql +0 -0
  37. data/init/oracle.sql +0 -0
  38. data/init/postgresql.sql +0 -0
  39. data/init/sqlite.sql +0 -0
  40. data/lib/arel_extensions/aliases.rb +14 -0
  41. data/lib/arel_extensions/attributes.rb +10 -2
  42. data/lib/arel_extensions/boolean_functions.rb +2 -4
  43. data/lib/arel_extensions/common_sql_functions.rb +12 -12
  44. data/lib/arel_extensions/comparators.rb +14 -14
  45. data/lib/arel_extensions/date_duration.rb +14 -9
  46. data/lib/arel_extensions/helpers.rb +62 -0
  47. data/lib/arel_extensions/insert_manager.rb +19 -17
  48. data/lib/arel_extensions/math.rb +48 -45
  49. data/lib/arel_extensions/math_functions.rb +18 -18
  50. data/lib/arel_extensions/nodes/abs.rb +0 -0
  51. data/lib/arel_extensions/nodes/aggregate_function.rb +0 -0
  52. data/lib/arel_extensions/nodes/blank.rb +1 -1
  53. data/lib/arel_extensions/nodes/case.rb +10 -12
  54. data/lib/arel_extensions/nodes/cast.rb +6 -6
  55. data/lib/arel_extensions/nodes/ceil.rb +0 -0
  56. data/lib/arel_extensions/nodes/change_case.rb +0 -0
  57. data/lib/arel_extensions/nodes/coalesce.rb +1 -1
  58. data/lib/arel_extensions/nodes/collate.rb +9 -9
  59. data/lib/arel_extensions/nodes/concat.rb +2 -2
  60. data/lib/arel_extensions/nodes/date_diff.rb +33 -14
  61. data/lib/arel_extensions/nodes/duration.rb +0 -0
  62. data/lib/arel_extensions/nodes/find_in_set.rb +0 -0
  63. data/lib/arel_extensions/nodes/floor.rb +0 -0
  64. data/lib/arel_extensions/nodes/format.rb +3 -2
  65. data/lib/arel_extensions/nodes/formatted_date.rb +42 -0
  66. data/lib/arel_extensions/nodes/formatted_number.rb +2 -2
  67. data/lib/arel_extensions/nodes/function.rb +22 -26
  68. data/lib/arel_extensions/nodes/is_null.rb +0 -0
  69. data/lib/arel_extensions/nodes/json.rb +15 -9
  70. data/lib/arel_extensions/nodes/length.rb +6 -0
  71. data/lib/arel_extensions/nodes/levenshtein_distance.rb +1 -1
  72. data/lib/arel_extensions/nodes/locate.rb +1 -1
  73. data/lib/arel_extensions/nodes/log10.rb +0 -0
  74. data/lib/arel_extensions/nodes/matches.rb +1 -1
  75. data/lib/arel_extensions/nodes/md5.rb +0 -0
  76. data/lib/arel_extensions/nodes/power.rb +0 -0
  77. data/lib/arel_extensions/nodes/rand.rb +0 -0
  78. data/lib/arel_extensions/nodes/repeat.rb +2 -2
  79. data/lib/arel_extensions/nodes/replace.rb +2 -10
  80. data/lib/arel_extensions/nodes/rollup.rb +36 -0
  81. data/lib/arel_extensions/nodes/round.rb +0 -0
  82. data/lib/arel_extensions/nodes/select.rb +10 -0
  83. data/lib/arel_extensions/nodes/soundex.rb +2 -2
  84. data/lib/arel_extensions/nodes/std.rb +0 -0
  85. data/lib/arel_extensions/nodes/substring.rb +1 -1
  86. data/lib/arel_extensions/nodes/sum.rb +0 -0
  87. data/lib/arel_extensions/nodes/then.rb +1 -1
  88. data/lib/arel_extensions/nodes/trim.rb +2 -2
  89. data/lib/arel_extensions/nodes/union.rb +5 -5
  90. data/lib/arel_extensions/nodes/union_all.rb +4 -4
  91. data/lib/arel_extensions/nodes/wday.rb +0 -0
  92. data/lib/arel_extensions/nodes.rb +0 -0
  93. data/lib/arel_extensions/null_functions.rb +16 -0
  94. data/lib/arel_extensions/predications.rb +10 -10
  95. data/lib/arel_extensions/railtie.rb +1 -1
  96. data/lib/arel_extensions/set_functions.rb +3 -3
  97. data/lib/arel_extensions/string_functions.rb +19 -10
  98. data/lib/arel_extensions/tasks.rb +2 -2
  99. data/lib/arel_extensions/version.rb +1 -1
  100. data/lib/arel_extensions/visitors/convert_format.rb +0 -0
  101. data/lib/arel_extensions/visitors/ibm_db.rb +20 -20
  102. data/lib/arel_extensions/visitors/mssql.rb +394 -169
  103. data/lib/arel_extensions/visitors/mysql.rb +238 -151
  104. data/lib/arel_extensions/visitors/oracle.rb +170 -131
  105. data/lib/arel_extensions/visitors/oracle12.rb +16 -16
  106. data/lib/arel_extensions/visitors/postgresql.rb +170 -140
  107. data/lib/arel_extensions/visitors/sqlite.rb +88 -87
  108. data/lib/arel_extensions/visitors/to_sql.rb +185 -156
  109. data/lib/arel_extensions/visitors.rb +73 -60
  110. data/lib/arel_extensions.rb +173 -36
  111. data/test/arelx_test_helper.rb +49 -1
  112. data/test/database.yml +13 -7
  113. data/test/real_db_test.rb +101 -83
  114. data/test/support/fake_record.rb +8 -2
  115. data/test/test_comparators.rb +5 -5
  116. data/test/visitors/test_bulk_insert_oracle.rb +5 -5
  117. data/test/visitors/test_bulk_insert_sqlite.rb +5 -5
  118. data/test/visitors/test_bulk_insert_to_sql.rb +5 -5
  119. data/test/visitors/test_oracle.rb +14 -14
  120. data/test/visitors/test_to_sql.rb +121 -93
  121. data/test/with_ar/all_agnostic_test.rb +630 -320
  122. data/test/with_ar/insert_agnostic_test.rb +25 -18
  123. data/test/with_ar/test_bulk_sqlite.rb +11 -7
  124. data/test/with_ar/test_math_sqlite.rb +18 -14
  125. data/test/with_ar/test_string_mysql.rb +26 -22
  126. data/test/with_ar/test_string_sqlite.rb +26 -22
  127. data/version_v1.rb +1 -1
  128. data/version_v2.rb +1 -1
  129. metadata +24 -26
  130. data/.travis/oracle/download.js +0 -152
  131. data/.travis/oracle/download.sh +0 -30
  132. data/.travis/oracle/download_ojdbc.js +0 -116
  133. data/.travis/oracle/install.sh +0 -34
  134. data/.travis/setup_accounts.sh +0 -9
  135. data/.travis/sqlite3/extension-functions.sh +0 -6
  136. data/.travis.yml +0 -193
  137. data/gemfiles/rails4.gemfile +0 -29
  138. data/gemfiles/rails5_0.gemfile +0 -29
@@ -2,97 +2,217 @@ module ArelExtensions
2
2
  module Visitors
3
3
  module MSSQL
4
4
 
5
- Arel::Visitors::MSSQL::DATE_MAPPING = {
6
- 'd' => 'day', 'm' => 'month', 'y' => 'year', 'wd' => 'weekday', 'w' => 'week', 'h' => 'hour', 'mn' => 'minute', 's' => 'second'
7
- }.freeze
8
-
9
- Arel::Visitors::MSSQL::DATE_FORMAT_DIRECTIVES = {
10
- '%Y' => 'YYYY', '%C' => '', '%y' => 'YY', '%m' => 'MM', '%B' => '', '%b' => '', '%^b' => '', # year, month
11
- '%d' => 'DD', '%e' => '', '%j' => '', '%w' => 'dw', '%A' => '', # day, weekday
12
- '%H' => 'hh', '%k' => '', '%I' => '', '%l' => '', '%P' => '', '%p' => '', # hours
13
- '%M' => 'mi', '%S' => 'ss', '%L' => 'ms', '%N' => 'ns', '%z' => 'tz'
14
- }.freeze
15
-
16
- Arel::Visitors::MSSQL::DATE_FORMAT_REGEX =
17
- Regexp.new(
18
- Arel::Visitors::MSSQL::DATE_FORMAT_DIRECTIVES
19
- .keys
20
- .map{|k| Regexp.escape(k)}
21
- .join('|')
22
- ).freeze
23
-
24
- # TODO; all others... http://www.sql-server-helper.com/tips/date-formats.aspx
25
- Arel::Visitors::MSSQL::DATE_CONVERT_FORMATS = {
26
- 'YYYY-MM-DD' => 120,
27
- 'YY-MM-DD' => 120,
28
- 'MM/DD/YYYY' => 101,
29
- 'MM-DD-YYYY' => 110,
30
- 'YYYY/MM/DD' => 111,
31
- 'DD-MM-YYYY' => 105,
32
- 'DD-MM-YY' => 5,
33
- 'DD.MM.YYYY' => 104,
34
- 'YYYY-MM-DDTHH:MM:SS:MMM' => 126
35
- }.freeze
5
+ MSSQL_CLASS_NAMES = %i[MSSQL SQLServer].freeze
6
+
7
+ mssql_class =
8
+ Arel::Visitors
9
+ .constants
10
+ .select { |c| Arel::Visitors.const_get(c).is_a?(Class) }
11
+ .find { |c| MSSQL_CLASS_NAMES.include?(c) }
12
+
13
+ # This guard is necessary because:
14
+ #
15
+ # 1. const_get(mssql_class) will fail when mssql_class is nil.
16
+ # 2. mssql_class could be nil under certain conditions:
17
+ # 1. especially on ruby 2.5 (and surprisingly not jruby 9.2) and 3.0+.
18
+ # 2. when not working with mssql itself.
19
+ if mssql_class
20
+ LOADED_VISITOR = Arel::Visitors.const_get(mssql_class)
21
+
22
+ LOADED_VISITOR::DATE_MAPPING = {
23
+ 'd' => 'day', 'm' => 'month', 'y' => 'year',
24
+ 'wd' => 'weekday', 'w' => 'week',
25
+ 'h' => 'hour', 'mn' => 'minute', 's' => 'second'
26
+ }.freeze
27
+
28
+ LOADED_VISITOR::DATE_FORMAT_DIRECTIVES = {
29
+ '%Y' => 'YYYY', '%C' => '', '%y' => 'YY', '%m' => 'MM', '%B' => 'month', '%^B' => '', '%b' => '', '%^b' => '', # year, month
30
+ '%V' => 'iso_week', '%G' => '', # ISO week number and year of week
31
+ '%d' => 'DD', '%e' => '' , '%j' => '' , '%w' => 'dw', %'a' => '', '%A' => 'weekday', # day, weekday
32
+ '%H' => 'hh', '%k' => '' , '%I' => '' , '%l' => '' , '%P' => '', '%p' => '', # hours
33
+ '%M' => 'mi', '%S' => 'ss', '%L' => 'ms', '%N' => 'ns', '%z' => 'tz'
34
+ }.freeze
35
+
36
+ LOADED_VISITOR::DATE_FORMAT_FORMAT = {
37
+ 'YY' => '0#', 'MM' => '0#', 'DD' => '0#',
38
+ 'hh' => '0#', 'mi' => '0#', 'ss' => '0#',
39
+ 'iso_week' => '0#'
40
+ }
41
+
42
+ LOADED_VISITOR::DATE_NAME = [
43
+ '%B', '%A'
44
+ ]
45
+
46
+ LOADED_VISITOR::DATE_FORMAT_REGEX =
47
+ Regexp.new(
48
+ LOADED_VISITOR::DATE_FORMAT_DIRECTIVES
49
+ .keys
50
+ .map{|k| Regexp.escape(k)}
51
+ .join('|')
52
+ ).freeze
53
+
54
+ # TODO; all others... http://www.sql-server-helper.com/tips/date-formats.aspx
55
+ LOADED_VISITOR::DATE_CONVERT_FORMATS = {
56
+ 'YYYY-MM-DD' => 120,
57
+ 'YY-MM-DD' => 120,
58
+ 'MM/DD/YYYY' => 101,
59
+ 'MM-DD-YYYY' => 110,
60
+ 'YYYY/MM/DD' => 111,
61
+ 'DD-MM-YYYY' => 105,
62
+ 'DD-MM-YY' => 5,
63
+ 'DD.MM.YYYY' => 104,
64
+ 'YYYY-MM-DDTHH:MM:SS:MMM' => 126
65
+ }.freeze
66
+ end
67
+
68
+ # Quoting in JRuby + AR < 5 requires special handling for MSSQL.
69
+ #
70
+ # It relied on @connection.quote, which in turn relied on column type for
71
+ # quoting. We need only to rely on the value type.
72
+ #
73
+ # It didn't handle numbers correctly: `quote(1, nil)` translated into
74
+ # `N'1'` which we don't want.
75
+ #
76
+ # The following is adapted from
77
+ # https://github.com/rails/rails/blob/main/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
78
+ #
79
+ if RUBY_PLATFORM == 'java' && Arel::VERSION.to_i <= 6
80
+ def quote_string(s)
81
+ s.gsub('\\', '\&\&').gsub("'", "''") # ' (for ruby-mode)
82
+ end
83
+
84
+ def quoted_binary(value) # :nodoc:
85
+ "'#{quote_string(value.to_s)}'"
86
+ end
87
+
88
+ def quoted_date(value)
89
+ if value.acts_like?(:time)
90
+ if (ActiveRecord.respond_to?(:default_timezone) && ActiveRecord.default_timezone == :utc) || ActiveRecord::Base.default_timezone == :utc
91
+ value = value.getutc if value.respond_to?(:getutc) && !value.utc?
92
+ else
93
+ value = value.getlocal if value.respond_to?(:getlocal)
94
+ end
95
+ end
96
+ # new versions of AR use `to_fs`, but we want max compatibility, and we're
97
+ # not going to write it over and over, so it's fine like that.
98
+ result = value.to_formatted_s(:db)
99
+ if value.respond_to?(:usec) && value.usec > 0
100
+ result << '.' << sprintf('%06d', value.usec)
101
+ else
102
+ result
103
+ end
104
+ end
105
+
106
+ def quoted_true
107
+ 'TRUE'
108
+ end
109
+
110
+ def quoted_false
111
+ 'FALSE'
112
+ end
113
+
114
+ def quoted_time(value) # :nodoc:
115
+ value = value.change(year: 2000, month: 1, day: 1)
116
+ quoted_date(value).sub(/\A\d{4}-\d{2}-\d{2} /, "")
117
+ end
118
+
119
+ def quote value, column = nil
120
+ case value
121
+ when Arel::Nodes::SqlLiteral
122
+ value
123
+ when String, Symbol, ActiveSupport::Multibyte::Chars
124
+ "'#{quote_string(value.to_s)}'"
125
+ when true
126
+ quoted_true
127
+ when false
128
+ quoted_false
129
+ when nil
130
+ 'NULL'
131
+ # BigDecimals need to be put in a non-normalized form and quoted.
132
+ when BigDecimal
133
+ value.to_s('F')
134
+ when Numeric, ActiveSupport::Duration
135
+ value.to_s
136
+ when Arel::VERSION.to_i > 6 && ActiveRecord::Type::Time::Value
137
+ "'#{quoted_time(value)}'"
138
+ when Date, Time
139
+ "'#{quoted_date(value)}'"
140
+ when Class
141
+ "'#{value}'"
142
+ else
143
+ raise TypeError, "can't quote #{value.class.name}"
144
+ end
145
+ end
146
+ end
147
+
148
+ alias_method(:old_primary_Key_From_Table, :primary_Key_From_Table) rescue nil
149
+ def primary_Key_From_Table t
150
+ return unless t
151
+
152
+ column_name = @connection.schema_cache.primary_keys(t.name) ||
153
+ @connection.schema_cache.columns_hash(t.name).first.try(:second).try(:name)
154
+ column_name ? t[column_name] : nil
155
+ end
36
156
 
37
157
  # Math Functions
38
158
  def visit_ArelExtensions_Nodes_Ceil o, collector
39
- collector << "CEILING("
159
+ collector << 'CEILING('
40
160
  collector = visit o.expr, collector
41
- collector << ")"
161
+ collector << ')'
42
162
  collector
43
163
  end
44
164
 
45
165
  def visit_ArelExtensions_Nodes_Log10 o, collector
46
- collector << "LOG10("
166
+ collector << 'LOG10('
47
167
  o.expressions.each_with_index { |arg, i|
48
168
  collector << Arel::Visitors::ToSql::COMMA if i != 0
49
169
  collector = visit arg, collector
50
170
  }
51
- collector << ")"
171
+ collector << ')'
52
172
  collector
53
173
  end
54
174
 
55
175
  def visit_ArelExtensions_Nodes_Power o, collector
56
- collector << "POWER("
176
+ collector << 'POWER('
57
177
  o.expressions.each_with_index { |arg, i|
58
178
  collector << Arel::Visitors::ToSql::COMMA if i != 0
59
179
  collector = visit arg, collector
60
180
  }
61
- collector << ")"
181
+ collector << ')'
62
182
  collector
63
183
  end
64
184
 
65
185
  def visit_ArelExtensions_Nodes_IsNull o, collector
66
- collector << "("
186
+ collector << '('
67
187
  collector = visit o.expr, collector
68
- collector << " IS NULL)"
188
+ collector << ' IS NULL)'
69
189
  collector
70
190
  end
71
191
 
72
192
  def visit_ArelExtensions_Nodes_IsNotNull o, collector
73
- collector << "("
193
+ collector << '('
74
194
  collector = visit o.expr, collector
75
- collector << " IS NOT NULL)"
195
+ collector << ' IS NOT NULL)'
76
196
  collector
77
197
  end
78
198
 
79
199
  def visit_ArelExtensions_Nodes_Concat o, collector
80
- collector << "CONCAT("
200
+ collector << 'CONCAT('
81
201
  o.expressions.each_with_index { |arg, i|
82
- collector << Arel::Visitors::MSSQL::COMMA if i != 0
202
+ collector << LOADED_VISITOR::COMMA if i != 0
83
203
  collector = visit arg, collector
84
204
  }
85
- collector << ")"
205
+ collector << ')'
86
206
  collector
87
207
  end
88
208
 
89
209
  def visit_ArelExtensions_Nodes_Repeat o, collector
90
- collector << "REPLICATE("
210
+ collector << 'REPLICATE('
91
211
  o.expressions.each_with_index { |arg, i|
92
212
  collector << Arel::Visitors::ToSql::COMMA if i != 0
93
213
  collector = visit arg, collector
94
214
  }
95
- collector << ")"
215
+ collector << ')'
96
216
  collector
97
217
  end
98
218
 
@@ -102,38 +222,38 @@ module ArelExtensions
102
222
  case o.right_node_type
103
223
  when :ruby_date, :ruby_time, :date, :datetime, :time
104
224
  collector << case o.left_node_type
105
- when :ruby_time, :datetime, :time then 'DATEDIFF(second'
106
- else 'DATEDIFF(day'
107
- end
108
- collector << Arel::Visitors::MSSQL::COMMA
225
+ when :ruby_time, :datetime, :time then 'DATEDIFF(second'
226
+ else 'DATEDIFF(day'
227
+ end
228
+ collector << LOADED_VISITOR::COMMA
109
229
  collector = visit o.right, collector
110
- collector << Arel::Visitors::MSSQL::COMMA
230
+ collector << LOADED_VISITOR::COMMA
111
231
  collector = visit o.left, collector
112
232
  collector << ')'
113
233
  else
114
234
  da = ArelExtensions::Nodes::DateAdd.new([])
115
- collector << "DATEADD("
235
+ collector << 'DATEADD('
116
236
  collector = visit da.mssql_datepart(o.right), collector
117
- collector << Arel::Visitors::MSSQL::COMMA
118
- collector << "-("
237
+ collector << LOADED_VISITOR::COMMA
238
+ collector << '-('
119
239
  collector = visit da.mssql_value(o.right), collector
120
- collector << ")"
121
- collector << Arel::Visitors::MSSQL::COMMA
240
+ collector << ')'
241
+ collector << LOADED_VISITOR::COMMA
122
242
  collector = visit o.left, collector
123
- collector << ")"
243
+ collector << ')'
124
244
  collector
125
245
  end
126
246
  collector
127
247
  end
128
248
 
129
249
  def visit_ArelExtensions_Nodes_DateAdd o, collector
130
- collector << "DATEADD("
250
+ collector << 'DATEADD('
131
251
  collector = visit o.mssql_datepart(o.right), collector
132
- collector << Arel::Visitors::MSSQL::COMMA
252
+ collector << LOADED_VISITOR::COMMA
133
253
  collector = visit o.mssql_value(o.right), collector
134
- collector << Arel::Visitors::MSSQL::COMMA
254
+ collector << LOADED_VISITOR::COMMA
135
255
  collector = visit o.left, collector
136
- collector << ")"
256
+ collector << ')'
137
257
  collector
138
258
  end
139
259
 
@@ -142,68 +262,77 @@ module ArelExtensions
142
262
  collector = visit o.right, collector
143
263
  else
144
264
  left = o.left.end_with?('i') ? o.left[0..-2] : o.left
145
- conv = ['h', 'mn', 's'].include?(o.left)
265
+ conv = %w[h mn s].include?(o.left)
146
266
  collector << 'DATEPART('
147
- collector << Arel::Visitors::MSSQL::DATE_MAPPING[left]
148
- collector << Arel::Visitors::MSSQL::COMMA
267
+ collector << LOADED_VISITOR::DATE_MAPPING[left]
268
+ collector << LOADED_VISITOR::COMMA
149
269
  collector << 'CONVERT(datetime,' if conv
150
270
  collector = visit o.right, collector
151
271
  collector << ')' if conv
152
- collector << ")"
272
+ collector << ')'
153
273
  end
154
274
  collector
155
275
  end
156
276
 
157
277
  def visit_ArelExtensions_Nodes_Length o, collector
158
- collector << "LEN("
159
- collector = visit o.expr, collector
160
- collector << ")"
161
- collector
278
+ if o.bytewise
279
+ collector << '(DATALENGTH('
280
+ collector = visit o.expr, collector
281
+ collector << ') / ISNULL(NULLIF(DATALENGTH(LEFT(COALESCE('
282
+ collector = visit o.expr, collector
283
+ collector << ", '#' ), 1 )), 0), 1))"
284
+ collector
285
+ else
286
+ collector << 'LEN('
287
+ collector = visit o.expr, collector
288
+ collector << ')'
289
+ collector
290
+ end
162
291
  end
163
292
 
164
293
  def visit_ArelExtensions_Nodes_Round o, collector
165
- collector << "ROUND("
294
+ collector << 'ROUND('
166
295
  o.expressions.each_with_index { |arg, i|
167
- collector << Arel::Visitors::MSSQL::COMMA if i != 0
296
+ collector << LOADED_VISITOR::COMMA if i != 0
168
297
  collector = visit arg, collector
169
298
  }
170
299
  if o.expressions.length == 1
171
- collector << Arel::Visitors::MSSQL::COMMA
172
- collector << "0"
300
+ collector << LOADED_VISITOR::COMMA
301
+ collector << '0'
173
302
  end
174
- collector << ")"
303
+ collector << ')'
175
304
  collector
176
305
  end
177
306
 
178
307
  def visit_ArelExtensions_Nodes_Locate o, collector
179
- collector << "CHARINDEX("
308
+ collector << 'CHARINDEX('
180
309
  collector = visit o.right, collector
181
- collector << Arel::Visitors::MSSQL::COMMA
310
+ collector << LOADED_VISITOR::COMMA
182
311
  collector = visit o.left, collector
183
- collector << ")"
312
+ collector << ')'
184
313
  collector
185
314
  end
186
315
 
187
316
  def visit_ArelExtensions_Nodes_Substring o, collector
188
317
  collector << 'SUBSTRING('
189
318
  collector = visit o.expressions[0], collector
190
- collector << Arel::Visitors::MSSQL::COMMA
319
+ collector << LOADED_VISITOR::COMMA
191
320
  collector = visit o.expressions[1], collector
192
- collector << Arel::Visitors::MSSQL::COMMA
321
+ collector << LOADED_VISITOR::COMMA
193
322
  collector = o.expressions[2] ? visit(o.expressions[2], collector) : visit(o.expressions[0].length, collector)
194
323
  collector << ')'
195
324
  collector
196
325
  end
197
326
 
198
327
  def visit_ArelExtensions_Nodes_Trim o, collector
199
- if o.right
200
- collector << "REPLACE(REPLACE(LTRIM(RTRIM(REPLACE(REPLACE("
328
+ # NOTE: in MSSQL's `blank`, o.right is the space char so we need to
329
+ # account for it.
330
+ if o.right && !/\A\s\Z/.match(o.right.expr)
331
+ collector << 'dbo.TrimChar('
201
332
  collector = visit o.left, collector
202
- collector << ", ' ', '~'), "
203
- collector = visit o.right, collector
204
- collector << ", ' '))), ' ', "
333
+ collector << Arel::Visitors::MSSQL::COMMA
205
334
  collector = visit o.right, collector
206
- collector << "), '~', ' ')"
335
+ collector << ')'
207
336
  else
208
337
  collector << "LTRIM(RTRIM("
209
338
  collector = visit o.left, collector
@@ -214,7 +343,7 @@ module ArelExtensions
214
343
 
215
344
  def visit_ArelExtensions_Nodes_Ltrim o, collector
216
345
  if o.right
217
- collector << "REPLACE(REPLACE(LTRIM(REPLACE(REPLACE("
346
+ collector << 'REPLACE(REPLACE(LTRIM(REPLACE(REPLACE('
218
347
  collector = visit o.left, collector
219
348
  collector << ", ' ', '~'), "
220
349
  collector = visit o.right, collector
@@ -222,16 +351,16 @@ module ArelExtensions
222
351
  collector = visit o.right, collector
223
352
  collector << "), '~', ' ')"
224
353
  else
225
- collector << "LTRIM("
354
+ collector << 'LTRIM('
226
355
  collector = visit o.left, collector
227
- collector << ")"
356
+ collector << ')'
228
357
  end
229
358
  collector
230
359
  end
231
360
 
232
361
  def visit_ArelExtensions_Nodes_Rtrim o, collector
233
362
  if o.right
234
- collector << "REPLACE(REPLACE(RTRIM(REPLACE(REPLACE("
363
+ collector << 'REPLACE(REPLACE(RTRIM(REPLACE(REPLACE('
235
364
  collector = visit o.left, collector
236
365
  collector << ", ' ', '~'), "
237
366
  collector = visit o.right, collector
@@ -239,9 +368,9 @@ module ArelExtensions
239
368
  collector = visit o.right, collector
240
369
  collector << "), '~', ' ')"
241
370
  else
242
- collector << "RTRIM("
371
+ collector << 'RTRIM('
243
372
  collector = visit o.left, collector
244
- collector << ")"
373
+ collector << ')'
245
374
  end
246
375
  collector
247
376
  end
@@ -255,32 +384,78 @@ module ArelExtensions
255
384
  end
256
385
 
257
386
  def visit_ArelExtensions_Nodes_Format o, collector
258
- f = ArelExtensions::Visitors::strftime_to_format(o.iso_format, Arel::Visitors::MSSQL::DATE_FORMAT_DIRECTIVES)
259
- if fmt = Arel::Visitors::MSSQL::DATE_CONVERT_FORMATS[f]
387
+ visit_ArelExtensions_Nodes_FormattedDate o, collector
388
+ end
389
+
390
+ def visit_ArelExtensions_Nodes_FormattedDate o, collector
391
+ f = ArelExtensions::Visitors::strftime_to_format(o.iso_format, LOADED_VISITOR::DATE_FORMAT_DIRECTIVES)
392
+ if fmt = LOADED_VISITOR::DATE_CONVERT_FORMATS[f]
260
393
  collector << "CONVERT(VARCHAR(#{f.length})"
261
- collector << Arel::Visitors::MSSQL::COMMA
394
+ collector << LOADED_VISITOR::COMMA
395
+ if o.time_zone
396
+ collector << 'CONVERT(datetime'
397
+ collector << LOADED_VISITOR::COMMA
398
+ collector << ' '
399
+ end
262
400
  collector = visit o.left, collector
263
- collector << Arel::Visitors::MSSQL::COMMA
401
+ case o.time_zone
402
+ when Hash
403
+ src_tz, dst_tz = o.time_zone.first
404
+ collector << ') AT TIME ZONE '
405
+ collector = visit Arel.quoted(src_tz), collector
406
+ collector << ' AT TIME ZONE '
407
+ collector = visit Arel.quoted(dst_tz), collector
408
+ when String
409
+ collector << ') AT TIME ZONE '
410
+ collector = visit Arel.quoted(o.time_zone), collector
411
+ end
412
+ collector << LOADED_VISITOR::COMMA
264
413
  collector << fmt.to_s
265
414
  collector << ')'
266
415
  collector
267
416
  else
268
417
  s = StringScanner.new o.iso_format
269
- collector << "("
418
+ collector << '('
270
419
  sep = ''
271
420
  while !s.eos?
272
421
  collector << sep
273
422
  sep = ' + '
274
423
  case
275
- when s.scan(Arel::Visitors::MSSQL::DATE_FORMAT_REGEX)
276
- dir = Arel::Visitors::MSSQL::DATE_FORMAT_DIRECTIVES[s.matched]
277
- collector << 'LTRIM(STR(DATEPART('
424
+ when s.scan(LOADED_VISITOR::DATE_FORMAT_REGEX)
425
+ dir = LOADED_VISITOR::DATE_FORMAT_DIRECTIVES[s.matched]
426
+ fmt = LOADED_VISITOR::DATE_FORMAT_FORMAT[dir]
427
+ date_name = LOADED_VISITOR::DATE_NAME.include?(s.matched)
428
+ collector << 'LTRIM(RTRIM('
429
+ collector << 'FORMAT(' if fmt
430
+ collector << 'STR(' if !fmt && !date_name
431
+ collector << (date_name ? 'DATENAME(' : 'DATEPART(')
278
432
  collector << dir
279
- collector << Arel::Visitors::MSSQL::COMMA
433
+ collector << LOADED_VISITOR::COMMA
434
+ if o.time_zone
435
+ collector << 'CONVERT(datetime'
436
+ collector << LOADED_VISITOR::COMMA
437
+ collector << ' '
438
+ end
280
439
  collector = visit o.left, collector
281
- collector << ')))'
440
+ case o.time_zone
441
+ when Hash
442
+ src_tz, dst_tz = o.time_zone.first.first, o.time_zone.first.second
443
+ collector << ') AT TIME ZONE '
444
+ collector = visit Arel.quoted(src_tz), collector
445
+ collector << ' AT TIME ZONE '
446
+ collector = visit Arel.quoted(dst_tz), collector
447
+ when String
448
+ collector << ') AT TIME ZONE '
449
+ collector = visit Arel.quoted(o.time_zone), collector
450
+ end
451
+ collector << ')'
452
+ collector << ')' if !fmt && !date_name
453
+ collector << LOADED_VISITOR::COMMA << "'#{fmt}')" if fmt
454
+ collector << '))'
455
+ when s.scan(/^%%/)
456
+ collector = visit Arel.quoted('%'), collector
282
457
  when s.scan(/[^%]+|./)
283
- collector = visit Arel::Nodes.build_quoted(s.matched), collector
458
+ collector = visit Arel.quoted(s.matched), collector
284
459
  end
285
460
  end
286
461
  collector << ')'
@@ -289,22 +464,22 @@ module ArelExtensions
289
464
  end
290
465
 
291
466
  def visit_ArelExtensions_Nodes_Replace o, collector
292
- collector << "REPLACE("
467
+ collector << 'REPLACE('
293
468
  o.expressions.each_with_index { |arg, i|
294
- collector << Arel::Visitors::MSSQL::COMMA if i != 0
469
+ collector << LOADED_VISITOR::COMMA if i != 0
295
470
  collector = visit arg, collector
296
471
  }
297
- collector << ")"
472
+ collector << ')'
298
473
  collector
299
474
  end
300
475
 
301
476
  def visit_ArelExtensions_Nodes_FindInSet o, collector
302
- collector << "dbo.FIND_IN_SET("
477
+ collector << 'dbo.FIND_IN_SET('
303
478
  o.expressions.each_with_index { |arg, i|
304
- collector << Arel::Visitors::MSSQL::COMMA if i != 0
479
+ collector << LOADED_VISITOR::COMMA if i != 0
305
480
  collector = visit arg, collector
306
481
  }
307
- collector << ")"
482
+ collector << ')'
308
483
  collector
309
484
  end
310
485
 
@@ -347,9 +522,9 @@ module ArelExtensions
347
522
  end
348
523
 
349
524
  def visit_ArelExtensions_Nodes_AiIMatches o, collector
350
- collector = visit o.left.collate(true,true), collector
525
+ collector = visit o.left.collate(true, true), collector
351
526
  collector << ' LIKE '
352
- collector = visit o.right.collate(true,true), collector
527
+ collector = visit o.right.collate(true, true), collector
353
528
  if o.escape
354
529
  collector << ' ESCAPE '
355
530
  visit o.escape, collector
@@ -387,6 +562,34 @@ module ArelExtensions
387
562
  collector
388
563
  end
389
564
 
565
+ alias_method(:old_visit_Arel_Nodes_As, :visit_Arel_Nodes_As) rescue nil
566
+ def visit_Arel_Nodes_As o, collector
567
+ if o.left.is_a?(Arel::Nodes::Binary)
568
+ collector << '('
569
+ collector = visit o.left, collector
570
+ collector << ')'
571
+ else
572
+ collector = visit o.left, collector
573
+ end
574
+ collector << ' AS '
575
+ # Sometimes these values are already quoted, if they are, don't double quote it
576
+ lft, rgt =
577
+ if o.right.is_a?(Arel::Nodes::SqlLiteral)
578
+ if Arel::VERSION.to_i >= 6 && o.right[0] != '[' && o.right[-1] != ']'
579
+ # This is a lie, it's not about arel version, but SQL Server's (>= 2000).
580
+ ['[', ']']
581
+ elsif o.right[0] != '"' && o.right[-1] != '"'
582
+ ['"', '"']
583
+ else
584
+ []
585
+ end
586
+ end
587
+ collector << lft if lft
588
+ collector = visit o.right, collector
589
+ collector << rgt if rgt
590
+ collector
591
+ end
592
+
390
593
  # SQL Server does not know about REGEXP
391
594
  def visit_Arel_Nodes_Regexp o, collector
392
595
  collector = visit o.left, collector
@@ -400,110 +603,121 @@ module ArelExtensions
400
603
  collector
401
604
  end
402
605
 
606
+ def visit_Arel_Nodes_RollUp(o, collector)
607
+ collector << "ROLLUP"
608
+ grouping_array_or_grouping_element o, collector
609
+ end
610
+
403
611
  # TODO;
404
612
  def visit_ArelExtensions_Nodes_GroupConcat o, collector
405
- collector << "(STRING_AGG("
613
+ collector << '(STRING_AGG('
406
614
  collector = visit o.left, collector
407
615
  collector << Arel::Visitors::Oracle::COMMA
408
616
  collector =
409
617
  if o.separator && o.separator != 'NULL'
410
618
  visit o.separator, collector
411
619
  else
412
- visit Arel::Nodes.build_quoted(','), collector
620
+ visit Arel.quoted(','), collector
413
621
  end
414
- collector << ") WITHIN GROUP (ORDER BY "
622
+ collector << ') WITHIN GROUP (ORDER BY '
415
623
  if o.order.present?
416
- o.order.each_with_index do |order,i|
624
+ o.order.each_with_index do |order, i|
417
625
  collector << Arel::Visitors::Oracle::COMMA if i != 0
418
626
  collector = visit order, collector
419
627
  end
420
628
  else
421
629
  collector = visit o.left, collector
422
630
  end
423
- collector << "))"
631
+ collector << '))'
424
632
  collector
425
633
  end
426
634
 
427
635
  def visit_ArelExtensions_Nodes_MD5 o, collector
428
636
  collector << "LOWER(CONVERT(NVARCHAR(32),HashBytes('MD5',CONVERT(VARCHAR,"
429
637
  collector = visit o.left, collector
430
- collector << ")),2))"
638
+ collector << ')),2))'
431
639
  collector
432
640
  end
433
641
 
434
642
  def visit_ArelExtensions_Nodes_Cast o, collector
435
- case o.as_attr
436
- when :string
437
- as_attr = Arel::Nodes::SqlLiteral.new('varchar')
438
- when :time
439
- as_attr = Arel::Nodes::SqlLiteral.new('time')
440
- when :date
441
- as_attr = Arel::Nodes::SqlLiteral.new('date')
442
- when :datetime
443
- as_attr = Arel::Nodes::SqlLiteral.new('datetime')
444
- when :number,:decimal, :float
445
- as_attr = Arel::Nodes::SqlLiteral.new('decimal(10,6)')
446
- when :int
447
- collector << "CAST(CAST("
448
- collector = visit o.left, collector
449
- collector << " AS decimal(10,0)) AS int)"
450
- return collector
451
- when :binary
452
- as_attr = Arel::Nodes::SqlLiteral.new('binary')
453
- else
454
- as_attr = Arel::Nodes::SqlLiteral.new(o.as_attr.to_s)
455
- end
456
- collector << "CAST("
643
+ as_attr =
644
+ case o.as_attr
645
+ when :string
646
+ 'varchar'
647
+ when :time
648
+ 'time'
649
+ when :date
650
+ 'date'
651
+ when :datetime
652
+ 'datetime'
653
+ when :number, :decimal, :float
654
+ 'decimal(10,6)'
655
+ when :int
656
+ collector << 'CAST(CAST('
657
+ collector = visit o.left, collector
658
+ collector << ' AS decimal(10,0)) AS int)'
659
+ return collector
660
+ when :binary
661
+ 'binary'
662
+ else
663
+ o.as_attr.to_s
664
+ end
665
+ collector << 'CAST('
457
666
  collector = visit o.left, collector
458
- collector << " AS "
459
- collector = visit as_attr, collector
460
- collector << ")"
667
+ collector << ' AS '
668
+ collector = visit Arel::Nodes::SqlLiteral.new(as_attr), collector
669
+ collector << ')'
461
670
  collector
462
671
  end
463
672
 
464
673
  def visit_ArelExtensions_Nodes_FormattedNumber o, collector
465
674
  col = o.left.coalesce(0)
466
- locale = Arel::Nodes.build_quoted(o.locale.tr('_','-'))
467
- param = Arel::Nodes.build_quoted("N#{o.precision}")
468
- sign = ArelExtensions::Nodes::Case.new.when(col<0).
675
+ locale = Arel.quoted(o.locale.tr('_', '-'))
676
+ param = Arel.quoted("N#{o.precision}")
677
+ sign = Arel.when(col < 0).
469
678
  then('-').
470
679
  else(o.flags.include?('+') ? '+' : (o.flags.include?(' ') ? ' ' : ''))
471
680
  sign_length = o.flags.include?('+') || o.flags.include?(' ') ?
472
- Arel::Nodes.build_quoted(1) :
473
- ArelExtensions::Nodes::Case.new.when(col<0).then(1).else(0)
681
+ Arel.quoted(1) :
682
+ Arel.when(col < 0).then(1).else(0)
474
683
 
475
684
  number =
476
685
  if o.scientific_notation
477
686
  ArelExtensions::Nodes::Concat.new([
478
- Arel::Nodes::NamedFunction.new('FORMAT',[
479
- col.abs/Arel::Nodes.build_quoted(10).pow(col.abs.log10.floor),
687
+ Arel::Nodes::NamedFunction.new('FORMAT', [
688
+ col.abs / Arel.quoted(10).pow(col.abs.log10.floor),
480
689
  param,
481
690
  locale
482
691
  ]),
483
692
  o.type,
484
- Arel::Nodes::NamedFunction.new('FORMAT',[
693
+ Arel::Nodes::NamedFunction.new('FORMAT', [
485
694
  col.abs.log10.floor,
486
- Arel::Nodes.build_quoted('N0'),
695
+ Arel.quoted('N0'),
487
696
  locale
488
697
  ])
489
698
  ])
490
699
  else
491
- Arel::Nodes::NamedFunction.new('FORMAT',[
492
- Arel::Nodes.build_quoted(col.abs),
700
+ Arel::Nodes::NamedFunction.new('FORMAT', [
701
+ Arel.quoted(col.abs),
493
702
  param,
494
703
  locale
495
704
  ])
496
705
  end
497
706
 
498
- repeated_char = (o.width == 0) ? Arel::Nodes.build_quoted('') : ArelExtensions::Nodes::Case.new().
499
- when(Arel::Nodes.build_quoted(o.width).abs-(number.length+sign_length)>0).
500
- then(Arel::Nodes.build_quoted(
501
- o.flags.include?('-') ? ' ' : (o.flags.include?('0') ? '0' : ' ')
502
- ).repeat(Arel::Nodes.build_quoted(o.width).abs-(number.length+sign_length))
503
- ).
504
- else('')
505
- before = (!o.flags.include?('0'))&&(!o.flags.include?('-')) ? repeated_char : ''
506
- middle = (o.flags.include?('0'))&&(!o.flags.include?('-')) ? repeated_char : ''
707
+ repeated_char =
708
+ if o.width == 0
709
+ Arel.quoted('')
710
+ else
711
+ Arel
712
+ .when(Arel.quoted(o.width).abs - (number.length + sign_length) > 0)
713
+ .then(Arel.quoted(
714
+ o.flags.include?('-') ? ' ' : (o.flags.include?('0') ? '0' : ' ')
715
+ ).repeat(Arel.quoted(o.width).abs - (number.length + sign_length))
716
+ )
717
+ .else('')
718
+ end
719
+ before = !o.flags.include?('0') && !o.flags.include?('-') ? repeated_char : ''
720
+ middle = o.flags.include?('0') && !o.flags.include?('-') ? repeated_char : ''
507
721
  after = o.flags.include?('-') ? repeated_char : ''
508
722
  full_number =
509
723
  ArelExtensions::Nodes::Concat.new([
@@ -513,27 +727,27 @@ module ArelExtensions
513
727
  number,
514
728
  after
515
729
  ])
516
- collector = visit ArelExtensions::Nodes::Concat.new([Arel::Nodes.build_quoted(o.prefix),full_number,Arel::Nodes.build_quoted(o.suffix)]), collector
730
+ collector = visit ArelExtensions::Nodes::Concat.new([Arel.quoted(o.prefix), full_number, Arel.quoted(o.suffix)]), collector
517
731
  collector
518
732
  end
519
733
 
520
734
  def visit_ArelExtensions_Nodes_Std o, collector
521
- collector << (o.unbiased_estimator ? "STDEV(" : "STDEVP(")
735
+ collector << (o.unbiased_estimator ? 'STDEV(' : 'STDEVP(')
522
736
  visit o.left, collector
523
- collector << ")"
737
+ collector << ')'
524
738
  collector
525
739
  end
526
740
 
527
741
  def visit_ArelExtensions_Nodes_Variance o, collector
528
- collector << (o.unbiased_estimator ? "VAR(" : "VARP(")
742
+ collector << (o.unbiased_estimator ? 'VAR(' : 'VARP(')
529
743
  visit o.left, collector
530
- collector << ")"
744
+ collector << ')'
531
745
  collector
532
746
  end
533
747
 
534
748
 
535
749
  def visit_ArelExtensions_Nodes_LevenshteinDistance o, collector
536
- collector << "dbo.LEVENSHTEIN_DISTANCE("
750
+ collector << 'dbo.LEVENSHTEIN_DISTANCE('
537
751
  collector = visit o.left, collector
538
752
  collector << Arel::Visitors::ToSql::COMMA
539
753
  collector = visit o.right, collector
@@ -542,19 +756,30 @@ module ArelExtensions
542
756
  end
543
757
 
544
758
 
545
- def visit_ArelExtensions_Nodes_JsonGet o,collector
759
+ def visit_ArelExtensions_Nodes_JsonGet o, collector
546
760
  collector << 'JSON_VALUE('
547
761
  collector = visit o.dict, collector
548
762
  collector << Arel::Visitors::MySQL::COMMA
549
763
  if o.key.is_a?(Integer)
550
764
  collector << "\"$[#{o.key}]\""
551
765
  else
552
- collector = visit Arel::Nodes.build_quoted('$.')+o.key, collector
766
+ collector = visit Arel.quoted('$.') + o.key, collector
553
767
  end
554
768
  collector << ')'
555
769
  collector
556
770
  end
557
771
 
772
+ # Utilized by GroupingSet, Cube & RollUp visitors to
773
+ # handle grouping aggregation semantics
774
+ def grouping_array_or_grouping_element(o, collector)
775
+ if o.expr.is_a? Array
776
+ collector << "( "
777
+ visit o.expr, collector
778
+ collector << " )"
779
+ else
780
+ visit o.expr, collector
781
+ end
782
+ end
558
783
  end
559
784
  end
560
785
  end