arel_extensions 2.0.24 → 2.1.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.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +358 -71
- data/Gemfile +8 -8
- data/README.md +107 -0
- data/gemfiles/rails5_2.gemfile +8 -7
- data/gemfiles/rails6.gemfile +7 -8
- data/gemfiles/rails6_1.gemfile +6 -7
- data/gemfiles/rails7.gemfile +22 -0
- data/lib/arel_extensions/aliases.rb +14 -0
- data/lib/arel_extensions/attributes.rb +2 -0
- data/lib/arel_extensions/date_duration.rb +2 -2
- data/lib/arel_extensions/helpers.rb +48 -0
- data/lib/arel_extensions/math.rb +17 -27
- data/lib/arel_extensions/nodes/case.rb +5 -10
- data/lib/arel_extensions/nodes/date_diff.rb +23 -4
- data/lib/arel_extensions/nodes/format.rb +3 -2
- data/lib/arel_extensions/nodes/function.rb +1 -7
- data/lib/arel_extensions/version.rb +1 -1
- data/lib/arel_extensions/visitors/mssql.rb +123 -51
- data/lib/arel_extensions/visitors/mysql.rb +23 -2
- data/lib/arel_extensions/visitors/oracle.rb +13 -1
- data/lib/arel_extensions/visitors/postgresql.rb +23 -5
- data/lib/arel_extensions/visitors/sqlite.rb +6 -3
- data/lib/arel_extensions/visitors/to_sql.rb +13 -8
- data/lib/arel_extensions.rb +27 -7
- data/test/arelx_test_helper.rb +45 -1
- data/test/database.yml +8 -2
- data/test/real_db_test.rb +5 -1
- data/test/support/fake_record.rb +1 -1
- data/test/visitors/test_to_sql.rb +38 -10
- data/test/with_ar/all_agnostic_test.rb +83 -5
- data/test/with_ar/insert_agnostic_test.rb +6 -2
- data/test/with_ar/test_bulk_sqlite.rb +6 -2
- data/test/with_ar/test_math_sqlite.rb +6 -2
- data/test/with_ar/test_string_mysql.rb +6 -2
- data/test/with_ar/test_string_sqlite.rb +6 -2
- data/version_v1.rb +1 -1
- data/version_v2.rb +1 -1
- metadata +6 -4
- data/appveyor.yml +0 -44
@@ -2,27 +2,37 @@ module ArelExtensions
|
|
2
2
|
module Visitors
|
3
3
|
module MSSQL
|
4
4
|
|
5
|
-
Arel::Visitors
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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 <<
|
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
|
-
|
106
|
-
|
107
|
-
|
108
|
-
collector <<
|
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 <<
|
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 <<
|
127
|
+
collector << LOADED_VISITOR::COMMA
|
118
128
|
collector << "-("
|
119
129
|
collector = visit da.mssql_value(o.right), collector
|
120
130
|
collector << ")"
|
121
|
-
collector <<
|
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 <<
|
142
|
+
collector << LOADED_VISITOR::COMMA
|
133
143
|
collector = visit o.mssql_value(o.right), collector
|
134
|
-
collector <<
|
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 <<
|
148
|
-
collector <<
|
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
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
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 <<
|
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 <<
|
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 <<
|
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 <<
|
209
|
+
collector << LOADED_VISITOR::COMMA
|
191
210
|
collector = visit o.expressions[1], collector
|
192
|
-
collector <<
|
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
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
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,
|
259
|
-
if fmt =
|
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 <<
|
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
|
-
|
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(
|
276
|
-
dir =
|
277
|
-
|
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 <<
|
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
|
-
|
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 <<
|
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 <<
|
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 =
|
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 =
|
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
|
-
|
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(
|
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(
|
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(
|
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(
|
618
|
+
Arel.when(s.is_null).then(make_json_null).else(make_json_string(s))
|
614
619
|
when :nil
|
615
|
-
|
620
|
+
make_json_null
|
616
621
|
else
|
617
|
-
ArelExtensions::Nodes::Cast.new([v, :string]).coalesce(
|
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])
|
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])
|
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
|
data/lib/arel_extensions.rb
CHANGED
@@ -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.
|
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
|
|
data/test/arelx_test_helper.rb
CHANGED
@@ -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
|
|