From 7fbc1c1b52f5e212f3980269fe9e5bfab1bb8e8e Mon Sep 17 00:00:00 2001 From: Brett Cassette Date: Fri, 1 Jul 2016 12:48:47 -0700 Subject: [PATCH] Add Redshift adapter (#133) --- Gemfile | 1 + README.md | 2 +- Rakefile | 6 +++++- lib/groupdate/magic.rb | 19 +++++++++++++++++++ test/redshift_test.rb | 18 ++++++++++++++++++ test/test_helper.rb | 23 +++++++++++++++++++++-- 6 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 test/redshift_test.rb diff --git a/Gemfile b/Gemfile index a6242c4..72912d8 100644 --- a/Gemfile +++ b/Gemfile @@ -4,3 +4,4 @@ source "https://rubygems.org" gemspec gem "activerecord", "~> 4.2.0" +gem "activerecord4-redshift-adapter", "~> 0.2.0" diff --git a/README.md b/README.md index 9158431..e5ac722 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ The simplest way to group by: Works with Rails 3.1+ -Supports PostgreSQL and MySQL, plus arrays and hashes +Supports PostgreSQL, MySQL, and Redshift, plus arrays and hashes [![Build Status](https://travis-ci.org/ankane/groupdate.svg?branch=master)](https://travis-ci.org/ankane/groupdate) diff --git a/Rakefile b/Rakefile index 0e34b52..bafe855 100644 --- a/Rakefile +++ b/Rakefile @@ -4,7 +4,7 @@ require "rake/testtask" task default: :test Rake::TestTask.new do |t| t.libs << "test" - t.pattern = "test/**/*_test.rb" + t.test_files = FileList["test/**/*_test.rb"].exclude(/redshift/) end namespace :test do @@ -16,4 +16,8 @@ namespace :test do t.libs << "test" t.pattern = "test/mysql_test.rb" end + Rake::TestTask.new(:redshift) do |t| + t.libs << "test" + t.pattern = "test/redshift_test.rb" + end end diff --git a/lib/groupdate/magic.rb b/lib/groupdate/magic.rb index 3b61b3f..af6b299 100644 --- a/lib/groupdate/magic.rb +++ b/lib/groupdate/magic.rb @@ -77,6 +77,25 @@ module Groupdate else ["(DATE_TRUNC('#{field}', (#{column}::timestamptz - INTERVAL '#{day_start} second') AT TIME ZONE ?) + INTERVAL '#{day_start} second') AT TIME ZONE ?", time_zone, time_zone] end + when "Redshift" + case field + when :day_of_week # Sunday = 0, Monday = 1, etc. + ["EXTRACT(DOW from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone] + when :hour_of_day + ["EXTRACT(HOUR from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone] + when :day_of_month + ["EXTRACT(DAY from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone] + when :month_of_year + ["EXTRACT(MONTH from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone] + when :week # start on Sunday, not Redshift default Monday + # Redshift does not return timezone information; it + # always says it is in UTC time, so we must convert + # back to UTC to play properly with the rest of Groupdate. + # + ["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, field, time_zone] + else + ["CONVERT_TIMEZONE(?, 'Etc/UTC', DATE_TRUNC(?, CONVERT_TIMEZONE(?, #{column}) - INTERVAL '#{day_start} second'))::timestamp + INTERVAL '#{day_start} second'", time_zone, field, time_zone] + end else raise "Connection adapter not supported: #{adapter_name}" end diff --git a/test/redshift_test.rb b/test/redshift_test.rb new file mode 100644 index 0000000..c7933ee --- /dev/null +++ b/test/redshift_test.rb @@ -0,0 +1,18 @@ +require_relative "test_helper" + +class TestRedshift < Minitest::Test + include TestGroupdate + include TestDatabase + + def setup + super + @@setup ||= begin + abort("REDSHIFT_URL environment variable must be set in order to run tests") unless ENV["REDSHIFT_URL"].present? + + ActiveRecord::Base.establish_connection(ENV["REDSHIFT_URL"]) + + create_redshift_tables + true + end + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index 760e303..88020f8 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -49,6 +49,22 @@ def create_tables end end +def create_redshift_tables + ActiveRecord::Migration.verbose = false + + if ActiveRecord::Migration.table_exists?(:users) + ActiveRecord::Migration.drop_table(:users, force: :cascade) + end + + if ActiveRecord::Migration.table_exists?(:posts) + ActiveRecord::Migration.drop_table(:posts, force: :cascade) + end + + ActiveRecord::Migration.execute "CREATE TABLE users (id INT IDENTITY(1,1) PRIMARY KEY, name VARCHAR(255), score INT, created_at DATETIME, created_on DATE);" + + ActiveRecord::Migration.execute "CREATE TABLE posts (id INT IDENTITY(1,1) PRIMARY KEY, user_id INT REFERENCES users, created_at DATETIME);" +end + module TestDatabase def test_zeros_previous_scope create_user "2013-05-01" @@ -332,8 +348,11 @@ module TestDatabase created_on: created_at ? Date.parse(created_at) : nil ) - # hack for MySQL adapter - # user.update_attributes(created_at: nil, created_on: nil) if created_at.nil? + # hack for Redshift adapter, which doesn't return id on creation... + user = User.last if user.id.nil? + + # hack for MySQL & Redshift adapters + user.update_attributes(created_at: nil, created_on: nil) if created_at.nil? user end -- libgit2 0.21.0