activerecord-import 0.25.0 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (141) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/test.yaml +151 -0
  3. data/.gitignore +5 -0
  4. data/.rubocop.yml +74 -8
  5. data/.rubocop_todo.yml +10 -16
  6. data/Brewfile +3 -1
  7. data/CHANGELOG.md +232 -2
  8. data/Dockerfile +23 -0
  9. data/Gemfile +26 -14
  10. data/LICENSE +21 -56
  11. data/README.markdown +612 -21
  12. data/Rakefile +4 -1
  13. data/activerecord-import.gemspec +6 -5
  14. data/benchmarks/benchmark.rb +10 -4
  15. data/benchmarks/lib/base.rb +4 -2
  16. data/benchmarks/lib/cli_parser.rb +4 -2
  17. data/benchmarks/lib/float.rb +2 -0
  18. data/benchmarks/lib/mysql2_benchmark.rb +2 -0
  19. data/benchmarks/lib/output_to_csv.rb +2 -0
  20. data/benchmarks/lib/output_to_html.rb +4 -2
  21. data/benchmarks/models/test_innodb.rb +2 -0
  22. data/benchmarks/models/test_memory.rb +2 -0
  23. data/benchmarks/models/test_myisam.rb +2 -0
  24. data/benchmarks/schema/{mysql_schema.rb → mysql2_schema.rb} +2 -0
  25. data/docker-compose.yml +34 -0
  26. data/gemfiles/4.2.gemfile +2 -0
  27. data/gemfiles/5.0.gemfile +2 -0
  28. data/gemfiles/5.1.gemfile +2 -0
  29. data/gemfiles/5.2.gemfile +2 -0
  30. data/gemfiles/6.0.gemfile +4 -0
  31. data/gemfiles/6.1.gemfile +4 -0
  32. data/gemfiles/7.0.gemfile +4 -0
  33. data/gemfiles/7.1.gemfile +3 -0
  34. data/lib/activerecord-import/active_record/adapters/abstract_adapter.rb +2 -0
  35. data/lib/activerecord-import/active_record/adapters/jdbcmysql_adapter.rb +6 -4
  36. data/lib/activerecord-import/active_record/adapters/jdbcpostgresql_adapter.rb +2 -0
  37. data/lib/activerecord-import/active_record/adapters/jdbcsqlite3_adapter.rb +2 -0
  38. data/lib/activerecord-import/active_record/adapters/mysql2_adapter.rb +2 -0
  39. data/lib/activerecord-import/active_record/adapters/postgresql_adapter.rb +2 -0
  40. data/lib/activerecord-import/active_record/adapters/seamless_database_pool_adapter.rb +2 -0
  41. data/lib/activerecord-import/active_record/adapters/sqlite3_adapter.rb +2 -0
  42. data/lib/activerecord-import/active_record/adapters/trilogy_adapter.rb +8 -0
  43. data/lib/activerecord-import/adapters/abstract_adapter.rb +15 -6
  44. data/lib/activerecord-import/adapters/em_mysql2_adapter.rb +2 -0
  45. data/lib/activerecord-import/adapters/mysql2_adapter.rb +2 -0
  46. data/lib/activerecord-import/adapters/mysql_adapter.rb +34 -29
  47. data/lib/activerecord-import/adapters/postgresql_adapter.rb +74 -55
  48. data/lib/activerecord-import/adapters/sqlite3_adapter.rb +138 -13
  49. data/lib/activerecord-import/adapters/trilogy_adapter.rb +7 -0
  50. data/lib/activerecord-import/base.rb +11 -2
  51. data/lib/activerecord-import/import.rb +290 -114
  52. data/lib/activerecord-import/mysql2.rb +2 -0
  53. data/lib/activerecord-import/postgresql.rb +2 -0
  54. data/lib/activerecord-import/sqlite3.rb +2 -0
  55. data/lib/activerecord-import/synchronize.rb +4 -2
  56. data/lib/activerecord-import/value_sets_parser.rb +5 -0
  57. data/lib/activerecord-import/version.rb +3 -1
  58. data/lib/activerecord-import.rb +2 -1
  59. data/test/adapters/jdbcmysql.rb +2 -0
  60. data/test/adapters/jdbcpostgresql.rb +2 -0
  61. data/test/adapters/jdbcsqlite3.rb +2 -0
  62. data/test/adapters/makara_postgis.rb +2 -0
  63. data/test/adapters/mysql2.rb +2 -0
  64. data/test/adapters/mysql2_makara.rb +2 -0
  65. data/test/adapters/mysql2spatial.rb +2 -0
  66. data/test/adapters/postgis.rb +2 -0
  67. data/test/adapters/postgresql.rb +2 -0
  68. data/test/adapters/postgresql_makara.rb +2 -0
  69. data/test/adapters/seamless_database_pool.rb +2 -0
  70. data/test/adapters/spatialite.rb +2 -0
  71. data/test/adapters/sqlite3.rb +2 -0
  72. data/test/adapters/trilogy.rb +9 -0
  73. data/test/database.yml.sample +7 -0
  74. data/test/{travis → github}/database.yml +7 -1
  75. data/test/import_test.rb +151 -8
  76. data/test/jdbcmysql/import_test.rb +5 -3
  77. data/test/jdbcpostgresql/import_test.rb +4 -2
  78. data/test/jdbcsqlite3/import_test.rb +4 -2
  79. data/test/makara_postgis/import_test.rb +4 -2
  80. data/test/models/account.rb +2 -0
  81. data/test/models/alarm.rb +2 -0
  82. data/test/models/animal.rb +8 -0
  83. data/test/models/author.rb +7 -0
  84. data/test/models/bike_maker.rb +3 -0
  85. data/test/models/book.rb +7 -2
  86. data/test/models/car.rb +2 -0
  87. data/test/models/card.rb +5 -0
  88. data/test/models/chapter.rb +2 -0
  89. data/test/models/composite_book.rb +19 -0
  90. data/test/models/composite_chapter.rb +9 -0
  91. data/test/models/customer.rb +18 -0
  92. data/test/models/deck.rb +8 -0
  93. data/test/models/dictionary.rb +2 -0
  94. data/test/models/discount.rb +2 -0
  95. data/test/models/end_note.rb +2 -0
  96. data/test/models/group.rb +2 -0
  97. data/test/models/order.rb +17 -0
  98. data/test/models/playing_card.rb +4 -0
  99. data/test/models/promotion.rb +2 -0
  100. data/test/models/question.rb +2 -0
  101. data/test/models/rule.rb +2 -0
  102. data/test/models/tag.rb +9 -1
  103. data/test/models/tag_alias.rb +11 -0
  104. data/test/models/topic.rb +7 -0
  105. data/test/models/user.rb +2 -0
  106. data/test/models/user_token.rb +3 -0
  107. data/test/models/vendor.rb +2 -0
  108. data/test/models/widget.rb +2 -0
  109. data/test/mysql2/import_test.rb +5 -3
  110. data/test/mysql2_makara/import_test.rb +5 -3
  111. data/test/mysqlspatial2/import_test.rb +5 -3
  112. data/test/postgis/import_test.rb +4 -2
  113. data/test/postgresql/import_test.rb +4 -2
  114. data/test/schema/generic_schema.rb +37 -1
  115. data/test/schema/jdbcpostgresql_schema.rb +3 -1
  116. data/test/schema/mysql2_schema.rb +2 -0
  117. data/test/schema/postgis_schema.rb +3 -1
  118. data/test/schema/postgresql_schema.rb +49 -0
  119. data/test/schema/sqlite3_schema.rb +15 -0
  120. data/test/schema/version.rb +2 -0
  121. data/test/sqlite3/import_test.rb +4 -2
  122. data/test/support/active_support/test_case_extensions.rb +2 -0
  123. data/test/support/assertions.rb +2 -0
  124. data/test/support/factories.rb +10 -8
  125. data/test/support/generate.rb +10 -8
  126. data/test/support/mysql/import_examples.rb +2 -1
  127. data/test/support/postgresql/import_examples.rb +152 -3
  128. data/test/support/shared_examples/on_duplicate_key_ignore.rb +2 -0
  129. data/test/support/shared_examples/on_duplicate_key_update.rb +122 -9
  130. data/test/support/shared_examples/recursive_import.rb +128 -2
  131. data/test/support/sqlite3/import_examples.rb +191 -26
  132. data/test/synchronize_test.rb +2 -0
  133. data/test/test_helper.rb +34 -7
  134. data/test/trilogy/import_test.rb +7 -0
  135. data/test/value_sets_bytes_parser_test.rb +3 -1
  136. data/test/value_sets_records_parser_test.rb +3 -1
  137. metadata +46 -16
  138. data/.travis.yml +0 -71
  139. data/gemfiles/3.2.gemfile +0 -2
  140. data/gemfiles/4.0.gemfile +0 -2
  141. data/gemfiles/4.1.gemfile +0 -2
