blue-shift 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/Gemfile +6 -0
- data/README.md +14 -23
- data/Rakefile +1 -0
- data/blueshift.gemspec +2 -1
- data/lib/blueshift.rb +6 -0
- data/lib/blueshift/migration.rb +59 -0
- data/lib/blueshift/version.rb +1 -1
- data/lib/sequel/adapters/redshift.rb +53 -8
- data/lib/sequel/extensions/redshift_schema_dumper.rb +52 -0
- data/lib/tasks/generate_migration.rake +22 -0
- data/lib/tasks/schema.rake +50 -0
- metadata +22 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7a52af4406d213a8437391b173949c1a052275e6
|
4
|
+
data.tar.gz: c9626f7ca6049730d8afbabe686b515b9e4a9ecf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 75d418562c8142619bb5e055d1645e75c8c5ca362f1dfbd2601339ca4c50103768f81a8afe28ce8920ac8b0ccc95fb18121810a47ad6f7974ff07f4a1843de2c
|
7
|
+
data.tar.gz: d96f4afb5b6cc5963eaa3c8693fe6d28f714724e5a435bb8affdd148211fb4dd953a4568cbf58be09eb3a3f3d23d46baeb4f213bd8d54b2399c5330106abc662
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -26,9 +26,9 @@ Or install it yourself as:
|
|
26
26
|
|
27
27
|
The Distribution Key. When specified, the :diststyle is set to :key unless otherwise specified
|
28
28
|
|
29
|
-
- `:diststyle` => `:even` (default), `:key`, or `:all`
|
29
|
+
- `:diststyle` => `:even` (implicit default), `:key`, or `:all`
|
30
30
|
|
31
|
-
The Distribution Style.
|
31
|
+
The Distribution Style. When `:distkey` is also specified, only `:key` DISTSTYLE is supported by Redshift.
|
32
32
|
|
33
33
|
- `:sortkeys` => a list of column names
|
34
34
|
|
@@ -54,45 +54,32 @@ end
|
|
54
54
|
|
55
55
|
### Migrations (coming soon)
|
56
56
|
|
57
|
-
Blueshift unifies migrations for your Postgres and Redshift databases into one file. Postgres migrations
|
58
|
-
(I hate that this is separated, but that is the state that our app currently exists in until we sort it out.)
|
57
|
+
Blueshift unifies migrations for your Postgres and Redshift databases into one file. Postgres migrations and Redshift migrations use Sequel.
|
59
58
|
|
60
59
|
```ruby
|
61
60
|
Blueshift.migration do
|
62
61
|
up do
|
63
|
-
# applies to Postgres
|
64
|
-
end
|
65
|
-
|
66
|
-
down do
|
67
|
-
# applies to Postgres + Redshift
|
68
|
-
end
|
69
|
-
end
|
70
|
-
```
|
71
|
-
|
72
|
-
If you want different migration behaviours for Redshift than for Postgres, you can override them by using `redup` and `reddown`:
|
73
|
-
|
74
|
-
```ruby
|
75
|
-
Blueshift.migration do
|
76
|
-
up do
|
77
|
-
# applies to Postgres only, because redup is defined below
|
62
|
+
# applies to Postgres only
|
78
63
|
end
|
79
64
|
|
80
65
|
down do
|
81
|
-
# applies to Postgres
|
66
|
+
# applies to Postgres only
|
82
67
|
end
|
83
68
|
|
84
69
|
redup do
|
85
70
|
# applies to Redshift only
|
86
71
|
end
|
72
|
+
|
73
|
+
reddown do
|
74
|
+
# applies to Redshift only
|
75
|
+
end
|
87
76
|
end
|
88
77
|
```
|
89
78
|
|
90
|
-
If you want your migration to only run on Postgres, you can specify
|
79
|
+
If you want your migration to only run on Postgres, you can specify an empty block:
|
91
80
|
|
92
81
|
```ruby
|
93
82
|
Blueshift.migration do
|
94
|
-
pg_only!
|
95
|
-
|
96
83
|
up do
|
97
84
|
# applies to Postgres only
|
98
85
|
end
|
@@ -100,6 +87,10 @@ Blueshift.migration do
|
|
100
87
|
down do
|
101
88
|
# applies to Postgres only
|
102
89
|
end
|
90
|
+
|
91
|
+
redup {}
|
92
|
+
|
93
|
+
reddown {}
|
103
94
|
end
|
104
95
|
```
|
105
96
|
|
data/Rakefile
CHANGED
data/blueshift.gemspec
CHANGED
@@ -18,10 +18,11 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
19
|
spec.require_paths = ['lib']
|
20
20
|
|
21
|
-
spec.add_dependency 'sequel', '~>
|
21
|
+
spec.add_dependency 'sequel', '~> 4'
|
22
22
|
spec.add_dependency 'pg', '~> 0'
|
23
23
|
|
24
24
|
spec.add_development_dependency 'bundler', '~> 1.10'
|
25
25
|
spec.add_development_dependency 'rake', '~> 10.0'
|
26
26
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
27
|
+
spec.add_development_dependency 'dotenv', '~> 2.1.0'
|
27
28
|
end
|
data/lib/blueshift.rb
CHANGED
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'sequel'
|
2
|
+
require 'sequel/extensions/migration'
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
module Blueshift
|
6
|
+
REDSHIFT_DB = Sequel.connect(ENV.fetch('REDSHIFT_URL'), logger: Logger.new('redshift.log'))
|
7
|
+
POSTGRES_DB = Sequel.connect(ENV.fetch('DATABASE_URL'), logger: Logger.new('postgres.log'))
|
8
|
+
|
9
|
+
class Migration
|
10
|
+
attr_reader :postgres_migration, :redshift_migration
|
11
|
+
MIGRATION_DIR = File.join(Dir.pwd, 'db/migrations')
|
12
|
+
|
13
|
+
def initialize(&block)
|
14
|
+
@postgres_migration = Sequel::SimpleMigration.new
|
15
|
+
@redshift_migration = Sequel::SimpleMigration.new
|
16
|
+
|
17
|
+
Sequel::Migration.descendants << self
|
18
|
+
instance_eval(&block)
|
19
|
+
validate!
|
20
|
+
end
|
21
|
+
|
22
|
+
def up(&block)
|
23
|
+
postgres_migration.up = block
|
24
|
+
end
|
25
|
+
|
26
|
+
def down(&block)
|
27
|
+
postgres_migration.down = block
|
28
|
+
end
|
29
|
+
|
30
|
+
def redup(&block)
|
31
|
+
redshift_migration.up = block
|
32
|
+
end
|
33
|
+
|
34
|
+
def reddown(&block)
|
35
|
+
redshift_migration.down = block
|
36
|
+
end
|
37
|
+
|
38
|
+
def apply(db, direction)
|
39
|
+
if db.is_a?(Sequel::Redshift::Database)
|
40
|
+
redshift_migration.apply(db, direction)
|
41
|
+
else
|
42
|
+
postgres_migration.apply(db, direction)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.run!
|
47
|
+
Sequel::Migrator.run(POSTGRES_DB, MIGRATION_DIR, column: :postgres_version)
|
48
|
+
Sequel::Migrator.run(REDSHIFT_DB, MIGRATION_DIR, column: :redshift_version)
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def validate!
|
54
|
+
unless [postgres_migration.up, postgres_migration.down, redshift_migration.up, redshift_migration.down].all?
|
55
|
+
raise ArgumentError, 'must declare blocks for up, down, redup, and reddown'
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/blueshift/version.rb
CHANGED
@@ -9,12 +9,6 @@ module Sequel
|
|
9
9
|
SORTSTYLES = [:compound, :interleaved].freeze
|
10
10
|
DISTSTYLES = [:even, :key, :all].freeze
|
11
11
|
|
12
|
-
def column_definition_primary_key_sql(sql, column)
|
13
|
-
result = super
|
14
|
-
result << ' IDENTITY' if result
|
15
|
-
result
|
16
|
-
end
|
17
|
-
|
18
12
|
def serial_primary_key_options
|
19
13
|
# redshift doesn't support serial type
|
20
14
|
super.merge(serial: false)
|
@@ -29,15 +23,21 @@ module Sequel
|
|
29
23
|
def create_table_sql(name, generator, options)
|
30
24
|
validate_options!(options)
|
31
25
|
super.tap do |sql|
|
26
|
+
sql << diststyle_sql(options)
|
32
27
|
sql << distkey_sql(options)
|
33
28
|
sql << sortstyle_sql(options)
|
34
29
|
end
|
35
30
|
end
|
36
31
|
|
32
|
+
def diststyle_sql(options)
|
33
|
+
if options[:diststyle]
|
34
|
+
" DISTSTYLE #{options[:diststyle].to_s.upcase}"
|
35
|
+
end.to_s
|
36
|
+
end
|
37
|
+
|
37
38
|
def distkey_sql(options)
|
38
39
|
if options[:distkey]
|
39
|
-
options[:
|
40
|
-
" DISTSTYLE #{options[:diststyle].to_s.upcase} DISTKEY (#{options[:distkey]})"
|
40
|
+
" DISTKEY (#{options[:distkey]})"
|
41
41
|
end.to_s
|
42
42
|
end
|
43
43
|
|
@@ -56,6 +56,51 @@ module Sequel
|
|
56
56
|
def invalid?(value, allowed)
|
57
57
|
value && !allowed.include?(value)
|
58
58
|
end
|
59
|
+
|
60
|
+
# OVERRIDE for Redshift. Now always expect the "id" column to be the primary key
|
61
|
+
# The dataset used for parsing table schemas, using the pg_* system catalogs.
|
62
|
+
def schema_parse_table(table_name, opts)
|
63
|
+
m = output_identifier_meth(opts[:dataset])
|
64
|
+
ds = metadata_dataset.select(:pg_attribute__attname___name,
|
65
|
+
SQL::Cast.new(:pg_attribute__atttypid, :integer).as(:oid),
|
66
|
+
SQL::Cast.new(:basetype__oid, :integer).as(:base_oid),
|
67
|
+
SQL::Function.new(:format_type, :basetype__oid, :pg_type__typtypmod).as(:db_base_type),
|
68
|
+
SQL::Function.new(:format_type, :pg_type__oid, :pg_attribute__atttypmod).as(:db_type),
|
69
|
+
SQL::Function.new(:pg_get_expr, :pg_attrdef__adbin, :pg_class__oid).as(:default),
|
70
|
+
SQL::BooleanExpression.new(:NOT, :pg_attribute__attnotnull).as(:allow_null),
|
71
|
+
SQL::Function.new(:COALESCE, SQL::BooleanExpression.from_value_pairs(:name => 'id'), false).as(:primary_key)).
|
72
|
+
from(:pg_class).
|
73
|
+
join(:pg_attribute, :attrelid=>:oid).
|
74
|
+
join(:pg_type, :oid=>:atttypid).
|
75
|
+
left_outer_join(:pg_type___basetype, :oid=>:typbasetype).
|
76
|
+
left_outer_join(:pg_attrdef, :adrelid=>:pg_class__oid, :adnum=>:pg_attribute__attnum).
|
77
|
+
left_outer_join(:pg_index, :indrelid=>:pg_class__oid, :indisprimary=>true).
|
78
|
+
filter(:pg_attribute__attisdropped=>false).
|
79
|
+
filter{|o| o.pg_attribute__attnum > 0}.
|
80
|
+
filter(:pg_class__oid=>regclass_oid(table_name, opts)).
|
81
|
+
order(:pg_attribute__attnum)
|
82
|
+
ds.map do |row|
|
83
|
+
row[:default] = nil if blank_object?(row[:default])
|
84
|
+
if row[:base_oid]
|
85
|
+
row[:domain_oid] = row[:oid]
|
86
|
+
row[:oid] = row.delete(:base_oid)
|
87
|
+
row[:db_domain_type] = row[:db_type]
|
88
|
+
row[:db_type] = row.delete(:db_base_type)
|
89
|
+
else
|
90
|
+
row.delete(:base_oid)
|
91
|
+
row.delete(:db_base_type)
|
92
|
+
end
|
93
|
+
row[:type] = schema_column_type(row[:db_type])
|
94
|
+
if row[:primary_key]
|
95
|
+
row[:auto_increment] = !!(row[:default] =~ /\Anextval/io)
|
96
|
+
end
|
97
|
+
[m.call(row.delete(:name)), row]
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# OVERRIDE since indexes are not enforced by Redshift
|
102
|
+
def indexes(table, opts=OPTS)
|
103
|
+
end
|
59
104
|
end
|
60
105
|
|
61
106
|
class Dataset < Postgres::Dataset
|
@@ -0,0 +1,52 @@
|
|
1
|
+
Sequel.extension :schema_dumper
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
module Redshift
|
5
|
+
module SchemaDumper
|
6
|
+
include Sequel::SchemaDumper
|
7
|
+
|
8
|
+
def dump_table_schema(table, options=OPTS)
|
9
|
+
gen = dump_table_generator(table, options)
|
10
|
+
commands = [gen.dump_columns, gen.dump_constraints, gen.dump_indexes].reject { |x| x == '' }.join("\n\n")
|
11
|
+
|
12
|
+
"create_table(#{table.inspect}#{table_options(table, gen, options)}) do\n#{commands.gsub(/^/o, ' ')}\nend"
|
13
|
+
end
|
14
|
+
|
15
|
+
def table_options(table, gen, options)
|
16
|
+
s = {distkey: table_distkey(table),
|
17
|
+
sortkeys: table_sortkeys(table),
|
18
|
+
sortstyle: table_sortstyle(table),
|
19
|
+
ignore_index_errors: (!options[:same_db] && options[:indexes] != false && !gen.indexes.empty?)
|
20
|
+
}.select { |_,v| v }.inspect[1...-1]
|
21
|
+
|
22
|
+
s.empty? ? s : ", #{s}"
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def table_distkey(table)
|
28
|
+
key = pg_table_def(table).filter(distkey: true).map(:column).first
|
29
|
+
key.to_sym if key
|
30
|
+
end
|
31
|
+
|
32
|
+
def table_sortkeys(table)
|
33
|
+
keys = sortkey_columns(table).map{ |r| r[:column].to_sym }
|
34
|
+
keys unless keys.empty?
|
35
|
+
end
|
36
|
+
|
37
|
+
def table_sortstyle(table)
|
38
|
+
:interleaved if sortkey_columns(table).any? { |row| row[:sortkey] < 0 }
|
39
|
+
end
|
40
|
+
|
41
|
+
def sortkey_columns(table)
|
42
|
+
pg_table_def(table).exclude(sortkey: 0).order(Sequel.function(:abs, :sortkey))
|
43
|
+
end
|
44
|
+
|
45
|
+
def pg_table_def(table)
|
46
|
+
self[:pg_table_def].where(schemaname: 'public', tablename: table.to_s).select(:column, :sortkey)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
Database.register_extension(:redshift_schema_dumper, Redshift::SchemaDumper)
|
52
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
namespace :blueshift do
|
4
|
+
namespace :g do
|
5
|
+
desc 'Generate a timestamped, empty Blueshift migration.'
|
6
|
+
task :migration, :name do |_, args|
|
7
|
+
if args[:name].nil?
|
8
|
+
puts 'You must specify a migration name (e.g. rake generate:migration[create_events])!'
|
9
|
+
exit false
|
10
|
+
end
|
11
|
+
|
12
|
+
content = "Blueshift.migration do\n up do\n \n end\n\n down do\n \n end\n\n redup do\n \n end\n\n reddown do\n \n end\nend\n"
|
13
|
+
timestamp = Time.now.strftime('%Y%m%d%H%M%S')
|
14
|
+
filename = File.join(Dir.pwd, 'db/migrations', "#{timestamp}_#{args[:name]}.rb")
|
15
|
+
|
16
|
+
FileUtils.mkdir_p(File.dirname(filename))
|
17
|
+
File.open(filename, 'w') { |f| f << content }
|
18
|
+
|
19
|
+
puts "Created the migration #{filename}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'dotenv'
|
2
|
+
Dotenv.load
|
3
|
+
require 'fileutils'
|
4
|
+
require 'blueshift'
|
5
|
+
|
6
|
+
path = File.join(Dir.pwd, 'db')
|
7
|
+
|
8
|
+
task :ensure_db_dir do
|
9
|
+
FileUtils.mkdir_p(path)
|
10
|
+
end
|
11
|
+
|
12
|
+
namespace :pg do
|
13
|
+
namespace :schema do
|
14
|
+
desc 'Dumps the Postgres schema to a file'
|
15
|
+
task :dump => :ensure_db_dir do
|
16
|
+
Blueshift::POSTGRES_DB.extension :redshift_schema_dumper
|
17
|
+
File.open(File.join(path, 'schema.rb'), 'w') { |f| f << Blueshift::POSTGRES_DB.dump_schema_migration(same_db: true) }
|
18
|
+
end
|
19
|
+
|
20
|
+
desc 'Loads the Postgres schema from the file to the database'
|
21
|
+
task :load => :ensure_db_dir do
|
22
|
+
eval(File.join(path, 'schema.rb')).apply(Blueshift::POSTGRES_DB, :up)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
namespace :redshift do
|
29
|
+
namespace :schema do
|
30
|
+
desc 'Dumps the Postgres schema to a file'
|
31
|
+
task :dump => :ensure_db_dir do
|
32
|
+
Blueshift::REDSHIFT_DB.extension :redshift_schema_dumper
|
33
|
+
File.open(File.join(path, 'schema_redshift.rb'), 'w') { |f| f << Blueshift::REDSHIFT_DB.dump_schema_migration(same_db: true) }
|
34
|
+
end
|
35
|
+
|
36
|
+
desc 'Loads the Postgres schema from the file to the database'
|
37
|
+
task :load => :ensure_db_dir do
|
38
|
+
eval(File.join(path, 'schema_redshift.rb')).apply(Blueshift::REDSHIFT_DB, :up)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
namespace :blueshift do
|
44
|
+
desc 'Runs migrations for both Postgres and Redshift'
|
45
|
+
task :migrate do
|
46
|
+
puts 'Running migrations for Postgres and Redshift...', ''
|
47
|
+
Blueshift::Migration.run!
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: blue-shift
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gabriel Mansour
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-03-
|
11
|
+
date: 2016-03-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sequel
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '4'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '4'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: pg
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -80,6 +80,20 @@ dependencies:
|
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '3.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: dotenv
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 2.1.0
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 2.1.0
|
83
97
|
description: Amazon Redshift adapter for Sequel
|
84
98
|
email:
|
85
99
|
- dev+gabriel@influitive.com
|
@@ -101,8 +115,12 @@ files:
|
|
101
115
|
- bin/setup
|
102
116
|
- blueshift.gemspec
|
103
117
|
- lib/blueshift.rb
|
118
|
+
- lib/blueshift/migration.rb
|
104
119
|
- lib/blueshift/version.rb
|
105
120
|
- lib/sequel/adapters/redshift.rb
|
121
|
+
- lib/sequel/extensions/redshift_schema_dumper.rb
|
122
|
+
- lib/tasks/generate_migration.rake
|
123
|
+
- lib/tasks/schema.rake
|
106
124
|
homepage: https://github.com/influitive/blueshift
|
107
125
|
licenses: []
|
108
126
|
metadata: {}
|