activerecord-import 2.3.0 → 3.0.0

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.
@@ -9,8 +9,6 @@ module ActiveRecord::Import::PostgreSQLAdapter
9
9
  def insert_many( sql, values, options = {}, *args ) # :nodoc:
10
10
  number_of_inserts = 1
11
11
  returned_values = {}
12
- ids = []
13
- results = []
14
12
 
15
13
  base_sql, post_sql = case sql
16
14
  when String
@@ -23,7 +21,9 @@ module ActiveRecord::Import::PostgreSQLAdapter
23
21
 
24
22
  selections = returning_selections(options)
25
23
  if selections.blank? || (options[:no_returning] && !options[:recursive])
26
- insert( sql2insert, *args )
24
+ # Don't use #insert here: ActiveRecord appends its own
25
+ # RETURNING <primary key> clause to statements run through it.
26
+ exec_update( sql2insert, *args )
27
27
  else
28
28
  returned_values = if selections.size > 1
29
29
  # Select composite columns
@@ -32,44 +32,14 @@ module ActiveRecord::Import::PostgreSQLAdapter
32
32
  else
33
33
  { values: select_values( sql2insert, *args ) }
34
34
  end
35
- clear_query_cache if query_cache_enabled
36
35
  end
36
+ clear_query_cache if query_cache_enabled
37
37
 
38
- if options[:returning].blank?
39
- ids = Array(returned_values[:values])
40
- elsif options[:primary_key].blank?
41
- options[:returning_columns] ||= returned_values[:columns]
42
- results = Array(returned_values[:values])
43
- else
44
- # split primary key and returning columns
45
- ids, results, options[:returning_columns] = split_ids_and_results(returned_values, options)
46
- end
38
+ ids, results = ids_and_results_from_returned_values(returned_values, options)
47
39
 
48
40
  ActiveRecord::Import::Result.new([], number_of_inserts, ids, results)
49
41
  end
50
42
 
51
- def split_ids_and_results( selections, options )
52
- ids = []
53
- returning_values = []
54
-
55
- columns = Array(selections[:columns])
56
- values = Array(selections[:values])
57
- id_indexes = Array(options[:primary_key]).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) }
60
-
61
- values.each do |value|
62
- value_array = Array(value)
63
- ids << id_indexes.map { |index| value_array[index] }
64
- returning_values << returning_indexes.map { |index| value_array[index] }
65
- end
66
-
67
- ids.map!(&:first) if id_indexes.size == 1
68
- returning_values.map!(&:first) if returning_columns.size == 1
69
-
70
- [ids, returning_values, returning_columns]
71
- end
72
-
73
43
  def next_value_for_sequence(sequence_name)
74
44
  %{nextval('#{sequence_name}')}
75
45
  end
@@ -96,18 +66,6 @@ module ActiveRecord::Import::PostgreSQLAdapter
96
66
  sql.uniq
97
67
  end
98
68
 
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
109
- end
110
-
111
69
  # Add a column to be updated on duplicate key update
112
70
  def add_column_for_on_duplicate_key_update( column, options = {} ) # :nodoc:
113
71
  arg = options[:on_duplicate_key_update]
@@ -171,7 +129,7 @@ module ActiveRecord::Import::PostgreSQLAdapter
171
129
 
172
130
  def sql_for_on_duplicate_key_update_as_array( table_name, model, locking_column, arr ) # :nodoc:
173
131
  results = arr.map do |column|
174
- original_column_name = model.attribute_alias?( column ) ? model.attribute_alias( column ) : column
132
+ original_column_name = model.column_name_for_import( column )
175
133
  qc = quote_column_name( original_column_name )
176
134
  "#{qc}=EXCLUDED.#{qc}"
177
135
  end
@@ -181,10 +139,10 @@ module ActiveRecord::Import::PostgreSQLAdapter
181
139
 
182
140
  def sql_for_on_duplicate_key_update_as_hash( table_name, model, locking_column, hsh ) # :nodoc:
183
141
  results = hsh.map do |column1, column2|
184
- original_column1_name = model.attribute_alias?( column1 ) ? model.attribute_alias( column1 ) : column1
142
+ original_column1_name = model.column_name_for_import( column1 )
185
143
  qc1 = quote_column_name( original_column1_name )
186
144
 
