activerecord-import 1.4.1 → 1.8.1
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/.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
|
|