@@ -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,59 +8,66 @@ 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
 
13
- base_sql, post_sql = if sql.is_a?( String )
14
- [sql, '']
15
- elsif sql.is_a?( Array )
16
- [sql.shift, sql.join( ' ' )]
15
+ base_sql, post_sql = case sql
16
+ when String
17
+ [sql, '']
18
+ when Array
19
+ [sql.shift, sql.join( ' ' )]
17
20
  end
18
21
 
19
22
  sql2insert = base_sql + values.join( ',' ) + post_sql
20
23
 
21
- columns = returning_columns(options)
22
- if columns.blank? || options[:no_returning]
24
+ selections = returning_selections(options)
25
+ if selections.blank? || (options[:no_returning] && !options[:recursive])
23
26
  insert( sql2insert, *args )
24
27
  else
25
- returned_values = if columns.size > 1
28
+ returned_values = if selections.size > 1
26
29
  # Select composite columns
27
- select_rows( sql2insert, *args )
30
+ db_result = select_all( sql2insert, *args )
31
+ { values: db_result.rows, columns: db_result.columns }
28
32
  else
29
- select_values( sql2insert, *args )
33
+ { values: select_values( sql2insert, *args ) }
30
34
  end
31
- query_cache.clear if query_cache_enabled
35
+ clear_query_cache if query_cache_enabled
32
36
  end
