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,12 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord::Import::PostgreSQLAdapter
2
4
  include ActiveRecord::Import::ImportSupport
3
5
  include ActiveRecord::Import::OnDuplicateKeyUpdateSupport
4
6
 
5
7
  MIN_VERSION_FOR_UPSERT = 90_500
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
11
+ returned_values = {}
9
12
  ids = []
13
+ results = []
10
14
 
11
15
  base_sql, post_sql = if sql.is_a?( String )
12
16
  [sql, '']
@@ -15,15 +19,54 @@ module ActiveRecord::Import::PostgreSQLAdapter
15
19
  end
16
20
 
17
21
  sql2insert = base_sql + values.join( ',' ) + post_sql
18
- if post_sql =~ /RETURNING\s/
19
- ids = select_values( sql2insert, *args )
20
- else
22
+
23
+ selections = returning_selections(options)
24
+ if selections.blank? || (options[:no_returning] && !options[:recursive])
21
25
  insert( sql2insert, *args )
26
+ else
27
+ returned_values = if selections.size > 1
28
+ # Select composite columns
29
+ db_result = select_all( sql2insert, *args )
30
+ { values: db_result.rows, columns: db_result.columns }
31
+ else
32
+ { values: select_values( sql2insert, *args ) }
33
+ end
34
+ clear_query_cache if query_cache_enabled
35
+ end
36
+
37
+ if options[:returning].blank?
38
+ ids = Array(returned_values[:values])
39
+ elsif options[:primary_key].blank?
40
+ options[:returning_columns] ||= returned_values[:columns]
41
+ results = Array(returned_values[:values])
42
+ else
43
+ # split primary key and returning columns
44
+ ids, results, options[:returning_columns] = split_ids_and_results(returned_values, options)
22
45
  end
23
46
 
24
- ActiveRecord::Base.connection.query_cache.clear
47
+ ActiveRecord::Import::Result.new([], number_of_inserts, ids, results)
48
+ end
49
+
50
+ def split_ids_and_results( selections, options )
51
+ ids = []
52
+ returning_values = []
53
+
54
+ columns = Array(selections[:columns])
55
+ values = Array(selections[:values])
56
+ id_indexes = Array(options[:primary_key]).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) }
59
+
60
+ values.each do |value|
61
+ value_array = Array(value)
62
+ ids << id_indexes.map { |index| value_array[index] }
63
+ returning_values << returning_indexes.map { |index| value_array[index] }
64
+ end
25
65
 
26
- [number_of_inserts, ids]
66
+ ids.map!(&:first) if id_indexes.size == 1
67
+ returning_values.map!(&:first) if returning_columns.size == 1
68
+
69
+ [ids, returning_values, returning_columns]
27
70
  end
28
71
 
29
72
  def next_value_for_sequence(sequence_name)
@@ -31,10 +74,36 @@ module ActiveRecord::Import::PostgreSQLAdapter
31
74
  end
32
75
 
33
76
  def post_sql_statements( table_name, options ) # :nodoc:
34
- if options[:no_returning] || options[:primary_key].blank?
35
- super(table_name, options)
36
- else
37
- super(table_name, options) << "RETURNING #{options[:primary_key]}"
77
+ sql = []
78
+
79
+ if supports_on_duplicate_key_update?
80
+ # Options :recursive and :on_duplicate_key_ignore are mutually exclusive
81
+ if (options[:ignore] || options[:on_duplicate_key_ignore]) && !options[:on_duplicate_key_update] && !options[:recursive]
82
+ sql << sql_for_on_duplicate_key_ignore( table_name, options[:on_duplicate_key_ignore] )
83
+ end
84
+ elsif logger && options[:on_duplicate_key_ignore] && !options[:on_duplicate_key_update]
85
+ logger.warn "Ignoring on_duplicate_key_ignore because it is not supported by the database."
86
+ end
87
+
88
+ sql += super(table_name, options)
89
+
90
+ selections = returning_selections(options)
91
+ unless selections.blank? || (options[:no_returning] && !options[:recursive])
92
+ sql << " RETURNING #{selections.join(', ')}"
93
+ end
94
+
95
+ sql
96
+ end
97
+
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
38
107
  end
