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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +91 -0
- data/README.markdown +91 -24
- data/lib/activerecord-import/adapters/abstract_adapter.rb +58 -0
- data/lib/activerecord-import/adapters/mysql_adapter.rb +69 -19
- data/lib/activerecord-import/adapters/postgresql_adapter.rb +9 -51
- data/lib/activerecord-import/adapters/sqlite3_adapter.rb +7 -3
- data/lib/activerecord-import/aliases.rb +42 -0
- data/lib/activerecord-import/base.rb +2 -44
- data/lib/activerecord-import/core.rb +62 -0
- data/lib/activerecord-import/import.rb +534 -327
- data/lib/activerecord-import/synchronize.rb +28 -16
- data/lib/activerecord-import/version.rb +1 -1
- data/lib/activerecord-import/without_aliases.rb +17 -0
- metadata +7 -4
|
@@ -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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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 "
|
|
4
|
-
require "active_record"
|
|
5
|
-
require "active_record/version"
|
|
3
|
+
require "activerecord-import/core"
|
|
6
4
|
|
|
7
|
-
|
|
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'
|