33
37
 
34
38
  if options[:returning].blank?
35
- ids = returned_values
39
+ ids = Array(returned_values[:values])
36
40
  elsif options[:primary_key].blank?
37
- results = returned_values
41
+ options[:returning_columns] ||= returned_values[:columns]
42
+ results = Array(returned_values[:values])
38
43
  else
39
44
  # split primary key and returning columns
40
- ids, results = split_ids_and_results(returned_values, columns, options)
45
+ ids, results, options[:returning_columns] = split_ids_and_results(returned_values, options)
41
46
  end
42
47
 
43
48
  ActiveRecord::Import::Result.new([], number_of_inserts, ids, results)
44
49
  end
45
50
 
46
- def split_ids_and_results(values, columns, options)
51
+ def split_ids_and_results( selections, options )
47
52
  ids = []
48
- results = []
53
+ returning_values = []
54
+
55
+ columns = Array(selections[:columns])
56
+ values = Array(selections[:values])
49
57
  id_indexes = Array(options[:primary_key]).map { |key| columns.index(key) }
50
- returning_indexes = Array(options[:returning]).map { |key| columns.index(key) }
58
+ returning_columns = columns.reject.with_index { |_, index| id_indexes.include?(index) }
59
+ returning_indexes = returning_columns.map { |column| columns.index(column) }
51
60
 
52
61
  values.each do |value|
53
62
  value_array = Array(value)
54
- ids << id_indexes.map { |i| value_array[i] }
55
- results << returning_indexes.map { |i| value_array[i] }
63
+ ids << id_indexes.map { |index| value_array[index] }
64
+ returning_values << returning_indexes.map { |index| value_array[index] }
56
65
  end
57
66
 
58
67
  ids.map!(&:first) if id_indexes.size == 1
59
- results.map!(&:first) if returning_indexes.size == 1
68
+ returning_values.map!(&:first) if returning_columns.size == 1
60
69
 
61
- [ids, results]
70
+ [ids, returning_values, returning_columns]
62
71
  end
63
72
 
64
73
  def next_value_for_sequence(sequence_name)
@@ -73,37 +82,43 @@ module ActiveRecord::Import::PostgreSQLAdapter
73
82
  if (options[:ignore] || options[:on_duplicate_key_ignore]) && !options[:on_duplicate_key_update] && !options[:recursive]
74
83
  sql << sql_for_on_duplicate_key_ignore( table_name, options[:on_duplicate_key_ignore] )