39
108
  end
40
109
 
@@ -63,65 +132,75 @@ module ActiveRecord::Import::PostgreSQLAdapter
63
132
  # Returns a generated ON CONFLICT DO UPDATE statement given the passed
64
133
  # in +args+.
65
134
  def sql_for_on_duplicate_key_update( table_name, *args ) # :nodoc:
66
- arg = args.first
135
+ arg, primary_key, locking_column = args
67
136
  arg = { columns: arg } if arg.is_a?( Array ) || arg.is_a?( String )
68
137
  return unless arg.is_a?( Hash )
69
138
 
70
- sql = " ON CONFLICT "
139
+ sql = ' ON CONFLICT '.dup
71
140
  conflict_target = sql_for_conflict_target( arg )
72
141
 
73
142
  columns = arg.fetch( :columns, [] )
143
+ condition = arg[:condition]
74
144
  if columns.respond_to?( :empty? ) && columns.empty?
75
145
  return sql << "#{conflict_target}DO NOTHING"
76
146
  end
77
147
 
78
- conflict_target ||= sql_for_default_conflict_target( table_name )
148
+ conflict_target ||= sql_for_default_conflict_target( table_name, primary_key )
79
149
  unless conflict_target
80
150
  raise ArgumentError, 'Expected :conflict_target or :constraint_name to be specified'
81
151
  end
82
152
 
83
153
  sql << "#{conflict_target}DO UPDATE SET "
84
154
  if columns.is_a?( Array )
85
- sql << sql_for_on_duplicate_key_update_as_array( table_name, columns )
155
+ sql << sql_for_on_duplicate_key_update_as_array( table_name, locking_column, columns )
86
156
  elsif columns.is_a?( Hash )
87
- sql << sql_for_on_duplicate_key_update_as_hash( table_name, columns )
157
+ sql << sql_for_on_duplicate_key_update_as_hash( table_name, locking_column, columns )
88
158
  elsif columns.is_a?( String )
89
159
  sql << columns
90
160
  else
91
161
  raise ArgumentError, 'Expected :columns to be an Array or Hash'
92
162
  end
163
+
164
+ sql << " WHERE #{condition}" if condition.present?
165
+
93
166
  sql
94
167
  end
95
168
 
96
- def sql_for_on_duplicate_key_update_as_array( table_name, arr ) # :nodoc:
169
+ def sql_for_on_duplicate_key_update_as_array( table_name, locking_column, arr ) # :nodoc:
97
170
  results = arr.map do |column|
98
171
  qc = quote_column_name( column )
99
172
  "#{qc}=EXCLUDED.#{qc}"
100
173
  end
174
+ increment_locking_column!(table_name, results, locking_column)
101
175
  results.join( ',' )
102
176
  end
103
177
 
104
- def sql_for_on_duplicate_key_update_as_hash( table_name, hsh ) # :nodoc:
178
+ def sql_for_on_duplicate_key_update_as_hash( table_name, locking_column, hsh ) # :nodoc:
105
179
  results = hsh.map do |column1, column2|
106
180
  qc1 = quote_column_name( column1 )
107
181
  qc2 = quote_column_name( column2 )
108
182
  "#{qc1}=EXCLUDED.#{qc2}"
109
183
  end
184
+ increment_locking_column!(table_name, results, locking_column)
110
185
  results.join( ',' )
111
186
  end
112
187
 
113
188
  def sql_for_conflict_target( args = {} )
114
189
  constraint_name = args[:constraint_name]
115
190
  conflict_target = args[:conflict_target]
116
- if constraint_name
191
+ index_predicate = args[:index_predicate]
192
+ if constraint_name.present?
117
193
  "ON CONSTRAINT #{constraint_name} "
