groupdate 4.3.0 → 5.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,3 @@
1
1
  module Groupdate
2
- VERSION = "4.3.0"
2
+ VERSION = "5.2.2"
3
3
  end
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.3.0
4
+ version: 5.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-12-27 00:00:00.000000000 Z
11
+ date: 2021-02-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -24,106 +24,8 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '5'
27
- - !ruby/object:Gem::Dependency
28
- name: bundler
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: '0'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ">="
39
- - !ruby/object:Gem::Version
40
- version: '0'
41
- - !ruby/object:Gem::Dependency
42
- name: rake
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: '0'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: '0'
55
- - !ruby/object:Gem::Dependency
56
- name: minitest
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: '0'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: '0'
69
- - !ruby/object:Gem::Dependency
70
- name: activerecord
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- version: '0'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: '0'
83
- - !ruby/object:Gem::Dependency
84
- name: pg
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - ">="
88
- - !ruby/object:Gem::Version
89
- version: '0'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - ">="
95
- - !ruby/object:Gem::Version
96
- version: '0'
97
- - !ruby/object:Gem::Dependency
98
- name: mysql2
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - ">="
102
- - !ruby/object:Gem::Version
103
- version: '0'
104
- type: :development
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - ">="
109
- - !ruby/object:Gem::Version
110
- version: '0'
111
- - !ruby/object:Gem::Dependency
112
- name: sqlite3
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - ">="
116
- - !ruby/object:Gem::Version
117
- version: '0'
118
- type: :development
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - ">="
123
- - !ruby/object:Gem::Version
124
- version: '0'
125
- description:
126
- email: andrew@chartkick.com
27
+ description:
28
+ email: andrew@ankane.org
127
29
  executables: []
128
30
  extensions: []
129
31
  extra_rdoc_files: []
@@ -134,18 +36,22 @@ files:
134
36
  - README.md
135
37
  - lib/groupdate.rb
136
38
  - lib/groupdate/active_record.rb
39
+ - lib/groupdate/adapters/base_adapter.rb
40
+ - lib/groupdate/adapters/mysql_adapter.rb
41
+ - lib/groupdate/adapters/postgresql_adapter.rb
42
+ - lib/groupdate/adapters/redshift_adapter.rb
43
+ - lib/groupdate/adapters/sqlite_adapter.rb
137
44
  - lib/groupdate/enumerable.rb
138
45
  - lib/groupdate/magic.rb
139
46
  - lib/groupdate/query_methods.rb
140
47
  - lib/groupdate/relation.rb
141
- - lib/groupdate/relation_builder.rb
142
48
  - lib/groupdate/series_builder.rb
143
49
  - lib/groupdate/version.rb
144
50
  homepage: https://github.com/ankane/groupdate
145
51
  licenses:
146
52
  - MIT
147
53
  metadata: {}
148
- post_install_message:
54
+ post_install_message:
149
55
  rdoc_options: []
150
56
  require_paths:
151
57
  - lib
@@ -160,8 +66,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
160
66
  - !ruby/object:Gem::Version
161
67
  version: '0'
162
68
  requirements: []
163
- rubygems_version: 3.1.2
164
- signing_key:
69
+ rubygems_version: 3.2.3
70
+ signing_key:
165
71
  specification_version: 4
166
72
  summary: The simplest way to group temporal data
167
73
  test_files: []
