groupdate 4.3.0 → 5.2.2

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