118
- elsif conflict_target
119
- '(' << Array( conflict_target ).join( ', ' ) << ') '
194
+ elsif conflict_target.present?
195
+ sql = '(' + Array( conflict_target ).reject( &:blank? ).join( ', ' ) + ') '
196
+ sql += "WHERE #{index_predicate} " if index_predicate
197
+ sql
120
198
  end
121
199
  end
122
200
 
123
- def sql_for_default_conflict_target( table_name )
124
- "(#{primary_key( table_name )}) "
201
+ def sql_for_default_conflict_target( table_name, primary_key )
202
+ conflict_target = Array(primary_key).join(', ')
203
+ "(#{conflict_target}) " if conflict_target.present?
125
204
  end
126
205
 
127
206
  # Return true if the statement is a duplicate key record error
@@ -129,15 +208,15 @@ module ActiveRecord::Import::PostgreSQLAdapter
129
208
  exception.is_a?(ActiveRecord::StatementInvalid) && exception.to_s.include?('duplicate key')
130
209
  end
131
210
 
132
- def supports_on_duplicate_key_update?(current_version = postgresql_version)
133
- current_version >= MIN_VERSION_FOR_UPSERT
211
+ def supports_on_duplicate_key_update?
212
+ database_version >= MIN_VERSION_FOR_UPSERT
134
213
  end
135
214
 
136
- def supports_on_duplicate_key_ignore?(current_version = postgresql_version)
137
- supports_on_duplicate_key_update?(current_version)
215
+ def supports_setting_primary_key_of_imported_objects?
216
+ true
138
217
  end
139
218
 
140
- def support_setting_primary_key_of_imported_objects?
141
- true
219
+ def database_version
220
+ defined?(postgresql_version) ? postgresql_version : super
142
221
  end
143
222
  end
@@ -1,25 +1,28 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord::Import::SQLite3Adapter
2
4
  include ActiveRecord::Import::ImportSupport
5
+ include ActiveRecord::Import::OnDuplicateKeyUpdateSupport
3
6
 
4
- MIN_VERSION_FOR_IMPORT = "3.7.11".freeze
7
+ MIN_VERSION_FOR_IMPORT = "3.7.11"
8
+ MIN_VERSION_FOR_UPSERT = "3.24.0"
5
9
  SQLITE_LIMIT_COMPOUND_SELECT = 500
6
10
 
7
11
  # Override our conformance to ActiveRecord::Import::ImportSupport interface
8
12
  # to ensure that we only support import in supported version of SQLite.
9
13
  # Which INSERT statements with multiple value sets was introduced in 3.7.11.
10
- def supports_import?(current_version = sqlite_version)
11
- if current_version >= MIN_VERSION_FOR_IMPORT
12
- true
13
- else
14
- false
15
- end
14
+ def supports_import?
15
+ database_version >= MIN_VERSION_FOR_IMPORT
16
+ end
17
+
18
+ def supports_on_duplicate_key_update?
19
+ database_version >= MIN_VERSION_FOR_UPSERT
16
20
  end
17
21
 
18
22
  # +sql+ can be a single string or an array. If it is an array all
19
23
  # elements that are in position >= 1 will be appended to the final SQL.
20
- def insert_many(sql, values, *args) # :nodoc:
24
+ def insert_many( sql, values, _options = {}, *args ) # :nodoc:
21
25
  number_of_inserts = 0
22
- ids = []
23
26
 
24
27
  base_sql, post_sql = if sql.is_a?( String )
25
28
  [sql, '']
@@ -30,22 +33,142 @@ module ActiveRecord::Import::SQLite3Adapter
30
33
  value_sets = ::ActiveRecord::Import::ValueSetsRecordsParser.parse(values,
31
34
  max_records: SQLITE_LIMIT_COMPOUND_SELECT)
32
35
 
