arel_extensions 1.3.0 → 1.3.3

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.
@@ -1,3 +1,5 @@
1
+ require 'arel_extensions/helpers'
2
+
1
3
  require 'arel_extensions/nodes'
2
4
  require 'arel_extensions/nodes/function'
3
5
  require 'arel_extensions/nodes/concat'
@@ -38,21 +40,17 @@ module ArelExtensions
38
40
  when Arel::Nodes::Function
39
41
  Arel.grouping(Arel::Nodes::Addition.new self, other)
40
42
  else
41
- begin
42
- col = Arel::Table.engine.connection.schema_cache.columns_hash(self.relation.table_name)[self.name.to_s]
43
- rescue Exception
44
- col = nil
45
- end
43
+ col = self.respond_to?(:relation)? ArelExtensions::column_of(self.relation.table_name, self.name.to_s) : nil
46
44
  if (!col) # if the column doesn't exist in the database
47
- Arel.grouping(Arel::Nodes::Addition.new(self, other))
45
+ Arel.grouping(Arel::Nodes::Addition.new(self, Arel::Nodes.build_quoted(other)))
48
46
  else
49
47
  arg = col.type
50
48
  if arg == :integer || (!arg)
51
49
  other = other.to_i if other.is_a?(String)
52
- Arel.grouping(Arel::Nodes::Addition.new self, other)
50
+ Arel.grouping(Arel::Nodes::Addition.new self, Arel::Nodes.build_quoted(other))
53
51
  elsif arg == :decimal || arg == :float
54
52
  other = Arel.sql(other) if other.is_a?(String) # Arel should accept Float & BigDecimal!
55
- Arel.grouping(Arel::Nodes::Addition.new self, other)
53
+ Arel.grouping(Arel::Nodes::Addition.new self, Arel::Nodes.build_quoted(other))
56
54
  elsif arg == :datetime || arg == :date
57
55
  ArelExtensions::Nodes::DateAdd.new [self, other]
58
56
  elsif arg == :string || arg == :text
@@ -68,41 +66,33 @@ module ArelExtensions
68
66
  case self
69
67
  when Arel::Nodes::Grouping
70
68
  if self.expr.left.is_a?(Date) || self.expr.left.is_a?(DateTime)
71
- Arel.grouping(ArelExtensions::Nodes::DateSub.new [self, other])
69
+ Arel.grouping(ArelExtensions::Nodes::DateSub.new [self, Arel::Nodes.build_quoted(other)])
72
70
  else
73
- Arel.grouping(Arel::Nodes::Subtraction.new(self, other))
71
+ Arel.grouping(Arel::Nodes::Subtraction.new(self, Arel::Nodes.build_quoted(other)))
74
72
  end
75
73
  when ArelExtensions::Nodes::Function, ArelExtensions::Nodes::Case
76
74
  case self.return_type
77
75
  when :string, :text # ???
78
- Arel.grouping(Arel::Nodes::Subtraction.new(self, other)) # ??
76
+ Arel.grouping(Arel::Nodes::Subtraction.new(self, Arel::Nodes.build_quoted(other))) # ??
79
77
  when :integer, :decimal, :float, :number
80
- Arel.grouping(Arel::Nodes::Subtraction.new(self, other))
78
+ Arel.grouping(Arel::Nodes::Subtraction.new(self, Arel::Nodes.build_quoted(other)))
81
79
  when :date, :datetime
82
- ArelExtensions::Nodes::DateSub.new [self, other]
80
+ ArelExtensions::Nodes::DateSub.new [self, Arel::Nodes.build_quoted(other)]
83
81
  else
84
- Arel.grouping(Arel::Nodes::Subtraction.new(self, other))
82
+ Arel.grouping(Arel::Nodes::Subtraction.new(self, Arel::Nodes.build_quoted(other)))
85
83
  end
86
84
  when Arel::Nodes::Function
87
- Arel.grouping(Arel::Nodes::Subtraction.new(self, other))
85
+ Arel.grouping(Arel::Nodes::Subtraction.new(self, Arel::Nodes.build_quoted(other)))
88
86
  else
