arel_extensions 1.3.0 → 1.3.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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