activerecord-import 1.4.1 → 1.8.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/test.yaml +53 -13
- data/.gitignore +4 -0
- data/.rubocop.yml +7 -4
- data/.rubocop_todo.yml +10 -16
- data/CHANGELOG.md +48 -1
- data/Dockerfile +23 -0
- data/Gemfile +15 -7
- data/README.markdown +44 -5
- data/Rakefile +1 -0
- data/activerecord-import.gemspec +4 -0
- data/benchmarks/benchmark.rb +3 -3
- data/benchmarks/lib/base.rb +2 -2
- data/benchmarks/lib/cli_parser.rb +2 -2
- data/docker-compose.yml +34 -0
- data/gemfiles/7.1.gemfile +3 -0
- data/gemfiles/7.2.gemfile +3 -0
- data/lib/activerecord-import/active_record/adapters/trilogy_adapter.rb +8 -0
- data/lib/activerecord-import/adapters/abstract_adapter.rb +6 -5
- data/lib/activerecord-import/adapters/mysql_adapter.rb +24 -18
- data/lib/activerecord-import/adapters/postgresql_adapter.rb +26 -18
- data/lib/activerecord-import/adapters/sqlite3_adapter.rb +29 -23
- data/lib/activerecord-import/adapters/trilogy_adapter.rb +7 -0
- data/lib/activerecord-import/import.rb +63 -28
- data/lib/activerecord-import/value_sets_parser.rb +1 -0
- data/lib/activerecord-import/version.rb +1 -1
- data/lib/activerecord-import.rb +0 -1
- data/test/adapters/trilogy.rb +9 -0
- data/test/database.yml.sample +7 -0
- data/test/github/database.yml +4 -0
- data/test/jdbcmysql/import_test.rb +3 -3
- data/test/jdbcpostgresql/import_test.rb +2 -2
- data/test/jdbcsqlite3/import_test.rb +2 -2
- data/test/makara_postgis/import_test.rb +2 -2
- data/test/models/author.rb +7 -0
- data/test/models/bike_maker.rb +1 -0
- data/test/models/book.rb +5 -2
- data/test/models/composite_book.rb +19 -0
- data/test/models/composite_chapter.rb +9 -0
- data/test/models/customer.rb +14 -4
- data/test/models/order.rb +13 -4
- data/test/models/tag.rb +6 -1
- data/test/models/tag_alias.rb +7 -1
- data/test/models/topic.rb +5 -0
- data/test/models/widget.rb +10 -3
- data/test/mysql2/import_test.rb +3 -3
- data/test/mysql2_makara/import_test.rb +3 -3
- data/test/mysqlspatial2/import_test.rb +3 -3
- data/test/postgis/import_test.rb +2 -2
- data/test/postgresql/import_test.rb +2 -2
- data/test/schema/generic_schema.rb +4 -1
- data/test/schema/jdbcpostgresql_schema.rb +1 -1
- data/test/schema/postgis_schema.rb +1 -1
- data/test/schema/postgresql_schema.rb +35 -4
- data/test/sqlite3/import_test.rb +2 -2
- data/test/support/postgresql/import_examples.rb +12 -0
- data/test/support/shared_examples/on_duplicate_key_update.rb +67 -10
- data/test/support/shared_examples/recursive_import.rb +67 -1
- data/test/test_helper.rb +6 -4
- data/test/trilogy/import_test.rb +7 -0
- data/test/value_sets_bytes_parser_test.rb +1 -1
- data/test/value_sets_records_parser_test.rb +1 -1
- metadata +24 -7
@@ -13,13 +13,14 @@ module ActiveRecord::Import::MysqlAdapter
|
|
13
13
|
# the number of inserts default
|
14
14
|
number_of_inserts = 0
|
15
15
|
|
16
|
-
base_sql, post_sql =
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
base_sql, post_sql = case sql
|
17
|
+
when String
|
18
|
+
[sql, '']
|
19
|
+
when Array
|
20
|
+
[sql.shift, sql.join( ' ' )]
|
20
21
|
end
|
21
22
|
|
22
|
-
sql_size = QUERY_OVERHEAD + base_sql.
|
23
|
+
sql_size = QUERY_OVERHEAD + base_sql.bytesize + post_sql.bytesize
|
23
24
|
|
24
25
|
# the number of bytes the requested insert statement values will take up
|
25
26
|
values_in_bytes = values.sum(&:bytesize)
|
@@ -33,7 +34,7 @@ module ActiveRecord::Import::MysqlAdapter
|
|
33
34
|
max = max_allowed_packet
|
34
35
|
|
35
36
|
# if we can insert it all as one statement
|
36
|
-
if
|
37
|
+
if max == NO_MAX_PACKET || total_bytes <= max || options[:force_single_insert]
|
37
38
|
number_of_inserts += 1
|
38
39
|
sql2insert = base_sql + values.join( ',' ) + post_sql
|
39
40
|
insert( sql2insert, *args )
|
@@ -85,13 +86,13 @@ module ActiveRecord::Import::MysqlAdapter
|
|
85
86
|
# in +args+.
|
86
87
|
def sql_for_on_duplicate_key_update( table_name, *args ) # :nodoc:
|
87
88
|
sql = ' ON DUPLICATE KEY UPDATE '.dup
|
88
|
-
arg = args
|
89
|
-
|
90
|
-
|
91
|
-
sql << sql_for_on_duplicate_key_update_as_array( table_name, locking_column, arg )
|
92
|
-
|
93
|
-
sql << sql_for_on_duplicate_key_update_as_hash( table_name, locking_column, arg )
|
94
|
-
|
89
|
+
arg, model, _primary_key, locking_column = args
|
90
|
+
case arg
|
91
|
+
when Array
|
92
|
+
sql << sql_for_on_duplicate_key_update_as_array( table_name, model, locking_column, arg )
|
93
|
+
when Hash
|
94
|
+
sql << sql_for_on_duplicate_key_update_as_hash( table_name, model, locking_column, arg )
|
95
|
+
when String
|
95
96
|
sql << arg
|
96
97
|
else
|
97
98
|
raise ArgumentError, "Expected Array or Hash"
|
@@ -99,19 +100,24 @@ module ActiveRecord::Import::MysqlAdapter
|
|
99
100
|
sql
|
100
101
|
end
|
101
102
|
|
102
|
-
def sql_for_on_duplicate_key_update_as_array( table_name, locking_column, arr ) # :nodoc:
|
103
|
+
def sql_for_on_duplicate_key_update_as_array( table_name, model, locking_column, arr ) # :nodoc:
|
103
104
|
results = arr.map do |column|
|
104
|
-
|
105
|
+
original_column_name = model.attribute_alias?( column ) ? model.attribute_alias( column ) : column
|
106
|
+
qc = quote_column_name( original_column_name )
|
105
107
|
"#{table_name}.#{qc}=VALUES(#{qc})"
|
106
108
|
end
|
107
109
|
increment_locking_column!(table_name, results, locking_column)
|
108
110
|
results.join( ',' )
|
109
111
|
end
|
110
112
|
|
111
|
-
def sql_for_on_duplicate_key_update_as_hash( table_name, locking_column, hsh ) # :nodoc:
|
113
|
+
def sql_for_on_duplicate_key_update_as_hash( table_name, model, locking_column, hsh ) # :nodoc:
|
112
114
|
results = hsh.map do |column1, column2|
|
113
|
-
|
114
|
-
|
115
|
+
original_column1_name = model.attribute_alias?( column1 ) ? model.attribute_alias( column1 ) : column1
|
116
|
+
qc1 = quote_column_name( original_column1_name )
|
117
|
+
|
118
|
+
original_column2_name = model.attribute_alias?( column2 ) ? model.attribute_alias( column2 ) : column2
|
119
|
+
qc2 = quote_column_name( original_column2_name )
|
120
|
+
|
115
121
|
"#{table_name}.#{qc1}=VALUES( #{qc2} )"
|
116
122
|
end
|
117
123
|
increment_locking_column!(table_name, results, locking_column)
|
@@ -12,10 +12,11 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
12
12
|
ids = []
|
13
13
|
results = []
|
14
14
|
|
15
|
-
base_sql, post_sql =
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
base_sql, post_sql = case sql
|
16
|
+
when String
|
17
|
+
[sql, '']
|
18
|
+
when Array
|
19
|
+
[sql.shift, sql.join( ' ' )]
|
19
20
|
end
|
20
21
|
|
21
22
|
sql2insert = base_sql + values.join( ',' ) + post_sql
|
@@ -110,13 +111,14 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
110
111
|
# Add a column to be updated on duplicate key update
|
111
112
|
def add_column_for_on_duplicate_key_update( column, options = {} ) # :nodoc:
|
112
113
|
arg = options[:on_duplicate_key_update]
|
113
|
-
|
114
|
+
case arg
|
115
|
+
when Hash
|
114
116
|
columns = arg.fetch( :columns ) { arg[:columns] = [] }
|
115
117
|
case columns
|
116
118
|
when Array then columns << column.to_sym unless columns.include?( column.to_sym )
|
117
119
|
when Hash then columns[column.to_sym] = column.to_sym
|
118
120
|
end
|
119
|
-
|
121
|
+
when Array
|
120
122
|
arg << column.to_sym unless arg.include?( column.to_sym )
|
121
123
|
end
|
122
124
|
end
|
@@ -132,7 +134,7 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
132
134
|
# Returns a generated ON CONFLICT DO UPDATE statement given the passed
|
133
135
|
# in +args+.
|
134
136
|
def sql_for_on_duplicate_key_update( table_name, *args ) # :nodoc:
|
135
|
-
arg, primary_key, locking_column = args
|
137
|
+
arg, model, primary_key, locking_column = args
|
136
138
|
arg = { columns: arg } if arg.is_a?( Array ) || arg.is_a?( String )
|
137
139
|
return unless arg.is_a?( Hash )
|
138
140
|
|
@@ -151,11 +153,12 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
151
153
|
end
|
152
154
|
|
153
155
|
sql << "#{conflict_target}DO UPDATE SET "
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
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
|
159
162
|
sql << columns
|
160
163
|
else
|
161
164
|
raise ArgumentError, 'Expected :columns to be an Array or Hash'
|
@@ -166,19 +169,24 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
166
169
|
sql
|
167
170
|
end
|
168
171
|
|
169
|
-
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:
|
170
173
|
results = arr.map do |column|
|
171
|
-
|
174
|
+
original_column_name = model.attribute_alias?( column ) ? model.attribute_alias( column ) : column
|
175
|
+
qc = quote_column_name( original_column_name )
|
172
176
|
"#{qc}=EXCLUDED.#{qc}"
|
173
177
|
end
|
174
178
|
increment_locking_column!(table_name, results, locking_column)
|
175
179
|
results.join( ',' )
|
176
180
|
end
|
177
181
|
|
178
|
-
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:
|
179
183
|
results = hsh.map do |column1, column2|
|
180
|
-
|
181
|
-
|
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
|
+
|
182
190
|
"#{qc1}=EXCLUDED.#{qc2}"
|
183
191
|
end
|
184
192
|
increment_locking_column!(table_name, results, locking_column)
|
@@ -192,7 +200,7 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
192
200
|
if constraint_name.present?
|
193
201
|
"ON CONSTRAINT #{constraint_name} "
|
194
202
|
elsif conflict_target.present?
|
195
|
-
sql =
|
203
|
+
sql = "(#{Array( conflict_target ).reject( &:blank? ).join( ', ' )}) "
|
196
204
|
sql += "WHERE #{index_predicate} " if index_predicate
|
197
205
|
sql
|
198
206
|
end
|
@@ -24,10 +24,11 @@ module ActiveRecord::Import::SQLite3Adapter
|
|
24
24
|
def insert_many( sql, values, _options = {}, *args ) # :nodoc:
|
25
25
|
number_of_inserts = 0
|
26
26
|
|
27
|
-
base_sql, post_sql =
|
28
|
-
|
29
|
-
|
30
|
-
|
27
|
+
base_sql, post_sql = case sql
|
28
|
+
when String
|
29
|
+
[sql, '']
|
30
|
+
when Array
|
31
|
+
[sql.shift, sql.join( ' ' )]
|
31
32
|
end
|
32
33
|
|
33
34
|
value_sets = ::ActiveRecord::Import::ValueSetsRecordsParser.parse(values,
|
@@ -56,11 +57,9 @@ module ActiveRecord::Import::SQLite3Adapter
|
|
56
57
|
def post_sql_statements( table_name, options ) # :nodoc:
|
57
58
|
sql = []
|
58
59
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
sql << sql_for_on_duplicate_key_ignore( options[:on_duplicate_key_ignore] )
|
63
|
-
end
|
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] )
|
64
63
|
end
|
65
64
|
|
66
65
|
sql + super
|
@@ -73,13 +72,14 @@ module ActiveRecord::Import::SQLite3Adapter
|
|
73
72
|
# Add a column to be updated on duplicate key update
|
74
73
|
def add_column_for_on_duplicate_key_update( column, options = {} ) # :nodoc:
|
75
74
|
arg = options[:on_duplicate_key_update]
|
76
|
-
|
75
|
+
case arg
|
76
|
+
when Hash
|
77
77
|
columns = arg.fetch( :columns ) { arg[:columns] = [] }
|
78
78
|
case columns
|
79
79
|
when Array then columns << column.to_sym unless columns.include?( column.to_sym )
|
80
80
|
when Hash then columns[column.to_sym] = column.to_sym
|
81
81
|
end
|
82
|
-
|
82
|
+
when Array
|
83
83
|
arg << column.to_sym unless arg.include?( column.to_sym )
|
84
84
|
end
|
85
85
|
end
|
@@ -95,7 +95,7 @@ module ActiveRecord::Import::SQLite3Adapter
|
|
95
95
|
# Returns a generated ON CONFLICT DO UPDATE statement given the passed
|
96
96
|
# in +args+.
|
97
97
|
def sql_for_on_duplicate_key_update( table_name, *args ) # :nodoc:
|
98
|
-
arg, primary_key, locking_column = args
|
98
|
+
arg, model, primary_key, locking_column = args
|
99
99
|
arg = { columns: arg } if arg.is_a?( Array ) || arg.is_a?( String )
|
100
100
|
return unless arg.is_a?( Hash )
|
101
101
|
|
@@ -114,11 +114,12 @@ module ActiveRecord::Import::SQLite3Adapter
|
|
114
114
|
end
|
115
115
|
|
116
116
|
sql << "#{conflict_target}DO UPDATE SET "
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
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
|
122
123
|
sql << columns
|
123
124
|
else
|
124
125
|
raise ArgumentError, 'Expected :columns to be an Array or Hash'
|
@@ -129,19 +130,24 @@ module ActiveRecord::Import::SQLite3Adapter
|
|
129
130
|
sql
|
130
131
|
end
|
131
132
|
|
132
|
-
def sql_for_on_duplicate_key_update_as_array( table_name, locking_column, arr ) # :nodoc:
|
133
|
+
def sql_for_on_duplicate_key_update_as_array( table_name, model, locking_column, arr ) # :nodoc:
|
133
134
|
results = arr.map do |column|
|
134
|
-
|
135
|
+
original_column_name = model.attribute_alias?( column ) ? model.attribute_alias( column ) : column
|
136
|
+
qc = quote_column_name( original_column_name )
|
135
137
|
"#{qc}=EXCLUDED.#{qc}"
|
136
138
|
end
|
137
139
|
increment_locking_column!(table_name, results, locking_column)
|
138
140
|
results.join( ',' )
|
139
141
|
end
|
140
142
|
|
141
|
-
def sql_for_on_duplicate_key_update_as_hash( table_name, locking_column, hsh ) # :nodoc:
|
143
|
+
def sql_for_on_duplicate_key_update_as_hash( table_name, model, locking_column, hsh ) # :nodoc:
|
142
144
|
results = hsh.map do |column1, column2|
|
143
|
-
|
144
|
-
|
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
|
+
|
145
151
|
"#{qc1}=EXCLUDED.#{qc2}"
|
146
152
|
end
|
147
153
|
increment_locking_column!(table_name, results, locking_column)
|
@@ -152,7 +158,7 @@ module ActiveRecord::Import::SQLite3Adapter
|
|
152
158
|
conflict_target = args[:conflict_target]
|
153
159
|
index_predicate = args[:index_predicate]
|
154
160
|
if conflict_target.present?
|
155
|
-
sql =
|
161
|
+
sql = "(#{Array( conflict_target ).reject( &:blank? ).join( ', ' )}) "
|
156
162
|
sql += "WHERE #{index_predicate} " if index_predicate
|
157
163
|
sql
|
158
164
|
end
|
@@ -4,17 +4,17 @@ require "ostruct"
|
|
4
4
|
|
5
5
|
module ActiveRecord::Import::ConnectionAdapters; end
|
6
6
|
|
7
|
-
module ActiveRecord::Import
|
7
|
+
module ActiveRecord::Import # :nodoc:
|
8
8
|
Result = Struct.new(:failed_instances, :num_inserts, :ids, :results)
|
9
9
|
|
10
|
-
module ImportSupport
|
11
|
-
def supports_import?
|
10
|
+
module ImportSupport # :nodoc:
|
11
|
+
def supports_import? # :nodoc:
|
12
12
|
true
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
|
-
module OnDuplicateKeyUpdateSupport
|
17
|
-
def supports_on_duplicate_key_update?
|
16
|
+
module OnDuplicateKeyUpdateSupport # :nodoc:
|
17
|
+
def supports_on_duplicate_key_update? # :nodoc:
|
18
18
|
true
|
19
19
|
end
|
20
20
|
end
|
@@ -62,6 +62,7 @@ module ActiveRecord::Import #:nodoc:
|
|
62
62
|
if @validate_callbacks.respond_to?(:chain, true)
|
63
63
|
@validate_callbacks.send(:chain).tap do |chain|
|
64
64
|
callback.instance_variable_set(:@filter, filter)
|
65
|
+
callback.instance_variable_set(:@compiled, nil)
|
65
66
|
chain[i] = callback
|
66
67
|
end
|
67
68
|
else
|
@@ -73,7 +74,7 @@ module ActiveRecord::Import #:nodoc:
|
|
73
74
|
end
|
74
75
|
|
75
76
|
def valid_model?(model)
|
76
|
-
init_validations(model.class) unless model.
|
77
|
+
init_validations(model.class) unless model.instance_of?(@validator_class)
|
77
78
|
|
78
79
|
validation_context = @options[:validate_with_context]
|
79
80
|
validation_context ||= (model.new_record? ? :create : :update)
|
@@ -85,11 +86,15 @@ module ActiveRecord::Import #:nodoc:
|
|
85
86
|
|
86
87
|
model.run_callbacks(:validation) do
|
87
88
|
if defined?(ActiveSupport::Callbacks::Filters::Environment) # ActiveRecord >= 4.1
|
88
|
-
runner = @validate_callbacks.compile
|
89
|
+
runner = if @validate_callbacks.method(:compile).arity == 0
|
90
|
+
@validate_callbacks.compile
|
91
|
+
else # ActiveRecord >= 7.1
|
92
|
+
@validate_callbacks.compile(nil)
|
93
|
+
end
|
89
94
|
env = ActiveSupport::Callbacks::Filters::Environment.new(model, false, nil)
|
90
95
|
if runner.respond_to?(:call) # ActiveRecord < 5.1
|
91
96
|
runner.call(env)
|
92
|
-
else # ActiveRecord 5.1
|
97
|
+
else # ActiveRecord >= 5.1
|
93
98
|
# Note that this is a gross simplification of ActiveSupport::Callbacks#run_callbacks.
|
94
99
|
# It's technically possible for there to exist an "around" callback in the
|
95
100
|
# :validate chain, but this would be an aberration, since Rails doesn't define
|
@@ -102,7 +107,8 @@ module ActiveRecord::Import #:nodoc:
|
|
102
107
|
# no real-world use case for it.
|
103
108
|
raise "The :validate callback chain contains an 'around' callback, which is unsupported" unless runner.final?
|
104
109
|
runner.invoke_before(env)
|
105
|
-
|
110
|
+
# Ensure a truthy value is returned. ActiveRecord < 7.2 always returned an array.
|
111
|
+
runner.invoke_after(env) || []
|
106
112
|
end
|
107
113
|
elsif @validate_callbacks.method(:compile).arity == 0 # ActiveRecord = 4.0
|
108
114
|
model.instance_eval @validate_callbacks.compile
|
@@ -165,7 +171,7 @@ class ActiveRecord::Associations::CollectionAssociation
|
|
165
171
|
m.public_send "#{reflection.type}=", owner.class.name if reflection.type
|
166
172
|
end
|
167
173
|
|
168
|
-
|
174
|
+
model_klass.bulk_import column_names, models, options
|
169
175
|
|
170
176
|
# supports array of hash objects
|
171
177
|
elsif args.last.is_a?( Array ) && args.last.first.is_a?(Hash)
|
@@ -204,11 +210,11 @@ class ActiveRecord::Associations::CollectionAssociation
|
|
204
210
|
end
|
205
211
|
end
|
206
212
|
|
207
|
-
|
213
|
+
model_klass.bulk_import column_names, array_of_attributes, options
|
208
214
|
|
209
215
|
# supports empty array
|
210
216
|
elsif args.last.is_a?( Array ) && args.last.empty?
|
211
|
-
|
217
|
+
ActiveRecord::Import::Result.new([], 0, [])
|
212
218
|
|
213
219
|
# supports 2-element array and array
|
214
220
|
elsif args.size == 2 && args.first.is_a?( Array ) && args.last.is_a?( Array )
|
@@ -239,7 +245,7 @@ class ActiveRecord::Associations::CollectionAssociation
|
|
239
245
|
end
|
240
246
|
end
|
241
247
|
|
242
|
-
|
248
|
+
model_klass.bulk_import column_names, array_of_attributes, options
|
243
249
|
else
|
244
250
|
raise ArgumentError, "Invalid arguments!"
|
245
251
|
end
|
@@ -553,7 +559,7 @@ class ActiveRecord::Base
|
|
553
559
|
options.merge!( args.pop ) if args.last.is_a? Hash
|
554
560
|
# making sure that current model's primary key is used
|
555
561
|
options[:primary_key] = primary_key
|
556
|
-
options[:locking_column] = locking_column if
|
562
|
+
options[:locking_column] = locking_column if locking_enabled?
|
557
563
|
|
558
564
|
is_validating = options[:validate_with_context].present? ? true : options[:validate]
|
559
565
|
validator = ActiveRecord::Import::Validator.new(self, options)
|
@@ -574,7 +580,7 @@ class ActiveRecord::Base
|
|
574
580
|
|
575
581
|
if models.first.id.nil?
|
576
582
|
Array(primary_key).each do |c|
|
577
|
-
if column_names.include?(c) &&
|
583
|
+
if column_names.include?(c) && schema_columns_hash[c].type == :uuid
|
578
584
|
column_names.delete(c)
|
579
585
|
end
|
580
586
|
end
|
@@ -697,7 +703,11 @@ class ActiveRecord::Base
|
|
697
703
|
return_obj = if is_validating
|
698
704
|
import_with_validations( column_names, array_of_attributes, options ) do |failed_instances|
|
699
705
|
if models
|
700
|
-
models.
|
706
|
+
models.each_with_index do |m, i|
|
707
|
+
next unless m.errors.any?
|
708
|
+
|
709
|
+
failed_instances << (options[:track_validation_failures] ? [i, m] : m)
|
710
|
+
end
|
701
711
|
else
|
702
712
|
# create instances for each of our column/value sets
|
703
713
|
arr = validations_array_for_column_names_and_attributes( column_names, array_of_attributes )
|
@@ -774,7 +784,10 @@ class ActiveRecord::Base
|
|
774
784
|
def import_without_validations_or_callbacks( column_names, array_of_attributes, options = {} )
|
775
785
|
return ActiveRecord::Import::Result.new([], 0, [], []) if array_of_attributes.empty?
|
776
786
|
|
777
|
-
column_names = column_names.map
|
787
|
+
column_names = column_names.map do |name|
|
788
|
+
original_name = attribute_alias?(name) ? attribute_alias(name) : name
|
789
|
+
original_name.to_sym
|
790
|
+
end
|
778
791
|
scope_columns, scope_values = scope_attributes.to_a.transpose
|
779
792
|
|
780
793
|
unless scope_columns.blank?
|
@@ -786,15 +799,13 @@ class ActiveRecord::Base
|
|
786
799
|
end
|
787
800
|
end
|
788
801
|
|
789
|
-
if finder_needs_type_condition?
|
790
|
-
|
791
|
-
|
792
|
-
array_of_attributes.each { |attrs| attrs << sti_name }
|
793
|
-
end
|
802
|
+
if finder_needs_type_condition? && !column_names.include?(inheritance_column.to_sym)
|
803
|
+
column_names << inheritance_column.to_sym
|
804
|
+
array_of_attributes.each { |attrs| attrs << sti_name }
|
794
805
|
end
|
795
806
|
|
796
807
|
columns = column_names.each_with_index.map do |name, i|
|
797
|
-
column =
|
808
|
+
column = schema_columns_hash[name.to_s]
|
798
809
|
raise ActiveRecord::Import::MissingColumnError.new(name.to_s, i) if column.nil?
|
799
810
|
column
|
800
811
|
end
|
@@ -848,6 +859,15 @@ class ActiveRecord::Base
|
|
848
859
|
|
849
860
|
private
|
850
861
|
|
862
|
+
def associated_options(options, associated_class)
|
863
|
+
return options unless options.key?(:recursive_on_duplicate_key_update)
|
864
|
+
|
865
|
+
table_name = associated_class.arel_table.name.to_sym
|
866
|
+
options.merge(
|
867
|
+
on_duplicate_key_update: options[:recursive_on_duplicate_key_update][table_name]
|
868
|
+
)
|
869
|
+
end
|
870
|
+
|
851
871
|
def set_attributes_and_mark_clean(models, import_result, timestamps, options)
|
852
872
|
return if models.nil?
|
853
873
|
models -= import_result.failed_instances
|
@@ -859,13 +879,13 @@ class ActiveRecord::Base
|
|
859
879
|
model.id = id
|
860
880
|
|
861
881
|
timestamps.each do |attr, value|
|
862
|
-
model.send(attr
|
882
|
+
model.send("#{attr}=", value) if model.send(attr).nil?
|
863
883
|
end
|
864
884
|
end
|
865
885
|
end
|
866
886
|
|
867
887
|
deserialize_value = lambda do |column, value|
|
868
|
-
column =
|
888
|
+
column = schema_columns_hash[column]
|
869
889
|
return value unless column
|
870
890
|
if respond_to?(:type_caster)
|
871
891
|
type = type_for_attribute(column.name)
|
@@ -932,7 +952,7 @@ class ActiveRecord::Base
|
|
932
952
|
association = association.target
|
933
953
|
next if association.blank? || model.public_send(column_name).present?
|
934
954
|
|
935
|
-
association_primary_key = Array(association_reflection.association_primary_key)[column_index]
|
955
|
+
association_primary_key = Array(association_reflection.association_primary_key.tr("[]:", "").split(", "))[column_index]
|
936
956
|
model.public_send("#{column_name}=", association.send(association_primary_key))
|
937
957
|
end
|
938
958
|
end
|
@@ -954,11 +974,23 @@ class ActiveRecord::Base
|
|
954
974
|
|
955
975
|
associated_objects_by_class.each_value do |associations|
|
956
976
|
associations.each_value do |associated_records|
|
957
|
-
|
977
|
+
next if associated_records.empty?
|
978
|
+
|
979
|
+
associated_class = associated_records.first.class
|
980
|
+
associated_class.bulk_import(associated_records,
|
981
|
+
associated_options(options, associated_class))
|
958
982
|
end
|
959
983
|
end
|
960
984
|
end
|
961
985
|
|
986
|
+
def schema_columns_hash
|
987
|
+
if respond_to?(:ignored_columns) && ignored_columns.any?
|
988
|
+
connection.schema_cache.columns_hash(table_name)
|
989
|
+
else
|
990
|
+
columns_hash
|
991
|
+
end
|
992
|
+
end
|
993
|
+
|
962
994
|
# We are eventually going to call Class.import <objects> so we build up a hash
|
963
995
|
# of class => objects to import.
|
964
996
|
def find_associated_objects_for_import(associated_objects_by_class, model)
|
@@ -979,7 +1011,10 @@ class ActiveRecord::Base
|
|
979
1011
|
|
980
1012
|
changed_objects = association.select { |a| a.new_record? || a.changed? }
|
981
1013
|
changed_objects.each do |child|
|
982
|
-
|
1014
|
+
Array(association_reflection.inverse_of&.foreign_key || association_reflection.foreign_key).each_with_index do |column, index|
|
1015
|
+
child.public_send("#{column}=", Array(model.id)[index])
|
1016
|
+
end
|
1017
|
+
|
983
1018
|
# For polymorphic associations
|
984
1019
|
association_name = if model.class.respond_to?(:polymorphic_name)
|
985
1020
|
model.class.polymorphic_name
|
data/lib/activerecord-import.rb
CHANGED
data/test/database.yml.sample
CHANGED
@@ -8,6 +8,7 @@ common: &common
|
|
8
8
|
mysql2: &mysql2
|
9
9
|
<<: *common
|
10
10
|
adapter: mysql2
|
11
|
+
host: mysql
|
11
12
|
|
12
13
|
mysql2spatial:
|
13
14
|
<<: *mysql2
|
@@ -19,6 +20,7 @@ postgresql: &postgresql
|
|
19
20
|
<<: *common
|
20
21
|
username: postgres
|
21
22
|
adapter: postgresql
|
23
|
+
host: postgresql
|
22
24
|
min_messages: warning
|
23
25
|
|
24
26
|
postresql_makara:
|
@@ -50,3 +52,8 @@ sqlite3: &sqlite3
|
|
50
52
|
|
51
53
|
spatialite:
|
52
54
|
<<: *sqlite3
|
55
|
+
|
56
|
+
trilogy:
|
57
|
+
<<: *common
|
58
|
+
adapter: trilogy
|
59
|
+
host: mysql
|
data/test/github/database.yml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require File.expand_path(File.dirname(__FILE__)
|
4
|
-
require File.expand_path(File.dirname(__FILE__)
|
5
|
-
require File.expand_path(File.dirname(__FILE__)
|
3
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../test_helper")
|
4
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../support/assertions")
|
5
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../support/mysql/import_examples")
|
6
6
|
|
7
7
|
should_support_mysql_import_functionality
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require File.expand_path(File.dirname(__FILE__)
|
4
|
-
require File.expand_path(File.dirname(__FILE__)
|
3
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../test_helper")
|
4
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../support/postgresql/import_examples")
|
5
5
|
|
6
6
|
should_support_postgresql_import_functionality
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require File.expand_path(File.dirname(__FILE__)
|
4
|
-
require File.expand_path(File.dirname(__FILE__)
|
3
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../test_helper")
|
4
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../support/sqlite3/import_examples")
|
5
5
|
|
6
6
|
should_support_sqlite3_import_functionality
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require File.expand_path(File.dirname(__FILE__)
|
4
|
-
require File.expand_path(File.dirname(__FILE__)
|
3
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../test_helper")
|
4
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../support/postgresql/import_examples")
|
5
5
|
|
6
6
|
should_support_postgresql_import_functionality
|
7
7
|
|