75
84
  end
76
- elsif options[:on_duplicate_key_ignore] && !options[:on_duplicate_key_update]
85
+ elsif logger && options[:on_duplicate_key_ignore] && !options[:on_duplicate_key_update]
77
86
  logger.warn "Ignoring on_duplicate_key_ignore because it is not supported by the database."
78
87
  end
79
88
 
80
89
  sql += super(table_name, options)
81
90
 
82
- columns = returning_columns(options)
83
- unless columns.blank? || options[:no_returning]
84
- sql << " RETURNING \"#{columns.join('", "')}\""
91
+ selections = returning_selections(options)
92
+ unless selections.blank? || (options[:no_returning] && !options[:recursive])
93
+ sql << " RETURNING #{selections.join(', ')}"
85
94
  end
86
95
 
87
96
  sql
88
97
  end
89
98
 
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
99
+ def returning_selections(options)
100
+ selections = []
101
+ column_names = Array(options[:model].column_names)
102
+
103
+ selections += Array(options[:primary_key]) if options[:primary_key].present?
104
+ selections += Array(options[:returning]) if options[:returning].present?
105
+
106
+ selections.map do |selection|
107
+ column_names.include?(selection.to_s) ? "\"#{selection}\"" : selection
108
+ end
95
109
  end
96
110
 
97
111
  # Add a column to be updated on duplicate key update
98
112
  def add_column_for_on_duplicate_key_update( column, options = {} ) # :nodoc:
99
113
  arg = options[:on_duplicate_key_update]
100
- if arg.is_a?( Hash )
114
+ case arg
115
+ when Hash
101
116
  columns = arg.fetch( :columns ) { arg[:columns] = [] }
102
117
  case columns
103
118
  when Array then columns << column.to_sym unless columns.include?( column.to_sym )
104
119
  when Hash then columns[column.to_sym] = column.to_sym
105
120
  end
106
- elsif arg.is_a?( Array )
121
+ when Array
107
122
  arg << column.to_sym unless arg.include?( column.to_sym )
108
123
  end
109
124
  end
@@ -119,11 +134,11 @@ module ActiveRecord::Import::PostgreSQLAdapter
119
134
  # Returns a generated ON CONFLICT DO UPDATE statement given the passed
120
135
  # in +args+.
121
136
  def sql_for_on_duplicate_key_update( table_name, *args ) # :nodoc:
122
- arg, primary_key, locking_column = args
137
+ arg, model, primary_key, locking_column = args
123
138
  arg = { columns: arg } if arg.is_a?( Array ) || arg.is_a?( String )
124
139
  return unless arg.is_a?( Hash )
125
140
 
126
- sql = ' ON CONFLICT '
141
+ sql = ' ON CONFLICT '.dup
127
142
  conflict_target = sql_for_conflict_target( arg )
128
143
 
129
144
  columns = arg.fetch( :columns, [] )
@@ -138,11 +153,12 @@ module ActiveRecord::Import::PostgreSQLAdapter
138
153
  end
139
154
 
140
155
  sql << "#{conflict_target}DO UPDATE SET "
141
- if columns.is_a?( Array )
142
- sql << sql_for_on_duplicate_key_update_as_array( table_name, locking_column, columns )
143
- elsif columns.is_a?( Hash )
144
- sql << sql_for_on_duplicate_key_update_as_hash( table_name, locking_column, columns )
145
- elsif columns.is_a?( String )
156
+ case columns
157
+ when Array
158
+ sql << sql_for_on_duplicate_key_update_as_array( table_name, model, locking_column, columns )
159
+ when Hash
160
+ sql << sql_for_on_duplicate_key_update_as_hash( table_name, model, locking_column, columns )
161
+ when String
146
162
  sql << columns
147
163
  else
148
164
  raise ArgumentError, 'Expected :columns to be an Array or Hash'
@@ -153,22 +169,27 @@ module ActiveRecord::Import::PostgreSQLAdapter
153
169
  sql
154
170
  end
155
171
 
156
- def sql_for_on_duplicate_key_update_as_array( table_name, locking_column, arr ) # :nodoc:
172
+ def sql_for_on_duplicate_key_update_as_array( table_name, model, locking_column, arr ) # :nodoc:
157
173
  results = arr.map do |column|
