Skip to content

Commit c0ec8d5

Browse files
committed
Add date_trunc function to MySQL
1 parent 181e959 commit c0ec8d5

File tree

8 files changed

+110
-6
lines changed

8 files changed

+110
-6
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,7 @@
66
/doc/
77
/pkg/
88
/spec/reports/
9+
.ruby-version
10+
.byebug_history
911
/tmp/
1012
.DS_STORE

README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ class PhoneFactModel < ActiveReporting::FactModel
219219
end
220220
```
221221

222-
### Implicit hierarchies with datetime columns (PostgreSQL support only)
222+
### Implicit hierarchies with datetime columns (PostgreSQL and MySQL* support only)
223223

224224
The fastest approach to group by certain date metrics is to create so-called "date dimensions". For
225225
those Postgres users that are restricted from organizing their data in this way, Postgres provides
@@ -235,7 +235,12 @@ end
235235

236236
When creating a metric, ActiveReporting will recognize implicit hierarchies for this dimension. The hierarchies correspond to the [values](https://www.postgresql.org/docs/8.1/static/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC) supported by PostgreSQL. (See example under the metric section, below.)
237237

238-
*NOTE*: PRs welcomed to support this functionality in other databases.
238+
*Before Using with MySQL
239+
You can add `date_trunc` function to a rails app. We have a migration generator that will generate a SQL
240+
statement to create it on your database. To do it run this command:
241+
```
242+
bin/rails generate active_reporting:mysql_function_migration
243+
```
239244

240245
## Configuring Dimension Filters
241246

lib/active_reporting/reporting_dimension.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
module ActiveReporting
55
class ReportingDimension
66
extend Forwardable
7-
SUPPORTED_DBS = %w[PostgreSQL PostGIS].freeze
7+
SUPPORTED_DBS = %w[PostgreSQL PostGIS Mysql2].freeze
88
# Values for the Postgres `date_trunc` method.
99
# See https://www.postgresql.org/docs/10/static/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC
1010
DATETIME_HIERARCHIES = %i[microseconds milliseconds second minute hour day week month quarter year decade
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# frozen_string_literal: true
2+
3+
require 'rails/generators/active_record'
4+
5+
module ActiveReporting
6+
class MysqlFunctionMigrationGenerator < ActiveRecord::Generators::Base
7+
argument :name, type: :string, default: 'no_name', banner: 'This migration has default name'
8+
source_root File.expand_path("../templates", __FILE__)
9+
10+
def copy_active_reporting_migration
11+
if mysql? && !migration_exists?
12+
migration_template(
13+
'migration.rb',
14+
"#{migration_path}/add_date_trunc_function_to_mysql.rb",
15+
migration_version: migration_version
16+
)
17+
end
18+
end
19+
20+
def migration_exists?
21+
Dir.glob(
22+
"#{File.join(destination_root, migration_path)}/[0-9]*_*.rb"
23+
).grep(/\d+_add_date_trunc_function_to_mysql.rb$/).present?
24+
end
25+
26+
def rails5_and_up?
27+
Rails::VERSION::MAJOR >= 5
28+
end
29+
30+
def mysql?
31+
config = ActiveRecord::Base.configurations[Rails.env]
32+
config && config['adapter'] == 'mysql2'
33+
end
34+
35+
def migration_version
36+
if rails5_and_up?
37+
"[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
38+
end
39+
end
40+
41+
def migration_path
42+
if Rails.version >= '5.0.3'
43+
db_migrate_path
44+
else
45+
@migration_path ||= File.join('db', 'migrate')
46+
end
47+
end
48+
end
49+
end
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# frozen_string_literal: true
2+
3+
class AddDateTruncFunctionToMysql < ActiveRecord::Migration[5.1]
4+
def up
5+
drop_function
6+
ActiveRecord::Base.connection.execute(date_trunc_function.strip.gsub(/\n/, ' '))
7+
end
8+
9+
def down
10+
drop_function
11+
end
12+
13+
private
14+
15+
def drop_function
16+
ActiveRecord::Base.connection.execute(
17+
<<-SQL
18+
DROP FUNCTION IF EXISTS date_trunc
19+
SQL
20+
)
21+
end
22+
23+
def date_trunc_function
24+
func = <<-SQL
25+
CREATE FUNCTION date_trunc(vInterval varchar(7), vDate timestamp)
26+
RETURNS timestamp
27+
begin
28+
declare toReturn timestamp;
29+
30+
IF vInterval = 'year' then set toReturn = date_add('1900-01-01', interval TIMESTAMPDIFF(YEAR, '1900-01-01', vDate) YEAR);
31+
elseif vInterval = 'quarter' then set toReturn = date_add('1900-01-01', interval TIMESTAMPDIFF(QUARTER, '1900-01-01', vDate) QUARTER);
32+
elseif vInterval = 'month' then set toReturn = date_add('1900-01-01', interval TIMESTAMPDIFF(MONTH, '1900-01-01', vDate) MONTH);
33+
elseif vInterval = 'week' then set toReturn = date_add('1900-01-01', interval TIMESTAMPDIFF(WEEK, '1900-01-01', vDate) WEEK);
34+
elseif vInterval = 'day' then set toReturn = date_add('1900-01-01', interval TIMESTAMPDIFF(DAY, '1900-01-01', vDate) DAY);
35+
elseif vInterval = 'hour' then set toReturn = date_add('1900-01-01', interval TIMESTAMPDIFF(HOUR, '1900-01-01', vDate) HOUR);
36+
elseif vInterval = 'minute' then set toReturn = date_add('1900-01-01', interval TIMESTAMPDIFF(MINUTE, '1900-01-01', vDate) MINUTE);
37+
END IF;
38+
39+
RETURN toReturn;
40+
END
41+
SQL
42+
func
43+
end
44+
end

test/active_reporting/report_test.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,10 @@ def test_report_runs_with_an_aggregate_other_than_count
3232
assert data.all? { |r| r.key?('a_metric') }
3333
end
3434

35+
# NOTE: In order to make this test pass with `mysql` you need to add
36+
# `date_trunc` function to your local mysql database
3537
def test_report_runs_with_a_date_grouping
36-
if ENV['DB'] == 'pg'
38+
if ENV['DB'] == 'pg' || ENV['DB'] == 'mysql'
3739
metric = ActiveReporting::Metric.new(:a_metric, fact_model: UserFactModel, dimensions: [{created_at: :month}])
3840
report = ActiveReporting::Report.new(metric)
3941
data = report.run

test/active_reporting/reporting_dimension_test.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def test_label_can_be_passed_in_if_dimension_is_herarchical
8787
def test_label_can_be_passed_in_if_dimension_is_datetime
8888
refute @user_dimension.hierarchical?
8989
assert @user_dimension.type == ActiveReporting::Dimension::TYPES[:degenerate]
90-
if ENV['DB'] == 'pg'
90+
if ENV['DB'] == 'pg' || ENV['DB'] == 'mysql'
9191
ActiveReporting::ReportingDimension.new(@user_dimension, label: :year)
9292
else
9393
assert_raises ActiveReporting::InvalidDimensionLabel do

test/test_helper.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
require 'minitest/pride'
1313

1414
db = ENV['DB'] || 'sqlite'
15+
db_user = ENV['DB_USER']
1516
case db
1617
when 'pg'
1718
ActiveRecord::Base.establish_connection(
@@ -25,7 +26,8 @@
2526
ActiveRecord::Base.establish_connection(
2627
adapter: 'mysql2',
2728
database: 'active_reporting_test',
28-
encoding: 'utf8'
29+
encoding: 'utf8',
30+
username: db_user
2931
)
3032
when 'sqlite'
3133
ActiveRecord::Base.establish_connection(

0 commit comments

Comments
 (0)