blue-shift 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: {}