activerecord-import 0.27.0 → 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 (129) 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 +175 -2
  7. data/Gemfile +13 -9
  8. data/LICENSE +21 -56
  9. data/README.markdown +525 -21
  10. data/Rakefile +2 -0
  11. data/activerecord-import.gemspec +6 -5
  12. data/benchmarks/benchmark.rb +7 -1
  13. data/benchmarks/lib/base.rb +2 -0
  14. data/benchmarks/lib/cli_parser.rb +3 -1
  15. data/benchmarks/lib/float.rb +2 -0
  16. data/benchmarks/lib/mysql2_benchmark.rb +2 -0
  17. data/benchmarks/lib/output_to_csv.rb +2 -0
  18. data/benchmarks/lib/output_to_html.rb +4 -2
  19. data/benchmarks/models/test_innodb.rb +2 -0
  20. data/benchmarks/models/test_memory.rb +2 -0
  21. data/benchmarks/models/test_myisam.rb +2 -0
  22. data/benchmarks/schema/{mysql_schema.rb → mysql2_schema.rb} +2 -0
  23. data/gemfiles/4.2.gemfile +2 -0
  24. data/gemfiles/5.0.gemfile +2 -0
  25. data/gemfiles/5.1.gemfile +2 -0
  26. data/gemfiles/5.2.gemfile +2 -0
  27. data/gemfiles/6.0.gemfile +4 -0
  28. data/gemfiles/6.1.gemfile +4 -0
  29. data/gemfiles/7.0.gemfile +4 -0
  30. data/lib/activerecord-import/active_record/adapters/abstract_adapter.rb +2 -0
  31. data/lib/activerecord-import/active_record/adapters/jdbcmysql_adapter.rb +6 -4
  32. data/lib/activerecord-import/active_record/adapters/jdbcpostgresql_adapter.rb +2 -0
  33. data/lib/activerecord-import/active_record/adapters/jdbcsqlite3_adapter.rb +2 -0
  34. data/lib/activerecord-import/active_record/adapters/mysql2_adapter.rb +2 -0
  35. data/lib/activerecord-import/active_record/adapters/postgresql_adapter.rb +2 -0
  36. data/lib/activerecord-import/active_record/adapters/seamless_database_pool_adapter.rb +2 -0
  37. data/lib/activerecord-import/active_record/adapters/sqlite3_adapter.rb +2 -0
  38. data/lib/activerecord-import/adapters/abstract_adapter.rb +9 -1
  39. data/lib/activerecord-import/adapters/em_mysql2_adapter.rb +2 -0
  40. data/lib/activerecord-import/adapters/mysql2_adapter.rb +2 -0
  41. data/lib/activerecord-import/adapters/mysql_adapter.rb +10 -11
  42. data/lib/activerecord-import/adapters/postgresql_adapter.rb +49 -38
  43. data/lib/activerecord-import/adapters/sqlite3_adapter.rb +21 -25
  44. data/lib/activerecord-import/base.rb +11 -2
  45. data/lib/activerecord-import/import.rb +180 -78
  46. data/lib/activerecord-import/mysql2.rb +2 -0
  47. data/lib/activerecord-import/postgresql.rb +2 -0
  48. data/lib/activerecord-import/sqlite3.rb +2 -0
  49. data/lib/activerecord-import/synchronize.rb +4 -2
  50. data/lib/activerecord-import/value_sets_parser.rb +4 -0
  51. data/lib/activerecord-import/version.rb +3 -1
  52. data/lib/activerecord-import.rb +3 -1
  53. data/test/adapters/jdbcmysql.rb +2 -0
  54. data/test/adapters/jdbcpostgresql.rb +2 -0
  55. data/test/adapters/jdbcsqlite3.rb +2 -0
  56. data/test/adapters/makara_postgis.rb +2 -0
  57. data/test/adapters/mysql2.rb +2 -0
  58. data/test/adapters/mysql2_makara.rb +2 -0
  59. data/test/adapters/mysql2spatial.rb +2 -0
  60. data/test/adapters/postgis.rb +2 -0
  61. data/test/adapters/postgresql.rb +2 -0
  62. data/test/adapters/postgresql_makara.rb +2 -0
  63. data/test/adapters/seamless_database_pool.rb +2 -0
  64. data/test/adapters/spatialite.rb +2 -0
  65. data/test/adapters/sqlite3.rb +2 -0
  66. data/test/{travis → github}/database.yml +3 -1
  67. data/test/import_test.rb +138 -4
  68. data/test/jdbcmysql/import_test.rb +2 -0
  69. data/test/jdbcpostgresql/import_test.rb +2 -0
  70. data/test/jdbcsqlite3/import_test.rb +2 -0
  71. data/test/makara_postgis/import_test.rb +2 -0
  72. data/test/models/account.rb +2 -0
  73. data/test/models/alarm.rb +2 -0
  74. data/test/models/animal.rb +8 -0
  75. data/test/models/bike_maker.rb +2 -0
  76. data/test/models/book.rb +2 -0
  77. data/test/models/car.rb +2 -0
  78. data/test/models/card.rb +5 -0
  79. data/test/models/chapter.rb +2 -0
  80. data/test/models/customer.rb +8 -0
  81. data/test/models/deck.rb +8 -0
  82. data/test/models/dictionary.rb +2 -0
  83. data/test/models/discount.rb +2 -0
  84. data/test/models/end_note.rb +2 -0
  85. data/test/models/group.rb +2 -0
  86. data/test/models/order.rb +8 -0
  87. data/test/models/playing_card.rb +4 -0
  88. data/test/models/promotion.rb +2 -0
  89. data/test/models/question.rb +2 -0
  90. data/test/models/rule.rb +2 -0
  91. data/test/models/tag.rb +3 -0
  92. data/test/models/tag_alias.rb +5 -0
  93. data/test/models/topic.rb +2 -0
  94. data/test/models/user.rb +2 -0
  95. data/test/models/user_token.rb +3 -0
  96. data/test/models/vendor.rb +2 -0
  97. data/test/models/widget.rb +2 -0
  98. data/test/mysql2/import_test.rb +2 -0
  99. data/test/mysql2_makara/import_test.rb +2 -0
  100. data/test/mysqlspatial2/import_test.rb +2 -0
  101. data/test/postgis/import_test.rb +2 -0
  102. data/test/postgresql/import_test.rb +2 -0
  103. data/test/schema/generic_schema.rb +33 -0
  104. data/test/schema/jdbcpostgresql_schema.rb +2 -0
  105. data/test/schema/mysql2_schema.rb +2 -0
  106. data/test/schema/postgis_schema.rb +2 -0
  107. data/test/schema/postgresql_schema.rb +18 -0
  108. data/test/schema/sqlite3_schema.rb +2 -0
  109. data/test/schema/version.rb +2 -0
  110. data/test/sqlite3/import_test.rb +2 -0
  111. data/test/support/active_support/test_case_extensions.rb +2 -0
  112. data/test/support/assertions.rb +2 -0
  113. data/test/support/factories.rb +2 -0
  114. data/test/support/generate.rb +4 -2
  115. data/test/support/mysql/import_examples.rb +2 -1
  116. data/test/support/postgresql/import_examples.rb +115 -2
  117. data/test/support/shared_examples/on_duplicate_key_ignore.rb +2 -0
  118. data/test/support/shared_examples/on_duplicate_key_update.rb +32 -0
  119. data/test/support/shared_examples/recursive_import.rb +61 -1
  120. data/test/support/sqlite3/import_examples.rb +4 -16
  121. data/test/synchronize_test.rb +2 -0
  122. data/test/test_helper.rb +27 -2
  123. data/test/value_sets_bytes_parser_test.rb +2 -0
  124. data/test/value_sets_records_parser_test.rb +2 -0
  125. metadata +29 -16
  126. data/.travis.yml +0 -71
  127. data/gemfiles/3.2.gemfile +0 -2
  128. data/gemfiles/4.0.gemfile +0 -2
  129. data/gemfiles/4.1.gemfile +0 -2