33
- value_sets.each do |value_set|
34
- number_of_inserts += 1
35
- sql2insert = base_sql + value_set.join( ',' ) + post_sql
36
- first_insert_id = insert( sql2insert, *args )
37
- last_insert_id = first_insert_id + value_set.size - 1
38
- ids.concat((first_insert_id..last_insert_id).to_a)
36
+ transaction(requires_new: true) do
37
+ value_sets.each do |value_set|
38
+ number_of_inserts += 1
39
+ sql2insert = base_sql + value_set.join( ',' ) + post_sql
40
+ insert( sql2insert, *args )
41
+ end
42
+ end
43
+
44
+ ActiveRecord::Import::Result.new([], number_of_inserts, [], [])
45
+ end
46
+
47
+ def pre_sql_statements( options )
48
+ sql = []
49
+ # Options :recursive and :on_duplicate_key_ignore are mutually exclusive
50
+ if !supports_on_duplicate_key_update? && (options[:ignore] || options[:on_duplicate_key_ignore])
51
+ sql << "OR IGNORE"
52
+ end
53
+ sql + super
54
+ end
55
+
56
+ def post_sql_statements( table_name, options ) # :nodoc:
57
+ sql = []
58
+
59
+ if supports_on_duplicate_key_update?
60
+ # Options :recursive and :on_duplicate_key_ignore are mutually exclusive
61
+ if (options[:ignore] || options[:on_duplicate_key_ignore]) && !options[:on_duplicate_key_update]
62
+ sql << sql_for_on_duplicate_key_ignore( options[:on_duplicate_key_ignore] )
63
+ end
39
64
  end
40
65
 
41
- [number_of_inserts, ids]
66
+ sql + super
42
67
  end
43
68
 
44
69
  def next_value_for_sequence(sequence_name)
45
70
  %{nextval('#{sequence_name}')}
46
71
  end
47
72
 
