active_record_data_loader 1.0.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/build.yml +51 -0
  3. data/.github/workflows/gem-push.yml +29 -0
  4. data/.rubocop.yml +39 -6
  5. data/CHANGELOG.md +42 -0
  6. data/Gemfile.lock +68 -66
  7. data/README.md +91 -8
  8. data/Rakefile +8 -2
  9. data/active_record_data_loader.gemspec +8 -5
  10. data/config/database.yml +9 -0
  11. data/docker-compose.yml +18 -0
  12. data/gemfiles/activerecord_6.gemfile +1 -1
  13. data/lib/active_record_data_loader/active_record/belongs_to_configuration.rb +1 -1
  14. data/lib/active_record_data_loader/active_record/column_configuration.rb +14 -4
  15. data/lib/active_record_data_loader/active_record/datetime_value_generator.rb +21 -0
  16. data/lib/active_record_data_loader/active_record/enum_value_generator.rb +28 -5
  17. data/lib/active_record_data_loader/active_record/integer_value_generator.rb +2 -2
  18. data/lib/active_record_data_loader/active_record/model_data_generator.rb +15 -2
  19. data/lib/active_record_data_loader/active_record/per_row_value_cache.rb +33 -0
  20. data/lib/active_record_data_loader/active_record/polymorphic_belongs_to_configuration.rb +1 -1
  21. data/lib/active_record_data_loader/active_record/text_value_generator.rb +1 -1
  22. data/lib/active_record_data_loader/bulk_insert_strategy.rb +4 -3
  23. data/lib/active_record_data_loader/configuration.rb +45 -3
  24. data/lib/active_record_data_loader/connection_handler.rb +74 -0
  25. data/lib/active_record_data_loader/connection_output_adapter.rb +20 -0
  26. data/lib/active_record_data_loader/copy_strategy.rb +20 -20
  27. data/lib/active_record_data_loader/file_output_adapter.rb +40 -0
  28. data/lib/active_record_data_loader/loader.rb +11 -27
  29. data/lib/active_record_data_loader/version.rb +1 -1
  30. data/lib/active_record_data_loader.rb +26 -14
  31. metadata +60 -11
  32. data/.travis.yml +0 -23
  33. data/config/database.yml.travis +0 -7
@@ -9,15 +9,17 @@ module ActiveRecordDataLoader
9
9
  integer: IntegerValueGenerator,
10
10
  string: TextValueGenerator,
11
11
  text: TextValueGenerator,
12
+ datetime: DatetimeValueGenerator,
12
13
  }.freeze
13
14
 
14
- def config_for(model_class:, ar_column:)
15
+ def config_for(model_class:, ar_column:, connection_factory:)
15
16
  raise_error_if_not_supported(model_class, ar_column)
16
17
 