187
- original_column2_name = model.attribute_alias?( column2 ) ? model.attribute_alias( column2 ) : column2
145
+ original_column2_name = model.column_name_for_import( column2 )
188
146
  qc2 = quote_column_name( original_column2_name )
189
147
 
190
148
  "#{qc1}=EXCLUDED.#{qc2}"
@@ -207,7 +165,7 @@ module ActiveRecord::Import::PostgreSQLAdapter
207
165
  end
208
166
 
209
167
  def sql_for_default_conflict_target( table_name, primary_key )
210
- conflict_target = Array(primary_key).join(', ')
168
+ conflict_target = Array(primary_key).map { |key| quote_column_name(key) }.join(', ')
211
169
  "(#{conflict_target}) " if conflict_target.present?
212
170
  end
213
171
 
@@ -19,6 +19,10 @@ module ActiveRecord::Import::SQLite3Adapter
19
19
  database_version >= MIN_VERSION_FOR_UPSERT
20
20
  end
21
21
 
22
+ def supports_default_keyword_in_values?
23
+ false
24
+ end
25
+
22
26
  # +sql+ can be a single string or an array. If it is an array all
23
27
  # elements that are in position >= 1 will be appended to the final SQL.
24
28
  def insert_many( sql, values, _options = {}, *args ) # :nodoc:
@@ -132,7 +136,7 @@ module ActiveRecord::Import::SQLite3Adapter
132
136
 
133
137
  def sql_for_on_duplicate_key_update_as_array( table_name, model, locking_column, arr ) # :nodoc:
134
138
  results = arr.map do |column|
135
- original_column_name = model.attribute_alias?( column ) ? model.attribute_alias( column ) : column
139
+ original_column_name = model.column_name_for_import( column )
136
140
  qc = quote_column_name( original_column_name )
137
141
  "#{qc}=EXCLUDED.#{qc}"
138
142
  end
@@ -142,10 +146,10 @@ module ActiveRecord::Import::SQLite3Adapter
142
146
 
143
147
  def sql_for_on_duplicate_key_update_as_hash( table_name, model, locking_column, hsh ) # :nodoc:
144
148
  results = hsh.map do |column1, column2|
145
- original_column1_name = model.attribute_alias?( column1 ) ? model.attribute_alias( column1 ) : column1
149
+ original_column1_name = model.column_name_for_import( column1 )
146
150
  qc1 = quote_column_name( original_column1_name )
147
151
 
148
- original_column2_name = model.attribute_alias?( column2 ) ? model.attribute_alias( column2 ) : column2
152
+ original_column2_name = model.column_name_for_import( column2 )
149
153
  qc2 = quote_column_name( original_column2_name )
150
154
 
151
155
  "#{qc1}=EXCLUDED.#{qc2}"
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord::Import
4
+ # Methods aliased to +bulk_import+ and +bulk_import!+, keyed by the class
5
+ # they're defined on.
6
+ ALIASES = {
7
+ ActiveRecord::Associations::CollectionProxy => { import: :bulk_import },
8
+ ActiveRecord::Associations::CollectionAssociation => { import: :bulk_import },
9
+ ActiveRecord::Base.singleton_class => { import: :bulk_import, import!: :bulk_import! }
10
+ }.freeze
11
+
12
+ # Aliases +import+ and +import!+ to +bulk_import+ and +bulk_import!+, unless
13
+ # they are already defined or aliases have been skipped.
14
+ def self.define_aliases
15
+ return if skip_aliases?
16
+
17
+ ALIASES.each do |klass, aliases|
18
+ aliases.each do |name, target|
19
+ klass.send(:alias_method, name, target) unless klass.method_defined?(name)
20
+ end
21
+ end
22
+ end
23
+
24
+ # Prevents the +import+ and +import!+ aliases from being defined, and removes
25
+ # them if they already have been. Used by "activerecord-import/without_aliases".
26
+ def self.skip_aliases!
27
+ @skip_aliases = true
28
+
29
+ ALIASES.each do |klass, aliases|
30
+ aliases.each do |name, target|
31
+ next unless klass.method_defined?(name)
32
+
33
+ method = klass.instance_method(name)
34
+ klass.send(:remove_method, name) if method.owner == klass && method.original_name == target
35
+ end
36
+ end
37
+ end
38
+
39
+ def self.skip_aliases?
40
+ @skip_aliases == true
41
+ end
42
+ end
@@ -1,47 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "pathname"
4
- require "active_record"
5
- require "active_record/version"
3
+ require "activerecord-import/core"
6
4
 
