groupdate 5.2.2 → 6.0.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 05ffc10283f2de5fa74bacff7a6368636f62e8ddb263dd33d191502c0b418edc
4
- data.tar.gz: 887827ef96299b9916f97e554706e670e83c99dad2522755c760348bdd3077cf
3
+ metadata.gz: 12cfc48ae509ac20872022dd07429dd7bd8f24a5bfd81dcb16242f3519cb5385
4
+ data.tar.gz: 3079f700f956f141184fbc5bc8244ad90380804cea9f15fef51300985380d13e
5
5
  SHA512:
6
- metadata.gz: 5d8cdf01f590ff7575539e39e9207e14eac7bd04417483fe453a7f1fb281e9bf9869678fbb7e2cc2381ccdfb678f47c6ca82b644ea291149ed48fdfba61963a3
7
- data.tar.gz: 190dd79d14e7b00a7aa5492a46cfa18d50b56af08bd489fc8e68560530e0edb91a124932a24444116dd0cda09c926834c9ac33ed2b4b8c5117601e24b2ee0eac
6
+ metadata.gz: 64b4e317731bc0032a914e4f07ca751ee276e41b7a30a2113bff9d9f2437196916c68e5f1f699c2e107861d0022b712b32315132cd0f530bb7caad5f41955e45
7
+ data.tar.gz: ea085f2e6985f4df40f591333935b88f8c59b2be87943f39241254dbbc8ac18cc1aaa2b54030880a804117ecdc0451a65463b07ea758d3fc93d99697a11a5711
data/CHANGELOG.md CHANGED
@@ -1,3 +1,24 @@
1
+ ## 6.0.1 (2022-01-16)
2
+
3
+ - Fixed incorrect results (error before 6.0) with `includes` with Active Record 6.1+
4
+
5
+ ## 6.0.0 (2022-01-15)
6
+
7
+ - Raise `ActiveRecord::UnknownAttributeReference` for non-attribute arguments
8
+ - Raise `ArgumentError` for ranges with string bounds
9
+ - Added `n` option for Redshift
10
+ - Changed SQL to return dates instead of times for day, week, month, quarter, and year
11
+ - Removed `dates` option
12
+ - Dropped support for Ruby < 2.6 and Rails < 5.2
13
+
14
+ ## 5.2.4 (2021-12-15)
15
+
16
+ - Simplified queries for Active Record 7 and MySQL
17
+
18
+ ## 5.2.3 (2021-12-06)
19
+
20
+ - Fixed error and warnings with Active Record 7
21
+
1
22
  ## 5.2.2 (2021-02-08)
2
23
 
