rails_redshift_replicator 0.0.1
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +3 -0
- data/Rakefile +34 -0
- data/app/assets/javascripts/rails_redshift_replicator/application.js +13 -0
- data/app/assets/stylesheets/rails_redshift_replicator/application.css +15 -0
- data/app/controllers/rails_redshift_replicator/application_controller.rb +5 -0
- data/app/helpers/rails_redshift_replicator/application_helper.rb +4 -0
- data/app/models/rails_redshift_replicator/replication.rb +98 -0
- data/app/views/layouts/rails_redshift_replicator/application.html.erb +14 -0
- data/config/locales/rails_redshift_replicator.en.yml +20 -0
- data/config/routes.rb +2 -0
- data/db/migrate/20160503214955_create_rails_redshift_replicator_replications.rb +24 -0
- data/db/migrate/20160509193335_create_table_rails_redshift_replicator_deleted_ids.rb +8 -0
- data/lib/generators/rails_redshift_replicator/install_generator.rb +25 -0
- data/lib/generators/templates/rails_redshift_replicator.rb +74 -0
- data/lib/rails_redshift_replicator.rb +229 -0
- data/lib/rails_redshift_replicator/adapters/generic.rb +40 -0
- data/lib/rails_redshift_replicator/adapters/mysql2.rb +22 -0
- data/lib/rails_redshift_replicator/adapters/postgresql.rb +37 -0
- data/lib/rails_redshift_replicator/adapters/sqlite.rb +27 -0
- data/lib/rails_redshift_replicator/deleter.rb +67 -0
- data/lib/rails_redshift_replicator/engine.rb +14 -0
- data/lib/rails_redshift_replicator/exporters/base.rb +215 -0
- data/lib/rails_redshift_replicator/exporters/full_replicator.rb +9 -0
- data/lib/rails_redshift_replicator/exporters/identity_replicator.rb +9 -0
- data/lib/rails_redshift_replicator/exporters/timed_replicator.rb +9 -0
- data/lib/rails_redshift_replicator/file_manager.rb +134 -0
- data/lib/rails_redshift_replicator/importers/base.rb +158 -0
- data/lib/rails_redshift_replicator/importers/full_replicator.rb +17 -0
- data/lib/rails_redshift_replicator/importers/identity_replicator.rb +15 -0
- data/lib/rails_redshift_replicator/importers/timed_replicator.rb +18 -0
- data/lib/rails_redshift_replicator/model/extension.rb +45 -0
- data/lib/rails_redshift_replicator/model/hair_trigger_extension.rb +8 -0
- data/lib/rails_redshift_replicator/replicable.rb +143 -0
- data/lib/rails_redshift_replicator/rlogger.rb +12 -0
- data/lib/rails_redshift_replicator/tools/analyze.rb +18 -0
- data/lib/rails_redshift_replicator/tools/vacuum.rb +77 -0
- data/lib/rails_redshift_replicator/version.rb +3 -0
- data/lib/tasks/rails_redshift_replicator_tasks.rake +4 -0
- data/spec/dummy/README.rdoc +28 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/models/post.rb +4 -0
- data/spec/dummy/app/models/tag.rb +4 -0
- data/spec/dummy/app/models/user.rb +5 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/bin/setup +29 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +26 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +37 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +41 -0
- data/spec/dummy/config/environments/production.rb +79 -0
- data/spec/dummy/config/environments/test.rb +42 -0
- data/spec/dummy/config/initializers/assets.rb +11 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/rails_redshift_replicator.rb +59 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/locales/rails_redshift_replicator.en.yml +19 -0
- data/spec/dummy/config/routes.rb +4 -0
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/migrate/20160504120421_create_test_tables.rb +40 -0
- data/spec/dummy/db/migrate/20160509225445_create_triggers_posts_delete_or_tags_delete_or_users_delete.rb +33 -0
- data/spec/dummy/db/migrate/20160511000937_create_rails_redshift_replicator_replications.rails_redshift_replicator.rb +25 -0
- data/spec/dummy/db/migrate/20160511000938_create_table_rails_redshift_replicator_deleted_ids.rails_redshift_replicator.rb +9 -0
- data/spec/dummy/db/schema.rb +99 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/development.log +1623 -0
- data/spec/dummy/log/test.log +95379 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/rails_redshift_replicator_development +0 -0
- data/spec/factories/rails_redshift_replicator_replications.rb +31 -0
- data/spec/integration/rails_redshift_replicator_spec.rb +148 -0
- data/spec/integration/setup_spec.rb +149 -0
- data/spec/lib/rails_redshift_replicator/deleter_spec.rb +90 -0
- data/spec/lib/rails_redshift_replicator/exporters/base_spec.rb +326 -0
- data/spec/lib/rails_redshift_replicator/exporters/full_replicator_spec.rb +33 -0
- data/spec/lib/rails_redshift_replicator/exporters/identity_replicator_spec.rb +40 -0
- data/spec/lib/rails_redshift_replicator/exporters/timed_replicator_spec.rb +43 -0
- data/spec/lib/rails_redshift_replicator/file_manager_spec.rb +90 -0
- data/spec/lib/rails_redshift_replicator/importers/base_spec.rb +102 -0
- data/spec/lib/rails_redshift_replicator/importers/full_replicator_spec.rb +27 -0
- data/spec/lib/rails_redshift_replicator/importers/identity_replicator_spec.rb +26 -0
- data/spec/lib/rails_redshift_replicator/importers/timed_replicator_spec.rb +26 -0
- data/spec/lib/rails_redshift_replicator/model/extension_spec.rb +36 -0
- data/spec/lib/rails_redshift_replicator/replicable_spec.rb +230 -0
- data/spec/lib/rails_redshift_replicator/rlogger_spec.rb +22 -0
- data/spec/lib/rails_redshift_replicator/tools/analyze_spec.rb +15 -0
- data/spec/lib/rails_redshift_replicator/tools/vacuum_spec.rb +65 -0
- data/spec/lib/rails_redshift_replicator_spec.rb +110 -0
- data/spec/models/rails_redshift_replicator/replication_spec.rb +104 -0
- data/spec/spec_helper.rb +36 -0
- data/spec/support/csv/invalid_user.csv +12 -0
- data/spec/support/csv/valid_post.csv +2 -0
- data/spec/support/csv/valid_tags_users.csv +1 -0
- data/spec/support/csv/valid_user.csv +2 -0
- data/spec/support/rails_redshift_replicator_helpers.rb +95 -0
- metadata +430 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe RailsRedshiftReplicator::Exporters::IdentityReplicator do
|
|
4
|
+
let(:replicable) { RailsRedshiftReplicator::Replicable.new(:identity_replicator, source_table: :users) }
|
|
5
|
+
let(:file_manager) { RailsRedshiftReplicator::FileManager.new}
|
|
6
|
+
let(:s3_bucket) { Aws::S3::Bucket.new(name: RailsRedshiftReplicator.s3_bucket_params[:bucket], client: file_manager.s3_client) }
|
|
7
|
+
describe "#replication_field", :focus do
|
|
8
|
+
it "returns the replicable replication_field" do
|
|
9
|
+
expect(RailsRedshiftReplicator::Exporters::IdentityReplicator.new(replicable).replication_field).to eq 'id'
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
describe 'Integration Test' do
|
|
13
|
+
before(:all) { recreate_users_table }
|
|
14
|
+
it "exports identity replicator type replication" do
|
|
15
|
+
model = :user
|
|
16
|
+
# first export
|
|
17
|
+
instance = create model
|
|
18
|
+
RailsRedshiftReplicator::Exporters::IdentityReplicator.new(replicable).export_and_upload
|
|
19
|
+
replication1 = RailsRedshiftReplicator::Replication.from_table(model.to_s.pluralize).last
|
|
20
|
+
expect(replication1.state).to eq "uploaded"
|
|
21
|
+
expect(replication1.record_count).to eq 1
|
|
22
|
+
file_body = s3_bucket.object("#{replication1.key}.aa").get.body.read
|
|
23
|
+
expect(file_body).to match(/#{instance.id},#{instance.login},#{instance.age}/)
|
|
24
|
+
# export without new records
|
|
25
|
+
RailsRedshiftReplicator::Exporters::IdentityReplicator.new(replicable).export_and_upload
|
|
26
|
+
expect(RailsRedshiftReplicator::Replication.from_table(model.to_s.pluralize).last).to eq replication1
|
|
27
|
+
replication1.imported!
|
|
28
|
+
# export after creating new records
|
|
29
|
+
3.times {create model}
|
|
30
|
+
RailsRedshiftReplicator::Exporters::IdentityReplicator.new(replicable).export_and_upload
|
|
31
|
+
replication2 = RailsRedshiftReplicator::Replication.from_table(model.to_s.pluralize).last
|
|
32
|
+
expect(replication2.record_count).to eq 3
|
|
33
|
+
replication2.imported!
|
|
34
|
+
# export after updating first record
|
|
35
|
+
instance.touch
|
|
36
|
+
RailsRedshiftReplicator::Exporters::IdentityReplicator.new(replicable).export_and_upload
|
|
37
|
+
expect(RailsRedshiftReplicator::Replication.from_table(model.to_s.pluralize).last).to eq replication2
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe RailsRedshiftReplicator::Exporters::TimedReplicator do
|
|
4
|
+
let(:file_manager) { RailsRedshiftReplicator::FileManager.new}
|
|
5
|
+
let(:replicable) { RailsRedshiftReplicator::Replicable.new(:full_replicator, source_table: :tags_users) }
|
|
6
|
+
let(:s3_bucket) { Aws::S3::Bucket.new(name: RailsRedshiftReplicator.s3_bucket_params[:bucket], client: file_manager.s3_client) }
|
|
7
|
+
let(:replicable) { RailsRedshiftReplicator::Replicable.new(:timed_replicator, source_table: :posts) }
|
|
8
|
+
describe "#replication_field", :focus do
|
|
9
|
+
it "returns the replicable replication_field" do
|
|
10
|
+
expect(RailsRedshiftReplicator::Exporters::TimedReplicator.new(replicable).replication_field).to eq 'updated_at'
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
describe 'Integration Test' do
|
|
14
|
+
before(:all) { recreate_posts_table }
|
|
15
|
+
it "exports timed replicator type replication" do
|
|
16
|
+
model = :post
|
|
17
|
+
# first export
|
|
18
|
+
instance = create model
|
|
19
|
+
RailsRedshiftReplicator::Exporters::TimedReplicator.new(replicable).export_and_upload
|
|
20
|
+
replication1 = RailsRedshiftReplicator::Replication.from_table(model.to_s.pluralize).last
|
|
21
|
+
expect(replication1.state).to eq "uploaded"
|
|
22
|
+
expect(replication1.record_count).to eq 1
|
|
23
|
+
file_body = s3_bucket.object("#{replication1.key}.aa").get.body.read
|
|
24
|
+
# file_body = RailsRedshiftReplicator::Exporters::Base.replication_bucket.files.get("#{replication1.key}.aa").body
|
|
25
|
+
expect(file_body).to match(/#{instance.id},#{instance.user_id},#{instance.content}/)
|
|
26
|
+
# export without new records
|
|
27
|
+
RailsRedshiftReplicator::Exporters::TimedReplicator.new(replicable).export_and_upload
|
|
28
|
+
expect(RailsRedshiftReplicator::Replication.from_table(model.to_s.pluralize).last).to eq replication1
|
|
29
|
+
replication1.imported!
|
|
30
|
+
# export after creating new records
|
|
31
|
+
3.times {create model}
|
|
32
|
+
RailsRedshiftReplicator::Exporters::TimedReplicator.new(replicable).export_and_upload
|
|
33
|
+
replication2 = RailsRedshiftReplicator::Replication.from_table(model.to_s.pluralize).last
|
|
34
|
+
expect(replication2.record_count).to eq 3
|
|
35
|
+
replication2.imported!
|
|
36
|
+
# export after updating first record
|
|
37
|
+
instance.touch
|
|
38
|
+
RailsRedshiftReplicator::Exporters::TimedReplicator.new(replicable).export_and_upload
|
|
39
|
+
replication3 = RailsRedshiftReplicator::Replication.from_table(model.to_s.pluralize).last
|
|
40
|
+
expect(replication3.record_count).to eq 1
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe RailsRedshiftReplicator::FileManager do
|
|
4
|
+
let(:user_replicable) { RailsRedshiftReplicator::Replicable.new(:identity_replicator, source_table: :users)}
|
|
5
|
+
let(:replication) { build :redshift_replication, slices: 4 }
|
|
6
|
+
let(:user_exporter) { RailsRedshiftReplicator::Exporters::IdentityReplicator.new(user_replicable, replication) }
|
|
7
|
+
let(:file_manager) { RailsRedshiftReplicator::FileManager.new(user_exporter)}
|
|
8
|
+
|
|
9
|
+
describe ".s3_file_key" do
|
|
10
|
+
it "returns s3 handler for file" do
|
|
11
|
+
expect(RailsRedshiftReplicator::FileManager.s3_file_key("users","file.csv")).to eq "rrr/users/file.csv"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
describe "#s3_client" do
|
|
16
|
+
it "returns connection to s3" do
|
|
17
|
+
expect(file_manager.s3_client).to be_a Aws::S3::Client
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
describe "#row_count_threshold" do
|
|
23
|
+
before { user_exporter.replication = build :redshift_replication, slices: 3 }
|
|
24
|
+
it "returns number of lines to split export files" do
|
|
25
|
+
expect(file_manager.row_count_threshold(200)).to eq 67
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
describe "#write_csv" do
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
describe "#split_file" do
|
|
33
|
+
before do
|
|
34
|
+
allow(file_manager).to receive(:local_file).and_return "/tmp/test"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "splits file in 4 parts" do
|
|
38
|
+
f = File.open("/tmp/test", "w")
|
|
39
|
+
100.times{ f.puts "nothing here" }
|
|
40
|
+
f.close
|
|
41
|
+
file_manager.split_file("test", 4)
|
|
42
|
+
%w(aa ab ac ad).each do |suffix|
|
|
43
|
+
File.exists?("/tmp/test.#{suffix}").should be true
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
describe "#file_key_in_format" do
|
|
49
|
+
context "with csv format" do
|
|
50
|
+
let(:file) { file_manager.file_key_in_format("file.csv", "csv") }
|
|
51
|
+
it "returns file handler on s3" do
|
|
52
|
+
expect(file).to eq "rrr/users/file.csv"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
context "with gzip format" do
|
|
56
|
+
let(:file) { file_manager.file_key_in_format("file.csv", "gzip") }
|
|
57
|
+
it "returns file handler on s3" do
|
|
58
|
+
expect(file).to eq "rrr/users/file.gz"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
describe "#gzipped" do
|
|
65
|
+
it "returns gz file extension" do
|
|
66
|
+
expect(file_manager.gzipped("file.csv")).to eq "file.gz"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
describe "#upload_csv", focus: true do
|
|
71
|
+
let(:bucket) { RailsRedshiftReplicator.s3_bucket_params[:bucket]}
|
|
72
|
+
let(:s3_bucket) { Aws::S3::Bucket.new(name: bucket, client: file_manager.s3_client) }
|
|
73
|
+
let(:file_names) { ["file", "file.aa", "file.ab"] }
|
|
74
|
+
let!(:files) { file_names.map{ |f| File.open("/tmp/#{f}", "w") } }
|
|
75
|
+
before { user_exporter.replication = build(:redshift_replication, key: "file") }
|
|
76
|
+
it "uploads the splitted files" do
|
|
77
|
+
file_manager.upload_csv(files.map(&:path))
|
|
78
|
+
expect(s3_bucket.object('rrr/users/file').exists?).to be false
|
|
79
|
+
expect(s3_bucket.object('rrr/users/file.aa').exists?).to be true
|
|
80
|
+
expect(s3_bucket.object('rrr/users/file.ab').exists?).to be true
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it "deletes local files afterwards" do
|
|
84
|
+
file_manager.upload_csv(files.map(&:path))
|
|
85
|
+
file_names.each do |file|
|
|
86
|
+
expect(File.exists?("/tmp/#{file}")).to be false
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe RailsRedshiftReplicator::Importers::Base do
|
|
4
|
+
let(:replication) { build :redshift_replication, target_table: "users", key: 'rrr/users/users_1.csv.0'}
|
|
5
|
+
let(:importer) { RailsRedshiftReplicator::Importers::IdentityReplicator.new(replication) }
|
|
6
|
+
|
|
7
|
+
describe '#evaluate_history_cap' do
|
|
8
|
+
before { 10.times {create :redshift_replication, source_table: 'users'} }
|
|
9
|
+
let!(:keep1) { create :redshift_replication, source_table: 'users' }
|
|
10
|
+
let!(:keep2) { create :redshift_replication, source_table: 'users' }
|
|
11
|
+
let!(:keep3) { create :redshift_replication, source_table: 'users' }
|
|
12
|
+
before { RailsRedshiftReplicator.history_cap = 3 }
|
|
13
|
+
it 'keeps only the records allowed by the history cap' do
|
|
14
|
+
importer.evaluate_history_cap
|
|
15
|
+
expect(RailsRedshiftReplicator::Replication.where(source_table: 'users')).to contain_exactly(keep1, keep2, keep3)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
describe "#copy" do
|
|
19
|
+
let(:exporter) { user_exporter }
|
|
20
|
+
before(:all) do
|
|
21
|
+
recreate_users_table
|
|
22
|
+
file_manager = RailsRedshiftReplicator::FileManager.new
|
|
23
|
+
# exporter = RailsRedshiftReplicator::Exporters::Base
|
|
24
|
+
file_manager.s3_client.put_object key: RailsRedshiftReplicator::FileManager.s3_file_key('users','valid_user.csv'), body: replication_file("valid_user.csv"), bucket: RailsRedshiftReplicator.s3_bucket_params[:bucket]
|
|
25
|
+
file_manager.s3_client.put_object key: RailsRedshiftReplicator::FileManager.s3_file_key('users','invalid_user.csv'), body: replication_file("invalid_user.csv"), bucket: RailsRedshiftReplicator.s3_bucket_params[:bucket]
|
|
26
|
+
# exporter.replication_bucket.files.create key: exporter.s3_file_key('users','invalid_user.csv'), body: replication_file("invalid_user.csv")
|
|
27
|
+
end
|
|
28
|
+
context "with valid file" do
|
|
29
|
+
before { importer.replication.key = RailsRedshiftReplicator::FileManager.s3_file_key('users','valid_user.csv')}
|
|
30
|
+
context "when flag as imported option is set to true" do
|
|
31
|
+
let(:options) { {mark_as_imported: true} }
|
|
32
|
+
it "flags as imported" do
|
|
33
|
+
expect{ importer.copy(importer.replication.target_table, options) }.to change(importer.replication, :state).to("imported")
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
context "when option to flag as imported is false" do
|
|
37
|
+
let(:options) { {mark_as_imported: false} }
|
|
38
|
+
it "doesn't flag as imported" do
|
|
39
|
+
expect{importer.copy(importer.replication.target_table, options)}.not_to change(importer.replication, :state)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
context "with invalid file" do
|
|
44
|
+
let(:options) { {mark_as_imported: true} }
|
|
45
|
+
before { importer.replication.key = RailsRedshiftReplicator::FileManager.s3_file_key('users','invalid_user.csv')}
|
|
46
|
+
it "finds error on redshift" do
|
|
47
|
+
expect(importer).to receive(:get_redshift_error)
|
|
48
|
+
importer.copy(importer.replication.target_table, options)
|
|
49
|
+
end
|
|
50
|
+
it "notifies error" do
|
|
51
|
+
expect(importer).to receive(:notify_error)
|
|
52
|
+
importer.copy(importer.replication.target_table, options)
|
|
53
|
+
end
|
|
54
|
+
it "doesn't drop target table on error by default" do
|
|
55
|
+
expect(importer).not_to receive(:drop_table)
|
|
56
|
+
importer.copy(importer.replication.target_table, options)
|
|
57
|
+
end
|
|
58
|
+
context 'when can drop temporary table on error' do
|
|
59
|
+
let(:options) { {mark_as_imported: true, can_drop_target_on_error: true} }
|
|
60
|
+
it 'drops table' do
|
|
61
|
+
expect(importer).to receive(:drop_table)
|
|
62
|
+
importer.copy(importer.replication.target_table, options)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
describe "#import_file" do
|
|
69
|
+
it "returns s3 location for file" do
|
|
70
|
+
expect(importer.import_file).to eq('s3://rrr-s3-test/rrr/users/users_1.csv.0')
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
describe "#copy_statement" do
|
|
75
|
+
before do
|
|
76
|
+
allow(importer).to receive(:import_file).and_return('users')
|
|
77
|
+
allow(RailsRedshiftReplicator).to receive(:aws_credentials).and_return({key: 1, secret: 2})
|
|
78
|
+
end
|
|
79
|
+
let(:statement) { "COPY users from 'users' REGION 'us-east-1' credentials 'aws_access_key_id=1;aws_secret_access_key=2' maxerror 0 acceptinvchars STATUPDATE true CSV"}
|
|
80
|
+
it "returns sql to execute COPY" do
|
|
81
|
+
expect(importer.copy_statement("users")).to eq(statement)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
describe "#drop_table" do
|
|
86
|
+
let(:sql) { "drop table if exists table_name" }
|
|
87
|
+
it "drops the temporary table created for the replication" do
|
|
88
|
+
expect(RailsRedshiftReplicator.connection).to receive(:exec).with(sql).once
|
|
89
|
+
importer.drop_table("table_name")
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
describe "#temporary_table_name" do
|
|
94
|
+
it "returns a random name for the temporary table" do
|
|
95
|
+
Timecop.freeze do
|
|
96
|
+
now = Time.now.to_i
|
|
97
|
+
expect(importer.temporary_table_name).to eq("temp_users_#{now}")
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe 'RailsRedshiftReplicator::Importers::FullReplicator' do
|
|
4
|
+
let(:exporter) { RailsRedshiftReplicator::Exporters::Base }
|
|
5
|
+
let(:file_manager) { RailsRedshiftReplicator::FileManager.new }
|
|
6
|
+
describe "import" do
|
|
7
|
+
before(:all) { recreate_tags_users_table }
|
|
8
|
+
let!(:replication) do
|
|
9
|
+
create :redshift_replication,
|
|
10
|
+
target_table: 'tags_users',
|
|
11
|
+
key: RailsRedshiftReplicator::FileManager.s3_file_key('tags_users','valid_tags_users.csv'),
|
|
12
|
+
state: 'uploaded',
|
|
13
|
+
replication_type: 'FullReplicator',
|
|
14
|
+
export_format: 'csv'
|
|
15
|
+
end
|
|
16
|
+
let(:full_importer) { RailsRedshiftReplicator::Importers::FullReplicator.new(replication) }
|
|
17
|
+
before do
|
|
18
|
+
file_manager.s3_client.put_object key: RailsRedshiftReplicator::FileManager.s3_file_key('tags_users','valid_tags_users.csv'),
|
|
19
|
+
body: replication_file("valid_tags_users.csv"),
|
|
20
|
+
bucket: RailsRedshiftReplicator.s3_bucket_params[:bucket]
|
|
21
|
+
end
|
|
22
|
+
it "performs import" do
|
|
23
|
+
expect { full_importer.import; replication.reload }.to change(replication, :state).from("uploaded").to("imported")
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe 'RailsRedshiftReplicator::Importers::IdentityReplicator' do
|
|
4
|
+
let(:exporter) { RailsRedshiftReplicator::Exporters::Base }
|
|
5
|
+
let(:file_manager) { RailsRedshiftReplicator::FileManager.new }
|
|
6
|
+
describe "import" do
|
|
7
|
+
before(:all) { recreate_users_table }
|
|
8
|
+
let!(:replication) do
|
|
9
|
+
create :redshift_replication,
|
|
10
|
+
target_table: 'users',
|
|
11
|
+
key: RailsRedshiftReplicator::FileManager.s3_file_key('users','valid_user.csv'),
|
|
12
|
+
state: 'uploaded',
|
|
13
|
+
replication_type: 'IdentityReplicator',
|
|
14
|
+
export_format: 'csv'
|
|
15
|
+
end
|
|
16
|
+
let(:user_importer) { RailsRedshiftReplicator::Importers::IdentityReplicator.new(replication) }
|
|
17
|
+
before do
|
|
18
|
+
file_manager.s3_client.put_object key: RailsRedshiftReplicator::FileManager.s3_file_key('users','valid_user.csv'),
|
|
19
|
+
body: replication_file("valid_user.csv"),
|
|
20
|
+
bucket: RailsRedshiftReplicator.s3_bucket_params[:bucket]
|
|
21
|
+
end
|
|
22
|
+
it "performs import" do
|
|
23
|
+
expect { user_importer.import; replication.reload }.to change(replication, :state).from("uploaded").to("imported")
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe 'RailsRedshiftReplicator::Importers::TimedReplicator' do
|
|
4
|
+
let(:exporter) { RailsRedshiftReplicator::Exporters::Base }
|
|
5
|
+
let(:file_manager) { RailsRedshiftReplicator::FileManager.new }
|
|
6
|
+
describe "import" do
|
|
7
|
+
before(:all) { recreate_posts_table }
|
|
8
|
+
let!(:replication) do
|
|
9
|
+
create :redshift_replication,
|
|
10
|
+
target_table: 'posts',
|
|
11
|
+
key: RailsRedshiftReplicator::FileManager.s3_file_key('posts','valid_post.csv'),
|
|
12
|
+
state: 'uploaded',
|
|
13
|
+
replication_type: 'TimedReplicator',
|
|
14
|
+
export_format: 'csv'
|
|
15
|
+
end
|
|
16
|
+
let(:post_importer) { RailsRedshiftReplicator::Importers::TimedReplicator.new(replication) }
|
|
17
|
+
before do
|
|
18
|
+
file_manager.s3_client.put_object key: RailsRedshiftReplicator::FileManager.s3_file_key('posts','valid_post.csv'),
|
|
19
|
+
body: replication_file("valid_post.csv"),
|
|
20
|
+
bucket: RailsRedshiftReplicator.s3_bucket_params[:bucket]
|
|
21
|
+
end
|
|
22
|
+
it "performs import" do
|
|
23
|
+
expect { post_importer.import; replication.reload }.to change(replication, :state).from("uploaded").to("imported")
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe RailsRedshiftReplicator::Model::Extension do
|
|
4
|
+
before { RailsRedshiftReplicator.debug_mode = true }
|
|
5
|
+
|
|
6
|
+
describe '.export', focus: true do
|
|
7
|
+
it 'forwards the command to its replicable class' do
|
|
8
|
+
expect(RailsRedshiftReplicator.replicables['users']).to receive(:export)
|
|
9
|
+
User.rrr_export
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
describe '.import', focus: true do
|
|
13
|
+
it 'forwards the command to its replicable class' do
|
|
14
|
+
expect(RailsRedshiftReplicator.replicables['users']).to receive(:import)
|
|
15
|
+
User.rrr_import
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
describe '.replicate', focus: true do
|
|
19
|
+
it 'forwards the command to its replicable class' do
|
|
20
|
+
expect(RailsRedshiftReplicator.replicables['users']).to receive(:replicate)
|
|
21
|
+
User.rrr_replicate
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
describe '.vacuum', focus: true do
|
|
25
|
+
it 'forwards the command to its replicable class' do
|
|
26
|
+
expect(RailsRedshiftReplicator.replicables['users']).to receive(:vacuum)
|
|
27
|
+
User.rrr_vacuum
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
describe '.analyze', focus: true do
|
|
31
|
+
it 'forwards the command to its replicable class' do
|
|
32
|
+
expect(RailsRedshiftReplicator.replicables['users']).to receive(:analyze)
|
|
33
|
+
User.rrr_analyze
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
describe RailsRedshiftReplicator::Replicable do
|
|
3
|
+
|
|
4
|
+
describe 'initialization' do
|
|
5
|
+
context 'when all options are given' do
|
|
6
|
+
let(:replicable) { RailsRedshiftReplicator::Replicable.new(:identity_replicator, source_table: 'users', target_table: 'custom_users', replication_field: 'created_at') }
|
|
7
|
+
it 'has the right properties' do
|
|
8
|
+
expect(replicable.source_table).to eq 'users'
|
|
9
|
+
expect(replicable.target_table).to eq 'custom_users'
|
|
10
|
+
expect(replicable.replication_field).to eq 'created_at'
|
|
11
|
+
expect(replicable.replication_type).to eq :identity_replicator
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
context 'with default options(idendity)' do
|
|
15
|
+
let(:replicable) { RailsRedshiftReplicator::Replicable.new(:identity_replicator, source_table: 'users') }
|
|
16
|
+
it 'has the right properties' do
|
|
17
|
+
expect(replicable.source_table).to eq 'users'
|
|
18
|
+
expect(replicable.target_table).to eq 'users'
|
|
19
|
+
expect(replicable.replication_field).to eq 'id'
|
|
20
|
+
expect(replicable.replication_type).to eq :identity_replicator
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
context 'with default options(timed)' do
|
|
24
|
+
let(:replicable) { RailsRedshiftReplicator::Replicable.new(:timed_replicator, source_table: 'users') }
|
|
25
|
+
it 'has the right properties' do
|
|
26
|
+
expect(replicable.source_table).to eq 'users'
|
|
27
|
+
expect(replicable.target_table).to eq 'users'
|
|
28
|
+
expect(replicable.replication_field).to eq 'updated_at'
|
|
29
|
+
expect(replicable.replication_type).to eq :timed_replicator
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
context 'with default options(full)' do
|
|
33
|
+
let(:replicable) { RailsRedshiftReplicator::Replicable.new(:full_replicator, source_table: 'users') }
|
|
34
|
+
it 'has the right properties' do
|
|
35
|
+
expect(replicable.source_table).to eq 'users'
|
|
36
|
+
expect(replicable.target_table).to eq 'users'
|
|
37
|
+
expect(replicable.replication_field).to be_nil
|
|
38
|
+
expect(replicable.replication_type).to eq :full_replicator
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
describe '#exporter_class' do
|
|
44
|
+
context 'when exists' do
|
|
45
|
+
let(:replicable) { RailsRedshiftReplicator::Replicable.new(:identity_replicator, source_table: 'users') }
|
|
46
|
+
it 'returns exporter class' do
|
|
47
|
+
expect(replicable.exporter_class).to eq RailsRedshiftReplicator::Exporters::IdentityReplicator
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
context "when doesn't exist" do
|
|
51
|
+
let(:replicable) { RailsRedshiftReplicator::Replicable.new(:none, source_table: 'users') }
|
|
52
|
+
it 'raises error' do
|
|
53
|
+
expect{replicable.exporter_class}.to raise_error(StandardError)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
describe '#importer_class' do
|
|
58
|
+
context 'when exists' do
|
|
59
|
+
let(:replicable) { RailsRedshiftReplicator::Replicable.new(:identity_replicator, source_table: 'users') }
|
|
60
|
+
it 'returns exporter class' do
|
|
61
|
+
expect(replicable.importer_class).to eq RailsRedshiftReplicator::Importers::IdentityReplicator
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
context "when doesn't exist" do
|
|
65
|
+
let(:replicable) { RailsRedshiftReplicator::Replicable.new(:none, source_table: 'users') }
|
|
66
|
+
it 'raises error' do
|
|
67
|
+
expect{replicable.importer_class}.to raise_error(StandardError)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
describe '#export' do
|
|
73
|
+
let(:replicable) { RailsRedshiftReplicator::Replicable.new(:identity_replicator, source_table: 'users') }
|
|
74
|
+
context 'without previous replications' do
|
|
75
|
+
it 'calls the correspondent exporter class' do
|
|
76
|
+
allow(RailsRedshiftReplicator::Exporters::IdentityReplicator).to receive_message_chain(:new, :export_and_upload)
|
|
77
|
+
expect(RailsRedshiftReplicator::Exporters::IdentityReplicator).to receive(:new).with(replicable, nil)
|
|
78
|
+
replicable.export
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
context 'with previous imported replication' do
|
|
82
|
+
let!(:previous_replication) { create :redshift_replication, source_table: 'users', replication_type: 'identity_replicator', state: 'imported'}
|
|
83
|
+
it 'calls the correspondent exporter class' do
|
|
84
|
+
allow(RailsRedshiftReplicator::Exporters::IdentityReplicator).to receive_message_chain(:new, :export_and_upload)
|
|
85
|
+
expect(RailsRedshiftReplicator::Exporters::IdentityReplicator).to receive(:new).with(replicable, nil)
|
|
86
|
+
replicable.export
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
context 'with previous uploading replication' do
|
|
90
|
+
let!(:previous_replication) { create :redshift_replication, source_table: 'users', replication_type: 'identity_replicator', state: 'uploading' }
|
|
91
|
+
context 'and max_retries is defined' do
|
|
92
|
+
before { RailsRedshiftReplicator.max_retries = 0 }
|
|
93
|
+
context 'and max_retries was reached' do
|
|
94
|
+
it 'calls the correspondent exporter class without the previous replication' do
|
|
95
|
+
allow(RailsRedshiftReplicator::Exporters::IdentityReplicator).to receive_message_chain(:new, :export_and_upload)
|
|
96
|
+
expect(RailsRedshiftReplicator::Exporters::IdentityReplicator).to receive(:new).with(replicable, nil)
|
|
97
|
+
replicable.export
|
|
98
|
+
end
|
|
99
|
+
it 'cancels the previous replication' do
|
|
100
|
+
allow(RailsRedshiftReplicator::Exporters::IdentityReplicator).to receive_message_chain(:new, :export_and_upload)
|
|
101
|
+
replicable.export
|
|
102
|
+
expect(previous_replication.reload).to be_canceled
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
context "and max_retries wasn't reached" do
|
|
106
|
+
before { RailsRedshiftReplicator.max_retries = 1 }
|
|
107
|
+
it 'calls the correspondent exporter class with previous replication' do
|
|
108
|
+
allow(RailsRedshiftReplicator::Exporters::IdentityReplicator).to receive_message_chain(:new, :export_and_upload)
|
|
109
|
+
expect(RailsRedshiftReplicator::Exporters::IdentityReplicator).to receive(:new).with(replicable, previous_replication)
|
|
110
|
+
replicable.export
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
context 'and max_retries is nil' do
|
|
115
|
+
before { RailsRedshiftReplicator.max_retries = nil }
|
|
116
|
+
it 'calls the correspondent exporter class with previous replication' do
|
|
117
|
+
allow(RailsRedshiftReplicator::Exporters::IdentityReplicator).to receive_message_chain(:new, :export_and_upload)
|
|
118
|
+
expect(RailsRedshiftReplicator::Exporters::IdentityReplicator).to receive(:new).with(replicable, previous_replication)
|
|
119
|
+
replicable.export
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
end
|
|
124
|
+
context 'with previous importing replication' do
|
|
125
|
+
before { RailsRedshiftReplicator.max_retries = nil }
|
|
126
|
+
let!(:previous_replication) { create :redshift_replication, source_table: 'users', replication_type: 'identity_replicator', state: 'importing' }
|
|
127
|
+
it 'calls the correspondent importer class' do
|
|
128
|
+
allow(RailsRedshiftReplicator::Importers::IdentityReplicator).to receive_message_chain(:new, :import)
|
|
129
|
+
expect(RailsRedshiftReplicator::Importers::IdentityReplicator).to receive(:new).with(previous_replication)
|
|
130
|
+
replicable.export
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
describe 'import' do
|
|
136
|
+
let(:replicable) { RailsRedshiftReplicator::Replicable.new(:identity_replicator, source_table: 'users') }
|
|
137
|
+
let!(:replication1) { create :redshift_replication, source_table: 'users', state: 'imported' }
|
|
138
|
+
let!(:replication2) { create :redshift_replication, source_table: 'posts' }
|
|
139
|
+
context 'without last replication' do
|
|
140
|
+
it 'does not call import' do
|
|
141
|
+
allow(RailsRedshiftReplicator::Importers::IdentityReplicator).to receive_message_chain(:new, :import)
|
|
142
|
+
expect(RailsRedshiftReplicator::Importers::IdentityReplicator).not_to receive(:new)
|
|
143
|
+
replicable.import
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
context 'with last replication' do
|
|
147
|
+
context 'and last replication is uploaded' do
|
|
148
|
+
let!(:replication) { create :redshift_replication, source_table: 'users', state: 'uploaded' }
|
|
149
|
+
it 'calls import with the last uploaded replication' do
|
|
150
|
+
allow(RailsRedshiftReplicator::Importers::IdentityReplicator).to receive_message_chain(:new, :import)
|
|
151
|
+
expect(RailsRedshiftReplicator::Importers::IdentityReplicator).to receive(:new).with(replication)
|
|
152
|
+
replicable.import
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
context 'and last replication is imported' do
|
|
156
|
+
it 'does not call import' do
|
|
157
|
+
allow(RailsRedshiftReplicator::Importers::IdentityReplicator).to receive_message_chain(:new, :import)
|
|
158
|
+
expect(RailsRedshiftReplicator::Importers::IdentityReplicator).not_to receive(:new)
|
|
159
|
+
replicable.import
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
context 'and last replication is importing' do
|
|
163
|
+
let!(:replication) { create :redshift_replication, source_table: 'users', state: 'importing' }
|
|
164
|
+
context 'and max_retries is not set' do
|
|
165
|
+
before { RailsRedshiftReplicator.max_retries = nil }
|
|
166
|
+
it 'calls the correspondent importer class resuming the replication' do
|
|
167
|
+
allow(RailsRedshiftReplicator::Importers::IdentityReplicator).to receive_message_chain(:new, :import)
|
|
168
|
+
expect(RailsRedshiftReplicator::Importers::IdentityReplicator).to receive(:new).with(replication)
|
|
169
|
+
replicable.import
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
context 'and max_retries is set' do
|
|
173
|
+
context "but wasn't reached" do
|
|
174
|
+
before { RailsRedshiftReplicator.max_retries = 2 }
|
|
175
|
+
it 'calls the correspondent importer class resuming the replication' do
|
|
176
|
+
allow(RailsRedshiftReplicator::Importers::IdentityReplicator).to receive_message_chain(:new, :import)
|
|
177
|
+
expect(RailsRedshiftReplicator::Importers::IdentityReplicator).to receive(:new).with(replication)
|
|
178
|
+
replicable.import
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
context 'and was reached' do
|
|
182
|
+
before { RailsRedshiftReplicator.max_retries = 0 }
|
|
183
|
+
it 'calls the correspondent importer class resuming the replication' do
|
|
184
|
+
allow(RailsRedshiftReplicator::Importers::IdentityReplicator).to receive_message_chain(:new, :import)
|
|
185
|
+
expect(RailsRedshiftReplicator::Importers::IdentityReplicator).not_to receive(:new).with(replication)
|
|
186
|
+
replicable.import
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
describe '#reset_last_record' do
|
|
195
|
+
let(:replicable) { RailsRedshiftReplicator::Replicable.new(:identity_replicator, source_table: 'users') }
|
|
196
|
+
context 'when there is no previous imported replications' do
|
|
197
|
+
it 'does not try to update' do
|
|
198
|
+
expect_any_instance_of(RailsRedshiftReplicator::Replication).not_to receive(:update_attribute)
|
|
199
|
+
replicable.reset_last_record
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
context 'when there is a previous imported replication' do
|
|
203
|
+
let!(:replication) { create :redshift_replication, source_table: 'users', state:'imported', last_record: '2' }
|
|
204
|
+
it 'resets most recent imported replication last record to perform full replication afterwards' do
|
|
205
|
+
replicable.reset_last_record
|
|
206
|
+
expect(replication.reload.last_record).to be_nil
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
describe '#vacuum' do
|
|
213
|
+
let(:replicable) { RailsRedshiftReplicator::Replicable.new(:identity_replicator, source_table: 'users', target_table: 'custom_users') }
|
|
214
|
+
it 'calls the central vacuum method' do
|
|
215
|
+
expect(RailsRedshiftReplicator).to receive(:vacuum).with('custom_users')
|
|
216
|
+
replicable.vacuum
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
describe '#analyze' do
|
|
221
|
+
let(:replicable) { RailsRedshiftReplicator::Replicable.new(:identity_replicator, source_table: 'users', target_table: 'custom_users') }
|
|
222
|
+
it 'calls the central vacuum method' do
|
|
223
|
+
expect(RailsRedshiftReplicator).to receive(:analyze).with('custom_users')
|
|
224
|
+
replicable.analyze
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
end
|