activerecord-import 0.14.1 → 1.4.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 (131) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/test.yaml +107 -0
  3. data/.gitignore +1 -0
  4. data/.rubocop.yml +74 -8
  5. data/Brewfile +3 -1
  6. data/CHANGELOG.md +448 -2
  7. data/Gemfile +26 -19
  8. data/LICENSE +21 -56
  9. data/README.markdown +568 -32
  10. data/Rakefile +5 -1
  11. data/activerecord-import.gemspec +8 -7
  12. data/benchmarks/README +2 -2
  13. data/benchmarks/benchmark.rb +8 -1
  14. data/benchmarks/lib/base.rb +2 -0
  15. data/benchmarks/lib/cli_parser.rb +5 -2
  16. data/benchmarks/lib/float.rb +2 -0
  17. data/benchmarks/lib/mysql2_benchmark.rb +2 -0
  18. data/benchmarks/lib/output_to_csv.rb +2 -0
  19. data/benchmarks/lib/output_to_html.rb +4 -2
  20. data/benchmarks/models/test_innodb.rb +2 -0
  21. data/benchmarks/models/test_memory.rb +2 -0
  22. data/benchmarks/models/test_myisam.rb +2 -0
  23. data/benchmarks/schema/{mysql_schema.rb → mysql2_schema.rb} +2 -0
  24. data/gemfiles/4.2.gemfile +4 -3
  25. data/gemfiles/5.0.gemfile +4 -3
  26. data/gemfiles/5.1.gemfile +4 -0
  27. data/gemfiles/5.2.gemfile +4 -0
  28. data/gemfiles/6.0.gemfile +4 -0
  29. data/gemfiles/6.1.gemfile +4 -0
  30. data/gemfiles/7.0.gemfile +4 -0
  31. data/lib/activerecord-import/active_record/adapters/abstract_adapter.rb +2 -0
  32. data/lib/activerecord-import/active_record/adapters/jdbcmysql_adapter.rb +6 -4
  33. data/lib/activerecord-import/active_record/adapters/jdbcpostgresql_adapter.rb +2 -0
  34. data/lib/activerecord-import/active_record/adapters/jdbcsqlite3_adapter.rb +8 -0
  35. data/lib/activerecord-import/active_record/adapters/mysql2_adapter.rb +2 -0
  36. data/lib/activerecord-import/active_record/adapters/postgresql_adapter.rb +2 -0
  37. data/lib/activerecord-import/active_record/adapters/seamless_database_pool_adapter.rb +2 -0
  38. data/lib/activerecord-import/active_record/adapters/sqlite3_adapter.rb +2 -0
  39. data/lib/activerecord-import/adapters/abstract_adapter.rb +12 -16
  40. data/lib/activerecord-import/adapters/em_mysql2_adapter.rb +2 -0
  41. data/lib/activerecord-import/adapters/mysql2_adapter.rb +2 -0
  42. data/lib/activerecord-import/adapters/mysql_adapter.rb +35 -18
  43. data/lib/activerecord-import/adapters/postgresql_adapter.rb +107 -28
  44. data/lib/activerecord-import/adapters/sqlite3_adapter.rb +141 -18
  45. data/lib/activerecord-import/base.rb +15 -8
  46. data/lib/activerecord-import/import.rb +642 -178
  47. data/lib/activerecord-import/mysql2.rb +2 -0
  48. data/lib/activerecord-import/postgresql.rb +2 -0
  49. data/lib/activerecord-import/sqlite3.rb +2 -0
  50. data/lib/activerecord-import/synchronize.rb +5 -3
  51. data/lib/activerecord-import/value_sets_parser.rb +29 -3
  52. data/lib/activerecord-import/version.rb +3 -1
  53. data/lib/activerecord-import.rb +5 -16
  54. data/test/adapters/jdbcmysql.rb +2 -0
  55. data/test/adapters/jdbcpostgresql.rb +2 -0
  56. data/test/adapters/jdbcsqlite3.rb +3 -0
  57. data/test/adapters/makara_postgis.rb +3 -0
  58. data/test/adapters/mysql2.rb +2 -0
  59. data/test/adapters/mysql2_makara.rb +2 -0
  60. data/test/adapters/mysql2spatial.rb +2 -0
  61. data/test/adapters/postgis.rb +2 -0
  62. data/test/adapters/postgresql.rb +2 -0
  63. data/test/adapters/postgresql_makara.rb +2 -0
  64. data/test/adapters/seamless_database_pool.rb +2 -0
  65. data/test/adapters/spatialite.rb +2 -0
  66. data/test/adapters/sqlite3.rb +2 -0
  67. data/test/{travis → github}/database.yml +7 -1
  68. data/test/import_test.rb +498 -32
  69. data/test/jdbcmysql/import_test.rb +2 -1
  70. data/test/jdbcpostgresql/import_test.rb +2 -1
  71. data/test/jdbcsqlite3/import_test.rb +6 -0
  72. data/test/makara_postgis/import_test.rb +10 -0
  73. data/test/models/account.rb +5 -0
  74. data/test/models/alarm.rb +4 -0
  75. data/test/models/animal.rb +8 -0
  76. data/test/models/bike_maker.rb +9 -0
  77. data/test/models/book.rb +4 -0
  78. data/test/models/car.rb +5 -0
  79. data/test/models/card.rb +5 -0
  80. data/test/models/chapter.rb +2 -0
  81. data/test/models/customer.rb +8 -0
  82. data/test/models/deck.rb +8 -0
  83. data/test/models/dictionary.rb +6 -0
  84. data/test/models/discount.rb +2 -0
  85. data/test/models/end_note.rb +2 -0
  86. data/test/models/group.rb +2 -0
  87. data/test/models/order.rb +8 -0
  88. data/test/models/playing_card.rb +4 -0
  89. data/test/models/promotion.rb +2 -0
  90. data/test/models/question.rb +2 -0
  91. data/test/models/rule.rb +2 -0
  92. data/test/models/tag.rb +7 -0
  93. data/test/models/tag_alias.rb +5 -0
  94. data/test/models/topic.rb +16 -0
  95. data/test/models/user.rb +5 -0
  96. data/test/models/user_token.rb +6 -0
  97. data/test/models/vendor.rb +9 -0
  98. data/test/models/widget.rb +18 -0
  99. data/test/mysql2/import_test.rb +2 -0
  100. data/test/mysql2_makara/import_test.rb +2 -0
  101. data/test/mysqlspatial2/import_test.rb +2 -0
  102. data/test/postgis/import_test.rb +6 -0
  103. data/test/postgresql/import_test.rb +2 -4
  104. data/test/schema/generic_schema.rb +88 -3
  105. data/test/schema/jdbcpostgresql_schema.rb +3 -0
  106. data/test/schema/mysql2_schema.rb +21 -0
  107. data/test/schema/postgis_schema.rb +3 -0
  108. data/test/schema/postgresql_schema.rb +63 -0
  109. data/test/schema/sqlite3_schema.rb +15 -0
  110. data/test/schema/version.rb +2 -0
  111. data/test/sqlite3/import_test.rb +4 -50
  112. data/test/support/active_support/test_case_extensions.rb +8 -1
  113. data/test/support/assertions.rb +2 -0
  114. data/test/support/factories.rb +17 -8
  115. data/test/support/generate.rb +10 -8
  116. data/test/support/mysql/import_examples.rb +17 -3
  117. data/test/support/postgresql/import_examples.rb +442 -9
  118. data/test/support/shared_examples/on_duplicate_key_ignore.rb +45 -0
  119. data/test/support/shared_examples/on_duplicate_key_update.rb +278 -1
  120. data/test/support/shared_examples/recursive_import.rb +137 -12
  121. data/test/support/sqlite3/import_examples.rb +232 -0
  122. data/test/synchronize_test.rb +10 -0
  123. data/test/test_helper.rb +44 -3
  124. data/test/value_sets_bytes_parser_test.rb +15 -2
  125. data/test/value_sets_records_parser_test.rb +2 -0
  126. metadata +74 -22
  127. data/.travis.yml +0 -52
  128. data/gemfiles/3.2.gemfile +0 -3
  129. data/gemfiles/4.0.gemfile +0 -3
  130. data/gemfiles/4.1.gemfile +0 -3
  131. data/test/schema/mysql_schema.rb +0 -16