89
- begin
90
- col = Arel::Table.engine.connection.schema_cache.columns_hash(self.relation.table_name)[self.name.to_s]
91
- rescue Exception
92
- col = nil
93
- end
87
+ col = ArelExtensions::column_of(self.relation.table_name, self.name.to_s)
94
88
  if (!col) # if the column doesn't exist in the database
95
- Arel.grouping(Arel::Nodes::Subtraction.new(self, other))
89
+ Arel.grouping(Arel::Nodes::Subtraction.new(self, Arel::Nodes.build_quoted(other)))
96
90
  else
97
91
  arg = col.type
98
92
  if (arg == :date || arg == :datetime)
99
93
  case other
100
94
  when Arel::Attributes::Attribute
101
- begin
102
- col2 = Arel::Table.engine.connection.schema_cache.columns_hash(other.relation.table_name)[other.name.to_s]
103
- rescue Exception
104
- col2 = nil
105
- end
95
+ col2 = ArelExtensions::column_of(other.relation.table_name, other.name.to_s)
106
96
  if (!col2) # if the column doesn't exist in the database
107
97
  ArelExtensions::Nodes::DateSub.new [self, other]
108
98
  else
@@ -127,7 +117,7 @@ module ArelExtensions
127
117
  when String
128
118
  Arel.grouping(Arel::Nodes::Subtraction.new(self, Arel.sql(other)))
129
119
  else
130
- Arel.grouping(Arel::Nodes::Subtraction.new(self, other))
120
+ Arel.grouping(Arel::Nodes::Subtraction.new(self, Arel::Nodes.build_quoted(other)))
131
121
  end
132
122
  end
133
123
  end
@@ -1,3 +1,5 @@
1
+ require 'arel_extensions/helpers'
2
+
1
3
  module ArelExtensions
2
4
  module Nodes
3
5
  if Gem::Version.new(Arel::VERSION) < Gem::Version.new("7.1.0")
@@ -57,11 +59,7 @@ module ArelExtensions
57
59
  when Date, DateTime,Time
58
60
  :datetime
59
61
  when Arel::Attributes::Attribute
60
- begin
61
- Arel::Table.engine.connection.schema_cache.columns_hash(obj.relation.table_name)[obj.name.to_s].type
62
- rescue Exception
63
- :string
64
- end
62
+ ArelExtensions::column_of(obj.relation.table_name, obj.name.to_s)&.type || :string
65
63
  else
66
64
  :string
67
65
  end
@@ -117,7 +117,16 @@ module ArelExtensions
117
117
  if @date_type == :date
118
118
  v.to_i / (24*3600)
119
119
  elsif @date_type == :datetime
120
- v.to_i
120
+ if v.parts.size == 1
121
+ # first entry in the dict v.parts; one of [:years, :months, :weeks, :days, :hours, :minutes, :seconds]
122
+ # | the value
123
+ # | |
124
+ # | |
125
+ # v v
126
+ v.parts.first.second
127
+ else
128
+ v.to_i
129
+ end
121
130
  end
122
131
  else
123
132
  v
@@ -130,7 +139,17 @@ module ArelExtensions
130
139
  if @date_type == :date
131
140
  Arel.sql('day')
132
141
  elsif @date_type == :datetime
133
- Arel.sql('second')
142
+ res = if v.parts.size == 1
143
+ # first entry in the dict v.parts; one of [:years, :months, :weeks, :days, :hours, :minutes, :seconds]
144
+ # | the key
145
+ # | | convert symbol to string
146
+ # | | | remove the plural suffix `s`
147
+ # v v v v
148
+ v.parts.first.first.to_s[0..-2]
149
+ else
150
+ 'second'
151
+ end
152
+ Arel.sql(res)
134
153
  end
135
154
  else
136
155
  if ArelExtensions::Nodes::Duration === v
@@ -141,9 +160,9 @@ module ArelExtensions
141
160
  when 'h','mn','s'
142
161
  Arel.sql('second')
143
162
  when /i\z/
144
- Arel.sql(Arel::Visitors::MSSQL::DATE_MAPPING[v.left[0..-2]])
163
+ Arel.sql(ArelExtensions::Visitors::MSSQL::LOADED_VISITOR::DATE_MAPPING[v.left[0..-2]])
145
164
  else
