groupdate 5.2.4 → 6.0.0
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/CHANGELOG.md +9 -0
- data/LICENSE.txt +1 -1
- data/README.md +9 -20
- data/lib/groupdate/adapters/base_adapter.rb +1 -26
- data/lib/groupdate/adapters/mysql_adapter.rb +15 -9
- data/lib/groupdate/adapters/postgresql_adapter.rb +9 -7
- data/lib/groupdate/adapters/sqlite_adapter.rb +4 -4
- data/lib/groupdate/magic.rb +39 -2
- data/lib/groupdate/series_builder.rb +5 -52
- data/lib/groupdate/version.rb +1 -1
- data/lib/groupdate.rb +2 -5
- metadata +6 -7
- data/lib/groupdate/adapters/redshift_adapter.rb +0 -39
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f0169fac01ba6b7b7a051ae37d2cc3d5b89b137b7bb3d609df7a633b534ac89b
|
4
|
+
data.tar.gz: ccdf270ed3726d0c0a52e9fafe0fb3b2281f6b31ac0a9a731eaedfbda8f42067
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aa3150d5c5edd803e225045d745faf191fef78f483fec13aac05f4fb7dc0ee04a1b7a9340f1f8cf35a8d1c79ed09a6e6e6c4432e094d54faf5c7af48385c5e68
|
7
|
+
data.tar.gz: 97137d089ec745cdbcf08d1c7999f3efefab9c8cb3dd39e4e033f28184b8e4d4955cb4f7777cd7e8f8d28a3dda8b3dce2826242322de1949c2f72ad11f24a32d
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
## 6.0.0 (2022-01-15)
|
2
|
+
|
3
|
+
- Changed SQL to return dates instead of times for day, week, month, quarter, and year
|
4
|
+
- Added `n` option for Redshift
|
5
|
+
- Removed `dates` option
|
6
|
+
- Raise `ActiveRecord::UnknownAttributeReference` for non-attribute arguments
|
7
|
+
- Raise `ArgumentError` for ranges with string bounds
|
8
|
+
- Dropped support for Ruby < 2.6 and Rails < 5.2
|
9
|
+
|
1
10
|
## 5.2.4 (2021-12-15)
|
2
11
|
|
3
12
|
- Simplified queries for Active Record 7 and MySQL
|
data/LICENSE.txt
CHANGED
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
|
25
|
+
gem "groupdate"
|
26
26
|
```
|
27
27
|
|
28
28
|
For MySQL and SQLite, also follow [these instructions](#additional-instructions).
|
@@ -210,19 +210,6 @@ 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
|
214
|
-
|
215
|
-
If passing user input as the column, be sure to sanitize it first [like you must](https://rails-sqli.org/) with `group`.
|
216
|
-
|
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
|
224
|
-
```
|
225
|
-
|
226
213
|
### Default Scopes
|
227
214
|
|
228
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:
|
@@ -252,7 +239,7 @@ users.group_by_day(series: true) { |u| u.created_at }
|
|
252
239
|
Count
|
253
240
|
|
254
241
|
```ruby
|
255
|
-
users.group_by_day { |u| u.created_at }.
|
242
|
+
users.group_by_day { |u| u.created_at }.to_h { |k, v| [k, v.count] }
|
256
243
|
```
|
257
244
|
|
258
245
|
## Additional Instructions
|
@@ -289,13 +276,15 @@ Groupdate.time_zone = false
|
|
289
276
|
|
290
277
|
## Upgrading
|
291
278
|
|
292
|
-
###
|
279
|
+
### 6.0
|
280
|
+
|
281
|
+
Groupdate 6.0 protects against unsafe input by default. For non-attribute arguments, use:
|
293
282
|
|
294
|
-
|
283
|
+
```ruby
|
284
|
+
User.group_by_day(Arel.sql(known_safe_value)).count
|
285
|
+
```
|
295
286
|
|
296
|
-
|
297
|
-
- The `day_start` option is now consistent between Active Record and enumerable
|
298
|
-
- Deprecated positional arguments for time zone and range have been removed
|
287
|
+
Also, the `dates` option has been removed.
|
299
288
|
|
300
289
|
## History
|
301
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 =
|
8
|
+
@column = column
|
12
9
|
@period = period
|
13
10
|
@time_zone = time_zone
|
14
11
|
@time_range = time_range
|
@@ -49,28 +46,6 @@ module Groupdate
|
|
49
46
|
["#{column} IS NOT NULL"]
|
50
47
|
end
|
51
48
|
end
|
52
|
-
|
53
|
-
# basic version of Active Record disallow_raw_sql!
|
54
|
-
# symbol = column (safe), Arel node = SQL (safe), other = untrusted
|
55
|
-
# matches table.column and column
|
56
|
-
def validate_column(column)
|
57
|
-
unless column.is_a?(Symbol) || column.is_a?(Arel::Nodes::SqlLiteral)
|
58
|
-
column = column.to_s
|
59
|
-
unless /\A\w+(\.\w+)?\z/i.match(column)
|
60
|
-
warn "[groupdate] Non-attribute argument: #{column}. Use Arel.sql() for known-safe values. This will raise an error in Groupdate 6"
|
61
|
-
end
|
62
|
-
end
|
63
|
-
column
|
64
|
-
end
|
65
|
-
|
66
|
-
# resolves eagerly
|
67
|
-
# need to convert both where_clause (easy)
|
68
|
-
# and group_clause (not easy) if want to avoid this
|
69
|
-
def resolve_column(relation, column)
|
70
|
-
node = relation.send(:relation).send(:arel_columns, [column]).first
|
71
|
-
node = Arel::Nodes::SqlLiteral.new(node) if node.is_a?(String)
|
72
|
-
relation.connection.visitor.accept(node, Arel::Collectors::SQLString.new).value
|
73
|
-
end
|
74
49
|
end
|
75
50
|
end
|
76
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
|
-
["
|
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
|
-
["
|
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
|
-
|
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]
|
@@ -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 ?)
|
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
|
-
|
27
|
-
|
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
|
-
["(
|
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
|
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
|
35
|
+
"%Y-%m-%d"
|
36
36
|
when :month
|
37
|
-
"%Y-%m-01
|
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
|
41
|
+
"%Y-01-01"
|
42
42
|
end
|
43
43
|
|
44
44
|
["strftime(?, #{column})", format]
|
data/lib/groupdate/magic.rb
CHANGED
@@ -22,7 +22,7 @@ module Groupdate
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def validate_keywords
|
25
|
-
known_keywords = [:time_zone, :
|
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,15 @@ 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
|
+
if day_start != 0
|
150
|
+
day_start_hour = day_start / 3600
|
151
|
+
day_start_min = (day_start % 3600) / 60
|
152
|
+
day_start_sec = (day_start % 3600) % 60
|
153
|
+
lambda { |k| k.in_time_zone(time_zone).change(hour: day_start_hour, min: day_start_min, sec: day_start_sec) }
|
154
|
+
else
|
155
|
+
lambda { |k| k.in_time_zone(time_zone) }
|
156
|
+
end
|
148
157
|
else
|
149
158
|
utc = ActiveSupport::TimeZone["UTC"]
|
150
159
|
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 +202,15 @@ module Groupdate
|
|
193
202
|
adapter = Groupdate.adapters[adapter_name]
|
194
203
|
raise Groupdate::Error, "Connection adapter not supported: #{adapter_name}" unless adapter
|
195
204
|
|
205
|
+
# very important
|
206
|
+
column = validate_column(field)
|
207
|
+
column = resolve_column(relation, column)
|
208
|
+
|
196
209
|
# generate ActiveRecord relation
|
197
210
|
relation =
|
198
211
|
adapter.new(
|
199
212
|
relation,
|
200
|
-
column:
|
213
|
+
column: column,
|
201
214
|
period: magic.period,
|
202
215
|
time_zone: magic.time_zone,
|
203
216
|
time_range: magic.time_range,
|
@@ -213,6 +226,30 @@ module Groupdate
|
|
213
226
|
relation
|
214
227
|
end
|
215
228
|
|
229
|
+
class << self
|
230
|
+
# basic version of Active Record disallow_raw_sql!
|
231
|
+
# symbol = column (safe), Arel node = SQL (safe), other = untrusted
|
232
|
+
# matches table.column and column
|
233
|
+
def validate_column(column)
|
234
|
+
unless column.is_a?(Symbol) || column.is_a?(Arel::Nodes::SqlLiteral)
|
235
|
+
column = column.to_s
|
236
|
+
unless /\A\w+(\.\w+)?\z/i.match(column)
|
237
|
+
raise ActiveRecord::UnknownAttributeReference, "Query method called with non-attribute argument(s): #{column.inspect}. Use Arel.sql() for known-safe values."
|
238
|
+
end
|
239
|
+
end
|
240
|
+
column
|
241
|
+
end
|
242
|
+
|
243
|
+
# resolves eagerly
|
244
|
+
# need to convert both where_clause (easy)
|
245
|
+
# and group_clause (not easy) if want to avoid this
|
246
|
+
def resolve_column(relation, column)
|
247
|
+
node = relation.send(:relation).send(:arel_columns, [column]).first
|
248
|
+
node = Arel::Nodes::SqlLiteral.new(node) if node.is_a?(String)
|
249
|
+
relation.connection.visitor.accept(node, Arel::Collectors::SQLString.new).value
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
216
253
|
# allow any options to keep flexible for future
|
217
254
|
def self.process_result(relation, result, **options)
|
218
255
|
relation.groupdate_values.reverse.each do |gv|
|
@@ -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 =
|
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 =
|
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 =
|
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)
|
268
|
+
elsif [:day, :week, :month, :quarter, :year].include?(period)
|
316
269
|
lambda { |k| k.to_date }
|
317
270
|
else
|
318
271
|
lambda { |k| k }
|
data/lib/groupdate/version.rb
CHANGED
data/lib/groupdate.rb
CHANGED
@@ -12,7 +12,6 @@ require "groupdate/version"
|
|
12
12
|
require "groupdate/adapters/base_adapter"
|
13
13
|
require "groupdate/adapters/mysql_adapter"
|
14
14
|
require "groupdate/adapters/postgresql_adapter"
|
15
|
-
require "groupdate/adapters/redshift_adapter"
|
16
15
|
require "groupdate/adapters/sqlite_adapter"
|
17
16
|
|
18
17
|
module Groupdate
|
@@ -21,10 +20,9 @@ module Groupdate
|
|
21
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]
|
22
21
|
METHODS = PERIODS.map { |v| :"group_by_#{v}" } + [:group_by_period]
|
23
22
|
|
24
|
-
mattr_accessor :week_start, :day_start, :time_zone
|
23
|
+
mattr_accessor :week_start, :day_start, :time_zone
|
25
24
|
self.week_start = :sunday
|
26
25
|
self.day_start = 0
|
27
|
-
self.dates = true
|
28
26
|
|
29
27
|
# api for gems like ActiveMedian
|
30
28
|
def self.process_result(relation, result, **options)
|
@@ -46,8 +44,7 @@ module Groupdate
|
|
46
44
|
end
|
47
45
|
|
48
46
|
Groupdate.register_adapter ["Mysql2", "Mysql2Spatial", "Mysql2Rgeo"], Groupdate::Adapters::MySQLAdapter
|
49
|
-
Groupdate.register_adapter ["PostgreSQL", "PostGIS"], Groupdate::Adapters::PostgreSQLAdapter
|
50
|
-
Groupdate.register_adapter "Redshift", Groupdate::Adapters::RedshiftAdapter
|
47
|
+
Groupdate.register_adapter ["PostgreSQL", "PostGIS", "Redshift"], Groupdate::Adapters::PostgreSQLAdapter
|
51
48
|
Groupdate.register_adapter "SQLite", Groupdate::Adapters::SQLiteAdapter
|
52
49
|
|
53
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:
|
4
|
+
version: 6.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
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.
|
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.
|
68
|
+
rubygems_version: 3.3.3
|
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
|