active_record_data_loader 1.0.1 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/build.yml +51 -0
  3. data/.github/workflows/codeql-analysis.yml +70 -0
  4. data/.github/workflows/gem-push.yml +29 -0
  5. data/.rubocop.yml +46 -7
  6. data/CHANGELOG.md +37 -1
  7. data/CODE_OF_CONDUCT.md +2 -2
  8. data/Gemfile.lock +72 -72
  9. data/README.md +162 -9
  10. data/Rakefile +8 -2
  11. data/active_record_data_loader.gemspec +8 -6
  12. data/config/database.yml +9 -0
  13. data/docker-compose.yml +18 -0
  14. data/gemfiles/activerecord_6.gemfile +1 -1
  15. data/lib/active_record_data_loader/active_record/{belongs_to_configuration.rb → belongs_to_data_provider.rb} +8 -7
  16. data/lib/active_record_data_loader/active_record/{column_configuration.rb → column_data_provider.rb} +14 -5
  17. data/lib/active_record_data_loader/active_record/datetime_value_generator.rb +1 -1
  18. data/lib/active_record_data_loader/active_record/enum_value_generator.rb +28 -5
  19. data/lib/active_record_data_loader/active_record/integer_value_generator.rb +2 -2
  20. data/lib/active_record_data_loader/active_record/list.rb +35 -0
  21. data/lib/active_record_data_loader/active_record/model_data_generator.rb +74 -6
  22. data/lib/active_record_data_loader/active_record/{polymorphic_belongs_to_configuration.rb → polymorphic_belongs_to_data_provider.rb} +12 -7
  23. data/lib/active_record_data_loader/active_record/text_value_generator.rb +1 -1
  24. data/lib/active_record_data_loader/active_record/unique_index_tracker.rb +67 -0
  25. data/lib/active_record_data_loader/bulk_insert_strategy.rb +16 -8
  26. data/lib/active_record_data_loader/configuration.rb +28 -3
  27. data/lib/active_record_data_loader/connection_handler.rb +52 -0
  28. data/lib/active_record_data_loader/copy_strategy.rb +38 -24
  29. data/lib/active_record_data_loader/data_faker.rb +12 -4
  30. data/lib/active_record_data_loader/dsl/model.rb +19 -2
  31. data/lib/active_record_data_loader/errors.rb +5 -0
  32. data/lib/active_record_data_loader/file_output_adapter.rb +48 -0
  33. data/lib/active_record_data_loader/loader.rb +57 -67
  34. data/lib/active_record_data_loader/null_output_adapter.rb +15 -0
  35. data/lib/active_record_data_loader/table_loader.rb +59 -0
  36. data/lib/active_record_data_loader/version.rb +1 -1
  37. data/lib/active_record_data_loader.rb +12 -36
  38. metadata +52 -15
  39. data/.travis.yml +0 -23
  40. data/config/database.yml.travis +0 -7
@@ -1,92 +1,82 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "benchmark"
4
-
5
3
  module ActiveRecordDataLoader
6
4
  class Loader
7
- class << self
8
- def load_data(
9
- data_generator:,
10
- total_rows:,
11
- batch_size:,
12
- configuration:
13
- )
14
- new(
15
- logger: configuration.logger,
16
- statement_timeout: configuration.statement_timeout,
17
- strategy: strategy_class.new(data_generator)
18
- ).load_data(batch_size, total_rows)
19
- end
20
-
21
- private
22
-
23
- def strategy_class
24
- if ::ActiveRecord::Base.connection.raw_connection.respond_to?(:copy_data)
25
- ActiveRecordDataLoader::CopyStrategy
26
- else
27
- ActiveRecordDataLoader::BulkInsertStrategy
28
- end
29
- end
30
- end
31
-
32
- def initialize(logger:, statement_timeout:, strategy:)
33
- @logger = logger
34
- @strategy = strategy
35
- @statement_timeout = statement_timeout
5
+ def initialize(configuration, definition)
6
+ @configuration = configuration
7
+ @definition = definition
36
8
  end
37
9
 
38
- def load_data(batch_size, total_rows)
39
- batch_count = (total_rows / batch_size.to_f).ceil
10
+ def load_data
11
+ ActiveRecordDataLoader::ActiveRecord::PerRowValueCache.clear
40
12
 
