arel_extensions 2.0.24 → 2.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +358 -71
  3. data/Gemfile +8 -8
  4. data/README.md +107 -0
  5. data/gemfiles/rails5_2.gemfile +8 -7
  6. data/gemfiles/rails6.gemfile +7 -8
  7. data/gemfiles/rails6_1.gemfile +6 -7
  8. data/gemfiles/rails7.gemfile +22 -0
  9. data/lib/arel_extensions/aliases.rb +14 -0
  10. data/lib/arel_extensions/attributes.rb +2 -0
  11. data/lib/arel_extensions/date_duration.rb +2 -2
  12. data/lib/arel_extensions/helpers.rb +48 -0
  13. data/lib/arel_extensions/math.rb +17 -27
  14. data/lib/arel_extensions/nodes/case.rb +5 -10
  15. data/lib/arel_extensions/nodes/date_diff.rb +23 -4
  16. data/lib/arel_extensions/nodes/format.rb +3 -2
  17. data/lib/arel_extensions/nodes/function.rb +1 -7
  18. data/lib/arel_extensions/version.rb +1 -1
  19. data/lib/arel_extensions/visitors/mssql.rb +123 -51
  20. data/lib/arel_extensions/visitors/mysql.rb +23 -2
  21. data/lib/arel_extensions/visitors/oracle.rb +13 -1
  22. data/lib/arel_extensions/visitors/postgresql.rb +23 -5
  23. data/lib/arel_extensions/visitors/sqlite.rb +6 -3
  24. data/lib/arel_extensions/visitors/to_sql.rb +13 -8
  25. data/lib/arel_extensions.rb +27 -7
  26. data/test/arelx_test_helper.rb +45 -1
  27. data/test/database.yml +8 -2
  28. data/test/real_db_test.rb +5 -1
  29. data/test/support/fake_record.rb +1 -1
  30. data/test/visitors/test_to_sql.rb +38 -10
  31. data/test/with_ar/all_agnostic_test.rb +83 -5
  32. data/test/with_ar/insert_agnostic_test.rb +6 -2
  33. data/test/with_ar/test_bulk_sqlite.rb +6 -2
  34. data/test/with_ar/test_math_sqlite.rb +6 -2
  35. data/test/with_ar/test_string_mysql.rb +6 -2
  36. data/test/with_ar/test_string_sqlite.rb +6 -2
  37. data/version_v1.rb +1 -1
  38. data/version_v2.rb +1 -1
  39. metadata +6 -4
  40. data/appveyor.yml +0 -44
@@ -2,27 +2,37 @@ module ArelExtensions
2
2
  module Visitors
3
3
  module MSSQL
4
4
 
