Commit 7fbc1c1b52f5e212f3980269fe9e5bfab1bb8e8e
Committed by
Andrew Kane
1 parent
5f8d5fba
Exists in
master
and in
6 other branches
Add Redshift adapter (#133)
Add Redshift adapter
Showing
6 changed files
with
65 additions
and
4 deletions
Show diff stats
Gemfile
README.md
@@ -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 | [](https://travis-ci.org/ankane/groupdate) | 18 | [](https://travis-ci.org/ankane/groupdate) |
19 | 19 |
Rakefile
@@ -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 |
@@ -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 |