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
|
+
module RailsRedshiftReplicator
|
|
2
|
+
module Adapters
|
|
3
|
+
class Generic
|
|
4
|
+
def initialize(ar_client)
|
|
5
|
+
@ar_client = ar_client
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
# Returns the AR connection
|
|
9
|
+
def connection
|
|
10
|
+
@connection ||= @ar_client
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Executes query using AR Adapter
|
|
14
|
+
# @param sql [String] sql to execute
|
|
15
|
+
def query_command(sql)
|
|
16
|
+
connection.query sql
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @return [String] id or timestamp
|
|
20
|
+
def last_record_query_command(sql)
|
|
21
|
+
connection.exec_query(sql).first['_last_record']
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Writes query results to a file
|
|
25
|
+
# @param file_path [String] path to output
|
|
26
|
+
# @param query_result [#each] Resultset from the query_command
|
|
27
|
+
# @return [Integer] number of records
|
|
28
|
+
def write(file_path, query_result)
|
|
29
|
+
line_number = 0
|
|
30
|
+
CSV.open(file_path, "w") do |csv|
|
|
31
|
+
query_result.each do |row|
|
|
32
|
+
csv << row.map{ |field| field.is_a?(String) ? field.gsub("\n", " ") : field }
|
|
33
|
+
line_number+=1
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
line_number
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module RailsRedshiftReplicator
|
|
2
|
+
module Adapters
|
|
3
|
+
class Mysql2 < Generic
|
|
4
|
+
|
|
5
|
+
# Executes query in stream mode to optimize memory usage, using Mysql2 driver.
|
|
6
|
+
# @param sql [String] sql to execute
|
|
7
|
+
def query_command(sql)
|
|
8
|
+
connection.query(sql, stream: true)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# @see RailsRedshiftReplicator::Adapters::Generic#last_record_query_command
|
|
12
|
+
def last_record_query_command(sql)
|
|
13
|
+
connection.query(sql, cast: false).first[0] rescue nil
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Returns mysql2 driver so that we may perform query with the stream option
|
|
17
|
+
def connection
|
|
18
|
+
@connection ||= @ar_client.instance_variable_get("@connection")
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module RailsRedshiftReplicator
|
|
2
|
+
module Adapters
|
|
3
|
+
class PostgreSQL < Generic
|
|
4
|
+
|
|
5
|
+
# Executes query in stream mode to optimize memory usage, using pg driver.
|
|
6
|
+
# @param sql [String] sql to execute
|
|
7
|
+
def query_command(sql)
|
|
8
|
+
connection.send_query(sql)
|
|
9
|
+
connection.set_single_row_mode
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def connection
|
|
13
|
+
@connection ||= @ar_client.instance_variable_get("@connection")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @see RailsRedshiftReplicator::Adapters::Generic#last_record_query_command
|
|
17
|
+
def last_record_query_command(sql)
|
|
18
|
+
@ar_client.exec_query(sql).first['_last_record']
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Writes query results to a file
|
|
22
|
+
# @param file_path [String] path to output
|
|
23
|
+
# @param query_result [#get_result] Resultset from the query_command
|
|
24
|
+
# @return [Integer] number of records
|
|
25
|
+
def write(file_path, query_result)
|
|
26
|
+
line_number = 0
|
|
27
|
+
CSV.open(file_path, "w") do |csv|
|
|
28
|
+
query_result.get_result.stream_each do |row|
|
|
29
|
+
csv << row.map{ |_,field| field.is_a?(String) ? field.gsub("\n", " ") : field }
|
|
30
|
+
line_number+=1
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
line_number
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module RailsRedshiftReplicator
|
|
2
|
+
module Adapters
|
|
3
|
+
class SQLite < Generic
|
|
4
|
+
|
|
5
|
+
# @param conn [ActiveRecord::ConnectionAdapter]
|
|
6
|
+
# @param sql [String] sql to execute
|
|
7
|
+
def query_command(sql)
|
|
8
|
+
connection.exec_query sql
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Writes query results to a file
|
|
12
|
+
# @param file_path [String] path to output
|
|
13
|
+
# @param query_result [#each] Resultset from the query_command
|
|
14
|
+
# @return [Integer] number of records
|
|
15
|
+
def write(file_path, query_result)
|
|
16
|
+
line_number = 0
|
|
17
|
+
CSV.open(file_path, "w") do |csv|
|
|
18
|
+
query_result.each do |row|
|
|
19
|
+
csv << row.map{ |_,field| field.is_a?(String) ? field.gsub("\n", " ") : field }
|
|
20
|
+
line_number+=1
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
line_number
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
module RailsRedshiftReplicator
|
|
2
|
+
class Deleter
|
|
3
|
+
attr_reader :replicable
|
|
4
|
+
def initialize(replicable)
|
|
5
|
+
@replicable = replicable
|
|
6
|
+
end
|
|
7
|
+
# Deletes ids on source database. This ensures that only the records deleted on target
|
|
8
|
+
# will be discarded on source
|
|
9
|
+
def purge_deleted
|
|
10
|
+
ActiveRecord::Base.connection.execute discard_deleted_statement
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Deletes records flagged for deletion on redshift
|
|
14
|
+
# @return [true, false] if deletion succeded
|
|
15
|
+
def delete_on_target
|
|
16
|
+
RailsRedshiftReplicator.connection.exec(delete_on_target_statement).result_status == 1
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Deletes records flagged for deletion on target and then delete the queue from source
|
|
20
|
+
def handle_delete_propagation
|
|
21
|
+
if replicable.tracking_deleted && has_deleted_ids?
|
|
22
|
+
RailsRedshiftReplicator.logger.info propagation_message(:propagating_deletes)
|
|
23
|
+
delete_on_target ? purge_deleted : RailsRedshiftReplicator.logger.error(propagation_message(:delete_propagation_error))
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def propagation_message(key)
|
|
28
|
+
I18n.t(key, table_name: replicable.source_table, count: deleted_ids.count, scope: :rails_redshift_replicator)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Retrives ids of objects enqueued for deletion for the replication source table
|
|
32
|
+
# @example
|
|
33
|
+
# deleted_ids #=> "1,2,3,4,5"
|
|
34
|
+
# @return [String] list of ids enqueued to delete on target
|
|
35
|
+
def deleted_ids
|
|
36
|
+
sql = <<-DR.squish
|
|
37
|
+
SELECT object_id
|
|
38
|
+
FROM rails_redshift_replicator_deleted_ids
|
|
39
|
+
WHERE source_table = '#{replicable.source_table}'
|
|
40
|
+
DR
|
|
41
|
+
ActiveRecord::Base.connection.execute(sql).map{ |r| r['object_id'] }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# If has objects to delete on target
|
|
45
|
+
# @return [true, false]
|
|
46
|
+
def has_deleted_ids?
|
|
47
|
+
deleted_ids.present?
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Builds the statement to perform a deletion on source
|
|
51
|
+
# @return [String] SQL statement
|
|
52
|
+
def discard_deleted_statement
|
|
53
|
+
sql = <<-DD.squish
|
|
54
|
+
DELETE FROM rails_redshift_replicator_deleted_ids
|
|
55
|
+
WHERE source_table = '#{replicable.source_table}'
|
|
56
|
+
AND object_id IN(#{deleted_ids.join(",")})
|
|
57
|
+
DD
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def delete_on_target_statement
|
|
61
|
+
sql = <<-DD.squish
|
|
62
|
+
DELETE FROM #{replicable.target_table}
|
|
63
|
+
WHERE id IN(#{deleted_ids.join(",")})
|
|
64
|
+
DD
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module RailsRedshiftReplicator
|
|
2
|
+
class Engine < ::Rails::Engine
|
|
3
|
+
isolate_namespace RailsRedshiftReplicator
|
|
4
|
+
|
|
5
|
+
config.generators do |g|
|
|
6
|
+
g.test_framework :rspec, :fixture => false
|
|
7
|
+
g.fixture_replacement :factory_girl, :dir => 'spec/factories'
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
initializer "rrr.initialisation" do |app|
|
|
11
|
+
# ActiveRecord::Base.send :include, RailsRedshiftReplicator::Model
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
require 'csv'
|
|
2
|
+
require 'rails_redshift_replicator/adapters/generic'
|
|
3
|
+
require 'rails_redshift_replicator/adapters/mysql2'
|
|
4
|
+
require 'rails_redshift_replicator/adapters/postgresql'
|
|
5
|
+
require 'rails_redshift_replicator/adapters/sqlite'
|
|
6
|
+
|
|
7
|
+
module RailsRedshiftReplicator
|
|
8
|
+
module Exporters
|
|
9
|
+
class Base
|
|
10
|
+
extend Forwardable
|
|
11
|
+
def_delegators :replicable, :replication_type, :source_table, :target_table, :replication_field, :exporter_class
|
|
12
|
+
attr_reader :replicable
|
|
13
|
+
attr_accessor :replication, :file_names, :errors
|
|
14
|
+
|
|
15
|
+
def initialize(replicable, current_replication = nil)
|
|
16
|
+
@replicable = replicable
|
|
17
|
+
@replication = current_replication
|
|
18
|
+
@file_names = nil
|
|
19
|
+
@errors = nil
|
|
20
|
+
check_target_table
|
|
21
|
+
check_index
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Exports and uploads selected records from the source_table
|
|
25
|
+
def export_and_upload(options = {})
|
|
26
|
+
RailsRedshiftReplicator::Deleter.new(replicable).handle_delete_propagation
|
|
27
|
+
files = export options
|
|
28
|
+
upload files
|
|
29
|
+
replication
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Lists indexes from source table
|
|
33
|
+
# @return [Array<String>] indexes from source table
|
|
34
|
+
def table_indexes
|
|
35
|
+
ActiveRecord::Base.connection.indexes(source_table).map{ |table| table.columns}.flatten | ["id"]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Verifies if the table has the recommended indexes to increase export performance.
|
|
39
|
+
# @return [true, false] if table has recommended indexes
|
|
40
|
+
def has_index?
|
|
41
|
+
replication_field.in? table_indexes
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Reports missing indexes
|
|
45
|
+
# @see #has_index?
|
|
46
|
+
def check_index
|
|
47
|
+
if !has_index? && replication_field.present?
|
|
48
|
+
RailsRedshiftReplicator.logger.warn I18n.t(:missing_indexes, replication_field: replication_field, table_name: source_table, scope: :rails_redshift_replicator)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Checks if target table exists on Redshift
|
|
53
|
+
# @return [true, false] if table exists
|
|
54
|
+
def check_target_table
|
|
55
|
+
unless fields_to_sync
|
|
56
|
+
message = I18n.t(:missing_table, table_name: target_table, scope: :rails_redshift_replicator)
|
|
57
|
+
RailsRedshiftReplicator.logger.error(message)
|
|
58
|
+
@errors = message
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Returns records do export
|
|
63
|
+
# @param from_record [Integer] initial record
|
|
64
|
+
# When the exporter type is identity, the record is an id or equivalent
|
|
65
|
+
# When the exporter type is timed, the record is the timestamp converted to epoch (date.to_i)
|
|
66
|
+
# @param options [Hash]
|
|
67
|
+
# @option :options [Boolean] :counts_only if should only return record count instead of records
|
|
68
|
+
# @return records [Array<Array>] each entry has its fields returned as an array
|
|
69
|
+
# @note Query cache is disabled to decrease memory usage.
|
|
70
|
+
def records(from_record = nil, option = {})
|
|
71
|
+
@records ||= begin
|
|
72
|
+
ActiveRecord::Base.uncached do
|
|
73
|
+
query_command build_query_sql(from_record, option)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Builds the SQL string based on replicable and exporter parameters
|
|
79
|
+
# @return sql [String] sql string to perform the export query
|
|
80
|
+
def build_query_sql(from_record = nil, option = {})
|
|
81
|
+
sql = ""
|
|
82
|
+
sql += "SELECT #{option[:counts_only] ? "count(1) as records_count" : fields_to_sync.join(",")}"
|
|
83
|
+
sql += " FROM #{source_table} WHERE 1=1"
|
|
84
|
+
sql += " AND #{replication_field} > '#{from_record}'" if from_record
|
|
85
|
+
sql += " AND #{replication_field} <= '#{last_record}' OR #{replication_field} IS NULL" if last_record
|
|
86
|
+
sql
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Returns an instance of a export connection adapter based on ActiveRecord::Base.connection
|
|
90
|
+
# These adapters are required to perform query execution and record retrival in taking advantage of each db driver.
|
|
91
|
+
# @return adapter [#query_command, #write, #last_record_query_command]
|
|
92
|
+
def connection_adapter
|
|
93
|
+
@connection_adapter ||= begin
|
|
94
|
+
adapter_class = if ar_client.adapter_name.in? %w(Mysql2 PostgreSQL SQLite)
|
|
95
|
+
"RailsRedshiftReplicator::Adapters::#{ar_client.adapter_name}".constantize
|
|
96
|
+
else
|
|
97
|
+
RailsRedshiftReplicator::Adapters::Generic
|
|
98
|
+
end
|
|
99
|
+
adapter_class.new ar_client
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Performs the query to retrive records to export
|
|
104
|
+
# @param sql [String] sql to execute
|
|
105
|
+
def query_command(sql)
|
|
106
|
+
RailsRedshiftReplicator.logger.debug I18n.t(:executing_query, scope: :rails_redshift_replicator, sql: sql, adapter: connection_adapter.class.name)
|
|
107
|
+
connection_adapter.query_command sql
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Returns the ActiveRecord connection adapter for the current database
|
|
111
|
+
def ar_client
|
|
112
|
+
@ar_client ||= ActiveRecord::Base.connection
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Retuns the value of last_record from the most recent complete replication record.
|
|
116
|
+
# @return [Integer, nil] last_record
|
|
117
|
+
# @note Some replication strategies may not use a replication_field(eg: FullExporter), so #from_record will be nil
|
|
118
|
+
def from_record
|
|
119
|
+
return if replication_field.blank?
|
|
120
|
+
last_replication.try(:last_record)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Exports results to CSV
|
|
124
|
+
# @return [String] file name
|
|
125
|
+
def export(options = {})
|
|
126
|
+
return if errors.present?
|
|
127
|
+
slices = options[:slices] || RailsRedshiftReplicator.redshift_slices.to_i
|
|
128
|
+
format = options[:format] || RailsRedshiftReplicator.preferred_format
|
|
129
|
+
file_name = file_manager.temp_file_name
|
|
130
|
+
initialize_replication(file_name, format, slices)
|
|
131
|
+
export_start = replication.exporting
|
|
132
|
+
counts = file_manager.write_csv file_name, records(from_record)
|
|
133
|
+
unless counts > 0
|
|
134
|
+
RailsRedshiftReplicator.logger.info I18n.t(:no_new_records, table_name: source_table, scope: :rails_redshift_replicator)
|
|
135
|
+
self.replication = nil
|
|
136
|
+
return
|
|
137
|
+
end
|
|
138
|
+
RailsRedshiftReplicator.logger.info I18n.t(:exporting_results, counts: counts, scope: :rails_redshift_replicator)
|
|
139
|
+
file_manager.split_file file_name, counts
|
|
140
|
+
replication.exported! export_duration: (Time.now-export_start).ceil, record_count: counts
|
|
141
|
+
@file_names = Dir.glob "#{file_manager.local_file(file_name)}*"
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# @param [String] nome do arquivo
|
|
145
|
+
# Uploads file to s3
|
|
146
|
+
# @param [String] file name
|
|
147
|
+
def upload(files = file_names)
|
|
148
|
+
return if errors.present? || files.blank?
|
|
149
|
+
upload_start = replication.uploading!
|
|
150
|
+
replication.gzip? ? file_manager.upload_gzip(files) : file_manager.upload_csv(files)
|
|
151
|
+
replication.uploaded! upload_duration: (Time.now-upload_start).ceil
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def file_manager
|
|
155
|
+
@file_manager ||= RailsRedshiftReplicator::FileManager.new(self)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Initialize replication record without saving
|
|
159
|
+
# @return [RailsRedshiftReplicator::Replication]
|
|
160
|
+
def initialize_replication(file_name, format, slices)
|
|
161
|
+
attrs = init_replication_attrs(file_name, format, slices)
|
|
162
|
+
@replication = replication.present? ? replication.assign_attributes(attrs) : RailsRedshiftReplicator::Replication.new(attrs)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def init_replication_attrs(file_name, format, slices)
|
|
166
|
+
{
|
|
167
|
+
key: file_manager.file_key_in_format(file_name, format),
|
|
168
|
+
last_record: last_record.to_s,
|
|
169
|
+
state: 'exporting',
|
|
170
|
+
replication_type: replication_type,
|
|
171
|
+
source_table: source_table,
|
|
172
|
+
target_table: target_table,
|
|
173
|
+
slices: slices,
|
|
174
|
+
first_record: from_record,
|
|
175
|
+
export_format: format
|
|
176
|
+
}
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Returns the last replication from a given table
|
|
180
|
+
# @return [RailsRedshiftReplicator::Replication] last replication from a given table
|
|
181
|
+
def last_replication
|
|
182
|
+
@last_replication ||= RailsRedshiftReplicator::Replication.from_table(source_table).last
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Retuns the last record to export using the replication_field criteria.
|
|
186
|
+
# @note last_record is an Integer and is computed based on the replication field.
|
|
187
|
+
# @return [Integer] content of the
|
|
188
|
+
def last_record
|
|
189
|
+
return if replication_field.blank?
|
|
190
|
+
@last_record ||= begin
|
|
191
|
+
sql = "SELECT max(#{replication_field}) as _last_record from #{source_table}"
|
|
192
|
+
connection_adapter.last_record_query_command(sql)
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Schema for the export table on redshift
|
|
197
|
+
# @return [Hash] array of fields per table
|
|
198
|
+
def redshift_schema
|
|
199
|
+
@schema ||= begin
|
|
200
|
+
result = ::RailsRedshiftReplicator.connection.exec("select tablename, \"column\", type from pg_table_def where tablename = '#{target_table}'")
|
|
201
|
+
result.to_a.group_by{ |el| el["tablename"] }
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Lists fields on redshift table
|
|
206
|
+
# @return [Array<String>] colunas na tabela do Redshift
|
|
207
|
+
def fields_to_sync
|
|
208
|
+
@fields_to_sync ||= begin
|
|
209
|
+
column_defs = redshift_schema[target_table]
|
|
210
|
+
column_defs.blank? ? nil : column_defs.map{ |col_def| col_def['column'] }
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|