activerecord-import 0.14.1 → 1.4.1

Sign up to get free protection for your applications and to get access to all the features.
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