@@ -1,13 +1,14 @@
1
- # -*- encoding: utf-8 -*-
1
+ # frozen_string_literal: true
2
+
2
3
  require File.expand_path('../lib/activerecord-import/version', __FILE__)
3
4
 
4
5
  Gem::Specification.new do |gem|
5
6
  gem.authors = ["Zach Dennis"]
6
7
  gem.email = ["zach.dennis@gmail.com"]
7
- gem.summary = "Bulk-loading extension for ActiveRecord"
8
- gem.description = "Extraction of the ActiveRecord::Base#import functionality from ar-extensions for Rails 3 and beyond"
9
- gem.homepage = "http://github.com/zdennis/activerecord-import"
10
- gem.license = "Ruby"
8
+ gem.summary = "Bulk insert extension for ActiveRecord"
9
+ gem.description = "A library for bulk inserting data using ActiveRecord."
10
+ gem.homepage = "https://github.com/zdennis/activerecord-import"
11
+ gem.license = "MIT"
11
12
 
12
13
  gem.files = `git ls-files`.split($\)
13
14
  gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
@@ -16,8 +17,8 @@ Gem::Specification.new do |gem|
16
17
  gem.require_paths = ["lib"]
17
18
  gem.version = ActiveRecord::Import::VERSION
18
19
 