146
- Arel.sql(Arel::Visitors::MSSQL::DATE_MAPPING[v.left])
165
+ Arel.sql(ArelExtensions::Visitors::MSSQL::LOADED_VISITOR::DATE_MAPPING[v.left])
147
166
  end
148
167
  end
149
168
  end
@@ -5,11 +5,12 @@ module ArelExtensions
5
5
  class Format < Function
6
6
  RETURN_TYPE = :string
7
7
 
8
- attr_accessor :col_type, :iso_format
8
+ attr_accessor :col_type, :iso_format, :time_zone
9
9
 
10
10
  def initialize expr
11
- col = expr.first
11
+ col = expr[0]
12
12
  @iso_format = convert_format(expr[1])
13
+ @time_zone = expr[2]
13
14
  @col_type = type_of_attribute(col)
14
15
  super [col, convert_to_string_node(@iso_format)]
15
16
  end
@@ -51,13 +51,7 @@ module ArelExtensions
51
51
  def type_of_attribute(att)
52
52
  case att
53
53
  when Arel::Attributes::Attribute
54
- begin
55
- if Arel::Table.engine.connection.tables.include? att.relation.table_name
56
- Arel::Table.engine.connection.schema_cache.columns_hash(att.relation.table_name)[att.name.to_s].type
57
- end
58
- rescue
59
- att
60
- end
54
+ ArelExtensions::column_of(att.relation.table_name, att.name.to_s)&.type || att
61
55
  when ArelExtensions::Nodes::Function
62
56
  att.return_type
63
57
  # else
@@ -1,3 +1,3 @@
1
1
  module ArelExtensions
2
- VERSION = "1.3.0".freeze
2
+ VERSION = "1.3.3".freeze
3
3
  end
@@ -2,27 +2,38 @@ 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
- '%d' => 'DD', '%e' => '', '%j' => '', '%w' => 'dw', '%A' => '', # day, weekday
12
- '%H' => 'hh', '%k' => '', '%I' => '', '%l' => '', '%P' => '', '%p' => '', # hours
17
+ '%V' => 'iso_week', '%G' => '', # ISO week number and year of week
18
+ '%d' => 'DD', '%e' => '', '%j' => '', '%w' => 'dw', '%A' => '', # day, weekday
19
+ '%H' => 'hh', '%k' => '', '%I' => '', '%l' => '', '%P' => '', '%p' => '', # hours
13
20
  '%M' => 'mi', '%S' => 'ss', '%L' => 'ms', '%N' => 'ns', '%z' => 'tz'
14
21
  }.freeze
15
22
 
16
- Arel::Visitors::MSSQL::DATE_FORMAT_REGEX =
23
+ LOADED_VISITOR::DATE_FORMAT_FORMAT = {
24
+ 'YY' => '0#', 'MM' => '0#', 'DD' => '0#', 'hh' => '0#', 'mi' => '0#', 'ss' => '0#', 'iso_week' => '0#'
25
+ }
26
+
27
+ LOADED_VISITOR::DATE_FORMAT_REGEX =
17
28
  Regexp.new(
18
- Arel::Visitors::MSSQL::DATE_FORMAT_DIRECTIVES
29
+ LOADED_VISITOR::DATE_FORMAT_DIRECTIVES
19
30
  .keys
20
31
  .map{|k| Regexp.escape(k)}
21
32
  .join('|')
22
33
  ).freeze
23
34
 
24
35
  # TODO; all others... http://www.sql-server-helper.com/tips/date-formats.aspx