17
18
  {
18
- ar_column.name.to_sym => VALUE_GENERATORS[ar_column.type].generator_for(
19
+ ar_column.name.to_sym => VALUE_GENERATORS[column_type(ar_column)].generator_for(
19
20
  model_class: model_class,
20
- ar_column: ar_column
21
+ ar_column: ar_column,
22
+ connection_factory: connection_factory
21
23
  ),
22
24
  }
23
25
  end
@@ -25,7 +27,7 @@ module ActiveRecordDataLoader
25
27
  def supported?(model_class:, ar_column:)
26
28
  return false if model_class.reflect_on_association(ar_column.name)
27
29
 
28
- VALUE_GENERATORS.keys.include?(ar_column.type)
30
+ VALUE_GENERATORS.keys.include?(column_type(ar_column))
29
31
  end
30
32
 
31
33
  private
@@ -37,6 +39,14 @@ module ActiveRecordDataLoader
37
39
  Column '#{ar_column.name}' of type '#{ar_column.type}' in model '#{model_class.name}' not supported"
38
40
  ERROR
39
41
  end
42
+
43
+ def column_type(ar_column)
44
+ if ar_column.type == :string && ar_column.sql_type.to_s.downcase.start_with?("enum")
45
+ :enum
46
+ else
47
+ ar_column.type
48
+ end
49
+ end
40
50
  end
41
51
  end
42
52
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecordDataLoader
4
+ module ActiveRecord
5
+ class DatetimeValueGenerator
6
+ class << self
7
+ def generator_for(model_class:, ar_column:, connection_factory: nil)
8
+ ->(row) { timestamp(model_class, row) }
9
+ end
10
+
11
+ private
12
+
13
+ def timestamp(model, row_number)
14
+ PerRowValueCache[:datetime].get_or_set(model: model, row: row_number) do
15
+ Time.now.utc
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -4,21 +4,44 @@ module ActiveRecordDataLoader
4
4
  module ActiveRecord
5
5
  class EnumValueGenerator
6
6
  class << self
7
- def generator_for(model_class:, ar_column:)
8
- values = enum_values_for(model_class, ar_column.sql_type)
7
+ def generator_for(model_class:, ar_column:, connection_factory:)
8
+ values = enum_values_for(ar_column.sql_type, connection_factory)
9
9
  -> { values.sample }
10
10
  end
11
11
 
12
12
  private
13
13
 
14
- def enum_values_for(model_class, enum_type)
15
- model_class
16
- .connection
14
+ def enum_values_for(enum_type, connection_factory)
15
+ connection = connection_factory.call
16
+
17
+ if connection.adapter_name.downcase.to_sym == :postgresql
18
+ postgres_enum_values_for(connection, enum_type)
19
+ elsif connection.adapter_name.downcase.to_s.start_with?("mysql")
20
+ mysql_enum_values_for(enum_type)
21
+ else
22
+ []
23
+ end
24
+ ensure
25
+ connection&.close
26
+ end
27
+
28
+ def postgres_enum_values_for(connection, enum_type)
29
+ connection
17
30
  .execute("SELECT unnest(enum_range(NULL::#{enum_type}))::text")
18
31
  .map(&:values)
19
32
  .flatten
20
33
  .compact
21
34
  end
35
+
36
+ def mysql_enum_values_for(enum_type)
37
+ enum_type
38
+ .to_s
39
+ .downcase
40
+ .gsub(/\Aenum\(|\)\Z/, "")
41
+ .split(",")
42
+ .map(&:strip)
43
+ .map { |s| s.gsub(/\A'|'\Z/, "") }
44
+ end
22
45
  end
23
46
  end
24
47
  end
@@ -4,8 +4,8 @@ module ActiveRecordDataLoader
4
4
  module ActiveRecord
5
5
  class IntegerValueGenerator
6
6
  class << self
7
- def generator_for(model_class:, ar_column:)
8
- range_limit = [(256**number_of_bytes(ar_column)) / 2 - 1, 1_000_000_000].min
7
+ def generator_for(model_class:, ar_column:, connection_factory: nil)
8
+ range_limit = [((256**number_of_bytes(ar_column)) / 2) - 1, 1_000_000_000].min
9
9
 
10
10
  -> { rand(0..range_limit) }
11
11
  end
@@ -5,12 +5,19 @@ module ActiveRecordDataLoader
5
5
  class ModelDataGenerator
6
6
  attr_reader :table
7
7
 
8
- def initialize(model:, column_settings:, polymorphic_settings: [], belongs_to_settings: [])
8
+ def initialize(
9
+ model:,
10
+ column_settings:,
11
+ connection_factory:,
12
+ polymorphic_settings: [],
13
+ belongs_to_settings: []
14
+ )
9
15
  @model_class = model
10
16
  @table = model.table_name
11
17
  @column_settings = column_settings
12
18
  @polymorphic_settings = polymorphic_settings
13
19
  @belongs_to_settings = belongs_to_settings.map { |s| [s.name, s.query] }.to_h
20
+ @connection_factory = connection_factory
14
21
  end
15
22
 
16
23
  def column_list
@@ -50,7 +57,13 @@ module ActiveRecordDataLoader
50
57
  .columns_hash
51
58
  .reject { |name| name == @model_class.primary_key }
52
59
  .select { |_, c| ColumnConfiguration.supported?(model_class: @model_class, ar_column: c) }
53
- .map { |_, c| ColumnConfiguration.config_for(model_class: @model_class, ar_column: c) }
60
+ .map do |_, c|
61
+ ColumnConfiguration.config_for(
62
+ model_class: @model_class,
63
+ ar_column: c,
64
+ connection_factory: @connection_factory
65
+ )
66
+ end
54
67
  .reduce({}, :merge)
55
68
  end
56
69
 
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecordDataLoader
4
+ module ActiveRecord
5
+ class PerRowValueCache
6
+ class << self
7
+ def [](key)
8
+ caches[key] ||= new
9
+ end
10
+
11
+ def clear
12
+ @caches = {}
13
+ end
14
+
15
+ private
16
+
17
+ def caches
18
+ @caches ||= clear
19
+ end
20
+ end
21
+
22
+ def initialize
23
+ @row_caches = Hash.new { |hash, key| hash[key] = {} }
24
+ end
25
+
26
+ def get_or_set(model:, row:)
27
+ @row_caches[model.name].shift if @row_caches[model.name].size > 1
28
+
29
+ @row_caches[model.name][row] ||= yield
30
+ end
31
+ end
32
+ end
33
+ end
@@ -46,7 +46,7 @@ module ActiveRecordDataLoader
46
46
  end
47
47
 
48
48
  def base_query(klass)
49
- if @settings.queries[klass]&.respond_to?(:call)
49
+ if @settings.queries[klass].respond_to?(:call)
50
50
  @settings.queries[klass].call.all
51
51
  else
52
52
  klass.all
@@ -12,7 +12,7 @@ module ActiveRecordDataLoader
12
12
  }.freeze