19
- gem.required_ruby_version = ">= 1.9.2"
20
+ gem.required_ruby_version = ">= 2.4.0"
20
21
 
21
- gem.add_runtime_dependency "activerecord", ">= 3.2"
22
+ gem.add_runtime_dependency "activerecord", ">= 4.2"
22
23
  gem.add_development_dependency "rake"
23
24
  end
data/benchmarks/README CHANGED
@@ -15,10 +15,10 @@ See "ruby benchmark.rb -h" for the complete listing of options.
15
15
  EXAMPLES
16
16
  --------
17
17
  To output to html format:
18
- ruby benchmark.rb --adapter=mysql --to-html=results.html
18
+ ruby benchmark.rb --adapter=mysql2 --to-html=results.html
19
19
 
20
20
  To output to csv format:
21
- ruby benchmark.rb --adapter=mysql --to-csv=results.csv
21
+ ruby benchmark.rb --adapter=mysql2 --to-csv=results.csv
22
22
 
23
23
  LIMITATIONS
24
24
  -----------
@@ -1,6 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'pathname'
2
4
  require "fileutils"
3
5
  require "active_record"
6
+ require "active_record/base"
4
7
 
5
8
  benchmark_dir = File.dirname(__FILE__)
6
9
 
@@ -19,7 +22,11 @@ FileUtils.mkdir_p 'log'
19
22
  ActiveRecord::Base.configurations["test"] = YAML.load_file(File.join(benchmark_dir, "../test/database.yml"))[options.adapter]
20
23
  ActiveRecord::Base.logger = Logger.new("log/test.log")
21
24
  ActiveRecord::Base.logger.level = Logger::DEBUG
22
- ActiveRecord::Base.default_timezone = :utc
25
+ if ActiveRecord.respond_to?(:default_timezone)
26
+ ActiveRecord.default_timezone = :utc
27
+ else
28
+ ActiveRecord::Base.default_timezone = :utc
29
+ end
23
30
 
24
31
  require "activerecord-import"
25
32
  ActiveRecord::Base.establish_connection(:test)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class BenchmarkBase