158
- qc = quote_column_name( column )
174
+ original_column_name = model.attribute_alias?( column ) ? model.attribute_alias( column ) : column
175
+ qc = quote_column_name( original_column_name )
159
176
  "#{qc}=EXCLUDED.#{qc}"
160
177
  end
161
- increment_locking_column!(results, locking_column)
178
+ increment_locking_column!(table_name, results, locking_column)
162
179
  results.join( ',' )
163
180
  end
164
181
 
165
- def sql_for_on_duplicate_key_update_as_hash( table_name, locking_column, hsh ) # :nodoc:
182
+ def sql_for_on_duplicate_key_update_as_hash( table_name, model, locking_column, hsh ) # :nodoc:
166
183
  results = hsh.map do |column1, column2|
167
- qc1 = quote_column_name( column1 )
168
- qc2 = quote_column_name( column2 )
184
+ original_column1_name = model.attribute_alias?( column1 ) ? model.attribute_alias( column1 ) : column1
185
+ qc1 = quote_column_name( original_column1_name )
186
+
187
+ original_column2_name = model.attribute_alias?( column2 ) ? model.attribute_alias( column2 ) : column2
188
+ qc2 = quote_column_name( original_column2_name )
189
+
169
190
  "#{qc1}=EXCLUDED.#{qc2}"
170
191
  end
171
- increment_locking_column!(results, locking_column)
192
+ increment_locking_column!(table_name, results, locking_column)
172
193
  results.join( ',' )
173
194
  end
174
195
 
@@ -179,9 +200,9 @@ module ActiveRecord::Import::PostgreSQLAdapter
179
200
  if constraint_name.present?
180
201
  "ON CONSTRAINT #{constraint_name} "
181
202
  elsif conflict_target.present?
182
- '(' << Array( conflict_target ).reject( &:blank? ).join( ', ' ) << ') '.tap do |sql|
183
- sql << "WHERE #{index_predicate} " if index_predicate
184
- end
203
+ sql = "(#{Array( conflict_target ).reject( &:blank? ).join( ', ' )}) "
204
+ sql += "WHERE #{index_predicate} " if index_predicate
205
+ sql
185
206
  end
186
207
  end
187
208
 
@@ -195,17 +216,15 @@ module ActiveRecord::Import::PostgreSQLAdapter
195
216
  exception.is_a?(ActiveRecord::StatementInvalid) && exception.to_s.include?('duplicate key')
196
217
  end
197
218
 
198
- def supports_on_duplicate_key_update?(current_version = postgresql_version)
199
- current_version >= MIN_VERSION_FOR_UPSERT
219
+ def supports_on_duplicate_key_update?
220
+ database_version >= MIN_VERSION_FOR_UPSERT
200
221
  end
201
222
 
202
223
  def supports_setting_primary_key_of_imported_objects?
203
224
  true
204
225
  end
205
226
 
206
- def increment_locking_column!(results, locking_column)
207
- if locking_column.present?
208
- results << "\"#{locking_column}\"=EXCLUDED.\"#{locking_column}\"+1"
209
- end
227
+ def database_version
228
+ defined?(postgresql_version) ? postgresql_version : super
210
229
  end
211
230
  end
@@ -1,18 +1,22 @@
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
@@ -20,10 +24,11 @@ module ActiveRecord::Import::SQLite3Adapter
20
24
  def insert_many( sql, values, _options = {}, *args ) # :nodoc:
21
25
  number_of_inserts = 0
22
26
 
23
- base_sql, post_sql = if sql.is_a?( String )
24
- [sql, '']
25
- elsif sql.is_a?( Array )
26
- [sql.shift, sql.join( ' ' )]
27
+ base_sql, post_sql = case sql
28
+ when String
29
+ [sql, '']
30
+ when Array
31
+ [sql.shift, sql.join( ' ' )]
27
32
  end
28
33
 