@@ -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,2 +1,4 @@
1
+ # frozen_string_literal: true
2
+
1
3
  gem 'activerecord', '~> 4.2.0'
2
4
  gem 'composite_primary_keys', '~> 8.0'
data/gemfiles/5.0.gemfile CHANGED
@@ -1,2 +1,4 @@
1
+ # frozen_string_literal: true
2
+
1
3
  gem 'activerecord', '~> 5.0.0'
2
4
  gem 'composite_primary_keys', '~> 9.0'
data/gemfiles/5.1.gemfile CHANGED
@@ -1,2 +1,4 @@
1
+ # frozen_string_literal: true
2
+
1
3
  gem 'activerecord', '~> 5.1.0'
2
4
  gem 'composite_primary_keys', '~> 10.0'
data/gemfiles/5.2.gemfile CHANGED
@@ -1,2 +1,4 @@
1
+ # frozen_string_literal: true
2
+
1
3
  gem 'activerecord', '~> 5.2.0'
2
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
 
@@ -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,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,3 +1,5 @@
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)
@@ -46,7 +48,7 @@ module ActiveRecord::Import::AbstractAdapter
46
48
 
47
49
  if supports_on_duplicate_key_update? && options[:on_duplicate_key_update]
48
50
  post_sql_statements << sql_for_on_duplicate_key_update( table_name, options[:on_duplicate_key_update], options[:primary_key], options[:locking_column] )