13
13
 
14
14
  class << self
15
- def generator_for(model_class:, ar_column:)
15
+ def generator_for(model_class:, ar_column:, connection_factory: nil)
16
16
  scenario = GENERATORS.keys.find { |m| send(m, model_class, ar_column) }
17
17
  generator = GENERATORS.fetch(scenario, -> { SecureRandom.uuid })
18
18
 
@@ -2,12 +2,13 @@
2
2
 
3
3
  module ActiveRecordDataLoader
4
4
  class BulkInsertStrategy
5
- def initialize(data_generator)
5
+ def initialize(data_generator, output_adapter)
6
6
  @data_generator = data_generator
7
+ @output_adapter = output_adapter
7
8
  end
8
9
 
9
10
  def load_batch(row_numbers, connection)
10
- connection.insert(<<~SQL)
11
+ output_adapter.insert(connection: connection, command: <<~SQL)
11
12
  INSERT INTO #{quoted_table_name(connection)} (#{column_list(connection)})
12
13
  VALUES #{values(row_numbers, connection)}
13
14
  SQL
@@ -23,7 +24,7 @@ module ActiveRecordDataLoader
23
24
 
24
25
  private
25
26
 
26
- attr_reader :data_generator
27
+ attr_reader :data_generator, :output_adapter
27
28
 
28
29
  def quoted_table_name(connection)
29
30
  @quoted_table_name ||= connection.quote_table_name(data_generator.table)
@@ -2,27 +2,69 @@
2
2
 
3
3
  module ActiveRecordDataLoader
4
4
  class Configuration
5
- attr_accessor :default_batch_size, :default_row_count, :logger, :statement_timeout
5
+ attr_accessor :connection_factory, :default_batch_size, :default_row_count,
6
+ :logger, :statement_timeout
7
+ attr_reader :output
6
8
 
7
9
  def initialize(
8
10
  default_batch_size: 100_000,
9
11
  default_row_count: 1,
10
12
  logger: nil,
11
- statement_timeout: "2min"
13
+ statement_timeout: "2min",
14
+ connection_factory: -> { ::ActiveRecord::Base.connection },
15
+ output: :connection
12
16
  )