7
- module ActiveRecord::Import
8
- ADAPTER_PATH = "activerecord-import/active_record/adapters"
9
-
10
- def self.base_adapter(adapter)
11
- case adapter
12
- when 'mysql2_makara' then 'mysql2'
13
- when 'mysql2spatial' then 'mysql2'
14
- when 'janus_mysql2' then 'mysql2'
15
- when 'spatialite' then 'sqlite3'
16
- when 'postgresql_makara' then 'postgresql'
17
- when 'makara_postgis' then 'postgresql'
18
- when 'postgis' then 'postgresql'
19
- when 'cockroachdb' then 'postgresql'
20
- when 'janus_trilogy' then 'trilogy'
21
- else adapter
22
- end
23
- end
24
-
25
- # Loads the import functionality for a specific database adapter
26
- def self.require_adapter(adapter)
27
- require File.join(ADAPTER_PATH, "/#{base_adapter(adapter)}_adapter")
28
- rescue LoadError
29
- # fallback
30
- end
31
-
32
- # Loads the import functionality for the passed in ActiveRecord connection
33
- def self.load_from_connection_pool(connection_pool)
34
- adapter =
35
- if connection_pool.respond_to?(:db_config) # ActiveRecord >= 6.1
36
- connection_pool.db_config.adapter
37
- else
38
- connection_pool.spec.config[:adapter]
39
- end
40
- require_adapter adapter
41
- end
42
- end
43
-
44
- require 'activerecord-import/import'
45
- require 'activerecord-import/active_record/adapters/abstract_adapter'
46
- require 'activerecord-import/synchronize'
47
- require 'activerecord-import/value_sets_parser'
5
+ ActiveRecord::Import.define_aliases
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
4
+ require "concurrent/map"
5
+ require "active_record"
6
+ require "active_record/version"
7
+
8
+ module ActiveRecord::Import
9
+ ADAPTER_PATH = "activerecord-import/active_record/adapters"
10
+ ATTEMPTED_ADAPTERS = Concurrent::Map.new # :nodoc:
11
+
12
+ def self.base_adapter(adapter)
13
+ case adapter
14
+ when 'mysql2_makara' then 'mysql2'
15
+ when 'mysql2spatial' then 'mysql2'
16
+ when 'janus_mysql2' then 'mysql2'
17
+ when 'spatialite' then 'sqlite3'
18
+ when 'postgresql_makara' then 'postgresql'
19
+ when 'makara_postgis' then 'postgresql'
20
+ when 'postgis' then 'postgresql'
21
+ when 'cockroachdb' then 'postgresql'
22
+ when 'janus_trilogy' then 'trilogy'
23
+ else adapter
24
+ end
25
+ end
26
+
27
+ # Loads the import functionality for a specific database adapter
28
+ def self.require_adapter(adapter)
29
+ require File.join(ADAPTER_PATH, "/#{base_adapter(adapter)}_adapter")
30
+ rescue LoadError
31
+ # fallback
32
+ end
33
+
34
+ # Loads the import functionality for the passed in ActiveRecord connection
35
+ def self.load_from_connection_pool(connection_pool)
36
+ require_adapter adapter_for(connection_pool)
37
+ end
38
+
39
+ # Loads the import functionality for the passed in ActiveRecord connection,
40
+ # unless it's been tried for the connection's adapter already. Import uses
41
+ # this when activerecord-import was required after the connection was
42
+ # established. Each adapter is only tried once, so an unsupported adapter
43
+ # doesn't search the load path on every import.
44
+ def self.load_from_connection_pool_once(connection_pool)
45
+ adapter = adapter_for(connection_pool)
46
+ ATTEMPTED_ADAPTERS.compute_if_absent(adapter) do
47
+ require_adapter adapter
48
+ true
49
+ end
50
+ end
51
+
52
+ def self.adapter_for(connection_pool) # :nodoc:
53
+ connection_pool.db_config.adapter
54
+ end
55
+ private_class_method :adapter_for
56
+ end
57
+
58
+ require 'activerecord-import/import'
59
+ require 'activerecord-import/active_record/adapters/abstract_adapter'
60
+ require 'activerecord-import/synchronize'
61
+ require 'activerecord-import/value_sets_parser'
62
+ require 'activerecord-import/aliases'