5
- Arel::Visitors::MSSQL::DATE_MAPPING = {
5
+ mssql_class = Arel::Visitors.constants.select { |c|
6
+ Arel::Visitors.const_get(c).is_a?(Class) && %i[MSSQL SQLServer].include?(c)
7
+ }.first
8
+
9
+ LOADED_VISITOR = Arel::Visitors.const_get(mssql_class) || Arel::Visitors.const_get('MSSQL')
10
+
11
+ LOADED_VISITOR::DATE_MAPPING = {
6
12
  'd' => 'day', 'm' => 'month', 'y' => 'year', 'wd' => 'weekday', 'w' => 'week', 'h' => 'hour', 'mn' => 'minute', 's' => 'second'
7
13
  }.freeze
8
14
 
9
- Arel::Visitors::MSSQL::DATE_FORMAT_DIRECTIVES = {
15
+ LOADED_VISITOR::DATE_FORMAT_DIRECTIVES = {
10
16
  '%Y' => 'YYYY', '%C' => '', '%y' => 'YY', '%m' => 'MM', '%B' => '', '%b' => '', '%^b' => '', # year, month
11
17
  '%d' => 'DD', '%e' => '', '%j' => '', '%w' => 'dw', '%A' => '', # day, weekday
12
18
  '%H' => 'hh', '%k' => '', '%I' => '', '%l' => '', '%P' => '', '%p' => '', # hours
13
19
  '%M' => 'mi', '%S' => 'ss', '%L' => 'ms', '%N' => 'ns', '%z' => 'tz'
14
20
  }.freeze
15
21
 
16
- Arel::Visitors::MSSQL::DATE_FORMAT_REGEX =
22
+ LOADED_VISITOR::DATE_FORMAT_FORMAT = {
23
+ 'YY' => '0#', 'MM' => '0#', 'DD' => '0#', 'hh' => '0#', 'mi' => '0#', 'ss' => '0#'
24
+ }
25
+
26
+ LOADED_VISITOR::DATE_FORMAT_REGEX =
17
27
  Regexp.new(
18
- Arel::Visitors::MSSQL::DATE_FORMAT_DIRECTIVES
28
+ LOADED_VISITOR::DATE_FORMAT_DIRECTIVES
19
29
  .keys
20
30
  .map{|k| Regexp.escape(k)}
21
31
  .join('|')
22
32
  ).freeze
23
33
 
24
34
  # TODO; all others... http://www.sql-server-helper.com/tips/date-formats.aspx
25
- Arel::Visitors::MSSQL::DATE_CONVERT_FORMATS = {
35
+ LOADED_VISITOR::DATE_CONVERT_FORMATS = {
26
36
  'YYYY-MM-DD' => 120,
27
37
  'YY-MM-DD' => 120,
28
38
  'MM/DD/YYYY' => 101,
@@ -79,7 +89,7 @@ module ArelExtensions
79
89
  def visit_ArelExtensions_Nodes_Concat o, collector
80
90
  collector << "CONCAT("
81
91
  o.expressions.each_with_index { |arg, i|
82
- collector << Arel::Visitors::MSSQL::COMMA if i != 0
92
+ collector << LOADED_VISITOR::COMMA if i != 0
83
93
  collector = visit arg, collector
84
94
  }
85
95
  collector << ")"
@@ -102,23 +112,23 @@ module ArelExtensions
102
112
  case o.right_node_type
103
113
  when :ruby_date, :ruby_time, :date, :datetime, :time
104
114
  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
115
+ when :ruby_time, :datetime, :time then 'DATEDIFF(second'
116
+ else 'DATEDIFF(day'
117
+ end
118
+ collector << LOADED_VISITOR::COMMA
109
119
  collector = visit o.right, collector
110
- collector << Arel::Visitors::MSSQL::COMMA
120
+ collector << LOADED_VISITOR::COMMA
111
121
  collector = visit o.left, collector
112
122
  collector << ')'
113
123
  else
114
124
  da = ArelExtensions::Nodes::DateAdd.new([])
115
125
  collector << "DATEADD("
116
126
  collector = visit da.mssql_datepart(o.right), collector
117
- collector << Arel::Visitors::MSSQL::COMMA
127
+ collector << LOADED_VISITOR::COMMA
118
128
  collector << "-("
119
129
  collector = visit da.mssql_value(o.right), collector
120
130
  collector << ")"
121
- collector << Arel::Visitors::MSSQL::COMMA
131
+ collector << LOADED_VISITOR::COMMA
122
132
  collector = visit o.left, collector
123
133
  collector << ")"
124
134
  collector
@@ -129,9 +139,9 @@ module ArelExtensions
129
139
  def visit_ArelExtensions_Nodes_DateAdd o, collector
130
140
  collector << "DATEADD("
131
141
  collector = visit o.mssql_datepart(o.right), collector
132
- collector << Arel::Visitors::MSSQL::COMMA
142
+ collector << LOADED_VISITOR::COMMA
133
143
  collector = visit o.mssql_value(o.right), collector
134
- collector << Arel::Visitors::MSSQL::COMMA
144
+ collector << LOADED_VISITOR::COMMA
135
145
  collector = visit o.left, collector
136
146
  collector << ")"
137
147
  collector
@@ -144,8 +154,8 @@ module ArelExtensions
144
154
  left = o.left.end_with?('i') ? o.left[0..-2] : o.left
145
155
  conv = ['h', 'mn', 's'].include?(o.left)
146
156
  collector << 'DATEPART('
147
- collector << Arel::Visitors::MSSQL::DATE_MAPPING[left]
148
- collector << Arel::Visitors::MSSQL::COMMA
157
+ collector << LOADED_VISITOR::DATE_MAPPING[left]
158
+ collector << LOADED_VISITOR::COMMA
149
159
  collector << 'CONVERT(datetime,' if conv
150
160
  collector = visit o.right, collector
151
161
  collector << ')' if conv
@@ -155,20 +165,29 @@ module ArelExtensions
155
165
  end
156
166
 
157
167
  def visit_ArelExtensions_Nodes_Length o, collector
158
- collector << "#{o.bytewise ? 'DATALENGTH' : 'LEN'}("
159
- collector = visit o.expr, collector
160
- collector << ")"
161
- collector
168
+ if o.bytewise
169
+ collector << "(DATALENGTH("
170
+ collector = visit o.expr, collector
171
+ collector << ") / ISNULL(NULLIF(DATALENGTH(LEFT(COALESCE("
172
+ collector = visit o.expr, collector
173
+ collector << ", '#' ), 1 )), 0), 1))"
174
+ collector
175
+ else
176
+ collector << "LEN("
177
+ collector = visit o.expr, collector
178
+ collector << ")"
179
+ collector
180
+ end
162
181
  end
163
182
 
164
183
  def visit_ArelExtensions_Nodes_Round o, collector
165
184
  collector << "ROUND("
166
185
  o.expressions.each_with_index { |arg, i|
167
- collector << Arel::Visitors::MSSQL::COMMA if i != 0
186
+ collector << LOADED_VISITOR::COMMA if i != 0
168
187
  collector = visit arg, collector
169
188
  }
170
189
  if o.expressions.length == 1
171
- collector << Arel::Visitors::MSSQL::COMMA
190
+ collector << LOADED_VISITOR::COMMA
172
191
  collector << "0"
173
192
  end
174
193
  collector << ")"
@@ -178,7 +197,7 @@ module ArelExtensions
178
197
  def visit_ArelExtensions_Nodes_Locate o, collector
179
198
  collector << "CHARINDEX("
180
199
  collector = visit o.right, collector
181
- collector << Arel::Visitors::MSSQL::COMMA
200
+ collector << LOADED_VISITOR::COMMA
182
201
  collector = visit o.left, collector
183
202
  collector << ")"
184
203
  collector
@@ -187,28 +206,20 @@ module ArelExtensions
187
206
  def visit_ArelExtensions_Nodes_Substring o, collector
188
207
  collector << 'SUBSTRING('
189
208
  collector = visit o.expressions[0], collector
190
- collector << Arel::Visitors::MSSQL::COMMA
209
+ collector << LOADED_VISITOR::COMMA
191
210
  collector = visit o.expressions[1], collector
192
- collector << Arel::Visitors::MSSQL::COMMA
211
+ collector << LOADED_VISITOR::COMMA
193
212
  collector = o.expressions[2] ? visit(o.expressions[2], collector) : visit(o.expressions[0].length, collector)
194
213
  collector << ')'
195
214
  collector
196
215
  end
197
216
 
198
217
  def visit_ArelExtensions_Nodes_Trim o, collector
199
- if o.right
200
- collector << "REPLACE(REPLACE(LTRIM(RTRIM(REPLACE(REPLACE("
201
- collector = visit o.left, collector
202
- collector << ", ' ', '~'), "
203
- collector = visit o.right, collector
204
- collector << ", ' '))), ' ', "
205
- collector = visit o.right, collector
206
- collector << "), '~', ' ')"
207
- else
208
- collector << "LTRIM(RTRIM("
209
- collector = visit o.left, collector
210
- collector << "))"
211
- end
218
+ collector << 'TRIM( '
219
+ collector = visit o.right, collector
220
+ collector << " FROM "
221
+ collector = visit o.left, collector
222
+ collector << ")"
212
223
  collector
213
224
  end
214
225
 
@@ -255,12 +266,28 @@ module ArelExtensions
255
266
  end
256
267
 
257
268
  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]
269
+ f = ArelExtensions::Visitors::strftime_to_format(o.iso_format, LOADED_VISITOR::DATE_FORMAT_DIRECTIVES)
270
+ if fmt = LOADED_VISITOR::DATE_CONVERT_FORMATS[f]
260
271
  collector << "CONVERT(VARCHAR(#{f.length})"
261
- collector << Arel::Visitors::MSSQL::COMMA
272
+ collector << LOADED_VISITOR::COMMA
273
+ if o.time_zone
274
+ collector << 'CONVERT(datetime'
275
+ collector << LOADED_VISITOR::COMMA
276
+ collector << ' '
277
+ end
262
278
  collector = visit o.left, collector
263
- collector << Arel::Visitors::MSSQL::COMMA
279
+ case o.time_zone
280
+ when Hash
281
+ src_tz, dst_tz = o.time_zone.first
282
+ collector << ') AT TIME ZONE '
283
+ collector = visit Arel::Nodes.build_quoted(src_tz), collector
284
+ collector << ' AT TIME ZONE '
285
+ collector = visit Arel::Nodes.build_quoted(dst_tz), collector
286
+ when String
287
+ collector << ') AT TIME ZONE '
288
+ collector = visit Arel::Nodes.build_quoted(o.time_zone), collector
289
+ end
290
+ collector << LOADED_VISITOR::COMMA
264
291
  collector << fmt.to_s
265
292
  collector << ')'
266
293
  collector
@@ -272,13 +299,38 @@ module ArelExtensions
272
299
  collector << sep
273
300
  sep = ' + '
274
301
  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('
302
+ when s.scan(LOADED_VISITOR::DATE_FORMAT_REGEX)
303
+ dir = LOADED_VISITOR::DATE_FORMAT_DIRECTIVES[s.matched]
304
+ fmt = LOADED_VISITOR::DATE_FORMAT_FORMAT[dir]
305
+ collector << 'TRIM('
306
+ collector << 'FORMAT(' if fmt
307
+ collector << 'STR(' if !fmt
308
+ collector << 'DATEPART('
278
309
  collector << dir
279
- collector << Arel::Visitors::MSSQL::COMMA
310
+ collector << LOADED_VISITOR::COMMA
311
+ if o.time_zone
312
+ collector << 'CONVERT(datetime'
313
+ collector << LOADED_VISITOR::COMMA
314
+ collector << ' '
315
+ end
280
316
  collector = visit o.left, collector
281
- collector << ')))'
317
+ case o.time_zone
318
+ when Hash
319
+ src_tz, dst_tz = o.time_zone.first.first, o.time_zone.first.second
320
+ collector << ") AT TIME ZONE "
321
+ collector = visit Arel::Nodes.build_quoted(src_tz), collector
322
+ collector << " AT TIME ZONE "
323
+ collector = visit Arel::Nodes.build_quoted(dst_tz), collector
324
+ when String
325
+ collector << ") AT TIME ZONE "
326
+ collector = visit Arel::Nodes.build_quoted(o.time_zone), collector
327
+ end
328
+ collector << ')'
329
+ collector << ')' if !fmt
330
+ collector << LOADED_VISITOR::COMMA << "'#{fmt}')" if fmt
331
+ collector << ')'
332
+ when s.scan(/^%%/)
333
+ collector = visit Arel::Nodes.build_quoted('%'), collector
282
334
  when s.scan(/[^%]+|./)
283
335
  collector = visit Arel::Nodes.build_quoted(s.matched), collector
284
336
  end
@@ -291,7 +343,7 @@ module ArelExtensions
291
343
  def visit_ArelExtensions_Nodes_Replace o, collector
292
344
  collector << "REPLACE("
293
345
  o.expressions.each_with_index { |arg, i|
294
- collector << Arel::Visitors::MSSQL::COMMA if i != 0
346
+ collector << LOADED_VISITOR::COMMA if i != 0
295
347
  collector = visit arg, collector
296
348
  }
297
349
  collector << ")"
@@ -301,7 +353,7 @@ module ArelExtensions
301
353
  def visit_ArelExtensions_Nodes_FindInSet o, collector
302
354
  collector << "dbo.FIND_IN_SET("
303
355
  o.expressions.each_with_index { |arg, i|
304
- collector << Arel::Visitors::MSSQL::COMMA if i != 0
356
+ collector << LOADED_VISITOR::COMMA if i != 0
305
357
  collector = visit arg, collector
306
358
  }
307
359
  collector << ")"
@@ -387,6 +439,27 @@ module ArelExtensions
387
439
  collector
388
440
  end
389
441
 
442
+ alias_method(:old_visit_Arel_Nodes_As, :visit_Arel_Nodes_As) rescue nil
443
+ def visit_Arel_Nodes_As o, collector
444
+ if o.left.is_a?(Arel::Nodes::Binary)
445
+ collector << '('
446
+ collector = visit o.left, collector
447
+ collector << ')'
448
+ else
449
+ collector = visit o.left, collector
450
+ end
451
+ collector << " AS "
452
+
453
+ # sometimes these values are already quoted, if they are, don't double quote it
454
+ quote = o.right.is_a?(Arel::Nodes::SqlLiteral) && o.right[0] != '"' && o.right[-1] != '"'
455
+
456
+ collector << '"' if quote
457
+ collector = visit o.right, collector
458
+ collector << '"' if quote
459
+
460
+ collector
461
+ end
462
+
390
463
  # SQL Server does not know about REGEXP
391
464
  def visit_Arel_Nodes_Regexp o, collector
392
465
  collector = visit o.left, collector
@@ -554,7 +627,6 @@ module ArelExtensions
554
627
  collector << ')'
555
628
  collector
556
629
  end
557
-
558
630
  end
559
631
  end
560
632
  end
@@ -209,7 +209,21 @@ module ArelExtensions
209
209
  when :date, :datetime, :time
210
210
  fmt = ArelExtensions::Visitors::strftime_to_format(o.iso_format, DATE_FORMAT_DIRECTIVES)
211
211
  collector << "DATE_FORMAT("
212
+ collector << "CONVERT_TZ(" if o.time_zone
212
213
  collector = visit o.left, collector
214
+ case o.time_zone
215
+ when Hash
216
+ src_tz, dst_tz = o.time_zone.first
217
+ collector << COMMA
218
+ collector = visit Arel::Nodes.build_quoted(src_tz), collector
219
+ collector << COMMA
220
+ collector = visit Arel::Nodes.build_quoted(dst_tz), collector
221
+ collector << ')'
222
+ when String
223
+ collector << COMMA << "'UTC'" << COMMA
224
+ collector = visit Arel::Nodes.build_quoted(o.time_zone), collector
225
+ collector << ')'
226
+ end
213
227
  collector << COMMA
214
228
  collector = visit Arel::Nodes.build_quoted(fmt), collector
215
229
  collector << ")"
@@ -354,9 +368,16 @@ module ArelExtensions
354
368
  else
355
369
  collector = visit o.left, collector
356
370
  end
357
- collector << " AS `"
371
+ collector << " AS "
372
+
373
+ # sometimes these values are already quoted, if they are, don't double quote it
374
+ quote = o.right.is_a?(Arel::Nodes::SqlLiteral) && o.right[0] != '`' && o.right[-1] != '`'
375
+
376
+ collector << '`' if quote
358
377
  collector = visit o.right, collector
359
- collector << "`"
378
+ collector << '`' if quote
379
+
380
+ collector
360
381
  collector
361
382
  end
362
383
 
@@ -447,7 +447,19 @@ module ArelExtensions
447
447
  def visit_ArelExtensions_Nodes_Format o, collector
448
448
  fmt = ArelExtensions::Visitors::strftime_to_format(o.iso_format, DATE_FORMAT_DIRECTIVES)
449
449
  collector << "TO_CHAR("
450
+ collector << "CAST(" if o.time_zone
450
451
  collector = visit o.left, collector
452
+ case o.time_zone
453
+ when Hash
454
+ src_tz, dst_tz = o.time_zone.first
455
+ collector << ' as timestamp) at time zone '
456
+ collector = visit Arel::Nodes.build_quoted(src_tz), collector
457
+ collecto < ' at time zone '
458
+ collector = visit Arel::Nodes.build_quoted(dst_tz), collector
459
+ when String
460
+ collector << ' as timestamp) at time zone '
461
+ collector = visit Arel::Nodes.build_quoted(o.time_zone), collector
462
+ end
451
463
  collector << COMMA
452
464
  collector = visit Arel::Nodes.build_quoted(fmt), collector
453
465
  collector << ")"
@@ -517,7 +529,7 @@ module ArelExtensions
517
529
  if element.is_a?(Time)
518
530
  ArelExtensions::Nodes::Format.new [element, '%H:%M:%S']
519
531
  elsif element.is_a?(Arel::Attributes::Attribute)
520
- col = Arel::Table.engine.connection.schema_cache.columns_hash(element.relation.table_name)[element.name.to_s]
532
+ col = ArelExtensions::column_of(element.relation.table_name, element.name.to_s)
521
533
  if col && (col.type == :time)
522
534
  ArelExtensions::Nodes::Format.new [element, '%H:%M:%S']
523
535
  else
@@ -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
 
@@ -169,7 +175,19 @@ module ArelExtensions
169
175
  def visit_ArelExtensions_Nodes_Format o, collector
170
176
  fmt = ArelExtensions::Visitors::strftime_to_format(o.iso_format, DATE_FORMAT_DIRECTIVES)
171
177
  collector << "TO_CHAR("
178
+ collector << '(' if o.time_zone
172
179
  collector = visit o.left, collector
180
+ case o.time_zone
181
+ when Hash
182
+ src_tz, dst_tz = o.time_zone.first
183
+ collector << ') AT TIME ZONE '
184
+ collector = visit Arel::Nodes.build_quoted(src_tz), collector
185
+ collector << ' AT TIME ZONE '
186
+ collector = visit Arel::Nodes.build_quoted(dst_tz), collector
187
+ when String
188
+ collector << ') AT TIME ZONE '
189
+ collector = visit Arel::Nodes.build_quoted(o.time_zone), collector
190
+ end
173
191
  collector << COMMA
174
192
  collector = visit Arel::Nodes.build_quoted(fmt), collector
175
193
  collector << ")"
@@ -283,9 +301,9 @@ module ArelExtensions
283
301
  return collector
284
302
  end
285
303
  end
286
- collector << "EXTRACT(#{DATE_MAPPING[o.left]} FROM "
304
+ collector << "EXTRACT(#{DATE_MAPPING[o.left]} FROM CAST("
287
305
  collector = visit o.right, collector
288
- collector << ")"
306
+ collector << " AS TIMESTAMP WITH TIME ZONE))"
289
307
  collector << " * (INTERVAL '1' #{interval})" if interval && o.with_interval
290
308
  collector
291
309
  end
@@ -368,7 +386,7 @@ module ArelExtensions
368
386
  when :number, :decimal, :float
369
387
  Arel::Nodes::SqlLiteral.new('numeric')
370
388
  when :datetime
371
- Arel::Nodes::SqlLiteral.new('timestamp')
389
+ Arel::Nodes::SqlLiteral.new('timestamp with time zone')
372
390
  when :date
373
391
  Arel::Nodes::SqlLiteral.new('date')
374
392
  when :binary
@@ -1,3 +1,5 @@
1
+ require 'arel_extensions/helpers'
2
+
1
3
  module ArelExtensions
2
4
  module Visitors
3
5
  class Arel::Visitors::SQLite
@@ -327,7 +329,7 @@ module ArelExtensions
327
329
  if element.is_a?(Time)
328
330
  return Arel::Nodes::NamedFunction.new('STRFTIME',[element, '%H:%M:%S'])
329
331
  elsif element.is_a?(Arel::Attributes::Attribute)
330
- col = Arel::Table.engine.connection.schema_cache.columns_hash(element.relation.table_name)[element.name.to_s]
332
+ col = ArelExtensions::column_of(element.relation.table_name, element.name.to_s)
331
333
  if col && (col.type == :time)
332
334
  return Arel::Nodes::NamedFunction.new('STRFTIME',[element, '%H:%M:%S'])
333
335
  else
@@ -379,9 +381,10 @@ module ArelExtensions
379
381
  else
380
382
  collector = visit o.left, collector
381
383
  end
382
- collector << " AS \""
384
+ sep = o.right.size > 1 && o.right[0] == '"' && o.right[-1] == '"' ? '' : '"'
385
+ collector << " AS #{sep}"
383
386
  collector = visit o.right, collector
384
- collector << "\""
387
+ collector << "#{sep}"
385
388
  collector
386
389
  end
387
390
 
@@ -8,10 +8,15 @@ module ArelExtensions
8
8
  def make_json_string expr
9
9
  Arel::Nodes.build_quoted('"') \
10
10
  + expr
11
+ .coalesce('')
11
12
  .replace('\\','\\\\').replace('"','\"').replace("\n", '\n') \
12
13
  + '"'
13
14
  end
14
15
 
16
+ def make_json_null
17
+ Arel::Nodes.build_quoted("null")
18
+ end
19
+
15
20
  # Math Functions
16
21
  def visit_ArelExtensions_Nodes_Abs o, collector
17
22
  collector << "ABS("
@@ -601,20 +606,20 @@ module ArelExtensions
601
606
  def json_value(o,v)
602
607
  case o.type_of_node(v)
603
608
  when :string
604
- Arel.when(v.is_null).then(Arel.null).else(make_json_string(v))
609
+ Arel.when(v.is_null).then(make_json_null).else(make_json_string(v))
605
610
  when :date
606
611
  s = v.format('%Y-%m-%d')
607
- Arel.when(s.is_null).then(Arel.null).else(make_json_string(s))
612
+ Arel.when(s.is_null).then(make_json_null).else(make_json_string(s))
608
613
  when :datetime
609
614
  s = v.format('%Y-%m-%dT%H:%M:%S')
610
- Arel.when(s.is_null).then(Arel.null).else(make_json_string(s))
615
+ Arel.when(s.is_null).then(make_json_null).else(make_json_string(s))
611
616
  when :time
612
617
  s = v.format('%H:%M:%S')
613
- Arel.when(s.is_null).then(Arel.null).else(make_json_string(s))
618
+ Arel.when(s.is_null).then(make_json_null).else(make_json_string(s))
614
619
  when :nil
615
- Arel.null
620
+ make_json_null
616
621
  else
617
- ArelExtensions::Nodes::Cast.new([v, :string]).coalesce(Arel.null)
622
+ ArelExtensions::Nodes::Cast.new([v, :string]).coalesce(make_json_null)
618
623
  end
619
624
  end
620
625
 
@@ -636,7 +641,7 @@ module ArelExtensions
636
641
  if i != 0
637
642
  res += ', '
638
643
  end
639
- res += make_json_string(ArelExtensions::Nodes::Cast.new([k, :string]).coalesce("")) + ': '
644
+ res += make_json_string(ArelExtensions::Nodes::Cast.new([k, :string])) + ': '
640
645
  res += json_value(o,v)
641
646
  end
642
647
  res += '}'
@@ -658,7 +663,7 @@ module ArelExtensions
658
663
  if i != 0
659
664
  res = res + ', '
660
665
  end
661
- kv = make_json_string(ArelExtensions::Nodes::Cast.new([k, :string]).coalesce("")) + ': '
666
+ kv = make_json_string(ArelExtensions::Nodes::Cast.new([k, :string])) + ': '
662
667
  kv += json_value(o,v)
663
668
  res = res + kv.group_concat(', ', order: Array(orders)).coalesce('')
664
669
  end
@@ -52,6 +52,7 @@ if Gem::Version.new(Arel::VERSION) >= Gem::Version.new("7.1.0")
52
52
  end
53
53
 
54
54
  require 'arel_extensions/version'
55
+ require 'arel_extensions/aliases'
55
56
  require 'arel_extensions/attributes'
56
57
  require 'arel_extensions/visitors'
57
58
  require 'arel_extensions/nodes'
@@ -76,7 +77,17 @@ require 'arel_extensions/nodes/soundex'
76
77
  require 'arel_extensions/nodes/cast'
77
78
  require 'arel_extensions/nodes/json'
78
79
 
79
-
80
+ # It seems like the code in lib/arel_extensions/visitors.rb that is supposed
81
+ # to inject ArelExtension is not enough. Different versions of the sqlserver
82
+ # adapter behave differently. It doesn't always proc, so we added this for
83
+ # coverage.
84
+ if defined?(Arel::Visitors::SQLServer)
85
+ Arel::Visitors.const_set('MSSQL', Arel::Visitors::SQLServer)
86
+ require 'arel_extensions/visitors/mssql'
87
+ class Arel::Visitors::SQLServer
88
+ include ArelExtensions::Visitors::MSSQL
89
+ end
90
+ end
80
91
 
81
92
  module Arel
82
93
  def self.rand
@@ -136,6 +147,7 @@ class Arel::Attributes::Attribute
136
147
  end
137
148
 
138
149
  class Arel::Nodes::Function
150
+ include ArelExtensions::Aliases
139
151
  include ArelExtensions::Math
140
152
  include ArelExtensions::Comparators
141
153
  include ArelExtensions::DateDuration
@@ -153,11 +165,6 @@ class Arel::Nodes::Function
153
165
  end
154
166
  res
155
167
  end
156
-
157
- def xas other
158
- Arel::Nodes::As.new(self, Arel.sql(other))
159
- end
160
-
161
168
  end
162
169
 
163
170
  class Arel::Nodes::Grouping
@@ -211,6 +218,15 @@ class Arel::SelectManager
211
218
  def as table_name
212
219
  Arel::Nodes::TableAlias.new(self, table_name)
213
220
  end
221
+
222
+ # Install an alias, if present.
223
+ def xas table_name
224
+ if table_name.present?
225
+ as table_name
226
+ else
227
+ self
228
+ end
229
+ end
214
230
  end
215
231
 
216
232
  class Arel::Nodes::As
@@ -220,7 +236,11 @@ end
220
236
  class Arel::Table
221
237
  alias_method(:old_alias, :alias) rescue nil
222
238
  def alias(name = "#{self.name}_2")
223
- name.blank? ? self : Arel::Nodes::TableAlias.new(self,name)
239
+ if name.present?
240
+ Arel::Nodes::TableAlias.new(self, name)
241
+ else
242
+ self
243
+ end
224
244
  end
225
245
  end
226
246
 
@@ -6,8 +6,52 @@ require 'active_record'
6
6
 
7
7
  require 'support/fake_record'
8
8
 
9
+ def colored(color, msg)
10
+ ENV["TERM"] =~ /^xterm|-256color$/ ? "\x1b[#{color}m#{msg}\x1b[89m\x1b[0m" : "#{msg}"
11
+ end
12
+
13
+ YELLOW = "33"
14
+
15
+ def warn(msg)
16
+ $stderr.puts(colored(YELLOW, msg))
17
+ end
18
+
19
+ # Load gems specific to databases
20
+ # NOTE:
21
+ # It's strongly advised to test each database on its own. Loading multiple
22
+ # backend gems leads to undefined behavior according to tests; the backend
23
+ # might not recognize the correct DB visitor and will fallback to `ToSQL`
24
+ # and screw all tests.
25
+ #
26
+ # The issue also seems to be related to arel version: at some point, arel
27
+ # dropped its wide support for DBs and kept Postgres, MySQL and SQLite.
28
+ # Here, we're just trying to load the correct ones.
29
+ db_and_gem = if RUBY_ENGINE == 'jruby'
30
+ {
31
+ 'oracle' => 'activerecord-oracle_enhanced-adapter',
32
+ 'mssql' => 'activerecord-jdbcsqlserver-adapter'
33
+ }
34
+ else
35
+ {
36
+ 'oracle' => 'activerecord-oracle_enhanced-adapter',
37
+ 'mssql' => 'activerecord-sqlserver-adapter'
38
+ }
39
+ end
40
+
41
+ def load_lib(gem)
42
+ if gem && (RUBY_ENGINE == 'jruby' || Arel::VERSION.to_i > 9)
43
+ begin
44
+ Gem::Specification.find_by_name(gem)
45
+ require gem
46
+ rescue Gem::MissingSpecError
47
+ warn "Warning: failed to load gem #{gem}. Are you sure it's installed?"
48
+ end
49
+ end
50
+ end
51
+
52
+ load_lib(db_and_gem[ENV['DB']])
53
+
9
54
  require 'arel_extensions'
10
- Arel::Table.engine = FakeRecord::Base.new
11
55
 
12
56
  $arel_silence_type_casting_deprecation = true
13
57