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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6eccf1e183a70d001c5616e75ba3f9b58a32b8d2
4
- data.tar.gz: 76375171326b20569bb81538873d4d33f17ab306
3
+ metadata.gz: 7a52af4406d213a8437391b173949c1a052275e6
4
+ data.tar.gz: c9626f7ca6049730d8afbabe686b515b9e4a9ecf
5
5
  SHA512:
6
- metadata.gz: 968c3d19b343b6069a0d9f6f6def75770dc35bb3ad4aa1f6fafe667b33b2ddde8c3d60ea8389ebec0e0a5ceb9459f19771268f27b70906260b7498854fcd595c
7
- data.tar.gz: abca983bf558a8e70fd4ff2e2bd31f98ed11da6c4fdc5c8f414e136a915db27396ccd0a2f9f7ac019bc5e1c634d4da644fa459eacb393ba4e9d3cb99eb133620
6
+ metadata.gz: 75d418562c8142619bb5e055d1645e75c8c5ca362f1dfbd2601339ca4c50103768f81a8afe28ce8920ac8b0ccc95fb18121810a47ad6f7974ff07f4a1843de2c
7
+ data.tar.gz: d96f4afb5b6cc5963eaa3c8693fe6d28f714724e5a435bb8affdd148211fb4dd953a4568cbf58be09eb3a3f3d23d46baeb4f213bd8d54b2399c5330106abc662
data/.gitignore CHANGED
@@ -7,3 +7,5 @@
7
7
  /pkg/
8
8
  /spec/reports/
9
9
  /tmp/
10
+ .env
11
+ *.log
data/Gemfile CHANGED
@@ -3,3 +3,9 @@ source 'https://rubygems.org'
3
3
  gem 'guard-rspec', group: :development
4
4
  # Specify your gem's dependencies in blueshift.gemspec
5
5
  gemspec
6
+
7
+ group :test do
8
+ gem 'pry'
9
+ end
10
+
11
+ gem 'dotenv'
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. This option has no effect unless `:distkey` is also specified
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 use ActiveRecord::Migration and Redshift uses Sequel.
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 + Redshift
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 + Redshift
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 it as an argument for the migration block:
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
@@ -1,5 +1,6 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rspec/core/rake_task"
3
+ Dir['lib/tasks/*.rake'].each{ |f| import f }
3
4
 
4
5
  RSpec::Core::RakeTask.new(:spec)
5
6
 
@@ -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', '~> 0'
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
@@ -1,4 +1,10 @@
1
1
  require 'blueshift/version'
2
+ require 'blueshift/migration'
2
3
  require 'sequel'
3
4
  require 'sequel/adapters/redshift'
4
5
 
6
+ module Blueshift
7
+ def self.migration(&block)
8
+ Blueshift::Migration.new(&block)
9
+ end
10
+ end
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Blueshift
2
- VERSION = '0.0.1'
2
+ VERSION = '0.0.2'
3
3
  end
@@ -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[:diststyle] ||= :key
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.1
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-07 00:00:00.000000000 Z
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: '0'
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: '0'
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: {}