49
- elsif options[:on_duplicate_key_update]
51
+ elsif logger && options[:on_duplicate_key_update]
50
52
  logger.warn "Ignoring on_duplicate_key_update because it is not supported by the database."
51
53
  end
52
54
 
@@ -59,6 +61,12 @@ module ActiveRecord::Import::AbstractAdapter
59
61
  post_sql_statements
60
62
  end
61
63
 
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
68
+ end
69
+
62
70
  def supports_on_duplicate_key_update?
63
71
  false
64
72
  end
@@ -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
@@ -56,9 +58,9 @@ module ActiveRecord::Import::MysqlAdapter
56
58
  # in a single packet
57
59
  def max_allowed_packet # :nodoc:
58
60
  @max_allowed_packet ||= begin
59
- result = execute( "SHOW VARIABLES like 'max_allowed_packet'" )
61
+ result = execute( "SELECT @@max_allowed_packet" )
60
62
  # original Mysql gem responds to #fetch_row while Mysql2 responds to #first
61
- 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]
62
64
  val.to_i
63
65
  end
64
66
  end
@@ -71,21 +73,18 @@ module ActiveRecord::Import::MysqlAdapter
71
73
 
72
74
  # Add a column to be updated on duplicate key update
73
75
  def add_column_for_on_duplicate_key_update( column, options = {} ) # :nodoc:
74
- if options.include?(:on_duplicate_key_update)
75
- columns = options[:on_duplicate_key_update]
76
+ if (columns = options[:on_duplicate_key_update])
76
77
  case columns
77
78
  when Array then columns << column.to_sym unless columns.include?(column.to_sym)
78
79
  when Hash then columns[column.to_sym] = column.to_sym
79
80
  end
80
- elsif !options[:ignore] && !options[:on_duplicate_key_ignore]
81
- options[:on_duplicate_key_update] = [column.to_sym]
82
81
  end
83
82
  end
84
83
 
85
84
  # Returns a generated ON DUPLICATE KEY UPDATE statement given the passed
86
85
  # in +args+.
87
86
  def sql_for_on_duplicate_key_update( table_name, *args ) # :nodoc:
88
- sql = ' ON DUPLICATE KEY UPDATE '
87
+ sql = ' ON DUPLICATE KEY UPDATE '.dup
89
88
  arg = args.first
90
89
  locking_column = args.last
91
90
  if arg.is_a?( Array )
@@ -105,7 +104,7 @@ module ActiveRecord::Import::MysqlAdapter
105
104
  qc = quote_column_name( column )
106
105
  "#{table_name}.#{qc}=VALUES(#{qc})"
107
106
  end
108
- increment_locking_column!(results, table_name, locking_column)
107
+ increment_locking_column!(table_name, results, locking_column)
109
108
  results.join( ',' )
110
109
  end
111
110
 
@@ -115,7 +114,7 @@ module ActiveRecord::Import::MysqlAdapter
115
114
  qc2 = quote_column_name( column2 )
116
115
  "#{table_name}.#{qc1}=VALUES( #{qc2} )"