2
4
  attr_reader :results
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'optparse'
2
4
  require 'ostruct'
3
5
 
@@ -8,7 +10,7 @@ require 'ostruct'
8
10
  # * t - the table types to test. ie: myisam, innodb, memory, temporary, etc.
9
11
  #
10
12
  module BenchmarkOptionParser
11
- BANNER = "Usage: ruby #{$0} [options]\nSee ruby #{$0} -h for more options.".freeze
13
+ BANNER = "Usage: ruby #{$0} [options]\nSee ruby #{$0} -h for more options."
12
14
 
13
15
  def self.print_banner
14
16
  puts BANNER
@@ -42,7 +44,8 @@ module BenchmarkOptionParser
42
44
  table_types: {},
43
45
  delete_on_finish: true,
44
46
  number_of_objects: [],
45
- outputs: [] )
47
+ outputs: []
48
+ )
46
49
 
47
50
  opt_parser = OptionParser.new do |opts|
48
51
  opts.banner = BANNER
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Taken from http://www.programmingishard.com/posts/show/128
2
4
  # Posted by rbates
3
5
  class Float
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Mysql2Benchmark < BenchmarkBase
2
4
  def benchmark_all( array_of_cols_and_vals )
3
5
  methods = self.methods.find_all { |m| m =~ /benchmark_/ }
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'csv'
2
4
 
3
5
  module OutputToCSV
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'erb'
2
4
 
3
5
  module OutputToHTML
4
- TEMPLATE_HEADER = <<"EOT".freeze
6
+ TEMPLATE_HEADER = <<"EOT"
5
7
  <div>
6
8
  All times are rounded to the nearest thousandth for display purposes. Speedups next to each time are computed
7
9
  before any rounding occurs. Also, all speedup calculations are computed by comparing a given time against
@@ -9,7 +11,7 @@ module OutputToHTML
9
11
  </div>
10
12
  EOT
11
13
 
12
- TEMPLATE = <<"EOT".freeze
14
+ TEMPLATE = <<"EOT"
13
15
  <style>
14
16
  td#benchmarkTitle {
15
17
  border: 1px solid black;
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class TestInnoDb < ActiveRecord::Base
2
4
  self.table_name = 'test_innodb'
3
5
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class TestMemory < ActiveRecord::Base
2
4
  self.table_name = 'test_memory'
3
5
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class TestMyISAM < ActiveRecord::Base
2
4
  self.table_name = 'test_myisam'
3
5
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ActiveRecord::Schema.define do
2
4
  create_table :test_myisam, options: 'ENGINE=MyISAM', force: true do |t|
3
5
  t.column :my_name, :string, null: false
data/gemfiles/4.2.gemfile CHANGED
@@ -1,3 +1,4 @@
1
- platforms :ruby do
2
- gem 'activerecord', '~> 4.2.0'
3
- end
1
+ # frozen_string_literal: true
2
+
3
+ gem 'activerecord', '~> 4.2.0'
4
+ gem 'composite_primary_keys', '~> 8.0'
data/gemfiles/5.0.gemfile CHANGED
@@ -1,3 +1,4 @@
1
- platforms :ruby do
2
- gem 'activerecord', '~> 5.0.0.beta3'
3
- end
1
+ # frozen_string_literal: true
2
+
3
+ gem 'activerecord', '~> 5.0.0'
4
+ gem 'composite_primary_keys', '~> 9.0'
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ gem 'activerecord', '~> 5.1.0'
4
+ gem 'composite_primary_keys', '~> 10.0'
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ gem 'activerecord', '~> 5.2.0'
4
+ gem 'composite_primary_keys', '~> 11.0'
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ gem 'activerecord', '~> 6.0.0'
4
+ gem 'composite_primary_keys', '~> 12.0'
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ gem 'activerecord', '~> 6.1.0'
4
+ gem 'composite_primary_keys', '~> 13.0'
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ gem 'activerecord', '~> 7.0.0'
4
+ gem 'composite_primary_keys', '~> 14.0'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "activerecord-import/adapters/abstract_adapter"
2
4
 