25
- Arel::Visitors::MSSQL::DATE_CONVERT_FORMATS = {
36
+ LOADED_VISITOR::DATE_CONVERT_FORMATS = {
26
37
  'YYYY-MM-DD' => 120,
27
38
  'YY-MM-DD' => 120,
28
39
  'MM/DD/YYYY' => 101,
@@ -79,7 +90,7 @@ module ArelExtensions
79
90
  def visit_ArelExtensions_Nodes_Concat o, collector
80
91
  collector << "CONCAT("
81
92
  o.expressions.each_with_index { |arg, i|
82
- collector << Arel::Visitors::MSSQL::COMMA if i != 0
93
+ collector << LOADED_VISITOR::COMMA if i != 0
83
94
  collector = visit arg, collector
84
95
  }
85
96
  collector << ")"
@@ -102,23 +113,23 @@ module ArelExtensions
102
113
  case o.right_node_type
103
114
  when :ruby_date, :ruby_time, :date, :datetime, :time
104
115
  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
116
+ when :ruby_time, :datetime, :time then 'DATEDIFF(second'
117
+ else 'DATEDIFF(day'
118
+ end
119
+ collector << LOADED_VISITOR::COMMA
109
120
  collector = visit o.right, collector
110
- collector << Arel::Visitors::MSSQL::COMMA
121
+ collector << LOADED_VISITOR::COMMA
111
122
  collector = visit o.left, collector
112
123
  collector << ')'
113
124
  else
114
125
  da = ArelExtensions::Nodes::DateAdd.new([])
115
126
  collector << "DATEADD("
116
127
  collector = visit da.mssql_datepart(o.right), collector
117
- collector << Arel::Visitors::MSSQL::COMMA
128
+ collector << LOADED_VISITOR::COMMA
118
129
  collector << "-("
119
130
  collector = visit da.mssql_value(o.right), collector
120
131
  collector << ")"
121
- collector << Arel::Visitors::MSSQL::COMMA
132
+ collector << LOADED_VISITOR::COMMA
122
133
  collector = visit o.left, collector
123
134
  collector << ")"
124
135
  collector
@@ -129,9 +140,9 @@ module ArelExtensions
129
140
  def visit_ArelExtensions_Nodes_DateAdd o, collector
130
141
  collector << "DATEADD("
131
142
  collector = visit o.mssql_datepart(o.right), collector
132
- collector << Arel::Visitors::MSSQL::COMMA
143
+ collector << LOADED_VISITOR::COMMA
133
144
  collector = visit o.mssql_value(o.right), collector
134
- collector << Arel::Visitors::MSSQL::COMMA
145
+ collector << LOADED_VISITOR::COMMA
135
146
  collector = visit o.left, collector
136
147
  collector << ")"
137
148
  collector
@@ -144,8 +155,8 @@ module ArelExtensions
144
155
  left = o.left.end_with?('i') ? o.left[0..-2] : o.left
145
156
  conv = ['h', 'mn', 's'].include?(o.left)
146
157
  collector << 'DATEPART('
147
- collector << Arel::Visitors::MSSQL::DATE_MAPPING[left]
148
- collector << Arel::Visitors::MSSQL::COMMA
158
+ collector << LOADED_VISITOR::DATE_MAPPING[left]
159
+ collector << LOADED_VISITOR::COMMA
149
160
  collector << 'CONVERT(datetime,' if conv
150
161
  collector = visit o.right, collector
151
162
  collector << ')' if conv
@@ -155,20 +166,29 @@ module ArelExtensions
155
166
  end
156
167
 
157
168
  def visit_ArelExtensions_Nodes_Length o, collector
158
- collector << "#{o.bytewise ? 'DATALENGTH' : 'LEN'}("
159
- collector = visit o.expr, collector
160
- collector << ")"
161
- collector
169
+ if o.bytewise
170
+ collector << "(DATALENGTH("
171
+ collector = visit o.expr, collector
172
+ collector << ") / ISNULL(NULLIF(DATALENGTH(LEFT(COALESCE("
173
+ collector = visit o.expr, collector
174
+ collector << ", '#' ), 1 )), 0), 1))"
175
+ collector
176
+ else
177
+ collector << "LEN("
178
+ collector = visit o.expr, collector
179
+ collector << ")"
180
+ collector
181
+ end
162
182
  end
163
183
 
164
184
  def visit_ArelExtensions_Nodes_Round o, collector
165
185
  collector << "ROUND("
166
186
  o.expressions.each_with_index { |arg, i|
167
- collector << Arel::Visitors::MSSQL::COMMA if i != 0
187
+ collector << LOADED_VISITOR::COMMA if i != 0
168
188
  collector = visit arg, collector
169
189
  }
170
190
  if o.expressions.length == 1
171
- collector << Arel::Visitors::MSSQL::COMMA
191
+ collector << LOADED_VISITOR::COMMA
172
192
  collector << "0"
173
193
  end
174
194
  collector << ")"
@@ -178,7 +198,7 @@ module ArelExtensions
178
198
  def visit_ArelExtensions_Nodes_Locate o, collector
179
199
  collector << "CHARINDEX("
180
200
  collector = visit o.right, collector
181
- collector << Arel::Visitors::MSSQL::COMMA
201
+ collector << LOADED_VISITOR::COMMA
182
202
  collector = visit o.left, collector
183
203
  collector << ")"
184
204
  collector
@@ -187,28 +207,20 @@ module ArelExtensions
187
207
  def visit_ArelExtensions_Nodes_Substring o, collector
188
208
  collector << 'SUBSTRING('
189
209
  collector = visit o.expressions[0], collector
190
- collector << Arel::Visitors::MSSQL::COMMA
210
+ collector << LOADED_VISITOR::COMMA
191
211
  collector = visit o.expressions[1], collector
192
- collector << Arel::Visitors::MSSQL::COMMA
212
+ collector << LOADED_VISITOR::COMMA
193
213
  collector = o.expressions[2] ? visit(o.expressions[2], collector) : visit(o.expressions[0].length, collector)
194
214
  collector << ')'
195
215
  collector
196
216
  end
197
217
 
198
218
  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
219
+ collector << 'TRIM( '
220
+ collector = visit o.right, collector
221
+ collector << " FROM "
222
+ collector = visit o.left, collector
223
+ collector << ")"
212
224
  collector
213
225
  end
214
226
 
@@ -255,12 +267,28 @@ module ArelExtensions
255
267
  end
256
268
 
257
269
  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]
270
+ f = ArelExtensions::Visitors::strftime_to_format(o.iso_format, LOADED_VISITOR::DATE_FORMAT_DIRECTIVES)
271
+ if fmt = LOADED_VISITOR::DATE_CONVERT_FORMATS[f]
260
272
  collector << "CONVERT(VARCHAR(#{f.length})"
261
- collector << Arel::Visitors::MSSQL::COMMA
273
+ collector << LOADED_VISITOR::COMMA
274
+ if o.time_zone
275
+ collector << 'CONVERT(datetime'
276
+ collector << LOADED_VISITOR::COMMA
277
+ collector << ' '
278
+ end
262
279
  collector = visit o.left, collector
263
- collector << Arel::Visitors::MSSQL::COMMA
280
+ case o.time_zone
281
+ when Hash
282
+ src_tz, dst_tz = o.time_zone.first
283
+ collector << ') AT TIME ZONE '
284
+ collector = visit Arel::Nodes.build_quoted(src_tz), collector
285
+ collector << ' AT TIME ZONE '
286
+ collector = visit Arel::Nodes.build_quoted(dst_tz), collector
287
+ when String
288
+ collector << ') AT TIME ZONE '
289
+ collector = visit Arel::Nodes.build_quoted(o.time_zone), collector
290
+ end
291
+ collector << LOADED_VISITOR::COMMA
264
292
  collector << fmt.to_s
265
293
  collector << ')'
266
294
  collector
@@ -272,13 +300,38 @@ module ArelExtensions
272
300
  collector << sep
273
301
  sep = ' + '
274
302
  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('
303
+ when s.scan(LOADED_VISITOR::DATE_FORMAT_REGEX)
304
+ dir = LOADED_VISITOR::DATE_FORMAT_DIRECTIVES[s.matched]
305
+ fmt = LOADED_VISITOR::DATE_FORMAT_FORMAT[dir]
306
+ collector << 'TRIM('
307
+ collector << 'FORMAT(' if fmt
308
+ collector << 'STR(' if !fmt
309
+ collector << 'DATEPART('
278
310
  collector << dir
279
- collector << Arel::Visitors::MSSQL::COMMA
311
+ collector << LOADED_VISITOR::COMMA
312
+ if o.time_zone
313
+ collector << 'CONVERT(datetime'
314
+ collector << LOADED_VISITOR::COMMA
315
+ collector << ' '
316
+ end
280
317
  collector = visit o.left, collector
281
- collector << ')))'
318
+ case o.time_zone
319
+ when Hash
320
+ src_tz, dst_tz = o.time_zone.first.first, o.time_zone.first.second
321
+ collector << ") AT TIME ZONE "
322
+ collector = visit Arel::Nodes.build_quoted(src_tz), collector
323
+ collector << " AT TIME ZONE "
324
+ collector = visit Arel::Nodes.build_quoted(dst_tz), collector
325
+ when String
326
+ collector << ") AT TIME ZONE "
327
+ collector = visit Arel::Nodes.build_quoted(o.time_zone), collector
328
+ end
329
+ collector << ')'
330
+ collector << ')' if !fmt
331
+ collector << LOADED_VISITOR::COMMA << "'#{fmt}')" if fmt
332
+ collector << ')'
333
+ when s.scan(/^%%/)
334
+ collector = visit Arel::Nodes.build_quoted('%'), collector
282
335
  when s.scan(/[^%]+|./)
283
336
  collector = visit Arel::Nodes.build_quoted(s.matched), collector
284
337
  end
@@ -291,7 +344,7 @@ module ArelExtensions
291
344
  def visit_ArelExtensions_Nodes_Replace o, collector
292
345
  collector << "REPLACE("
293
346
  o.expressions.each_with_index { |arg, i|
294
- collector << Arel::Visitors::MSSQL::COMMA if i != 0
347
+ collector << LOADED_VISITOR::COMMA if i != 0
295
348
  collector = visit arg, collector
296
349
  }
297
350
  collector << ")"
@@ -301,7 +354,7 @@ module ArelExtensions
301
354
  def visit_ArelExtensions_Nodes_FindInSet o, collector
302
355
  collector << "dbo.FIND_IN_SET("
303
356
  o.expressions.each_with_index { |arg, i|
304
- collector << Arel::Visitors::MSSQL::COMMA if i != 0
357
+ collector << LOADED_VISITOR::COMMA if i != 0
305
358
  collector = visit arg, collector
306
359
  }
307
360
  collector << ")"
@@ -387,6 +440,27 @@ module ArelExtensions
387
440
  collector
388
441
  end
389
442
 
443
+ alias_method(:old_visit_Arel_Nodes_As, :visit_Arel_Nodes_As) rescue nil
444
+ def visit_Arel_Nodes_As o, collector
445
+ if o.left.is_a?(Arel::Nodes::Binary)
446
+ collector << '('
447
+ collector = visit o.left, collector
448
+ collector << ')'
449
+ else
450
+ collector = visit o.left, collector
451
+ end
452
+ collector << " AS "
453
+
454
+ # sometimes these values are already quoted, if they are, don't double quote it
455
+ quote = o.right.is_a?(Arel::Nodes::SqlLiteral) && o.right[0] != '"' && o.right[-1] != '"'
456
+
457
+ collector << '"' if quote
458
+ collector = visit o.right, collector
459
+ collector << '"' if quote
460
+
461
+ collector
462
+ end
463
+
390
464
  # SQL Server does not know about REGEXP
391
465
  def visit_Arel_Nodes_Regexp o, collector
392
466
  collector = visit o.left, collector
@@ -554,7 +628,6 @@ module ArelExtensions
554
628
  collector << ')'
555
629
  collector
556
630
  end
557
-
558
631
  end
559
632
  end
560
633
  end
@@ -8,6 +8,7 @@ module ArelExtensions
8
8
 
9
9
  DATE_FORMAT_DIRECTIVES = { # ISO C / POSIX
10
10
  '%Y' => '%Y', '%C' => '', '%y' => '%y', '%m' => '%m', '%B' => '%M', '%b' => '%b', '%^b' => '%b', # year, month
11
+ '%V' => '%v', '%G' => '%x', # ISO week number and year of week
11
12
  '%d' => '%d', '%e' => '%e', '%j' => '%j', '%w' => '%w', '%A' => '%W', # day, weekday
12
13
  '%H' => '%H', '%k' => '%k', '%I' => '%I', '%l' => '%l', '%P' => '%p', '%p' => '%p', # hours
13
14
  '%M' => '%i', '%S' => '%S', '%L' => '', '%N' => '%f', '%z' => ''
@@ -209,7 +210,21 @@ module ArelExtensions
209
210
  when :date, :datetime, :time
210
211
  fmt = ArelExtensions::Visitors::strftime_to_format(o.iso_format, DATE_FORMAT_DIRECTIVES)
211
212
  collector << "DATE_FORMAT("
213
+ collector << "CONVERT_TZ(" if o.time_zone
212
214
  collector = visit o.left, collector
215
+ case o.time_zone
216
+ when Hash
217
+ src_tz, dst_tz = o.time_zone.first
218
+ collector << COMMA
219
+ collector = visit Arel::Nodes.build_quoted(src_tz), collector
220
+ collector << COMMA
221
+ collector = visit Arel::Nodes.build_quoted(dst_tz), collector
222
+ collector << ')'
223
+ when String
224
+ collector << COMMA << "'UTC'" << COMMA
225
+ collector = visit Arel::Nodes.build_quoted(o.time_zone), collector
226
+ collector << ')'
227
+ end
213
228
  collector << COMMA
214
229
  collector = visit Arel::Nodes.build_quoted(fmt), collector
215
230
  collector << ")"
@@ -6,6 +6,7 @@ module ArelExtensions
6
6
  DATE_MAPPING = {'d' => 'DAY', 'm' => 'MONTH', 'w' => 'IW', 'y' => 'YEAR', 'wd' => 'D', 'h' => 'HOUR', 'mn' => 'MINUTE', 's' => 'SECOND'}
7
7
  DATE_FORMAT_DIRECTIVES = {
8
8
  '%Y' => 'YYYY', '%C' => 'CC', '%y' => 'YY', '%m' => 'MM', '%B' => 'Month', '%^B' => 'MONTH', '%b' => 'Mon', '%^b' => 'MON',
9
+ '%V' => 'IW', '%G' => 'IYYY', # ISO week number and year of week
9
10
  '%d' => 'DD', '%e' => 'FMDD', '%j' => 'DDD', '%w' => '', '%A' => 'Day', # day, weekday
10
11
  '%H' => 'HH24', '%k' => '', '%I' => 'HH', '%l' => '', '%P' => 'am', '%p' => 'AM', # hours
11
12
  '%M' => 'MI', '%S' => 'SS', '%L' => 'MS', '%N' => 'US', '%z' => 'tz' # seconds, subseconds
@@ -447,7 +448,19 @@ module ArelExtensions
447
448
  def visit_ArelExtensions_Nodes_Format o, collector
448
449
  fmt = ArelExtensions::Visitors::strftime_to_format(o.iso_format, DATE_FORMAT_DIRECTIVES)
449
450
  collector << "TO_CHAR("
451
+ collector << "CAST(" if o.time_zone
450
452
  collector = visit o.left, collector
453
+ case o.time_zone
454
+ when Hash
455
+ src_tz, dst_tz = o.time_zone.first
456
+ collector << ' as timestamp) at time zone '
457
+ collector = visit Arel::Nodes.build_quoted(src_tz), collector
458
+ collecto < ' at time zone '
459
+ collector = visit Arel::Nodes.build_quoted(dst_tz), collector
460
+ when String
461
+ collector << ' as timestamp) at time zone '
462
+ collector = visit Arel::Nodes.build_quoted(o.time_zone), collector
463
+ end
451
464
  collector << COMMA
452
465
  collector = visit Arel::Nodes.build_quoted(fmt), collector
453
466
  collector << ")"
@@ -517,7 +530,7 @@ module ArelExtensions
517
530
  if element.is_a?(Time)
518
531
  ArelExtensions::Nodes::Format.new [element, '%H:%M:%S']
519
532
  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]
533
+ col = ArelExtensions::column_of(element.relation.table_name, element.name.to_s)
521
534
  if col && (col.type == :time)
522
535
  ArelExtensions::Nodes::Format.new [element, '%H:%M:%S']
523
536
  else
@@ -9,9 +9,10 @@ module ArelExtensions
9
9
  DATE_FORMAT_DIRECTIVES = {
10
10
  '%Y' => 'YYYY', '%C' => 'CC', '%y' => 'YY',
11
11
  '%m' => 'MM', '%B' => 'Month', '%^B' => 'MONTH', '%b' => 'Mon', '%^b' => 'MON',
12
- '%d' => 'DD', '%e' => 'FMDD', '%j' => 'DDD', '%w' => '', '%A' => 'Day', # day, weekday
12
+ '%V' => 'IW', '%G' => 'IYYY', # ISO week number and year of week
13
+ '%d' => 'DD', '%e' => 'FMDD', '%j' => 'DDD', '%w' => '', '%A' => 'Day', # day, weekday
13
14
  '%H' => 'HH24', '%k' => '', '%I' => 'HH', '%l' => '', '%P' => 'am', '%p' => 'AM', # hours
14
- '%M' => 'MI', '%S' => 'SS', '%L' => 'MS', '%N' => 'US', '%z' => 'tz', # seconds, subseconds
15
+ '%M' => 'MI', '%S' => 'SS', '%L' => 'MS', '%N' => 'US', '%z' => 'tz', # seconds, subseconds
15
16
  '%%' => '%',
16
17
  }.freeze
17
18
 
@@ -90,11 +91,11 @@ module ArelExtensions
90
91
 
91
92
  # sometimes these values are already quoted, if they are, don't double quote it
92
93
  quote = o.right.is_a?(Arel::Nodes::SqlLiteral) && o.right[0] != '"' && o.right[-1] != '"'
93
-
94
+
94
95
  collector << '"' if quote
95
96
  collector = visit o.right, collector
96
97
  collector << '"' if quote
97
-
98
+
98
99
  collector
99
100
  end
100
101
 
@@ -175,7 +176,19 @@ module ArelExtensions
175
176
  def visit_ArelExtensions_Nodes_Format o, collector
176
177
  fmt = ArelExtensions::Visitors::strftime_to_format(o.iso_format, DATE_FORMAT_DIRECTIVES)
177
178
  collector << "TO_CHAR("
179
+ collector << '(' if o.time_zone
178
180
  collector = visit o.left, collector
181
+ case o.time_zone
182
+ when Hash
183
+ src_tz, dst_tz = o.time_zone.first
184
+ collector << ') AT TIME ZONE '
185
+ collector = visit Arel::Nodes.build_quoted(src_tz), collector
186
+ collector << ' AT TIME ZONE '
187
+ collector = visit Arel::Nodes.build_quoted(dst_tz), collector
188
+ when String
189
+ collector << ') AT TIME ZONE '
190
+ collector = visit Arel::Nodes.build_quoted(o.time_zone), collector
191
+ end
179
192
  collector << COMMA
180
193
  collector = visit Arel::Nodes.build_quoted(fmt), collector
181
194
  collector << ")"
@@ -289,9 +302,9 @@ module ArelExtensions
289
302
  return collector
290
303
  end
291
304
  end
292
- collector << "EXTRACT(#{DATE_MAPPING[o.left]} FROM "
305
+ collector << "EXTRACT(#{DATE_MAPPING[o.left]} FROM CAST("
293
306
  collector = visit o.right, collector
294
- collector << ")"
307
+ collector << " AS TIMESTAMP WITH TIME ZONE))"
295
308
  collector << " * (INTERVAL '1' #{interval})" if interval && o.with_interval
296
309
  collector
297
310
  end
@@ -374,7 +387,7 @@ module ArelExtensions
374
387
  when :number, :decimal, :float
375
388
  Arel::Nodes::SqlLiteral.new('numeric')
376
389
  when :datetime
377
- Arel::Nodes::SqlLiteral.new('timestamp')
390
+ Arel::Nodes::SqlLiteral.new('timestamp with time zone')
378
391
  when :date
379
392
  Arel::Nodes::SqlLiteral.new('date')
380
393
  when :binary