Skip to content

Commit

Permalink
Add date_trunc function to MySQL
Browse files Browse the repository at this point in the history
  • Loading branch information
andresgutgon committed Oct 11, 2018
1 parent 181e959 commit 442a3c6
Show file tree
Hide file tree
Showing 8 changed files with 110 additions and 6 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,7 @@
/doc/
/pkg/
/spec/reports/
.ruby-version
.byebug_history
/tmp/
.DS_STORE
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ class PhoneFactModel < ActiveReporting::FactModel
end
```

### Implicit hierarchies with datetime columns (PostgreSQL support only)
### Implicit hierarchies with datetime columns (PostgreSQL and MySQL* support only)

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

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.)

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

## Configuring Dimension Filters

Expand Down
2 changes: 1 addition & 1 deletion lib/active_reporting/reporting_dimension.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
module ActiveReporting
class ReportingDimension
extend Forwardable
SUPPORTED_DBS = %w[PostgreSQL PostGIS].freeze
SUPPORTED_DBS = %w[PostgreSQL PostGIS Mysql2].freeze
# Values for the Postgres `date_trunc` method.
# See https://www.postgresql.org/docs/10/static/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC
DATETIME_HIERARCHIES = %i[microseconds milliseconds second minute hour day week month quarter year decade
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# frozen_string_literal: true

require 'rails/generators/active_record'

module ActiveReporting
class MysqlFunctionMigrationGenerator < ActiveRecord::Generators::Base
argument :name, type: :string, default: 'no_name', banner: 'This migration has default name'
source_root File.expand_path("../templates", __FILE__)

def copy_active_reporting_migration
if mysql? && !migration_exists?
migration_template(
'migration.rb',
"#{migration_path}/add_date_trunc_function_to_mysql.rb",
migration_version: migration_version
)
end
end

def migration_exists?
Dir.glob(
"#{File.join(destination_root, migration_path)}/[0-9]*_*.rb"
).grep(/\d+_add_date_trunc_function_to_mysql.rb$/).present?
end

def rails5_and_up?
Rails::VERSION::MAJOR >= 5
end

def mysql?
config = ActiveRecord::Base.configurations[Rails.env]
config && config['adapter'] == 'mysql2'
end

def migration_version
if rails5_and_up?
"[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
end
end

def migration_path
if Rails.version >= '5.0.3'
db_migrate_path
else
@migration_path ||= File.join('db', 'migrate')
end
end
end
end
44 changes: 44 additions & 0 deletions lib/generators/active_reporting/templates/migration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# frozen_string_literal: true

class AddDateTruncFunctionToMysql < ActiveRecord::Migration[5.1]
def up
drop_function
ActiveRecord::Base.connection.execute(date_trunc_function.strip.gsub(/\n/, ' '))
end

def down
drop_function
end

private

def drop_function
ActiveRecord::Base.connection.execute(
<<-SQL
DROP FUNCTION IF EXISTS date_trunc
SQL
)
end

def date_trunc_function
func = <<-SQL
CREATE FUNCTION date_trunc(vInterval varchar(7), vDate timestamp)
RETURNS timestamp
begin
declare toReturn timestamp;
IF vInterval = 'year' then set toReturn = date_add('1900-01-01', interval TIMESTAMPDIFF(YEAR, '1900-01-01', vDate) YEAR);
elseif vInterval = 'quarter' then set toReturn = date_add('1900-01-01', interval TIMESTAMPDIFF(QUARTER, '1900-01-01', vDate) QUARTER);
elseif vInterval = 'month' then set toReturn = date_add('1900-01-01', interval TIMESTAMPDIFF(MONTH, '1900-01-01', vDate) MONTH);
elseif vInterval = 'week' then set toReturn = date_add('1900-01-01', interval TIMESTAMPDIFF(WEEK, '1900-01-01', vDate) WEEK);
elseif vInterval = 'day' then set toReturn = date_add('1900-01-01', interval TIMESTAMPDIFF(DAY, '1900-01-01', vDate) DAY);
elseif vInterval = 'hour' then set toReturn = date_add('1900-01-01', interval TIMESTAMPDIFF(HOUR, '1900-01-01', vDate) HOUR);
elseif vInterval = 'minute' then set toReturn = date_add('1900-01-01', interval TIMESTAMPDIFF(MINUTE, '1900-01-01', vDate) MINUTE);
END IF;
RETURN toReturn;
END
SQL
func
end
end
4 changes: 3 additions & 1 deletion test/active_reporting/report_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@ def test_report_runs_with_an_aggregate_other_than_count
assert data.all? { |r| r.key?('a_metric') }
end

# NOTE: In order to make this test pass with `mysql` you need to add
# `date_trunc` function to your local mysql database
def test_report_runs_with_a_date_grouping
if ENV['DB'] == 'pg'
if ENV['DB'] == 'pg' || ENV['DB'] == 'mysql'
metric = ActiveReporting::Metric.new(:a_metric, fact_model: UserFactModel, dimensions: [{created_at: :month}])
report = ActiveReporting::Report.new(metric)
data = report.run
Expand Down
2 changes: 1 addition & 1 deletion test/active_reporting/reporting_dimension_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def test_label_can_be_passed_in_if_dimension_is_herarchical
def test_label_can_be_passed_in_if_dimension_is_datetime
refute @user_dimension.hierarchical?
assert @user_dimension.type == ActiveReporting::Dimension::TYPES[:degenerate]
if ENV['DB'] == 'pg'
if ENV['DB'] == 'pg' || ENV['DB'] == 'mysql'
ActiveReporting::ReportingDimension.new(@user_dimension, label: :year)
else
assert_raises ActiveReporting::InvalidDimensionLabel do
Expand Down
4 changes: 3 additions & 1 deletion test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
require 'minitest/pride'

db = ENV['DB'] || 'sqlite'
db_user = ENV['DB_USER']
case db
when 'pg'
ActiveRecord::Base.establish_connection(
Expand All @@ -25,7 +26,8 @@
ActiveRecord::Base.establish_connection(
adapter: 'mysql2',
database: 'active_reporting_test',
encoding: 'utf8'
encoding: 'utf8',
username: db_user
)
when 'sqlite'
ActiveRecord::Base.establish_connection(
Expand Down

0 comments on commit 442a3c6

Please sign in to comment.