3
5
  module ActiveRecord # :nodoc:
@@ -1,6 +1,8 @@
1
- require "active_record/connection_adapters/mysql_adapter"
2
- require "activerecord-import/adapters/mysql_adapter"
1
+ # frozen_string_literal: true
3
2
 
4
- class ActiveRecord::ConnectionAdapters::MysqlAdapter
5
- include ActiveRecord::Import::MysqlAdapter
3
+ require "active_record/connection_adapters/mysql2_adapter"
4
+ require "activerecord-import/adapters/mysql2_adapter"
5
+
6
+ class ActiveRecord::ConnectionAdapters::Mysql2Adapter
7
+ include ActiveRecord::Import::Mysql2Adapter
6
8
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_record/connection_adapters/postgresql_adapter"
2
4
  require "activerecord-import/adapters/postgresql_adapter"
3
5
 
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/connection_adapters/sqlite3_adapter"
4
+ require "activerecord-import/adapters/sqlite3_adapter"
5
+
6
+ class ActiveRecord::ConnectionAdapters::SQLite3Adapter
7
+ include ActiveRecord::Import::SQLite3Adapter
8
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_record/connection_adapters/mysql2_adapter"
2
4
  require "activerecord-import/adapters/mysql2_adapter"
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_record/connection_adapters/postgresql_adapter"
2
4
  require "activerecord-import/adapters/postgresql_adapter"
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "seamless_database_pool"
2
4
  require "active_record/connection_adapters/seamless_database_pool_adapter"
3
5
  require "activerecord-import/adapters/mysql_adapter"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_record/connection_adapters/sqlite3_adapter"
2
4
  require "activerecord-import/adapters/sqlite3_adapter"
3
5
 
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord::Import::AbstractAdapter
2
4
  module InstanceMethods
3
5
  def next_value_for_sequence(sequence_name)