117
116
  end
118
- increment_locking_column!(results, table_name, locking_column)
117
+ increment_locking_column!(table_name, results, locking_column)
119
118
  results.join( ',')
120
119
  end
121
120
 
@@ -124,9 +123,9 @@ module ActiveRecord::Import::MysqlAdapter
124
123
  exception.is_a?(ActiveRecord::StatementInvalid) && exception.to_s.include?('Duplicate entry')
125
124
  end
126
125
 
127
- def increment_locking_column!(results, table_name, locking_column)
126
+ def increment_locking_column!(table_name, results, locking_column)
128
127
  if locking_column.present?
129
- results << "#{table_name}.`#{locking_column}`=`#{locking_column}`+1"
128
+ results << "`#{locking_column}`=#{table_name}.`#{locking_column}`+1"
130
129
  end
131
130
  end
132
131
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord::Import::PostgreSQLAdapter
2
4
  include ActiveRecord::Import::ImportSupport
3
5
  include ActiveRecord::Import::OnDuplicateKeyUpdateSupport
@@ -6,7 +8,7 @@ module ActiveRecord::Import::PostgreSQLAdapter
6
8
 
7
9
  def insert_many( sql, values, options = {}, *args ) # :nodoc:
8
10
  number_of_inserts = 1
9
- returned_values = []
11
+ returned_values = {}
10
12
  ids = []
11
13
  results = []
12
14
 
@@ -18,47 +20,53 @@ module ActiveRecord::Import::PostgreSQLAdapter
18
20
 
19
21
  sql2insert = base_sql + values.join( ',' ) + post_sql
20
22
 
21
- columns = returning_columns(options)
22
- if columns.blank? || options[:no_returning]
23
+ selections = returning_selections(options)
24
+ if selections.blank? || (options[:no_returning] && !options[:recursive])
23
25
  insert( sql2insert, *args )
24
26
  else
25
- returned_values = if columns.size > 1
27
+ returned_values = if selections.size > 1
26
28
  # Select composite columns
27
- select_rows( sql2insert, *args )
29
+ db_result = select_all( sql2insert, *args )
30
+ { values: db_result.rows, columns: db_result.columns }
28
31
  else
29
- select_values( sql2insert, *args )
32
+ { values: select_values( sql2insert, *args ) }
30
33
  end
31
- query_cache.clear if query_cache_enabled
34
+ clear_query_cache if query_cache_enabled
32
35
  end
33
36
 
34
37
  if options[:returning].blank?
35
- ids = returned_values
38
+ ids = Array(returned_values[:values])
36
39
  elsif options[:primary_key].blank?
37
- results = returned_values
40
+ options[:returning_columns] ||= returned_values[:columns]
41
+ results = Array(returned_values[:values])
38
42
  else
39
43
  # split primary key and returning columns
40
- ids, results = split_ids_and_results(returned_values, columns, options)
44
+ ids, results, options[:returning_columns] = split_ids_and_results(returned_values, options)
41
45
  end
42
46
 
43
47
  ActiveRecord::Import::Result.new([], number_of_inserts, ids, results)
44
48
  end
45
49
 
46
- def split_ids_and_results(values, columns, options)
50
+ def split_ids_and_results( selections, options )
47
51
  ids = []
48
- results = []
52
+ returning_values = []
53
+
54
+ columns = Array(selections[:columns])
55
+ values = Array(selections[:values])
49
56
  id_indexes = Array(options[:primary_key]).map { |key| columns.index(key) }
50
- returning_indexes = Array(options[:returning]).map { |key| columns.index(key) }
57
+ returning_columns = columns.reject.with_index { |_, index| id_indexes.include?(index) }
58
+ returning_indexes = returning_columns.map { |column| columns.index(column) }
51
59
 
52
60
  values.each do |value|
53
61
  value_array = Array(value)
54
- ids << id_indexes.map { |i| value_array[i] }
55
- results << returning_indexes.map { |i| value_array[i] }
62
+ ids << id_indexes.map { |index| value_array[index] }
63
+ returning_values << returning_indexes.map { |index| value_array[index] }
56
64
  end
57
65
 