@@ -1,194 +0,0 @@
1
- module Groupdate
2
- class RelationBuilder
3
- attr_reader :period, :column, :day_start, :week_start
4
-
5
- def initialize(relation, column:, period:, time_zone:, time_range:, week_start:, day_start:)
6
- @relation = relation
7
- @column = resolve_column(relation, column)
8
- @period = period
9
- @time_zone = time_zone
10
- @time_range = time_range
11
- @week_start = week_start
12
- @day_start = day_start
13
-
14
- if relation.default_timezone == :local
15
- raise Groupdate::Error, "ActiveRecord::Base.default_timezone must be :utc to use Groupdate"
16
- end
17
- end
18
-
19
- def generate
20
- @relation.group(group_clause).where(*where_clause)
21
- end
22
-
23
- private
24
-
25
- def group_clause
26
- time_zone = @time_zone.tzinfo.name
27
- adapter_name = @relation.connection.adapter_name
28
- query =
29
- case adapter_name
30
- when "MySQL", "Mysql2", "Mysql2Spatial", 'Mysql2Rgeo'
31
- case period
32
- when :day_of_week
33
- ["DAYOFWEEK(CONVERT_TZ(DATE_SUB(#{column}, INTERVAL #{day_start} second), '+00:00', ?)) - 1", time_zone]
34
- when :day_of_year
35
- ["DAYOFYEAR(CONVERT_TZ(DATE_SUB(#{column}, INTERVAL #{day_start} second), '+00:00', ?))", time_zone]
36
- when :hour_of_day
37
- ["(EXTRACT(HOUR from CONVERT_TZ(#{column}, '+00:00', ?)) + 24 - #{day_start / 3600}) % 24", time_zone]
38
- when :minute_of_hour
39
- ["(EXTRACT(MINUTE from CONVERT_TZ(#{column}, '+00:00', ?)))", time_zone]
40
- when :day_of_month
41
- ["DAYOFMONTH(CONVERT_TZ(DATE_SUB(#{column}, INTERVAL #{day_start} second), '+00:00', ?))", time_zone]
42
- when :month_of_year
43
- ["MONTH(CONVERT_TZ(DATE_SUB(#{column}, INTERVAL #{day_start} second), '+00:00', ?))", time_zone]
44
- when :week
45
- ["CONVERT_TZ(DATE_FORMAT(CONVERT_TZ(DATE_SUB(#{column}, INTERVAL ((#{7 - week_start} + WEEKDAY(CONVERT_TZ(#{column}, '+00:00', ?) - INTERVAL #{day_start} second)) % 7) DAY) - INTERVAL #{day_start} second, '+00:00', ?), '%Y-%m-%d 00:00:00') + INTERVAL #{day_start} second, ?, '+00:00')", time_zone, time_zone, time_zone]
46
- when :quarter
47
- ["DATE_ADD(CONVERT_TZ(DATE_FORMAT(DATE(CONCAT(EXTRACT(YEAR FROM CONVERT_TZ(DATE_SUB(#{column}, INTERVAL #{day_start} second), '+00:00', ?)), '-', LPAD(1 + 3 * (QUARTER(CONVERT_TZ(DATE_SUB(#{column}, INTERVAL #{day_start} second), '+00:00', ?)) - 1), 2, '00'), '-01')), '%Y-%m-%d %H:%i:%S'), ?, '+00:00'), INTERVAL #{day_start} second)", time_zone, time_zone, time_zone]
48
- else
49
- format =
50
- case period
51
- when :second
52
- "%Y-%m-%d %H:%i:%S"
53
- when :minute
54
- "%Y-%m-%d %H:%i:00"
55
- when :hour
56
- "%Y-%m-%d %H:00:00"
57
- when :day
58
- "%Y-%m-%d 00:00:00"
59
- when :month
60
- "%Y-%m-01 00:00:00"
61
- else # year
62
- "%Y-01-01 00:00:00"
63
- end
64
-
65
- ["DATE_ADD(CONVERT_TZ(DATE_FORMAT(CONVERT_TZ(DATE_SUB(#{column}, INTERVAL #{day_start} second), '+00:00', ?), '#{format}'), ?, '+00:00'), INTERVAL #{day_start} second)", time_zone, time_zone]
66
- end
67
- when "PostgreSQL", "PostGIS"
68
- case period
69
- when :day_of_week
70
- ["EXTRACT(DOW from #{column}::timestamptz AT TIME ZONE ? - INTERVAL '#{day_start} second')::integer", time_zone]
71
- when :day_of_year
72
- ["EXTRACT(DOY from #{column}::timestamptz AT TIME ZONE ? - INTERVAL '#{day_start} second')::integer", time_zone]
73
- when :hour_of_day
74
- ["EXTRACT(HOUR from #{column}::timestamptz AT TIME ZONE ? - INTERVAL '#{day_start} second')::integer", time_zone]
75
- when :minute_of_hour
76
- ["EXTRACT(MINUTE from #{column}::timestamptz AT TIME ZONE ? - INTERVAL '#{day_start} second')::integer", time_zone]
77
- when :day_of_month
78
- ["EXTRACT(DAY from #{column}::timestamptz AT TIME ZONE ? - INTERVAL '#{day_start} second')::integer", time_zone]
79
- when :month_of_year
80
- ["EXTRACT(MONTH from #{column}::timestamptz AT TIME ZONE ? - INTERVAL '#{day_start} second')::integer", time_zone]
81
- when :week # start on Sunday, not PostgreSQL default Monday
82
- ["(DATE_TRUNC('#{period}', (#{column}::timestamptz - INTERVAL '#{week_start} day' - INTERVAL '#{day_start} second') AT TIME ZONE ?) + INTERVAL '#{week_start} day' + INTERVAL '#{day_start} second') AT TIME ZONE ?", time_zone, time_zone]
83
- else
84
- ["(DATE_TRUNC('#{period}', (#{column}::timestamptz - INTERVAL '#{day_start} second') AT TIME ZONE ?) + INTERVAL '#{day_start} second') AT TIME ZONE ?", time_zone, time_zone]
85
- end
86
- when "SQLite"
87
- raise Groupdate::Error, "Time zones not supported for SQLite" unless @time_zone.utc_offset.zero?
88
- raise Groupdate::Error, "day_start not supported for SQLite" unless day_start.zero?
89
- raise Groupdate::Error, "week_start not supported for SQLite" unless week_start == 6
90
-
91
- if period == :week
92
- ["strftime('%%Y-%%m-%%d 00:00:00 UTC', #{column}, '-6 days', 'weekday 0')"]
93
- else
94
- format =
95
- case period
96
- when :hour_of_day
97
- "%H"
98
- when :minute_of_hour
99
- "%M"
100
- when :day_of_week
101
- "%w"
102
- when :day_of_month
103
- "%d"
104
- when :month_of_year
105
- "%m"
106
- when :day_of_year
107
- "%j"
108
- when :second
109
- "%Y-%m-%d %H:%M:%S UTC"
110
- when :minute
111
- "%Y-%m-%d %H:%M:00 UTC"
112
- when :hour
113
- "%Y-%m-%d %H:00:00 UTC"
114
- when :day
115
- "%Y-%m-%d 00:00:00 UTC"
116
- when :month
117
- "%Y-%m-01 00:00:00 UTC"
118
- when :quarter
119
- raise Groupdate::Error, "Quarter not supported for SQLite"
120
- else # year
121
- "%Y-01-01 00:00:00 UTC"
122
- end
123
-
124
- ["strftime('#{format.gsub(/%/, '%%')}', #{column})"]
125
- end
126
- when "Redshift"
127
- case period
128
- when :day_of_week
129
- ["EXTRACT(DOW from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
130
- when :hour_of_day
131
- ["EXTRACT(HOUR from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
132
- when :minute_of_hour
133
- ["EXTRACT(MINUTE from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
134
- when :day_of_month
135
- ["EXTRACT(DAY from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
136
- when :day_of_year
137
- ["EXTRACT(DOY from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
138
- when :month_of_year
139
- ["EXTRACT(MONTH from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
140
- when :week # start on Sunday, not Redshift default Monday
141
- # Redshift does not return timezone information; it
142
- # always says it is in UTC time, so we must convert
143
- # back to UTC to play properly with the rest of Groupdate.
144
- #
145
- ["CONVERT_TIMEZONE(?, 'Etc/UTC', DATE_TRUNC(?, CONVERT_TIMEZONE(?, #{column}) - INTERVAL '#{week_start} day' - INTERVAL '#{day_start} second'))::timestamp + INTERVAL '#{week_start} day' + INTERVAL '#{day_start} second'", time_zone, period, time_zone]
146
- else
147
- ["CONVERT_TIMEZONE(?, 'Etc/UTC', DATE_TRUNC(?, CONVERT_TIMEZONE(?, #{column}) - INTERVAL '#{day_start} second'))::timestamp + INTERVAL '#{day_start} second'", time_zone, period, time_zone]
148
- end
149
- else
150
- raise Groupdate::Error, "Connection adapter not supported: #{adapter_name}"
151
- end
152
-
153
- if adapter_name == "MySQL" && period == :week
154
- query[0] = "CAST(#{query[0]} AS DATETIME)"
155
- end
156
-
157
- clause = @relation.send(:sanitize_sql_array, query)
158
-
159
- # cleaner queries in logs
160
- clause = clean_group_clause_postgresql(clause)
161
- clean_group_clause_mysql(clause)
162
- end
163
-
164
- def clean_group_clause_postgresql(clause)
165
- clause.gsub(/ (\-|\+) INTERVAL '0 second'/, "")
166
- end
167
-
168
- def clean_group_clause_mysql(clause)
169
- clause = clause.gsub("DATE_SUB(#{column}, INTERVAL 0 second)", "#{column}")
170
- if clause.start_with?("DATE_ADD(") && clause.end_with?(", INTERVAL 0 second)")
171
- clause = clause[9..-21]
172
- end
173
- clause
174
- end
175
-
176
- def where_clause
177
- if @time_range.is_a?(Range)
178
- op = @time_range.exclude_end? ? "<" : "<="
179
- ["#{column} >= ? AND #{column} #{op} ?", @time_range.first, @time_range.last]
180
- else
181
- ["#{column} IS NOT NULL"]
182
- end
183
- end
184
-
185
- # resolves eagerly
186
- # need to convert both where_clause (easy)
187
- # and group_clause (not easy) if want to avoid this
188
- def resolve_column(relation, column)
189
- node = relation.send(:relation).send(:arel_columns, [column]).first
190
- node = Arel::Nodes::SqlLiteral.new(node) if node.is_a?(String)
191
- relation.connection.visitor.accept(node, Arel::Collectors::SQLString.new).value
192
- end
193
- end
194
- end