48
- def support_setting_primary_key_of_imported_objects?
49
- true
73
+ # Add a column to be updated on duplicate key update
74
+ def add_column_for_on_duplicate_key_update( column, options = {} ) # :nodoc:
75
+ arg = options[:on_duplicate_key_update]
76
+ if arg.is_a?( Hash )
77
+ columns = arg.fetch( :columns ) { arg[:columns] = [] }
78
+ case columns
79
+ when Array then columns << column.to_sym unless columns.include?( column.to_sym )
80
+ when Hash then columns[column.to_sym] = column.to_sym
81
+ end
82
+ elsif arg.is_a?( Array )
83
+ arg << column.to_sym unless arg.include?( column.to_sym )
84
+ end
85
+ end
86
+
87
+ # Returns a generated ON CONFLICT DO NOTHING statement given the passed
88
+ # in +args+.
89
+ def sql_for_on_duplicate_key_ignore( *args ) # :nodoc:
90
+ arg = args.first
91
+ conflict_target = sql_for_conflict_target( arg ) if arg.is_a?( Hash )
92
+ " ON CONFLICT #{conflict_target}DO NOTHING"
93
+ end
94
+
95
+ # Returns a generated ON CONFLICT DO UPDATE statement given the passed
96
+ # in +args+.
97
+ def sql_for_on_duplicate_key_update( table_name, *args ) # :nodoc:
98
+ arg, primary_key, locking_column = args
99
+ arg = { columns: arg } if arg.is_a?( Array ) || arg.is_a?( String )
100
+ return unless arg.is_a?( Hash )
101
+
102
+ sql = ' ON CONFLICT '.dup
103
+ conflict_target = sql_for_conflict_target( arg )
104
+
105
+ columns = arg.fetch( :columns, [] )
106
+ condition = arg[:condition]
107
+ if columns.respond_to?( :empty? ) && columns.empty?
108
+ return sql << "#{conflict_target}DO NOTHING"
109
+ end
110
+
111
+ conflict_target ||= sql_for_default_conflict_target( primary_key )
112
+ unless conflict_target
113
+ raise ArgumentError, 'Expected :conflict_target to be specified'
114
+ end
115
+
116
+ sql << "#{conflict_target}DO UPDATE SET "
117
+ if columns.is_a?( Array )
118
+ sql << sql_for_on_duplicate_key_update_as_array( table_name, locking_column, columns )
119
+ elsif columns.is_a?( Hash )
120
+ sql << sql_for_on_duplicate_key_update_as_hash( table_name, locking_column, columns )
121
+ elsif columns.is_a?( String )
122
+ sql << columns
123
+ else
124
+ raise ArgumentError, 'Expected :columns to be an Array or Hash'
125
+ end
126
+
127
+ sql << " WHERE #{condition}" if condition.present?
128
+
129
+ sql
130
+ end
131
+
132
+ def sql_for_on_duplicate_key_update_as_array( table_name, locking_column, arr ) # :nodoc:
133
+ results = arr.map do |column|
134
+ qc = quote_column_name( column )
135
+ "#{qc}=EXCLUDED.#{qc}"
136
+ end
137
+ increment_locking_column!(table_name, results, locking_column)
138
+ results.join( ',' )
139
+ end
140
+
141
+ def sql_for_on_duplicate_key_update_as_hash( table_name, locking_column, hsh ) # :nodoc:
142
+ results = hsh.map do |column1, column2|
143
+ qc1 = quote_column_name( column1 )
144
+ qc2 = quote_column_name( column2 )
145
+ "#{qc1}=EXCLUDED.#{qc2}"
146
+ end
147
+ increment_locking_column!(table_name, results, locking_column)
148
+ results.join( ',' )
149
+ end
150
+
151
+ def sql_for_conflict_target( args = {} )
152
+ conflict_target = args[:conflict_target]
153
+ index_predicate = args[:index_predicate]
154
+ if conflict_target.present?
155
+ sql = '(' + Array( conflict_target ).reject( &:blank? ).join( ', ' ) + ') '
156
+ sql += "WHERE #{index_predicate} " if index_predicate
157
+ sql
158
+ end
159
+ end
160
+
161
+ def sql_for_default_conflict_target( primary_key )
162
+ conflict_target = Array(primary_key).join(', ')
163
+ "(#{conflict_target}) " if conflict_target.present?
164
+ end
165
+
166
+ # Return true if the statement is a duplicate key record error
167
+ def duplicate_key_update_error?(exception) # :nodoc:
168
+ exception.is_a?(ActiveRecord::StatementInvalid) && exception.to_s.include?('duplicate key')
169
+ end
170
+
171
+ def database_version
172
+ defined?(sqlite_version) ? sqlite_version : super
50
173
  end
51
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
@@ -11,24 +13,29 @@ module ActiveRecord::Import
11
13
  when 'mysql2spatial' then 'mysql2'
12
14
  when 'spatialite' then 'sqlite3'
13
15
  when 'postgresql_makara' then 'postgresql'
16
+ when 'makara_postgis' then 'postgresql'
14
17
  when 'postgis' then 'postgresql'
18
+ when 'cockroachdb' then 'postgresql'
15
19
  else adapter
16
20
  end
17
21
  end
18
22
 
19
23
  # Loads the import functionality for a specific database adapter
20
24
  def self.require_adapter(adapter)
21
- require File.join(ADAPTER_PATH, "/abstract_adapter")
22
- begin
23
- require File.join(ADAPTER_PATH, "/#{base_adapter(adapter)}_adapter")
24
- rescue LoadError
25
- # fallback
26
- end
25
+ require File.join(ADAPTER_PATH, "/#{base_adapter(adapter)}_adapter")
26
+ rescue LoadError
27
+ # fallback
27
28
  end
28
29
 
29
30
  # Loads the import functionality for the passed in ActiveRecord connection
30
31
  def self.load_from_connection_pool(connection_pool)
31
- 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
32
39
  end
33
40
  end
34
41