58
66
  ids.map!(&:first) if id_indexes.size == 1
59
- results.map!(&:first) if returning_indexes.size == 1
67
+ returning_values.map!(&:first) if returning_columns.size == 1
60
68
 
61
- [ids, results]
69
+ [ids, returning_values, returning_columns]
62
70
  end
63
71
 
64
72
  def next_value_for_sequence(sequence_name)
@@ -73,25 +81,30 @@ module ActiveRecord::Import::PostgreSQLAdapter
73
81
  if (options[:ignore] || options[:on_duplicate_key_ignore]) && !options[:on_duplicate_key_update] && !options[:recursive]
74
82
  sql << sql_for_on_duplicate_key_ignore( table_name, options[:on_duplicate_key_ignore] )
75
83
  end
76
- elsif options[:on_duplicate_key_ignore] && !options[:on_duplicate_key_update]
84
+ elsif logger && options[:on_duplicate_key_ignore] && !options[:on_duplicate_key_update]
77
85
  logger.warn "Ignoring on_duplicate_key_ignore because it is not supported by the database."
78
86
  end
79
87
 
80
88
  sql += super(table_name, options)
81
89
 
82
- columns = returning_columns(options)
83
- unless columns.blank? || options[:no_returning]
84
- sql << " RETURNING \"#{columns.join('", "')}\""
90
+ selections = returning_selections(options)
91
+ unless selections.blank? || (options[:no_returning] && !options[:recursive])
92
+ sql << " RETURNING #{selections.join(', ')}"
85
93
  end
86
94
 
87
95
  sql
88
96
  end
89
97
 
90
- def returning_columns(options)
91
- columns = []
92
- columns += Array(options[:primary_key]) if options[:primary_key].present?
93
- columns |= Array(options[:returning]) if options[:returning].present?
94
- columns
98
+ def returning_selections(options)
99
+ selections = []
100
+ column_names = Array(options[:model].column_names)
101
+
102
+ selections += Array(options[:primary_key]) if options[:primary_key].present?
103
+ selections += Array(options[:returning]) if options[:returning].present?
104
+
105
+ selections.map do |selection|
106
+ column_names.include?(selection.to_s) ? "\"#{selection}\"" : selection
107
+ end
95
108
  end
96
109
 
97
110
  # Add a column to be updated on duplicate key update
@@ -123,7 +136,7 @@ module ActiveRecord::Import::PostgreSQLAdapter
123
136
  arg = { columns: arg } if arg.is_a?( Array ) || arg.is_a?( String )
124
137
  return unless arg.is_a?( Hash )
125
138
 
126
- sql = ' ON CONFLICT '
139
+ sql = ' ON CONFLICT '.dup
127
140
  conflict_target = sql_for_conflict_target( arg )
128
141
 
129
142
  columns = arg.fetch( :columns, [] )
@@ -158,7 +171,7 @@ module ActiveRecord::Import::PostgreSQLAdapter
158
171
  qc = quote_column_name( column )
159
172
  "#{qc}=EXCLUDED.#{qc}"
160
173
  end
161
- increment_locking_column!(results, locking_column)
174
+ increment_locking_column!(table_name, results, locking_column)
162
175
  results.join( ',' )
163
176
  end
164
177
 
@@ -168,7 +181,7 @@ module ActiveRecord::Import::PostgreSQLAdapter
168
181
  qc2 = quote_column_name( column2 )
169
182
  "#{qc1}=EXCLUDED.#{qc2}"
170
183
  end
171
- increment_locking_column!(results, locking_column)
184
+ increment_locking_column!(table_name, results, locking_column)
172
185
  results.join( ',' )
173
186
  end
174
187
 
@@ -179,9 +192,9 @@ module ActiveRecord::Import::PostgreSQLAdapter
179
192
  if constraint_name.present?
180
193
  "ON CONSTRAINT #{constraint_name} "
181
194
  elsif conflict_target.present?
182
- '(' << Array( conflict_target ).reject( &:blank? ).join( ', ' ) << ') '.tap do |sql|
183
- sql << "WHERE #{index_predicate} " if index_predicate
184
- end
195
+ sql = '(' + Array( conflict_target ).reject( &:blank? ).join( ', ' ) + ') '
196
+ sql += "WHERE #{index_predicate} " if index_predicate
197
+ sql
185
198
  end