13
17
  @default_batch_size = default_batch_size
14
18
  @default_row_count = default_row_count
15
19
  @logger = logger || default_logger
16
20
  @statement_timeout = statement_timeout
21
+ @connection_factory = connection_factory
22
+ self.output = output
23
+ end
24
+
25
+ def output=(output)
26
+ @output = validate_output(output || { type: :connection })
27
+ end
28
+
29
+ def output_adapter
30
+ if output.fetch(:type) == :file
31
+ ActiveRecordDataLoader::FileOutputAdapter.new(output)
32
+ else
33
+ ActiveRecordDataLoader::ConnectionOutputAdapter.new
34
+ end
35
+ end
36
+
37
+ def connection_handler
38
+ ActiveRecordDataLoader::ConnectionHandler.new(
39
+ connection_factory: connection_factory,
40
+ statement_timeout: statement_timeout,
41
+ output_adapter: output_adapter
42
+ )
17
43
  end
18
44
 
19
45
  private
20
46
 
47
+ OUTPUT_OPTIONS_BY_TYPE = { connection: %i[type], file: %i[type filename] }.freeze
48
+
49
+ def validate_output(output)
50
+ if %i[file connection].include?(output)
51
+ { type: output }
52
+ elsif output.is_a?(Hash)
53
+ raise "The output hash must contain a :type key with either :connection or :file" \
54
+ unless %i[file connection].include?(output[:type])
55
+
56
+ output.slice(*OUTPUT_OPTIONS_BY_TYPE[output[:type]])
57
+ else
58
+ raise "The output configuration parameter must be either a symbol for :connection or :file, "\
59
+ "or a hash with more detailed output options."
60
+ end
61
+ end
62
+
21
63
  def default_logger
22
64
  if defined?(Rails) && Rails.respond_to?(:logger)
23
65
  Rails.logger
24
66
  else
25
- Logger.new(STDOUT, level: :info)
67
+ Logger.new($stdout, level: :info)
26
68
  end
27
69
  end
28
70
  end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecordDataLoader
4
+ class ConnectionHandler
5
+ def initialize(connection_factory:, statement_timeout:, output_adapter:)
6
+ @connection_factory = connection_factory
7
+ @statement_timeout = statement_timeout
8
+ @output_adapter = output_adapter
9
+ end
10
+
11
+ def with_connection
12
+ connection = open_connection
13
+ if postgres?(connection)
14
+ original_timeout = retrieve_statement_timeout(connection)
15
+ update_statement_timeout(connection, statement_timeout)
16
+ yield connection
17
+ update_statement_timeout(connection, original_timeout)
18
+ else
19
+ yield connection
20
+ end
21
+ ensure
22
+ connection&.close
23
+ end
24
+
25
+ # When the output is going to a script file, there are two places to update the
26
+ # statement_timeout. The connection itself needs to have the timeout updated
27
+ # because we are reading data from the connection to come up with related data
28
+ # while generating the data. Also, the final SQL script file needs the timeout
29
+ # updated so that when those \COPY commands are executed they have the higher
30
+ # timeout as well.
31
+ def with_statement_timeout_for_output
32
+ return yield unless output_adapter.needs_timeout_output?
33
+
34
+ original_timeout = begin
35
+ connection = open_connection
36
+ retrieve_statement_timeout(connection) if postgres?(connection)
37
+ ensure
38
+ connection&.close
39
+ end
40
+
41
+ if original_timeout
42
+ output_adapter.execute(statement_timeout_set_command(statement_timeout))
43
+ yield
44
+ output_adapter.execute(statement_timeout_set_command(original_timeout))
45
+ else
46
+ yield
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ attr_reader :connection_factory, :statement_timeout, :output_adapter
53
+
54
+ def retrieve_statement_timeout(connection)
55
+ connection.execute("SHOW statement_timeout").first["statement_timeout"]
56
+ end
57
+
58
+ def update_statement_timeout(connection, timeout)
59
+ connection.execute(statement_timeout_set_command(timeout))
60
+ end
61
+
62
+ def statement_timeout_set_command(timeout)
63
+ "SET statement_timeout = \"#{timeout}\""
64
+ end
65
+
66
+ def open_connection
67
+ connection_factory.call
68
+ end
69
+
70
+ def postgres?(connection)
71
+ connection.adapter_name.downcase.to_sym == :postgresql
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecordDataLoader
4
+ class ConnectionOutputAdapter
5
+ def needs_timeout_output?
6
+ false
7
+ end
8
+
9
+ def copy(connection:, table:, columns:, data:, row_numbers:)
10
+ raw_connection = connection.raw_connection
11
+ raw_connection.copy_data("COPY #{table} (#{columns}) FROM STDIN WITH (FORMAT CSV)") do
12
+ raw_connection.put_copy_data(data.join("\n"))
13
+ end
14
+ end
15
+
16
+ def insert(connection:, command:)
17
+ connection.insert(command)
18
+ end
19
+ end
20
+ end
@@ -2,15 +2,19 @@
2
2
 