4
6
  %(#{sequence_name}.nextval)
5
7
  end
6
8
 
7
- def insert_many( sql, values, *args ) # :nodoc:
9
+ def insert_many( sql, values, _options = {}, *args ) # :nodoc:
8
10
  number_of_inserts = 1
9
11
 
10
12
  base_sql, post_sql = if sql.is_a?( String )
@@ -16,14 +18,13 @@ module ActiveRecord::Import::AbstractAdapter
16
18
  sql2insert = base_sql + values.join( ',' ) + post_sql
17
19
  insert( sql2insert, *args )
18
20
 
19
- [number_of_inserts, []]
21
+ ActiveRecord::Import::Result.new([], number_of_inserts, [], [])
20
22
  end
21
23
 
22
24
  def pre_sql_statements(options)
23
25
  sql = []
24
26
  sql << options[:pre_sql] if options[:pre_sql]
25
27
  sql << options[:command] if options[:command]
26
- sql << "IGNORE" if options[:ignore]
27
28
 
28
29
  # add keywords like IGNORE or DELAYED
29
30
  if options[:keywords].is_a?(Array)
@@ -45,15 +46,10 @@ module ActiveRecord::Import::AbstractAdapter
45
46
  def post_sql_statements( table_name, options ) # :nodoc:
46
47
  post_sql_statements = []
47
48
 
48
- if supports_on_duplicate_key_update?
49
- if options[:on_duplicate_key_ignore] && respond_to?(:sql_for_on_duplicate_key_ignore)
50
- # Options :recursive and :on_duplicate_key_ignore are mutually exclusive
51
- unless options[:recursive]
52
- post_sql_statements << sql_for_on_duplicate_key_ignore( table_name, options[:on_duplicate_key_ignore] )
53
- end
54
- elsif options[:on_duplicate_key_update]
55
- post_sql_statements << sql_for_on_duplicate_key_update( table_name, options[:on_duplicate_key_update] )
56
- end
49
+ if supports_on_duplicate_key_update? && options[:on_duplicate_key_update]
50
+ post_sql_statements << sql_for_on_duplicate_key_update( table_name, options[:on_duplicate_key_update], options[:primary_key], options[:locking_column] )
51
+ elsif logger && options[:on_duplicate_key_update]
52
+ logger.warn "Ignoring on_duplicate_key_update because it is not supported by the database."
57
53
  end
58
54
 
59
55
  # custom user post_sql
@@ -65,10 +61,10 @@ module ActiveRecord::Import::AbstractAdapter
65
61
  post_sql_statements
66
62
  end
67
63
 
68
- # Returns the maximum number of bytes that the server will allow
69
- # in a single packet
70
- def max_allowed_packet
71
- NO_MAX_PACKET
64
+ def increment_locking_column!(table_name, results, locking_column)
65
+ if locking_column.present?
66
+ results << "\"#{locking_column}\"=#{table_name}.\"#{locking_column}\"+1"
67
+ end
72
68
  end
73
69
 
74
70
  def supports_on_duplicate_key_update?
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "activerecord-import/adapters/mysql_adapter"
2
4
 
3
5
  module ActiveRecord::Import::EMMysql2Adapter
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "activerecord-import/adapters/mysql_adapter"
2
4
 
3
5
  module ActiveRecord::Import::Mysql2Adapter
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord::Import::MysqlAdapter
2
4
  include ActiveRecord::Import::ImportSupport
3
5
  include ActiveRecord::Import::OnDuplicateKeyUpdateSupport
@@ -7,7 +9,7 @@ module ActiveRecord::Import::MysqlAdapter
7
9
 
8
10
  # +sql+ can be a single string or an array. If it is an array all
9
11
  # elements that are in position >= 1 will be appended to the final SQL.
10
- def insert_many( sql, values, *args ) # :nodoc:
12
+ def insert_many( sql, values, options = {}, *args ) # :nodoc:
11
13
  # the number of inserts default
12
14
  number_of_inserts = 0
13
15
 
@@ -31,7 +33,7 @@ module ActiveRecord::Import::MysqlAdapter
31
33
  max = max_allowed_packet
32
34
 
33
35
  # if we can insert it all as one statement
34
- if NO_MAX_PACKET == max || total_bytes < max
36
+ if NO_MAX_PACKET == max || total_bytes <= max || options[:force_single_insert]
35
37
  number_of_inserts += 1
36
38
  sql2insert = base_sql + values.join( ',' ) + post_sql
37
39
  insert( sql2insert, *args )
@@ -39,49 +41,56 @@ module ActiveRecord::Import::MysqlAdapter
39
41
  value_sets = ::ActiveRecord::Import::ValueSetsBytesParser.parse(values,
40
42
  reserved_bytes: sql_size,
41
43
  max_bytes: max)
42
- value_sets.each do |value_set|
43
- number_of_inserts += 1
44
- sql2insert = base_sql + value_set.join( ',' ) + post_sql
45
- insert( sql2insert, *args )
44
+
45
+ transaction(requires_new: true) do
46
+ value_sets.each do |value_set|
47
+ number_of_inserts += 1
48
+ sql2insert = base_sql + value_set.join( ',' ) + post_sql
49
+ insert( sql2insert, *args )
50
+ end
46
51
  end
47
52
  end
48
53
 
49
- [number_of_inserts, []]
54
+ ActiveRecord::Import::Result.new([], number_of_inserts, [], [])
50
55
  end
51
56
 
52
57
  # Returns the maximum number of bytes that the server will allow
53
58
  # in a single packet
54
59
  def max_allowed_packet # :nodoc:
55
60
  @max_allowed_packet ||= begin
56
- result = execute( "SHOW VARIABLES like 'max_allowed_packet';" )
61
+ result = execute( "SELECT @@max_allowed_packet" )
57
62
  # original Mysql gem responds to #fetch_row while Mysql2 responds to #first
58
- val = result.respond_to?(:fetch_row) ? result.fetch_row[1] : result.first[1]
63
+ val = result.respond_to?(:fetch_row) ? result.fetch_row[0] : result.first[0]
59
64
  val.to_i
60
65
  end
61
66
  end
62
67
 
68
+ def pre_sql_statements( options)
69
+ sql = []
70
+ sql << "IGNORE" if options[:ignore] || options[:on_duplicate_key_ignore]
71
+ sql + super
72
+ end
73
+
63
74
  # Add a column to be updated on duplicate key update
64
75
  def add_column_for_on_duplicate_key_update( column, options = {} ) # :nodoc:
65
- if options.include?(:on_duplicate_key_update)
66
- columns = options[:on_duplicate_key_update]
76
+ if (columns = options[:on_duplicate_key_update])
67
77
  case columns
68
78
  when Array then columns << column.to_sym unless columns.include?(column.to_sym)
69
79
  when Hash then columns[column.to_sym] = column.to_sym
70
80
  end
71
- else
72
- options[:on_duplicate_key_update] = [column.to_sym]
73
81
  end
74
82
  end
75
83
 
76
84
  # Returns a generated ON DUPLICATE KEY UPDATE statement given the passed
77
85
  # in +args+.
78
86
  def sql_for_on_duplicate_key_update( table_name, *args ) # :nodoc:
79
- sql = ' ON DUPLICATE KEY UPDATE '
87
+ sql = ' ON DUPLICATE KEY UPDATE '.dup
80
88
  arg = args.first
89
+ locking_column = args.last
81
90
  if arg.is_a?( Array )
82
- sql << sql_for_on_duplicate_key_update_as_array( table_name, arg )
91
+ sql << sql_for_on_duplicate_key_update_as_array( table_name, locking_column, arg )
83
92
  elsif arg.is_a?( Hash )
84
- sql << sql_for_on_duplicate_key_update_as_hash( table_name, arg )
93
+ sql << sql_for_on_duplicate_key_update_as_hash( table_name, locking_column, arg )
85
94
  elsif arg.is_a?( String )
86
95
  sql << arg
87
96
  else
@@ -90,20 +99,22 @@ module ActiveRecord::Import::MysqlAdapter
90
99
  sql
91
100
  end
92
101
 
93
- def sql_for_on_duplicate_key_update_as_array( table_name, arr ) # :nodoc:
102
+ def sql_for_on_duplicate_key_update_as_array( table_name, locking_column, arr ) # :nodoc:
94
103
  results = arr.map do |column|
95
104
  qc = quote_column_name( column )
96
105
  "#{table_name}.#{qc}=VALUES(#{qc})"
97
106
  end
107
+ increment_locking_column!(table_name, results, locking_column)
98
108
  results.join( ',' )
99
109
  end
100
110
 
101
- def sql_for_on_duplicate_key_update_as_hash( table_name, hsh ) # :nodoc:
111
+ def sql_for_on_duplicate_key_update_as_hash( table_name, locking_column, hsh ) # :nodoc:
102
112
  results = hsh.map do |column1, column2|
103
113
  qc1 = quote_column_name( column1 )
104
114
  qc2 = quote_column_name( column2 )
105
115
  "#{table_name}.#{qc1}=VALUES( #{qc2} )"
106
116
  end
117
+ increment_locking_column!(table_name, results, locking_column)
107
118
  results.join( ',')
108
119
  end
109
120
 
@@ -111,4 +122,10 @@ module ActiveRecord::Import::MysqlAdapter
111
122
  def duplicate_key_update_error?(exception) # :nodoc:
112
123
  exception.is_a?(ActiveRecord::StatementInvalid) && exception.to_s.include?('Duplicate entry')
113
124
  end
125
+
126
+ def increment_locking_column!(table_name, results, locking_column)
127
+ if locking_column.present?
128
+ results << "`#{locking_column}`=#{table_name}.`#{locking_column}`+1"
129
+ end
130
+ end
114
131
  end