186
199
  end
187
200
 
@@ -195,17 +208,15 @@ module ActiveRecord::Import::PostgreSQLAdapter
195
208
  exception.is_a?(ActiveRecord::StatementInvalid) && exception.to_s.include?('duplicate key')
196
209
  end
197
210
 
198
- def supports_on_duplicate_key_update?(current_version = postgresql_version)
199
- current_version >= MIN_VERSION_FOR_UPSERT
211
+ def supports_on_duplicate_key_update?
212
+ database_version >= MIN_VERSION_FOR_UPSERT
200
213
  end
201
214
 
202
215
  def supports_setting_primary_key_of_imported_objects?
203
216
  true
204
217
  end
205
218
 
206
- def increment_locking_column!(results, locking_column)
207
- if locking_column.present?
208
- results << "\"#{locking_column}\"=EXCLUDED.\"#{locking_column}\"+1"
209
- end
219
+ def database_version
220
+ defined?(postgresql_version) ? postgresql_version : super
210
221
  end
211
222
  end
@@ -1,24 +1,22 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord::Import::SQLite3Adapter
2
4
  include ActiveRecord::Import::ImportSupport
3
5
  include ActiveRecord::Import::OnDuplicateKeyUpdateSupport
4
6
 
5
- MIN_VERSION_FOR_IMPORT = "3.7.11".freeze
6
- MIN_VERSION_FOR_UPSERT = "3.24.0".freeze
7
+ MIN_VERSION_FOR_IMPORT = "3.7.11"
8
+ MIN_VERSION_FOR_UPSERT = "3.24.0"
7
9
  SQLITE_LIMIT_COMPOUND_SELECT = 500
8
10
 
9
11
  # Override our conformance to ActiveRecord::Import::ImportSupport interface
10
12
  # to ensure that we only support import in supported version of SQLite.
11
13
  # Which INSERT statements with multiple value sets was introduced in 3.7.11.
12
- def supports_import?(current_version = sqlite_version)
13
- if current_version >= MIN_VERSION_FOR_IMPORT
14
- true
15
- else
16
- false
17
- end
14
+ def supports_import?
15
+ database_version >= MIN_VERSION_FOR_IMPORT
18
16
  end
19
17
 
20
- def supports_on_duplicate_key_update?(current_version = sqlite_version)
21
- current_version >= MIN_VERSION_FOR_UPSERT
18
+ def supports_on_duplicate_key_update?
19
+ database_version >= MIN_VERSION_FOR_UPSERT
22
20
  end
23
21
 
24
22
  # +sql+ can be a single string or an array. If it is an array all
@@ -96,12 +94,12 @@ module ActiveRecord::Import::SQLite3Adapter
96
94
 
97
95
  # Returns a generated ON CONFLICT DO UPDATE statement given the passed
98
96
  # in +args+.
99
- def sql_for_on_duplicate_key_update( _table_name, *args ) # :nodoc:
97
+ def sql_for_on_duplicate_key_update( table_name, *args ) # :nodoc:
100
98
  arg, primary_key, locking_column = args
101
99
  arg = { columns: arg } if arg.is_a?( Array ) || arg.is_a?( String )
102
100
  return unless arg.is_a?( Hash )
103
101
 
104
- sql = ' ON CONFLICT '
102
+ sql = ' ON CONFLICT '.dup
105
103
  conflict_target = sql_for_conflict_target( arg )
106
104
 
107
105
  columns = arg.fetch( :columns, [] )
@@ -117,9 +115,9 @@ module ActiveRecord::Import::SQLite3Adapter
117
115
 
118
116
  sql << "#{conflict_target}DO UPDATE SET "
119
117
  if columns.is_a?( Array )
120
- sql << sql_for_on_duplicate_key_update_as_array( locking_column, columns )
118
+ sql << sql_for_on_duplicate_key_update_as_array( table_name, locking_column, columns )
121
119
  elsif columns.is_a?( Hash )