3
3
  module ActiveRecordDataLoader
4
4
  class CopyStrategy
5
- def initialize(data_generator)
5
+ def initialize(data_generator, output_adapter)
6
6
  @data_generator = data_generator
7
+ @output_adapter = output_adapter
7
8
  end
8
9
 
9
10
  def load_batch(row_numbers, connection)
10
- csv_data = csv_data_batch(row_numbers, connection)
11
-
12
- raw_connection = connection.raw_connection
13
- raw_connection.copy_data(copy_command(connection)) { raw_connection.put_copy_data(csv_data) }
11
+ output_adapter.copy(
12
+ connection: connection,
13
+ table: table_name_for_copy(connection),
14
+ columns: columns_for_copy(connection),
15
+ data: csv_rows(row_numbers, connection),
16
+ row_numbers: row_numbers
17
+ )
14
18
  end
15
19
 
16
20
  def table_name
@@ -23,27 +27,23 @@ module ActiveRecordDataLoader
23
27
 
24
28
  private
25
29
 
26
- attr_reader :data_generator
30
+ attr_reader :data_generator, :output_adapter
27
31
 
28
- def csv_data_batch(row_numbers, connection)
32
+ def csv_rows(row_numbers, connection)
29
33
  row_numbers.map do |i|
30
34
  data_generator.generate_row(i).map { |d| quote_data(d, connection) }.join(",")
31
- end.join("\n")
35
+ end
32
36
  end
33
37
 
34
- def copy_command(connection)
35
- @copy_command ||= begin
36
- quoted_table_name = connection.quote_table_name(data_generator.table)
37
- columns = data_generator
38
- .column_list
39
- .map { |c| connection.quote_column_name(c) }
40
- .join(", ")
38
+ def table_name_for_copy(connection)
39
+ @table_name_for_copy ||= connection.quote_table_name(data_generator.table)
40
+ end
41
41
 
42
- <<~SQL
43
- COPY #{quoted_table_name} (#{columns})
44
- FROM STDIN WITH (FORMAT CSV)
45
- SQL
46
- end
42
+ def columns_for_copy(connection)
43
+ @columns_for_copy ||= data_generator
44
+ .column_list
45
+ .map { |c| connection.quote_column_name(c) }
46
+ .join(", ")
47
47
  end
48
48
 
