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.
Files changed (116) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +3 -0
  4. data/Rakefile +34 -0
  5. data/app/assets/javascripts/rails_redshift_replicator/application.js +13 -0
  6. data/app/assets/stylesheets/rails_redshift_replicator/application.css +15 -0
  7. data/app/controllers/rails_redshift_replicator/application_controller.rb +5 -0
  8. data/app/helpers/rails_redshift_replicator/application_helper.rb +4 -0
  9. data/app/models/rails_redshift_replicator/replication.rb +98 -0
  10. data/app/views/layouts/rails_redshift_replicator/application.html.erb +14 -0
  11. data/config/locales/rails_redshift_replicator.en.yml +20 -0
  12. data/config/routes.rb +2 -0
  13. data/db/migrate/20160503214955_create_rails_redshift_replicator_replications.rb +24 -0
  14. data/db/migrate/20160509193335_create_table_rails_redshift_replicator_deleted_ids.rb +8 -0
  15. data/lib/generators/rails_redshift_replicator/install_generator.rb +25 -0
  16. data/lib/generators/templates/rails_redshift_replicator.rb +74 -0
  17. data/lib/rails_redshift_replicator.rb +229 -0
  18. data/lib/rails_redshift_replicator/adapters/generic.rb +40 -0
  19. data/lib/rails_redshift_replicator/adapters/mysql2.rb +22 -0
  20. data/lib/rails_redshift_replicator/adapters/postgresql.rb +37 -0
  21. data/lib/rails_redshift_replicator/adapters/sqlite.rb +27 -0
  22. data/lib/rails_redshift_replicator/deleter.rb +67 -0
  23. data/lib/rails_redshift_replicator/engine.rb +14 -0
  24. data/lib/rails_redshift_replicator/exporters/base.rb +215 -0
  25. data/lib/rails_redshift_replicator/exporters/full_replicator.rb +9 -0
  26. data/lib/rails_redshift_replicator/exporters/identity_replicator.rb +9 -0
  27. data/lib/rails_redshift_replicator/exporters/timed_replicator.rb +9 -0
  28. data/lib/rails_redshift_replicator/file_manager.rb +134 -0
  29. data/lib/rails_redshift_replicator/importers/base.rb +158 -0
  30. data/lib/rails_redshift_replicator/importers/full_replicator.rb +17 -0
  31. data/lib/rails_redshift_replicator/importers/identity_replicator.rb +15 -0
  32. data/lib/rails_redshift_replicator/importers/timed_replicator.rb +18 -0
  33. data/lib/rails_redshift_replicator/model/extension.rb +45 -0
  34. data/lib/rails_redshift_replicator/model/hair_trigger_extension.rb +8 -0
  35. data/lib/rails_redshift_replicator/replicable.rb +143 -0
  36. data/lib/rails_redshift_replicator/rlogger.rb +12 -0
  37. data/lib/rails_redshift_replicator/tools/analyze.rb +18 -0
  38. data/lib/rails_redshift_replicator/tools/vacuum.rb +77 -0
  39. data/lib/rails_redshift_replicator/version.rb +3 -0
  40. data/lib/tasks/rails_redshift_replicator_tasks.rake +4 -0
  41. data/spec/dummy/README.rdoc +28 -0
  42. data/spec/dummy/Rakefile +6 -0
  43. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  44. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  45. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  46. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  47. data/spec/dummy/app/models/post.rb +4 -0
  48. data/spec/dummy/app/models/tag.rb +4 -0
  49. data/spec/dummy/app/models/user.rb +5 -0
  50. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  51. data/spec/dummy/bin/bundle +3 -0
  52. data/spec/dummy/bin/rails +4 -0
  53. data/spec/dummy/bin/rake +4 -0
  54. data/spec/dummy/bin/setup +29 -0
  55. data/spec/dummy/config.ru +4 -0
  56. data/spec/dummy/config/application.rb +26 -0
  57. data/spec/dummy/config/boot.rb +5 -0
  58. data/spec/dummy/config/database.yml +37 -0
  59. data/spec/dummy/config/environment.rb +5 -0
  60. data/spec/dummy/config/environments/development.rb +41 -0
  61. data/spec/dummy/config/environments/production.rb +79 -0
  62. data/spec/dummy/config/environments/test.rb +42 -0
  63. data/spec/dummy/config/initializers/assets.rb +11 -0
  64. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  65. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  66. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  67. data/spec/dummy/config/initializers/inflections.rb +16 -0
  68. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  69. data/spec/dummy/config/initializers/rails_redshift_replicator.rb +59 -0
  70. data/spec/dummy/config/initializers/session_store.rb +3 -0
  71. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  72. data/spec/dummy/config/locales/en.yml +23 -0
  73. data/spec/dummy/config/locales/rails_redshift_replicator.en.yml +19 -0
  74. data/spec/dummy/config/routes.rb +4 -0
  75. data/spec/dummy/config/secrets.yml +22 -0
  76. data/spec/dummy/db/development.sqlite3 +0 -0
  77. data/spec/dummy/db/migrate/20160504120421_create_test_tables.rb +40 -0
  78. data/spec/dummy/db/migrate/20160509225445_create_triggers_posts_delete_or_tags_delete_or_users_delete.rb +33 -0
  79. data/spec/dummy/db/migrate/20160511000937_create_rails_redshift_replicator_replications.rails_redshift_replicator.rb +25 -0
  80. data/spec/dummy/db/migrate/20160511000938_create_table_rails_redshift_replicator_deleted_ids.rails_redshift_replicator.rb +9 -0
  81. data/spec/dummy/db/schema.rb +99 -0
  82. data/spec/dummy/db/test.sqlite3 +0 -0
  83. data/spec/dummy/log/development.log +1623 -0
  84. data/spec/dummy/log/test.log +95379 -0
  85. data/spec/dummy/public/404.html +67 -0
  86. data/spec/dummy/public/422.html +67 -0
  87. data/spec/dummy/public/500.html +66 -0
  88. data/spec/dummy/public/favicon.ico +0 -0
  89. data/spec/dummy/rails_redshift_replicator_development +0 -0
  90. data/spec/factories/rails_redshift_replicator_replications.rb +31 -0
  91. data/spec/integration/rails_redshift_replicator_spec.rb +148 -0
  92. data/spec/integration/setup_spec.rb +149 -0
  93. data/spec/lib/rails_redshift_replicator/deleter_spec.rb +90 -0
  94. data/spec/lib/rails_redshift_replicator/exporters/base_spec.rb +326 -0
  95. data/spec/lib/rails_redshift_replicator/exporters/full_replicator_spec.rb +33 -0
  96. data/spec/lib/rails_redshift_replicator/exporters/identity_replicator_spec.rb +40 -0
  97. data/spec/lib/rails_redshift_replicator/exporters/timed_replicator_spec.rb +43 -0
  98. data/spec/lib/rails_redshift_replicator/file_manager_spec.rb +90 -0
  99. data/spec/lib/rails_redshift_replicator/importers/base_spec.rb +102 -0
  100. data/spec/lib/rails_redshift_replicator/importers/full_replicator_spec.rb +27 -0
  101. data/spec/lib/rails_redshift_replicator/importers/identity_replicator_spec.rb +26 -0
  102. data/spec/lib/rails_redshift_replicator/importers/timed_replicator_spec.rb +26 -0
  103. data/spec/lib/rails_redshift_replicator/model/extension_spec.rb +36 -0
  104. data/spec/lib/rails_redshift_replicator/replicable_spec.rb +230 -0
  105. data/spec/lib/rails_redshift_replicator/rlogger_spec.rb +22 -0
  106. data/spec/lib/rails_redshift_replicator/tools/analyze_spec.rb +15 -0
  107. data/spec/lib/rails_redshift_replicator/tools/vacuum_spec.rb +65 -0
  108. data/spec/lib/rails_redshift_replicator_spec.rb +110 -0
  109. data/spec/models/rails_redshift_replicator/replication_spec.rb +104 -0
  110. data/spec/spec_helper.rb +36 -0
  111. data/spec/support/csv/invalid_user.csv +12 -0
  112. data/spec/support/csv/valid_post.csv +2 -0
  113. data/spec/support/csv/valid_tags_users.csv +1 -0
  114. data/spec/support/csv/valid_user.csv +2 -0
  115. data/spec/support/rails_redshift_replicator_helpers.rb +95 -0
  116. 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