122
- sql << sql_for_on_duplicate_key_update_as_hash( locking_column, columns )
120
+ sql << sql_for_on_duplicate_key_update_as_hash( table_name, locking_column, columns )
123
121
  elsif columns.is_a?( String )
124
122
  sql << columns
125
123
  else
@@ -131,22 +129,22 @@ module ActiveRecord::Import::SQLite3Adapter
131
129
  sql
132
130
  end
133
131
 
134
- def sql_for_on_duplicate_key_update_as_array( locking_column, arr ) # :nodoc:
132
+ def sql_for_on_duplicate_key_update_as_array( table_name, locking_column, arr ) # :nodoc:
135
133
  results = arr.map do |column|
136
134
  qc = quote_column_name( column )
137
135
  "#{qc}=EXCLUDED.#{qc}"
138
136
  end
139
- increment_locking_column!(results, locking_column)
137
+ increment_locking_column!(table_name, results, locking_column)
140
138
  results.join( ',' )
141
139
  end
142
140
 
143
- def sql_for_on_duplicate_key_update_as_hash( locking_column, hsh ) # :nodoc:
141
+ def sql_for_on_duplicate_key_update_as_hash( table_name, locking_column, hsh ) # :nodoc:
144
142
  results = hsh.map do |column1, column2|
145
143
  qc1 = quote_column_name( column1 )
146
144
  qc2 = quote_column_name( column2 )
147
145
  "#{qc1}=EXCLUDED.#{qc2}"
148
146
  end
149
- increment_locking_column!(results, locking_column)
147
+ increment_locking_column!(table_name, results, locking_column)
150
148
  results.join( ',' )
151
149
  end
152
150
 
@@ -154,9 +152,9 @@ module ActiveRecord::Import::SQLite3Adapter
154
152
  conflict_target = args[:conflict_target]
155
153
  index_predicate = args[:index_predicate]
156
154
  if conflict_target.present?
157
- '(' << Array( conflict_target ).reject( &:blank? ).join( ', ' ) << ') '.tap do |sql|
158
- sql << "WHERE #{index_predicate} " if index_predicate
159
- end
155
+ sql = '(' + Array( conflict_target ).reject( &:blank? ).join( ', ' ) + ') '
156
+ sql += "WHERE #{index_predicate} " if index_predicate
157
+ sql
160
158
  end
161
159
  end
162
160
 
@@ -170,9 +168,7 @@ module ActiveRecord::Import::SQLite3Adapter
170
168
  exception.is_a?(ActiveRecord::StatementInvalid) && exception.to_s.include?('duplicate key')
171
169
  end
172
170
 
173
- def increment_locking_column!(results, locking_column)
174
- if locking_column.present?
175
- results << "\"#{locking_column}\"=EXCLUDED.\"#{locking_column}\"+1"
176
- end
171
+ def database_version
172
+ defined?(sqlite_version) ? sqlite_version : super
177
173
  end
178
174
  end
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "pathname"
2
4
  require "active_record"
3
5
  require "active_record/version"
4
6
 
5
7
  module ActiveRecord::Import
6
- ADAPTER_PATH = "activerecord-import/active_record/adapters".freeze
8
+ ADAPTER_PATH = "activerecord-import/active_record/adapters"
7
9
 
8
10
  def self.base_adapter(adapter)
9
11
  case adapter
@@ -13,6 +15,7 @@ module ActiveRecord::Import
13
15
  when 'postgresql_makara' then 'postgresql'
14
16
  when 'makara_postgis' then 'postgresql'
15
17
  when 'postgis' then 'postgresql'
18
+ when 'cockroachdb' then 'postgresql'
16
19
  else adapter
17
20
  end
18
21
  end
@@ -26,7 +29,13 @@ module ActiveRecord::Import
26
29
 
27
30
  # Loads the import functionality for the passed in ActiveRecord connection
28
31
  def self.load_from_connection_pool(connection_pool)
29
- require_adapter connection_pool.spec.config[:adapter]
32
+ adapter =
33
+ if connection_pool.respond_to?(:db_config) # ActiveRecord >= 6.1
34
+ connection_pool.db_config.adapter
35
+ else
36
+ connection_pool.spec.config[:adapter]
37
+ end
38
+ require_adapter adapter
30
39
  end
31
40
  end
32
41