41
- logger.info(
42
- "[ActiveRecordDataLoader] "\
43
- "Loading #{total_rows} row(s) into '#{strategy.table_name}' via #{strategy.name}. "\
44
- "#{batch_size} row(s) per batch, #{batch_count} batch(es)."
45
- )
46
- total_time = Benchmark.realtime do
47
- load_in_batches(batch_size, total_rows, batch_count)
13
+ file_adapter_class.with_output_options(file_adapter_options) do |file_adapter|
14
+ definition.models.map { |m| load_model(m, file_adapter) }
48
15
  end
49
- logger.info(
50
- "[ActiveRecordDataLoader] "\
51
- "Completed loading #{total_rows} row(s) into '#{strategy.table_name}' "\
52
- "in #{total_time} seconds."
53
- )
54
16
  end
55
17
 
56
18
  private
57
19
 
58
- attr_reader :strategy, :statement_timeout, :logger
20
+ attr_reader :definition, :configuration
59
21
 
60
- def load_in_batches(batch_size, total_rows, batch_count)
61
- with_connection do |connection|
62
- total_rows.times.each_slice(batch_size).with_index do |row_numbers, i|
63
- time = Benchmark.realtime { strategy.load_batch(row_numbers, connection) }
22
+ def load_model(model, file_adapter)
23
+ ActiveRecordDataLoader::TableLoader.load_data(
24
+ batch_size: model.batch_size,
25
+ total_rows: model.row_count,
26
+ connection_handler: connection_handler,
27
+ strategy: strategy_class.new(generator(model), file_adapter),
28
+ logger: configuration.logger
29
+ )
30
+ end
64
31
 
65
- logger.debug(
66
- "[ActiveRecordDataLoader] "\
67
- "Completed batch #{i + 1}/#{batch_count}, #{row_numbers.count} row(s) in #{time} seconds"
68
- )
69
- end
70
- end
32
+ def generator(model)
33
+ ActiveRecordDataLoader::ActiveRecord::ModelDataGenerator.new(
34
+ model: model.klass,
35
+ column_settings: model.columns,
36
+ polymorphic_settings: model.polymorphic_associations,
37
+ belongs_to_settings: model.belongs_to_associations,
38
+ connection_factory: configuration.connection_factory,
39
+ raise_on_duplicates: model.raise_on_duplicates_flag,
40
+ max_duplicate_retries: model.max_duplicate_retries,
41
+ logger: configuration.logger
42
+ )
71
43
  end
72
44
 
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)
45
+ def file_adapter_class
46
+ if configuration.output.present?
47
+ ActiveRecordDataLoader::FileOutputAdapter
79
48
  else
80
- yield ::ActiveRecord::Base.connection
49
+ ActiveRecordDataLoader::NullOutputAdapter
81
50
  end
82
51
  end
83
52
 
84
- def retrieve_statement_timeout
85
- ::ActiveRecord::Base.connection.execute("SHOW statement_timeout").first["statement_timeout"]
53
+ def file_adapter_options
54
+ timeout_commands =
55
+ if connection_handler.supports_timeout?
56
+ {
57
+ pre_command: connection_handler.timeout_set_command,
58
+ post_command: connection_handler.reset_timeout_command,
59
+ }
60
+ else
61
+ {}
62
+ end
63
+
64
+ timeout_commands.merge(filename: configuration.output)
86
65
  end
87
66
 
88
- def update_statement_timeout(timeout)
89
- ::ActiveRecord::Base.connection.execute("SET statement_timeout = \"#{timeout}\"")
67
+ def strategy_class
68
+ @strategy_class ||= if connection_handler.supports_copy?
69
+ ActiveRecordDataLoader::CopyStrategy
70
+ else
71
+ ActiveRecordDataLoader::BulkInsertStrategy
72
+ end
73
+ end
74
+
75
+ def connection_handler
76
+ @connection_handler ||= ActiveRecordDataLoader::ConnectionHandler.new(
77
+ connection_factory: configuration.connection_factory,
78
+ statement_timeout: configuration.statement_timeout
79
+ )
90
80
  end
91
81
  end