49
49
  def quote_data(data, connection)
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecordDataLoader
4
+ class FileOutputAdapter
5
+ def initialize(options)
6
+ @filename = options.fetch(:filename, "active_record_data_loader_script.sql")
7
+ @file_basename = File.basename(@filename, File.extname(@filename))
8
+ @path = File.expand_path(File.dirname(@filename))
9
+ end
10
+
11
+ def needs_timeout_output?
12
+ true
13
+ end
14
+
15
+ def copy(connection:, table:, columns:, data:, row_numbers:)
16
+ data_filename = data_filename(table, row_numbers)
17
+ File.open(data_filename, "w") { |f| f.puts(data) }
18
+ File.open(@filename, "a") do |file|
19
+ file.puts("\\COPY #{table} (#{columns}) FROM '#{data_filename}' WITH (FORMAT CSV);")
20
+ end
21
+ end
22
+
23
+ def insert(connection:, command:)
24
+ execute(command)
25
+ end
26
+
27
+ def execute(command)
28
+ File.open(@filename, "a") { |f| f.puts("#{command.gsub("\n", ' ')};") }
29
+ end
30
+
31
+ private
32
+
33
+ def data_filename(table, row_numbers)
34
+ File.join(
35
+ @path,
36
+ "#{@file_basename}_#{table.gsub(/"/, '')}_rows_#{row_numbers[0]}_to_#{row_numbers[-1]}.csv"
37
+ )
38
+ end
39
+ end
40
+ end
@@ -13,15 +13,18 @@ module ActiveRecordDataLoader
13
13
  )
14
14
  new(
15
15
  logger: configuration.logger,
16
- statement_timeout: configuration.statement_timeout,
17
- strategy: strategy_class.new(data_generator)
16
+ connection_handler: configuration.connection_handler,
17
+ strategy: strategy_class(configuration.connection_factory).new(
18
+ data_generator,
19
+ configuration.output_adapter
20
+ )
18
21
  ).load_data(batch_size, total_rows)
19
22
  end
20
23
 
21
24
  private
22
25
 
23
- def strategy_class
24
- if ::ActiveRecord::Base.connection.raw_connection.respond_to?(:copy_data)
26
+ def strategy_class(connection_factory)
27
+ if connection_factory.call.raw_connection.respond_to?(:copy_data)
25
28
  ActiveRecordDataLoader::CopyStrategy
26
29
  else
27
30
  ActiveRecordDataLoader::BulkInsertStrategy
@@ -29,10 +32,10 @@ module ActiveRecordDataLoader
29
32
  end
30
33
  end
31
34
 
32
- def initialize(logger:, statement_timeout:, strategy:)
35
+ def initialize(logger:, connection_handler:, strategy:)
33
36
  @logger = logger
37
+ @connection_handler = connection_handler
34
38
  @strategy = strategy
35
- @statement_timeout = statement_timeout
36
39
  end
37
40
 
38
41
  def load_data(batch_size, total_rows)
@@ -55,10 +58,10 @@ module ActiveRecordDataLoader
55
58
 
56
59
  private
57
60
 
58
- attr_reader :strategy, :statement_timeout, :logger
61
+ attr_reader :strategy, :connection_handler, :logger
59
62
 
60
63
  def load_in_batches(batch_size, total_rows, batch_count)
61
- with_connection do |connection|
64
+ connection_handler.with_connection do |connection|
62
65
  total_rows.times.each_slice(batch_size).with_index do |row_numbers, i|
63
66
  time = Benchmark.realtime { strategy.load_batch(row_numbers, connection) }
64
67
 
@@ -69,24 +72,5 @@ module ActiveRecordDataLoader
69
72
  end
70
73
  end
71
74
  end
72
-
73
- def with_connection
74
- if ::ActiveRecord::Base.connection.adapter_name.downcase.to_sym == :postgresql
75
- original_timeout = retrieve_statement_timeout
76
- update_statement_timeout(statement_timeout)
77
- yield ::ActiveRecord::Base.connection
78
- update_statement_timeout(original_timeout)
79
- else
80
- yield ::ActiveRecord::Base.connection
81
- end
82
- end
83
-
84
- def retrieve_statement_timeout
85
- ::ActiveRecord::Base.connection.execute("SHOW statement_timeout").first["statement_timeout"]
86
- end
87
-
88
- def update_statement_timeout(timeout)
89
- ::ActiveRecord::Base.connection.execute("SET statement_timeout = \"#{timeout}\"")
90
- end
91
75
  end