29
34
  value_sets = ::ActiveRecord::Import::ValueSetsRecordsParser.parse(values,
@@ -40,16 +45,136 @@ module ActiveRecord::Import::SQLite3Adapter
40
45
  ActiveRecord::Import::Result.new([], number_of_inserts, [], [])
41
46
  end
42
47
 
43
- def pre_sql_statements( options)
48
+ def pre_sql_statements( options )
44
49
  sql = []
45
50
  # Options :recursive and :on_duplicate_key_ignore are mutually exclusive
46
- if (options[:ignore] || options[:on_duplicate_key_ignore]) && !options[:recursive]
51
+ if !supports_on_duplicate_key_update? && (options[:ignore] || options[:on_duplicate_key_ignore])
47
52
  sql << "OR IGNORE"
48
53
  end
49
54
  sql + super
50
55
  end
51
56
 
57
+ def post_sql_statements( table_name, options ) # :nodoc:
58
+ sql = []
59
+
60
+ # Options :recursive and :on_duplicate_key_ignore are mutually exclusive
61
+ if supports_on_duplicate_key_update? && ((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
64
+
65
+ sql + super
66
+ end
67
+
52
68
  def next_value_for_sequence(sequence_name)
53
69
  %{nextval('#{sequence_name}')}
54
70
  end
71
+
72
+ # Add a column to be updated on duplicate key update
73
+ def add_column_for_on_duplicate_key_update( column, options = {} ) # :nodoc:
74
+ arg = options[:on_duplicate_key_update]
75
+ case arg
76
+ when 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
+ when 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, model, 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
+ case columns
118
+ when Array
119
+ sql << sql_for_on_duplicate_key_update_as_array( table_name, model, locking_column, columns )
120
+ when Hash
121
+ sql << sql_for_on_duplicate_key_update_as_hash( table_name, model, locking_column, columns )
122
+ when String
123
+ sql << columns
124
+ else
125
+ raise ArgumentError, 'Expected :columns to be an Array or Hash'
126
+ end
127
+
128
+ sql << " WHERE #{condition}" if condition.present?
129
+
130
+ sql
131
+ end
132
+
133
+ def sql_for_on_duplicate_key_update_as_array( table_name, model, locking_column, arr ) # :nodoc:
134
+ results = arr.map do |column|
135
+ original_column_name = model.attribute_alias?( column ) ? model.attribute_alias( column ) : column
136
+ qc = quote_column_name( original_column_name )
137
+ "#{qc}=EXCLUDED.#{qc}"
138
+ end
139
+ increment_locking_column!(table_name, results, locking_column)
140
+ results.join( ',' )
141
+ end
142
+
143
+ def sql_for_on_duplicate_key_update_as_hash( table_name, model, locking_column, hsh ) # :nodoc:
144
+ results = hsh.map do |column1, column2|
145
+ original_column1_name = model.attribute_alias?( column1 ) ? model.attribute_alias( column1 ) : column1
146
+ qc1 = quote_column_name( original_column1_name )
147
+
148
+ original_column2_name = model.attribute_alias?( column2 ) ? model.attribute_alias( column2 ) : column2
149
+ qc2 = quote_column_name( original_column2_name )
150
+
151
+ "#{qc1}=EXCLUDED.#{qc2}"
152
+ end
153
+ increment_locking_column!(table_name, results, locking_column)
154
+ results.join( ',' )
155
+ end
156
+
157
+ def sql_for_conflict_target( args = {} )
158
+ conflict_target = args[:conflict_target]
159
+ index_predicate = args[:index_predicate]
160
+ if conflict_target.present?
161
+ sql = "(#{Array( conflict_target ).reject( &:blank? ).join( ', ' )}) "
162
+ sql += "WHERE #{index_predicate} " if index_predicate
163
+ sql
164
+ end
165
+ end
166
+
167
+ def sql_for_default_conflict_target( primary_key )
168
+ conflict_target = Array(primary_key).join(', ')
169
+ "(#{conflict_target}) " if conflict_target.present?
170
+ end
171
+
172
+ # Return true if the statement is a duplicate key record error
173
+ def duplicate_key_update_error?(exception) # :nodoc:
174
+ exception.is_a?(ActiveRecord::StatementInvalid) && exception.to_s.include?('duplicate key')
175
+ end
176
+
177
+ def database_version
178
+ defined?(sqlite_version) ? sqlite_version : super
179
+ end
55
180
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "activerecord-import/adapters/mysql_adapter"
4
+
5
+ module ActiveRecord::Import::TrilogyAdapter
6
+ include ActiveRecord::Import::MysqlAdapter
7
+ 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