92
82
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecordDataLoader
4
+ class NullOutputAdapter
5
+ def self.with_output_options(_options)
6
+ yield new
7
+ end
8
+
9
+ def copy(table:, columns:, data:, row_numbers:); end
10
+
11
+ def insert(command); end
12
+
13
+ def write_command(command); end
14
+ end
15
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "benchmark"
4
+
5
+ module ActiveRecordDataLoader
6
+ class TableLoader
7
+ def self.load_data(
8
+ total_rows:,
9
+ batch_size:,
10
+ logger:,
11
+ connection_handler:,
12
+ strategy:
13
+ )
14
+ new(logger: logger, connection_handler: connection_handler, strategy: strategy)
15
+ .load_data(batch_size, total_rows)
16
+ end
17
+
18
+ def initialize(logger:, connection_handler:, strategy:)
19
+ @logger = logger
20
+ @connection_handler = connection_handler
21
+ @strategy = strategy
22
+ end
23
+
24
+ def load_data(batch_size, total_rows)
25
+ batch_count = (total_rows / batch_size.to_f).ceil
26
+
27
+ logger.info(
28
+ "[ActiveRecordDataLoader] "\
29
+ "Loading #{total_rows} row(s) into '#{strategy.table_name}' via #{strategy.name}. "\
30
+ "#{batch_size} row(s) per batch, #{batch_count} batch(es)."
31
+ )
32
+ total_time = Benchmark.realtime do
33
+ load_in_batches(batch_size, total_rows, batch_count)
34
+ end
35
+ logger.info(
36
+ "[ActiveRecordDataLoader] "\
37
+ "Completed loading #{total_rows} row(s) into '#{strategy.table_name}' "\
38
+ "in #{total_time} seconds."
39
+ )
40
+ end
41
+
42
+ private
43
+
44
+ attr_reader :strategy, :connection_handler, :logger
45
+
46
+ def load_in_batches(batch_size, total_rows, batch_count)
47
+ connection_handler.with_connection do |connection|
48
+ total_rows.times.each_slice(batch_size).with_index do |row_numbers, i|
49
+ time = Benchmark.realtime { strategy.load_batch(row_numbers, connection) }
50
+
51
+ logger.debug(
52
+ "[ActiveRecordDataLoader] "\
53
+ "Completed batch #{i + 1}/#{batch_count}, #{row_numbers.count} row(s) in #{time} seconds"
54
+ )
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecordDataLoader
4
- VERSION = "1.0.1"
4
+ VERSION = "1.3.0"
5
5
  end
@@ -2,29 +2,36 @@
2
2
 
3
3
  require "active_record_data_loader/version"
4
4
  require "active_record"
5
+ require "active_record_data_loader/errors"
5
6
  require "active_record_data_loader/configuration"
7
+ require "active_record_data_loader/connection_handler"
6
8
  require "active_record_data_loader/data_faker"
9
+ require "active_record_data_loader/active_record/list"
7
10
  require "active_record_data_loader/active_record/per_row_value_cache"
8
11
  require "active_record_data_loader/active_record/integer_value_generator"
9
12
  require "active_record_data_loader/active_record/text_value_generator"
10
13
  require "active_record_data_loader/active_record/enum_value_generator"
11
14
  require "active_record_data_loader/active_record/datetime_value_generator"
12
- require "active_record_data_loader/active_record/column_configuration"
13
- require "active_record_data_loader/active_record/belongs_to_configuration"
14
- require "active_record_data_loader/active_record/polymorphic_belongs_to_configuration"
15
+ require "active_record_data_loader/active_record/column_data_provider"
16
+ require "active_record_data_loader/active_record/belongs_to_data_provider"
17
+ require "active_record_data_loader/active_record/polymorphic_belongs_to_data_provider"
18
+ require "active_record_data_loader/active_record/unique_index_tracker"
15
19
  require "active_record_data_loader/active_record/model_data_generator"
16
20
  require "active_record_data_loader/dsl/belongs_to_association"
17
21
  require "active_record_data_loader/dsl/polymorphic_association"
18
22
  require "active_record_data_loader/dsl/model"
19
23
  require "active_record_data_loader/dsl/definition"