92
76
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecordDataLoader
4
- VERSION = "1.0.0"
4
+ VERSION = "1.2.0"
5
5
  end
@@ -3,10 +3,13 @@
3
3
  require "active_record_data_loader/version"
4
4
  require "active_record"
5
5
  require "active_record_data_loader/configuration"
6
+ require "active_record_data_loader/connection_handler"
6
7
  require "active_record_data_loader/data_faker"
8
+ require "active_record_data_loader/active_record/per_row_value_cache"
7
9
  require "active_record_data_loader/active_record/integer_value_generator"
8
10
  require "active_record_data_loader/active_record/text_value_generator"
9
11
  require "active_record_data_loader/active_record/enum_value_generator"
12
+ require "active_record_data_loader/active_record/datetime_value_generator"
10
13
  require "active_record_data_loader/active_record/column_configuration"
11
14
  require "active_record_data_loader/active_record/belongs_to_configuration"
12
15
  require "active_record_data_loader/active_record/polymorphic_belongs_to_configuration"
@@ -15,6 +18,8 @@ require "active_record_data_loader/dsl/belongs_to_association"
15
18
  require "active_record_data_loader/dsl/polymorphic_association"
16
19
  require "active_record_data_loader/dsl/model"
17
20
  require "active_record_data_loader/dsl/definition"
21
+ require "active_record_data_loader/connection_output_adapter"
22
+ require "active_record_data_loader/file_output_adapter"
18
23
  require "active_record_data_loader/copy_strategy"
19
24
  require "active_record_data_loader/bulk_insert_strategy"
20
25
  require "active_record_data_loader/loader"
@@ -22,7 +27,7 @@ require "active_record_data_loader/loader"
22
27
  module ActiveRecordDataLoader
23
28
  def self.define(config = ActiveRecordDataLoader.configuration, &block)
24
29
  LoaderProxy.new(
25
- configuration,
30
+ config,
26
31
  ActiveRecordDataLoader::Dsl::Definition.new(config).tap { |l| l.instance_eval(&block) }
27
32
  )
28
33
  end
@@ -42,25 +47,32 @@ module ActiveRecordDataLoader
42
47
  end
43
48
 
44
49
  def load_data
45
- definition.models.map do |m|
46
- generator = ActiveRecordDataLoader::ActiveRecord::ModelDataGenerator.new(
47
- model: m.klass,
48
- column_settings: m.columns,
49
- polymorphic_settings: m.polymorphic_associations,
50
- belongs_to_settings: m.belongs_to_associations
51
- )
50
+ ActiveRecordDataLoader::ActiveRecord::PerRowValueCache.clear
52
51
 
53
- ActiveRecordDataLoader::Loader.load_data(
54
- data_generator: generator,
55
- batch_size: m.batch_size,
56
- total_rows: m.row_count,
57
- configuration: configuration
58
- )
52
+ configuration.connection_handler.with_statement_timeout_for_output do
53
+ definition.models.map { |m| load_model(m) }
59
54
  end
60
55
  end
61
56
 
62
57
  private
63
58
 
64
59
  attr_reader :definition, :configuration
60
+
61
+ def load_model(model)
62
+ generator = ActiveRecordDataLoader::ActiveRecord::ModelDataGenerator.new(
63
+ model: model.klass,
64
+ column_settings: model.columns,
65
+ polymorphic_settings: model.polymorphic_associations,
66
+ belongs_to_settings: model.belongs_to_associations,
67
+ connection_factory: configuration.connection_factory
68
+ )
69
+
70
+ ActiveRecordDataLoader::Loader.load_data(
71
+ data_generator: generator,
72
+ batch_size: model.batch_size,
73
+ total_rows: model.row_count,
74
+ configuration: configuration
75
+ )
76
+ end
65
77
  end
66
78
  end