3
24
  - Added support for `nil..nil` ranges in `range` option
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2013-2021 Andrew Kane
1
+ Copyright (c) 2013-2022 Andrew Kane
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -22,7 +22,7 @@ Supports PostgreSQL, MySQL, and Redshift, plus arrays and hashes (and limited su
22
22
  Add this line to your application’s Gemfile:
23
23
 
24
24
  ```ruby
25
- gem 'groupdate'
25
+ gem "groupdate"
26
26
  ```
27
27
 
28
28
  For MySQL and SQLite, also follow [these instructions](#additional-instructions).
@@ -210,17 +210,12 @@ If grouping on date columns which don’t need time zone conversion, use:
210
210
  User.group_by_week(:created_on, time_zone: false).count
211
211
  ```
212
212
 
213
- ### User Input
213
+ ### Default Scopes
214
214
 
215
- If passing user input as the column, be sure to sanitize it first [like you must](https://rails-sqli.org/) with `group`.
215
+ If you use Postgres and have a default scope that uses `order`, you may get a `column must appear in the GROUP BY clause` error (just like with Active Record’s `group` method). Remove the `order` scope with:
216
216
 
217
217
  ```ruby
218
- column = params[:column]
219
-
220
- # check against permitted columns
221
- raise "Unpermitted column" unless ["column_a", "column_b"].include?(column)
222
-
223
- User.group_by_day(column).count
218
+ User.unscope(:order).group_by_day(:count).count
224
219
  ```
225
220
 
226
221
  ## Arrays and Hashes
@@ -244,7 +239,7 @@ users.group_by_day(series: true) { |u| u.created_at }
244
239
  Count
245
240
 
246
241
  ```ruby
247
- users.group_by_day { |u| u.created_at }.map { |k, v| [k, v.count] }.to_h
242
+ users.group_by_day { |u| u.created_at }.to_h { |k, v| [k, v.count] }
248
243
  ```
249
244
 
250
245
  ## Additional Instructions
@@ -257,8 +252,6 @@ users.group_by_day { |u| u.created_at }.map { |k, v| [k, v.count] }.to_h
257
252
  mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root mysql
258
253
  ```
259
254
 
260
- or copy and paste [these statements](https://gist.githubusercontent.com/ankane/1d6b0022173186accbf0/raw/time_zone_support.sql) into a SQL console.
261
-
262
255
  You can confirm it worked with:
263
256
 
264
257
  ```sql
@@ -283,13 +276,15 @@ Groupdate.time_zone = false
283
276
 
284
277
  ## Upgrading
285
278
 
286
- ### 5.0
279
+ ### 6.0
287
280
 
288
- Groupdate 5.0 brings a number of improvements. Here are a few to be aware of:
281
+ Groupdate 6.0 protects against unsafe input by default. For non-attribute arguments, use:
282
+
283
+ ```ruby
284
+ User.group_by_day(Arel.sql(known_safe_value)).count
285
+ ```
289
286
 
290
- - The `week_start` option is now supported for SQLite
291
- - The `day_start` option is now consistent between Active Record and enumerable
292
- - Deprecated positional arguments for time zone and range have been removed
287
+ Also, the `dates` option has been removed.
293
288
 
294
289
  ## History
295
290
 
@@ -4,11 +4,8 @@ module Groupdate
4
4
  attr_reader :period, :column, :day_start, :week_start, :n_seconds
5
5
 
6
6
  def initialize(relation, column:, period:, time_zone:, time_range:, week_start:, day_start:, n_seconds:)
7
- # very important
8
- column = validate_column(column)
9
-
10
7
  @relation = relation
11
- @column = resolve_column(relation, column)
8
+ @column = column
12
9
  @period = period
13
10
  @time_zone = time_zone
14
11
  @time_range = time_range
@@ -16,8 +13,14 @@ module Groupdate
16
13
  @day_start = day_start
17
14
  @n_seconds = n_seconds
18
15
 
19
- if relation.default_timezone == :local
20
- raise Groupdate::Error, "ActiveRecord::Base.default_timezone must be :utc to use Groupdate"
16
+ if ActiveRecord::VERSION::MAJOR >= 7
17
+ if ActiveRecord.default_timezone == :local
18
+ raise Groupdate::Error, "ActiveRecord.default_timezone must be :utc to use Groupdate"
19
+ end
20
+ else
21
+ if relation.default_timezone == :local
22
+ raise Groupdate::Error, "ActiveRecord::Base.default_timezone must be :utc to use Groupdate"
23
+ end
21
24
  end
22
25
  end
23
26
 
@@ -43,28 +46,6 @@ module Groupdate
43
46
  ["#{column} IS NOT NULL"]
44
47
  end
45
48
  end
46
-
47
- # basic version of Active Record disallow_raw_sql!
48
- # symbol = column (safe), Arel node = SQL (safe), other = untrusted
49
- # matches table.column and column
50
- def validate_column(column)
51
- unless column.is_a?(Symbol) || column.is_a?(Arel::Nodes::SqlLiteral)
52
- column = column.to_s
53
- unless /\A\w+(\.\w+)?\z/i.match(column)
54
- warn "[groupdate] Non-attribute argument: #{column}. Use Arel.sql() for known-safe values. This will raise an error in Groupdate 6"
55
- end
56
- end
57
- column
58
- end
59
-
60
- # resolves eagerly
61
- # need to convert both where_clause (easy)
62
- # and group_clause (not easy) if want to avoid this
63
- def resolve_column(relation, column)
64
- node = relation.send(:relation).send(:arel_columns, [column]).first
65
- node = Arel::Nodes::SqlLiteral.new(node) if node.is_a?(String)
66
- relation.connection.visitor.accept(node, Arel::Collectors::SQLString.new).value
67
- end
68
49
  end
69
50
  end
70
51
  end
@@ -20,9 +20,21 @@ module Groupdate
20
20
  when :month_of_year
21
21
  ["MONTH(#{day_start_column})", time_zone, day_start]
22
22
  when :week
23
- ["CONVERT_TZ(DATE_FORMAT(#{day_start_column} - INTERVAL ((? + DAYOFWEEK(#{day_start_column})) % 7) DAY, '%Y-%m-%d 00:00:00') + INTERVAL ? second, ?, '+00:00')", time_zone, day_start, 12 - week_start, time_zone, day_start, day_start, time_zone]
23
+ ["CAST(DATE_FORMAT(#{day_start_column} - INTERVAL ((? + DAYOFWEEK(#{day_start_column})) % 7) DAY, '%Y-%m-%d') AS DATE)", time_zone, day_start, 12 - week_start, time_zone, day_start]
24
24
  when :quarter
25
- ["CONVERT_TZ(DATE_FORMAT(DATE(CONCAT(YEAR(#{day_start_column}), '-', LPAD(1 + 3 * (QUARTER(#{day_start_column}) - 1), 2, '00'), '-01')), '%Y-%m-%d %H:%i:%S') + INTERVAL ? second, ?, '+00:00')", time_zone, day_start, time_zone, day_start, day_start, time_zone]
25
+ ["CAST(CONCAT(YEAR(#{day_start_column}), '-', LPAD(1 + 3 * (QUARTER(#{day_start_column}) - 1), 2, '00'), '-01') AS DATE)", time_zone, day_start, time_zone, day_start]
26
+ when :day, :month, :year
27
+ format =
28
+ case period
29
+ when :day
30
+ "%Y-%m-%d"
31
+ when :month
32
+ "%Y-%m-01"
33
+ else # year
34
+ "%Y-01-01"
35
+ end
36
+
37
+ ["CAST(DATE_FORMAT(#{day_start_column}, ?) AS DATE)", time_zone, day_start, format]
26
38
  when :custom
27
39
  ["FROM_UNIXTIME((UNIX_TIMESTAMP(#{column}) DIV ?) * ?)", n_seconds, n_seconds]
28
40
  else
@@ -32,14 +44,8 @@ module Groupdate
32
44
  "%Y-%m-%d %H:%i:%S"
33
45
  when :minute
34
46
  "%Y-%m-%d %H:%i:00"
35
- when :hour
47
+ else # hour
36
48
  "%Y-%m-%d %H:00:00"
37
- when :day
38
- "%Y-%m-%d 00:00:00"
39
- when :month
40
- "%Y-%m-01 00:00:00"
41
- else # year
42
- "%Y-01-01 00:00:00"
43
49
  end
44
50
 
45
51
  ["CONVERT_TZ(DATE_FORMAT(#{day_start_column}, ?) + INTERVAL ? second, ?, '+00:00')", time_zone, day_start, format, day_start, time_zone]
@@ -49,7 +55,8 @@ module Groupdate
49
55
  end
50
56
 
51
57
  def clean_group_clause(clause)
52
- clause.gsub(/ (\-|\+) INTERVAL 0 second/, "")
58
+ # zero quoted in Active Record 7+
59
+ clause.gsub(/ (\-|\+) INTERVAL 0 second/, "").gsub(/ (\-|\+) INTERVAL '0' second/, "")
53
60
  end
54
61
  end
55
62
  end
@@ -21,16 +21,18 @@ module Groupdate
21
21
  when :month_of_year
22
22
  ["EXTRACT(MONTH FROM #{day_start_column})::integer", time_zone, day_start_interval]
23
23
  when :week
24
- ["(DATE_TRUNC('day', #{day_start_column} - INTERVAL '1 day' * ((? + EXTRACT(DOW FROM #{day_start_column})::integer) % 7)) + INTERVAL ?) AT TIME ZONE ?", time_zone, day_start_interval, 13 - week_start, time_zone, day_start_interval, day_start_interval, time_zone]
24
+ ["(DATE_TRUNC('day', #{day_start_column} - INTERVAL '1 day' * ((? + EXTRACT(DOW FROM #{day_start_column})::integer) % 7)) + INTERVAL ?)::date", time_zone, day_start_interval, 13 - week_start, time_zone, day_start_interval, day_start_interval]
25
25
  when :custom
26
- ["TO_TIMESTAMP(FLOOR(EXTRACT(EPOCH FROM #{column}::timestamptz) / ?) * ?)", n_seconds, n_seconds]
27
- else
28
- if day_start == 0
29
- # prettier
30
- ["DATE_TRUNC(?, #{day_start_column}) AT TIME ZONE ?", period, time_zone, day_start_interval, time_zone]
26
+ if @relation.connection.adapter_name == "Redshift"
27
+ ["TIMESTAMP 'epoch' + (FLOOR(EXTRACT(EPOCH FROM #{column}::timestamp) / ?) * ?) * INTERVAL '1 second'", n_seconds, n_seconds]
31
28
  else
32
- ["(DATE_TRUNC(?, #{day_start_column}) + INTERVAL ?) AT TIME ZONE ?", period, time_zone, day_start_interval, day_start_interval, time_zone]
29
+ ["TO_TIMESTAMP(FLOOR(EXTRACT(EPOCH FROM #{column}::timestamptz) / ?) * ?)", n_seconds, n_seconds]
33
30
  end
31
+ when :day, :month, :quarter, :year
32
+ ["DATE_TRUNC(?, #{day_start_column})::date", period, time_zone, day_start_interval]
33
+ else
34
+ # day start is always 0 for seconds, minute, hour
35
+ ["DATE_TRUNC(?, #{day_start_column}) AT TIME ZONE ?", period, time_zone, day_start_interval, time_zone]
34
36
  end
35
37
 
36
38
  clean_group_clause(@relation.send(:sanitize_sql_array, query))
@@ -7,7 +7,7 @@ module Groupdate
7
7
 
8
8
  query =
9
9
  if period == :week
10
- ["strftime('%Y-%m-%d 00:00:00 UTC', #{column}, '-6 days', ?)", "weekday #{(week_start + 1) % 7}"]
10
+ ["strftime('%Y-%m-%d', #{column}, '-6 days', ?)", "weekday #{(week_start + 1) % 7}"]
11
11
  elsif period == :custom
12
12
  ["datetime((strftime('%s', #{column}) / ?) * ?, 'unixepoch')", n_seconds, n_seconds]
13
13
  else
@@ -32,13 +32,13 @@ module Groupdate
32
32
  when :hour
33
33
  "%Y-%m-%d %H:00:00 UTC"
34
34
  when :day
35
- "%Y-%m-%d 00:00:00 UTC"
35
+ "%Y-%m-%d"
36
36
  when :month
37
- "%Y-%m-01 00:00:00 UTC"
37
+ "%Y-%m-01"
38
38
  when :quarter
39
39
  raise Groupdate::Error, "Quarter not supported for SQLite"
40
40
  else # year
41
- "%Y-01-01 00:00:00 UTC"
41
+ "%Y-01-01"
42
42
  end
43
43
 
44
44
  ["strftime(?, #{column})", format]
@@ -22,7 +22,7 @@ module Groupdate
22
22
  end
23
23
 
24
24
  def validate_keywords
25
- known_keywords = [:time_zone, :dates, :series, :format, :locale, :range, :reverse]
25
+ known_keywords = [:time_zone, :series, :format, :locale, :range, :reverse]
26
26
 
27
27
  if %i[week day_of_week].include?(period)
28
28
  known_keywords << :week_start
@@ -145,6 +145,16 @@ module Groupdate
145
145
  lambda { |k| k.to_i }
146
146
  when :day_of_week
147
147
  lambda { |k| (k.to_i - 1 - week_start) % 7 }
148
+ when :day, :week, :month, :quarter, :year
149
+ # TODO keep as date
150
+ if day_start != 0
151
+ day_start_hour = day_start / 3600
152
+ day_start_min = (day_start % 3600) / 60
153
+ day_start_sec = (day_start % 3600) % 60
154
+ lambda { |k| k.in_time_zone(time_zone).change(hour: day_start_hour, min: day_start_min, sec: day_start_sec) }
155
+ else
156
+ lambda { |k| k.in_time_zone(time_zone) }
157
+ end
148
158
  else
149
159
  utc = ActiveSupport::TimeZone["UTC"]
150
160
  lambda { |k| (k.is_a?(String) || !k.respond_to?(:to_time) ? utc.parse(k.to_s) : k.to_time).in_time_zone(time_zone) }
@@ -193,11 +203,15 @@ module Groupdate
193
203
  adapter = Groupdate.adapters[adapter_name]
194
204
  raise Groupdate::Error, "Connection adapter not supported: #{adapter_name}" unless adapter
195
205
 
206
+ # very important
207
+ column = validate_column(field)
208
+ column = resolve_column(relation, column)
209
+
196
210
  # generate ActiveRecord relation
197
211
  relation =
198
212
  adapter.new(
199
213
  relation,
200
- column: field,
214
+ column: column,
201
215
  period: magic.period,
202
216
  time_zone: magic.time_zone,
203
217
  time_range: magic.time_range,
@@ -213,6 +227,30 @@ module Groupdate
213
227
  relation
214
228
  end
215
229
 
230
+ class << self
231
+ # basic version of Active Record disallow_raw_sql!
232
+ # symbol = column (safe), Arel node = SQL (safe), other = untrusted
233
+ # matches table.column and column
234
+ def validate_column(column)
235
+ unless column.is_a?(Symbol) || column.is_a?(Arel::Nodes::SqlLiteral)
236
+ column = column.to_s
237
+ unless /\A\w+(\.\w+)?\z/i.match(column)
238
+ raise ActiveRecord::UnknownAttributeReference, "Query method called with non-attribute argument(s): #{column.inspect}. Use Arel.sql() for known-safe values."
239
+ end
240
+ end
241
+ column
242
+ end
243
+
244
+ # resolves eagerly
245
+ # need to convert both where_clause (easy)
246
+ # and group_clause (not easy) if want to avoid this
247
+ def resolve_column(relation, column)
248
+ node = relation.send(:relation).send(:arel_columns, [column]).first
249
+ node = Arel::Nodes::SqlLiteral.new(node) if node.is_a?(String)
250
+ relation.connection.visitor.accept(node, Arel::Collectors::SQLString.new).value
251
+ end
252
+ end
253
+
216
254
  # allow any options to keep flexible for future
217
255
  def self.process_result(relation, result, **options)
218
256
  relation.groupdate_values.reverse.each do |gv|
@@ -9,6 +9,9 @@ module Groupdate
9
9
  end
10
10
 
11
11
  def calculate(*args, &block)
12
+ # prevent calculate from being called twice
13
+ return super if ActiveRecord::VERSION::STRING.to_f >= 6.1 && has_include?(args[1])
14
+
12
15
  default_value = [:count, :sum].include?(args[0]) ? 0 : nil
13
16
  Groupdate.process_result(self, super, default_value: default_value)
14
17
  end
@@ -2,8 +2,6 @@ module Groupdate
2
2
  class SeriesBuilder
3
3
  attr_reader :period, :time_zone, :day_start, :week_start, :n_seconds, :options
4
4
 
5
- CHECK_PERIODS = [:day, :week, :month, :quarter, :year]
6
-
7
5
  def initialize(period:, time_zone:, day_start:, week_start:, n_seconds:, **options)
8
6
  @period = period
9
7
  @time_zone = time_zone
@@ -23,41 +21,12 @@ module Groupdate
23
21
  verified_data[k] = data.delete(k)
24
22
  end
25
23
 
26
- # this is a fun one
27
- # PostgreSQL and Ruby both return the 2nd hour when converting/parsing a backward DST change
28
- # Other databases and Active Support return the 1st hour (as expected)
29
- # Active Support good: ActiveSupport::TimeZone["America/Los_Angeles"].parse("2013-11-03 01:00:00")
30
- # MySQL good: SELECT CONVERT_TZ('2013-11-03 01:00:00', 'America/Los_Angeles', 'Etc/UTC');
31
- # Ruby not good: Time.parse("2013-11-03 01:00:00")
32
- # PostgreSQL not good: SELECT '2013-11-03 01:00:00'::timestamp AT TIME ZONE 'America/Los_Angeles';
33
- # we need to account for this here
34
- if series_default && CHECK_PERIODS.include?(period)
35
- data.each do |k, v|
36
- key = multiple_groups ? k[group_index] : k
37
- # TODO only do this for PostgreSQL
38
- # this may mask some inconsistent time zone errors
39
- # but not sure there's a better approach
40
- if key.hour == (key - 1.hour).hour && series.include?(key - 1.hour)
41
- key -= 1.hour
42
- if multiple_groups
43
- k[group_index] = key
44
- else
45
- k = key
46
- end
47
- verified_data[k] = v
48
- elsif key != round_time(key)
49
- # only need to show what database returned since it will cast in Ruby time zone
50
- raise Groupdate::Error, "Database and Ruby have inconsistent time zone info. Database returned #{key}"
51
- end
52
- end
53
- end
54
-
55
24
  unless entire_series?(series_default)
56
25
  series = series.select { |k| verified_data[k] }
57
26
  end
58
27
 
59
28
  value = 0
60
- result = Hash[series.map do |k|
29
+ result = series.to_h do |k|
61
30
  value = verified_data[k] || (@options[:carry_forward] && value) || default_value
62
31
  key =
63
32
  if multiple_groups
@@ -67,7 +36,7 @@ module Groupdate
67
36
  end
68
37
 
69
38
  [key, value]
70
- end]
39
+ end
71
40
 
72
41
  result
73
42
  end
@@ -81,7 +50,7 @@ module Groupdate
81
50
 
82
51
  if day_start != 0
83
52
  # apply day_start to a time object that's not affected by DST
84
- time = change_zone.call(time, utc)
53
+ time = time.change(zone: utc)
85
54
  time -= day_start.seconds
86
55
  end
87
56
 
@@ -121,23 +90,12 @@ module Groupdate
121
90
 
122
91
  if day_start != 0 && time.is_a?(Time)
123
92
  time += day_start.seconds
124
- time = change_zone.call(time, time_zone)
93
+ time = time.change(zone: time_zone)
125
94
  end
126
95
 
127
96
  time
128
97
  end
129
98
 
130
- def change_zone
131
- @change_zone ||= begin
132
- if ActiveSupport::VERSION::STRING >= "5.2"
133
- ->(time, zone) { time.change(zone: zone) }
134
- else
135
- # TODO make more efficient
136
- ->(time, zone) { zone.parse(time.strftime("%Y-%m-%d %H:%M:%S")) }
137
- end
138
- end
139
- end
140
-
141
99
  def time_range
142
100
  @time_range ||= begin
143
101
  time_range = options[:range]
@@ -148,10 +106,6 @@ module Groupdate
148
106
  case v
149
107
  when nil, Date, Time
150
108
  # good
151
- when String
152
- # TODO raise error in Groupdate 6
153
- warn "[groupdate] Range bounds should be Date or Time, not #{v.class.name}. This will raise an error in Groupdate 6"
154
- break
155
109
  else
156
110
  raise ArgumentError, "Range bounds should be Date or Time, not #{v.class.name}"
157
111
  end
@@ -289,7 +243,6 @@ module Groupdate
289
243
  def key_format
290
244
  @key_format ||= begin
291
245
  locale = options[:locale] || I18n.locale
292
- use_dates = options.key?(:dates) ? options[:dates] : Groupdate.dates
293
246
 
294
247
  if options[:format]
295
248
  if options[:format].respond_to?(:call)
@@ -312,7 +265,7 @@ module Groupdate
312
265
  I18n.localize(key, format: options[:format], locale: locale)
313
266
  end
314
267
  end
315
- elsif [:day, :week, :month, :quarter, :year].include?(period) && use_dates
268
+ elsif [:day, :week, :month, :quarter, :year].include?(period)
316
269
  lambda { |k| k.to_date }
317
270
  else
318
271
  lambda { |k| k }
@@ -1,3 +1,3 @@
1
1
  module Groupdate
2
- VERSION = "5.2.2"
2
+ VERSION = "6.0.1"
3
3
  end
data/lib/groupdate.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # dependencies
2
+ require "active_support"
2
3
  require "active_support/core_ext/module/attribute_accessors"
3
4
  require "active_support/time"
4
5
 
@@ -11,7 +12,6 @@ require "groupdate/version"
11
12
  require "groupdate/adapters/base_adapter"
12
13
  require "groupdate/adapters/mysql_adapter"
13
14
  require "groupdate/adapters/postgresql_adapter"
14
- require "groupdate/adapters/redshift_adapter"
15
15
  require "groupdate/adapters/sqlite_adapter"
16
16
 
17
17
  module Groupdate
@@ -20,10 +20,9 @@ module Groupdate
20
20
  PERIODS = [:second, :minute, :hour, :day, :week, :month, :quarter, :year, :day_of_week, :hour_of_day, :minute_of_hour, :day_of_month, :day_of_year, :month_of_year]
21
21
  METHODS = PERIODS.map { |v| :"group_by_#{v}" } + [:group_by_period]
22
22
 
23
- mattr_accessor :week_start, :day_start, :time_zone, :dates
23
+ mattr_accessor :week_start, :day_start, :time_zone
24
24
  self.week_start = :sunday
25
25
  self.day_start = 0
26
- self.dates = true
27
26
 
28
27
  # api for gems like ActiveMedian
29
28
  def self.process_result(relation, result, **options)
@@ -45,8 +44,7 @@ module Groupdate
45
44
  end
46
45
 
47
46
  Groupdate.register_adapter ["Mysql2", "Mysql2Spatial", "Mysql2Rgeo"], Groupdate::Adapters::MySQLAdapter
48
- Groupdate.register_adapter ["PostgreSQL", "PostGIS"], Groupdate::Adapters::PostgreSQLAdapter
49
- Groupdate.register_adapter "Redshift", Groupdate::Adapters::RedshiftAdapter
47
+ Groupdate.register_adapter ["PostgreSQL", "PostGIS", "Redshift"], Groupdate::Adapters::PostgreSQLAdapter
50
48
  Groupdate.register_adapter "SQLite", Groupdate::Adapters::SQLiteAdapter
51
49
 
52
50
  require "groupdate/enumerable"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: groupdate
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.2.2
4
+ version: 6.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-02-08 00:00:00.000000000 Z
11
+ date: 2022-01-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '5'
19
+ version: '5.2'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '5'
26
+ version: '5.2'
27
27
  description:
28
28
  email: andrew@ankane.org
29
29
  executables: []
@@ -39,7 +39,6 @@ files:
39
39
  - lib/groupdate/adapters/base_adapter.rb
40
40
  - lib/groupdate/adapters/mysql_adapter.rb
41
41
  - lib/groupdate/adapters/postgresql_adapter.rb
42
- - lib/groupdate/adapters/redshift_adapter.rb
43
42
  - lib/groupdate/adapters/sqlite_adapter.rb
44
43
  - lib/groupdate/enumerable.rb
45
44
  - lib/groupdate/magic.rb
@@ -59,14 +58,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
59
58
  requirements:
60
59
  - - ">="
61
60
  - !ruby/object:Gem::Version
62
- version: '2.4'
61
+ version: '2.6'
63
62
  required_rubygems_version: !ruby/object:Gem::Requirement
64
63
  requirements:
65
64
  - - ">="
66
65
  - !ruby/object:Gem::Version
67
66
  version: '0'
68
67
  requirements: []
69
- rubygems_version: 3.2.3
68
+ rubygems_version: 3.2.32
70
69
  signing_key:
71
70
  specification_version: 4
72
71
  summary: The simplest way to group temporal data
@@ -1,39 +0,0 @@
1
- module Groupdate
2
- module Adapters
3
- class RedshiftAdapter < BaseAdapter
4
- def group_clause
5
- time_zone = @time_zone.tzinfo.name
6
- day_start_column = "CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL ?"
7
- day_start_interval = "#{day_start} second"
8
-
9
- query =
10
- case period
11
- when :minute_of_hour
12
- ["EXTRACT(MINUTE from #{day_start_column})::integer", time_zone, day_start_interval]
13
- when :hour_of_day
14
- ["EXTRACT(HOUR from #{day_start_column})::integer", time_zone, day_start_interval]
15
- when :day_of_week
16
- ["EXTRACT(DOW from #{day_start_column})::integer", time_zone, day_start_interval]
17
- when :day_of_month
18
- ["EXTRACT(DAY from #{day_start_column})::integer", time_zone, day_start_interval]
19
- when :day_of_year
20
- ["EXTRACT(DOY from #{day_start_column})::integer", time_zone, day_start_interval]
21
- when :month_of_year
22
- ["EXTRACT(MONTH from #{day_start_column})::integer", time_zone, day_start_interval]
23
- when :week # start on Sunday, not Redshift default Monday
24
- # Redshift does not return timezone information; it
25
- # always says it is in UTC time, so we must convert
26
- # back to UTC to play properly with the rest of Groupdate.
27
- week_start_interval = "#{week_start} day"
28
- ["CONVERT_TIMEZONE(?, 'Etc/UTC', DATE_TRUNC('week', #{day_start_column} - INTERVAL ?) + INTERVAL ? + INTERVAL ?)::timestamp", time_zone, time_zone, day_start_interval, week_start_interval, week_start_interval, day_start_interval]
29
- when :custom
30
- raise Groupdate::Error, "Not implemented yet"
31
- else
32
- ["CONVERT_TIMEZONE(?, 'Etc/UTC', DATE_TRUNC(?, #{day_start_column}) + INTERVAL ?)::timestamp", time_zone, period, time_zone, day_start_interval, day_start_interval]
33
- end
34
-
35
- @relation.send(:sanitize_sql_array, query)
36
- end
37
- end
38
- end
39
- end