24
+ require "active_record_data_loader/file_output_adapter"
25
+ require "active_record_data_loader/null_output_adapter"
20
26
  require "active_record_data_loader/copy_strategy"
21
27
  require "active_record_data_loader/bulk_insert_strategy"
28
+ require "active_record_data_loader/table_loader"
22
29
  require "active_record_data_loader/loader"
23
30
 
24
31
  module ActiveRecordDataLoader
25
32
  def self.define(config = ActiveRecordDataLoader.configuration, &block)
26
- LoaderProxy.new(
27
- configuration,
33
+ ActiveRecordDataLoader::Loader.new(
34
+ config,
28
35
  ActiveRecordDataLoader::Dsl::Definition.new(config).tap { |l| l.instance_eval(&block) }
29
36
  )
30
37
  end
@@ -36,35 +43,4 @@ module ActiveRecordDataLoader
36
43
  def self.configuration
37
44
  @configuration ||= ActiveRecordDataLoader::Configuration.new
38
45
  end
39
-
40
- class LoaderProxy
41
- def initialize(configuration, definition)
42
- @configuration = configuration
43
- @definition = definition
44
- end
45
-
46
- def load_data
47
- ActiveRecordDataLoader::ActiveRecord::PerRowValueCache.clear
48
-
49
- definition.models.map do |m|
50
- generator = ActiveRecordDataLoader::ActiveRecord::ModelDataGenerator.new(
51
- model: m.klass,
52
- column_settings: m.columns,
53
- polymorphic_settings: m.polymorphic_associations,
54
- belongs_to_settings: m.belongs_to_associations
55
- )
56
-
57
- ActiveRecordDataLoader::Loader.load_data(
58
- data_generator: generator,
59
- batch_size: m.batch_size,
60
- total_rows: m.row_count,
61
- configuration: configuration
62
- )
63
- end
64
- end
65
-
66
- private
67
-
68
- attr_reader :definition, :configuration
69
- end
70
46
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_record_data_loader
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alejandro Beiderman
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-06-17 00:00:00.000000000 Z
11
+ date: 2021-12-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '4.0'
19
+ version: '5.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '4.0'
26
+ version: '5.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: appraisal
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -53,7 +53,7 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: '1.16'
55
55
  - !ruby/object:Gem::Dependency
56
- name: coveralls
56
+ name: mysql2
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - ">="
@@ -100,14 +100,14 @@ dependencies:
100
100
  requirements:
101
101
  - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: '12.0'
103
+ version: '13.0'
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
- version: '12.0'
110
+ version: '13.0'
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: rspec
113
113
  requirement: !ruby/object:Gem::Requirement
@@ -150,6 +150,34 @@ dependencies:
150
150
  - - ">="
151
151
  - !ruby/object:Gem::Version
152
152
  version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: simplecov
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: simplecov-lcov
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
153
181
  - !ruby/object:Gem::Dependency
154
182
  name: sqlite3
155
183
  requirement: !ruby/object:Gem::Requirement
@@ -180,17 +208,19 @@ dependencies:
180
208
  version: '0'
181
209
  description: A utility to bulk load test data for performance testing.
182
210
  email:
183
- - abeiderman@gmail.com
211
+ - active_record_data_loader@ossprojects.dev
184
212
  executables:
185
213
  - console
186
214
  - setup
187
215
  extensions: []
188
216
  extra_rdoc_files: []
189
217
  files:
218
+ - ".github/workflows/build.yml"
219
+ - ".github/workflows/codeql-analysis.yml"
220
+ - ".github/workflows/gem-push.yml"
190
221
  - ".gitignore"
191
222
  - ".rspec"
192
223
  - ".rubocop.yml"
193
- - ".travis.yml"
194
224
  - Appraisals
195
225
  - CHANGELOG.md
196
226
  - CODE_OF_CONDUCT.md
@@ -203,7 +233,7 @@ files:
203
233
  - bin/console
204
234
  - bin/setup
205
235
  - config/database.yml
206
- - config/database.yml.travis
236
+ - docker-compose.yml
207
237
  - gemfiles/.bundle/config
208
238
  - gemfiles/activerecord_5.gemfile
209
239
  - gemfiles/activerecord_6.gemfile
@@ -211,24 +241,31 @@ files:
211
241
  - gemfiles/ffaker.gemfile
212
242
  - gemfiles/rails.gemfile
213
243
  - lib/active_record_data_loader.rb
214
- - lib/active_record_data_loader/active_record/belongs_to_configuration.rb
215
- - lib/active_record_data_loader/active_record/column_configuration.rb
244
+ - lib/active_record_data_loader/active_record/belongs_to_data_provider.rb
245
+ - lib/active_record_data_loader/active_record/column_data_provider.rb
216
246
  - lib/active_record_data_loader/active_record/datetime_value_generator.rb
217
247
  - lib/active_record_data_loader/active_record/enum_value_generator.rb
218
248
  - lib/active_record_data_loader/active_record/integer_value_generator.rb
249
+ - lib/active_record_data_loader/active_record/list.rb
219
250
  - lib/active_record_data_loader/active_record/model_data_generator.rb
220
251
  - lib/active_record_data_loader/active_record/per_row_value_cache.rb
221
- - lib/active_record_data_loader/active_record/polymorphic_belongs_to_configuration.rb
252
+ - lib/active_record_data_loader/active_record/polymorphic_belongs_to_data_provider.rb
222
253
  - lib/active_record_data_loader/active_record/text_value_generator.rb
254
+ - lib/active_record_data_loader/active_record/unique_index_tracker.rb
223
255
  - lib/active_record_data_loader/bulk_insert_strategy.rb
224
256
  - lib/active_record_data_loader/configuration.rb
257
+ - lib/active_record_data_loader/connection_handler.rb
225
258
  - lib/active_record_data_loader/copy_strategy.rb
226
259
  - lib/active_record_data_loader/data_faker.rb
227
260
  - lib/active_record_data_loader/dsl/belongs_to_association.rb
228
261
  - lib/active_record_data_loader/dsl/definition.rb
229
262
  - lib/active_record_data_loader/dsl/model.rb
230
263
  - lib/active_record_data_loader/dsl/polymorphic_association.rb
264
+ - lib/active_record_data_loader/errors.rb
265
+ - lib/active_record_data_loader/file_output_adapter.rb
231
266
  - lib/active_record_data_loader/loader.rb
267
+ - lib/active_record_data_loader/null_output_adapter.rb
268
+ - lib/active_record_data_loader/table_loader.rb
232
269
  - lib/active_record_data_loader/version.rb
233
270
  - log/.keep
234
271
  homepage:
@@ -244,14 +281,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
244
281
  requirements:
245
282
  - - ">="
246
283
  - !ruby/object:Gem::Version
247
- version: 2.3.0
284
+ version: 2.5.0
248
285
  required_rubygems_version: !ruby/object:Gem::Requirement
249
286
  requirements:
250
287
  - - ">="
251
288
  - !ruby/object:Gem::Version
252
289
  version: '0'
253
290
  requirements: []
254
- rubygems_version: 3.0.3
291
+ rubygems_version: 3.0.3.1
255
292
  signing_key:
256
293
  specification_version: 4
257
294
  summary: A utility to bulk load test data for performance testing.
data/.travis.yml DELETED
@@ -1,23 +0,0 @@
1
- sudo: false
2
- language: ruby
3
- env:
4
- - COVERALLS_PARALLEL=true
5
- rvm:
6
- - 2.3.8
7
- - 2.4.6
8
- - 2.5.5
9
- - 2.6.3
10
- gemfile:
11
- - gemfiles/activerecord_5.gemfile
12
- - gemfiles/rails.gemfile
13
- - gemfiles/faker.gemfile
14
- - gemfiles/ffaker.gemfile
15
- services:
16
- - postgresql
17
- notifications:
18
- webhooks: https://coveralls.io/webhook
19
- before_install: "gem update --system && gem install bundler"
20
- before_script:
21
- - psql -c 'create database test;' -U postgres
22
- - cp config/database.yml.travis config/database.yml
23
- script: "bundle exec rake"
@@ -1,7 +0,0 @@
1
- postgres:
2
- adapter: "postgresql"
3
- database: "test"
4
-
5
- sqlite3:
6
- adapter: "sqlite3"
7
- database: ":memory:"