Commit 7fbc1c1b52f5e212f3980269fe9e5bfab1bb8e8e

Authored by Brett Cassette
Committed by Andrew Kane
1 parent 5f8d5fba

Add Redshift adapter (#133)

Add Redshift adapter
@@ -4,3 +4,4 @@ source "https://rubygems.org" @@ -4,3 +4,4 @@ source "https://rubygems.org"
4 gemspec 4 gemspec
5 5
6 gem "activerecord", "~> 4.2.0" 6 gem "activerecord", "~> 4.2.0"
  7 +gem "activerecord4-redshift-adapter", "~> 0.2.0"
@@ -13,7 +13,7 @@ The simplest way to group by: @@ -13,7 +13,7 @@ The simplest way to group by:
13 13
14 Works with Rails 3.1+ 14 Works with Rails 3.1+
15 15
16 -Supports PostgreSQL and MySQL, plus arrays and hashes 16 +Supports PostgreSQL, MySQL, and Redshift, plus arrays and hashes
17 17
18 [![Build Status](https://travis-ci.org/ankane/groupdate.svg?branch=master)](https://travis-ci.org/ankane/groupdate) 18 [![Build Status](https://travis-ci.org/ankane/groupdate.svg?branch=master)](https://travis-ci.org/ankane/groupdate)
19 19
@@ -4,7 +4,7 @@ require "rake/testtask" @@ -4,7 +4,7 @@ require "rake/testtask"
4 task default: :test 4 task default: :test
5 Rake::TestTask.new do |t| 5 Rake::TestTask.new do |t|
6 t.libs << "test" 6 t.libs << "test"
7 - t.pattern = "test/**/*_test.rb" 7 + t.test_files = FileList["test/**/*_test.rb"].exclude(/redshift/)
8 end 8 end
9 9
10 namespace :test do 10 namespace :test do
@@ -16,4 +16,8 @@ namespace :test do @@ -16,4 +16,8 @@ namespace :test do
16 t.libs << "test" 16 t.libs << "test"
17 t.pattern = "test/mysql_test.rb" 17 t.pattern = "test/mysql_test.rb"
18 end 18 end
  19 + Rake::TestTask.new(:redshift) do |t|
  20 + t.libs << "test"
  21 + t.pattern = "test/redshift_test.rb"
  22 + end
19 end 23 end
lib/groupdate/magic.rb
@@ -77,6 +77,25 @@ module Groupdate @@ -77,6 +77,25 @@ module Groupdate
77 else 77 else
78 ["(DATE_TRUNC('#{field}', (#{column}::timestamptz - INTERVAL '#{day_start} second') AT TIME ZONE ?) + INTERVAL '#{day_start} second') AT TIME ZONE ?", time_zone, time_zone] 78 ["(DATE_TRUNC('#{field}', (#{column}::timestamptz - INTERVAL '#{day_start} second') AT TIME ZONE ?) + INTERVAL '#{day_start} second') AT TIME ZONE ?", time_zone, time_zone]
79 end 79 end
  80 + when "Redshift"
  81 + case field
  82 + when :day_of_week # Sunday = 0, Monday = 1, etc.
  83 + ["EXTRACT(DOW from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
  84 + when :hour_of_day
  85 + ["EXTRACT(HOUR from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
  86 + when :day_of_month
  87 + ["EXTRACT(DAY from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
  88 + when :month_of_year
  89 + ["EXTRACT(MONTH from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
  90 + when :week # start on Sunday, not Redshift default Monday
  91 + # Redshift does not return timezone information; it
  92 + # always says it is in UTC time, so we must convert
  93 + # back to UTC to play properly with the rest of Groupdate.
  94 + #
  95 + ["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]
  96 + else
  97 + ["CONVERT_TIMEZONE(?, 'Etc/UTC', DATE_TRUNC(?, CONVERT_TIMEZONE(?, #{column}) - INTERVAL '#{day_start} second'))::timestamp + INTERVAL '#{day_start} second'", time_zone, field, time_zone]
  98 + end
80 else 99 else
81 raise "Connection adapter not supported: #{adapter_name}" 100 raise "Connection adapter not supported: #{adapter_name}"
82 end 101 end
test/redshift_test.rb 0 → 100644
@@ -0,0 +1,18 @@ @@ -0,0 +1,18 @@
  1 +require_relative "test_helper"
  2 +
  3 +class TestRedshift < Minitest::Test
  4 + include TestGroupdate
  5 + include TestDatabase
  6 +
  7 + def setup
  8 + super
  9 + @@setup ||= begin
  10 + abort("REDSHIFT_URL environment variable must be set in order to run tests") unless ENV["REDSHIFT_URL"].present?
  11 +
  12 + ActiveRecord::Base.establish_connection(ENV["REDSHIFT_URL"])
  13 +
  14 + create_redshift_tables
  15 + true
  16 + end
  17 + end
  18 +end
test/test_helper.rb
@@ -49,6 +49,22 @@ def create_tables @@ -49,6 +49,22 @@ def create_tables
49 end 49 end
50 end 50 end
51 51
  52 +def create_redshift_tables
  53 + ActiveRecord::Migration.verbose = false
  54 +
  55 + if ActiveRecord::Migration.table_exists?(:users)
  56 + ActiveRecord::Migration.drop_table(:users, force: :cascade)
  57 + end
  58 +
  59 + if ActiveRecord::Migration.table_exists?(:posts)
  60 + ActiveRecord::Migration.drop_table(:posts, force: :cascade)
  61 + end
  62 +
  63 + ActiveRecord::Migration.execute "CREATE TABLE users (id INT IDENTITY(1,1) PRIMARY KEY, name VARCHAR(255), score INT, created_at DATETIME, created_on DATE);"
  64 +
  65 + ActiveRecord::Migration.execute "CREATE TABLE posts (id INT IDENTITY(1,1) PRIMARY KEY, user_id INT REFERENCES users, created_at DATETIME);"
  66 +end
  67 +
52 module TestDatabase 68 module TestDatabase
53 def test_zeros_previous_scope 69 def test_zeros_previous_scope
54 create_user "2013-05-01" 70 create_user "2013-05-01"
@@ -332,8 +348,11 @@ module TestDatabase @@ -332,8 +348,11 @@ module TestDatabase
332 created_on: created_at ? Date.parse(created_at) : nil 348 created_on: created_at ? Date.parse(created_at) : nil
333 ) 349 )
334 350
335 - # hack for MySQL adapter  
336 - # user.update_attributes(created_at: nil, created_on: nil) if created_at.nil? 351 + # hack for Redshift adapter, which doesn't return id on creation...
  352 + user = User.last if user.id.nil?
  353 +
  354 + # hack for MySQL & Redshift adapters
  355 + user.update_attributes(created_at: nil, created_on: nil) if created_at.nil?
337 356
338 user 357 user
339 end 358 end