activerecord-import 1.3.0 → 1.4.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 +39 -10
- data/.rubocop.yml +74 -8
- data/Brewfile +3 -1
- data/CHANGELOG.md +12 -0
- data/Gemfile +5 -7
- data/README.markdown +9 -8
- data/Rakefile +2 -0
- data/activerecord-import.gemspec +2 -1
- data/benchmarks/benchmark.rb +7 -1
- data/benchmarks/lib/base.rb +2 -0
- data/benchmarks/lib/cli_parser.rb +3 -1
- data/benchmarks/lib/float.rb +2 -0
- data/benchmarks/lib/mysql2_benchmark.rb +2 -0
- data/benchmarks/lib/output_to_csv.rb +2 -0
- data/benchmarks/lib/output_to_html.rb +4 -2
- data/benchmarks/models/test_innodb.rb +2 -0
- data/benchmarks/models/test_memory.rb +2 -0
- data/benchmarks/models/test_myisam.rb +2 -0
- data/benchmarks/schema/mysql2_schema.rb +2 -0
- data/gemfiles/4.2.gemfile +2 -0
- data/gemfiles/5.0.gemfile +2 -0
- data/gemfiles/5.1.gemfile +2 -0
- data/gemfiles/5.2.gemfile +2 -0
- data/gemfiles/6.0.gemfile +2 -0
- data/gemfiles/6.1.gemfile +2 -0
- data/gemfiles/7.0.gemfile +4 -1
- data/lib/activerecord-import/active_record/adapters/abstract_adapter.rb +2 -0
- data/lib/activerecord-import/active_record/adapters/jdbcmysql_adapter.rb +2 -0
- data/lib/activerecord-import/active_record/adapters/jdbcpostgresql_adapter.rb +2 -0
- data/lib/activerecord-import/active_record/adapters/jdbcsqlite3_adapter.rb +2 -0
- data/lib/activerecord-import/active_record/adapters/mysql2_adapter.rb +2 -0
- data/lib/activerecord-import/active_record/adapters/postgresql_adapter.rb +2 -0
- data/lib/activerecord-import/active_record/adapters/seamless_database_pool_adapter.rb +2 -0
- data/lib/activerecord-import/active_record/adapters/sqlite3_adapter.rb +2 -0
- data/lib/activerecord-import/adapters/abstract_adapter.rb +2 -0
- data/lib/activerecord-import/adapters/em_mysql2_adapter.rb +2 -0
- data/lib/activerecord-import/adapters/mysql2_adapter.rb +2 -0
- data/lib/activerecord-import/adapters/mysql_adapter.rb +3 -1
- data/lib/activerecord-import/adapters/postgresql_adapter.rb +41 -28
- data/lib/activerecord-import/adapters/sqlite3_adapter.rb +8 -6
- data/lib/activerecord-import/base.rb +3 -1
- data/lib/activerecord-import/import.rb +21 -10
- data/lib/activerecord-import/mysql2.rb +2 -0
- data/lib/activerecord-import/postgresql.rb +2 -0
- data/lib/activerecord-import/sqlite3.rb +2 -0
- data/lib/activerecord-import/synchronize.rb +2 -0
- data/lib/activerecord-import/value_sets_parser.rb +2 -0
- data/lib/activerecord-import/version.rb +3 -1
- data/lib/activerecord-import.rb +3 -1
- data/test/adapters/jdbcmysql.rb +2 -0
- data/test/adapters/jdbcpostgresql.rb +2 -0
- data/test/adapters/jdbcsqlite3.rb +2 -0
- data/test/adapters/makara_postgis.rb +2 -0
- data/test/adapters/mysql2.rb +2 -0
- data/test/adapters/mysql2_makara.rb +2 -0
- data/test/adapters/mysql2spatial.rb +2 -0
- data/test/adapters/postgis.rb +2 -0
- data/test/adapters/postgresql.rb +2 -0
- data/test/adapters/postgresql_makara.rb +2 -0
- data/test/adapters/seamless_database_pool.rb +2 -0
- data/test/adapters/spatialite.rb +2 -0
- data/test/adapters/sqlite3.rb +2 -0
- data/test/import_test.rb +26 -1
- data/test/jdbcmysql/import_test.rb +2 -0
- data/test/jdbcpostgresql/import_test.rb +2 -0
- data/test/jdbcsqlite3/import_test.rb +2 -0
- data/test/makara_postgis/import_test.rb +2 -0
- data/test/models/account.rb +2 -0
- data/test/models/alarm.rb +2 -0
- data/test/models/animal.rb +2 -0
- data/test/models/bike_maker.rb +2 -0
- data/test/models/book.rb +2 -0
- data/test/models/car.rb +2 -0
- data/test/models/card.rb +2 -0
- data/test/models/chapter.rb +2 -0
- data/test/models/customer.rb +2 -0
- data/test/models/deck.rb +2 -0
- data/test/models/dictionary.rb +2 -0
- data/test/models/discount.rb +2 -0
- data/test/models/end_note.rb +2 -0
- data/test/models/group.rb +2 -0
- data/test/models/order.rb +2 -0
- data/test/models/playing_card.rb +2 -0
- data/test/models/promotion.rb +2 -0
- data/test/models/question.rb +2 -0
- data/test/models/rule.rb +2 -0
- data/test/models/tag.rb +3 -0
- data/test/models/tag_alias.rb +5 -0
- data/test/models/topic.rb +2 -0
- data/test/models/user.rb +2 -0
- data/test/models/user_token.rb +2 -0
- data/test/models/vendor.rb +2 -0
- data/test/models/widget.rb +2 -0
- data/test/mysql2/import_test.rb +2 -0
- data/test/mysql2_makara/import_test.rb +2 -0
- data/test/mysqlspatial2/import_test.rb +2 -0
- data/test/postgis/import_test.rb +2 -0
- data/test/postgresql/import_test.rb +2 -0
- data/test/schema/generic_schema.rb +8 -0
- data/test/schema/jdbcpostgresql_schema.rb +2 -0
- data/test/schema/mysql2_schema.rb +2 -0
- data/test/schema/postgis_schema.rb +2 -0
- data/test/schema/postgresql_schema.rb +2 -0
- data/test/schema/sqlite3_schema.rb +2 -0
- data/test/schema/version.rb +2 -0
- data/test/sqlite3/import_test.rb +2 -0
- data/test/support/active_support/test_case_extensions.rb +2 -0
- data/test/support/assertions.rb +2 -0
- data/test/support/factories.rb +2 -0
- data/test/support/generate.rb +4 -2
- data/test/support/mysql/import_examples.rb +2 -1
- data/test/support/postgresql/import_examples.rb +41 -2
- data/test/support/shared_examples/on_duplicate_key_ignore.rb +2 -0
- data/test/support/shared_examples/on_duplicate_key_update.rb +2 -0
- data/test/support/shared_examples/recursive_import.rb +2 -0
- data/test/support/sqlite3/import_examples.rb +2 -1
- data/test/synchronize_test.rb +2 -0
- data/test/test_helper.rb +18 -2
- data/test/value_sets_bytes_parser_test.rb +2 -0
- data/test/value_sets_records_parser_test.rb +2 -0
- metadata +5 -3
@@ -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,7 +8,7 @@ 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
|
|
@@ -18,47 +20,53 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
18
20
|
|
19
21
|
sql2insert = base_sql + values.join( ',' ) + post_sql
|
20
22
|
|
21
|
-
|
22
|
-
if
|
23
|
+
selections = returning_selections(options)
|
24
|
+
if selections.blank? || (options[:no_returning] && !options[:recursive])
|
23
25
|
insert( sql2insert, *args )
|
24
26
|
else
|
25
|
-
returned_values = if
|
27
|
+
returned_values = if selections.size > 1
|
26
28
|
# Select composite columns
|
27
|
-
|
29
|
+
db_result = select_all( sql2insert, *args )
|
30
|
+
{ values: db_result.rows, columns: db_result.columns }
|
28
31
|
else
|
29
|
-
select_values( sql2insert, *args )
|
32
|
+
{ values: select_values( sql2insert, *args ) }
|
30
33
|
end
|
31
34
|
clear_query_cache if query_cache_enabled
|
32
35
|
end
|
33
36
|
|
34
37
|
if options[:returning].blank?
|
35
|
-
ids = returned_values
|
38
|
+
ids = Array(returned_values[:values])
|
36
39
|
elsif options[:primary_key].blank?
|
37
|
-
|
40
|
+
options[:returning_columns] ||= returned_values[:columns]
|
41
|
+
results = Array(returned_values[:values])
|
38
42
|
else
|
39
43
|
# split primary key and returning columns
|
40
|
-
ids, results = split_ids_and_results(returned_values,
|
44
|
+
ids, results, options[:returning_columns] = split_ids_and_results(returned_values, options)
|
41
45
|
end
|
42
46
|
|
43
47
|
ActiveRecord::Import::Result.new([], number_of_inserts, ids, results)
|
44
48
|
end
|
45
49
|
|
46
|
-
def split_ids_and_results(
|
50
|
+
def split_ids_and_results( selections, options )
|
47
51
|
ids = []
|
48
|
-
|
52
|
+
returning_values = []
|
53
|
+
|
54
|
+
columns = Array(selections[:columns])
|
55
|
+
values = Array(selections[:values])
|
49
56
|
id_indexes = Array(options[:primary_key]).map { |key| columns.index(key) }
|
50
|
-
|
57
|
+
returning_columns = columns.reject.with_index { |_, index| id_indexes.include?(index) }
|
58
|
+
returning_indexes = returning_columns.map { |column| columns.index(column) }
|
51
59
|
|
52
60
|
values.each do |value|
|
53
61
|
value_array = Array(value)
|
54
|
-
ids << id_indexes.map { |
|
55
|
-
|
62
|
+
ids << id_indexes.map { |index| value_array[index] }
|
63
|
+
returning_values << returning_indexes.map { |index| value_array[index] }
|
56
64
|
end
|
57
65
|
|
58
66
|
ids.map!(&:first) if id_indexes.size == 1
|
59
|
-
|
67
|
+
returning_values.map!(&:first) if returning_columns.size == 1
|
60
68
|
|
61
|
-
[ids,
|
69
|
+
[ids, returning_values, returning_columns]
|
62
70
|
end
|
63
71
|
|
64
72
|
def next_value_for_sequence(sequence_name)
|
@@ -79,19 +87,24 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
79
87
|
|
80
88
|
sql += super(table_name, options)
|
81
89
|
|
82
|
-
|
83
|
-
unless
|
84
|
-
sql << " RETURNING
|
90
|
+
selections = returning_selections(options)
|
91
|
+
unless selections.blank? || (options[:no_returning] && !options[:recursive])
|
92
|
+
sql << " RETURNING #{selections.join(', ')}"
|
85
93
|
end
|
86
94
|
|
87
95
|
sql
|
88
96
|
end
|
89
97
|
|
90
|
-
def
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
98
|
+
def returning_selections(options)
|
99
|
+
selections = []
|
100
|
+
column_names = Array(options[:model].column_names)
|
101
|
+
|
102
|
+
selections += Array(options[:primary_key]) if options[:primary_key].present?
|
103
|
+
selections += Array(options[:returning]) if options[:returning].present?
|
104
|
+
|
105
|
+
selections.map do |selection|
|
106
|
+
column_names.include?(selection.to_s) ? "\"#{selection}\"" : selection
|
107
|
+
end
|
95
108
|
end
|
96
109
|
|
97
110
|
# Add a column to be updated on duplicate key update
|
@@ -123,7 +136,7 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
123
136
|
arg = { columns: arg } if arg.is_a?( Array ) || arg.is_a?( String )
|
124
137
|
return unless arg.is_a?( Hash )
|
125
138
|
|
126
|
-
sql = ' ON CONFLICT '
|
139
|
+
sql = ' ON CONFLICT '.dup
|
127
140
|
conflict_target = sql_for_conflict_target( arg )
|
128
141
|
|
129
142
|
columns = arg.fetch( :columns, [] )
|
@@ -179,9 +192,9 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
179
192
|
if constraint_name.present?
|
180
193
|
"ON CONSTRAINT #{constraint_name} "
|
181
194
|
elsif conflict_target.present?
|
182
|
-
'('
|
183
|
-
|
184
|
-
|
195
|
+
sql = '(' + Array( conflict_target ).reject( &:blank? ).join( ', ' ) + ') '
|
196
|
+
sql += "WHERE #{index_predicate} " if index_predicate
|
197
|
+
sql
|
185
198
|
end
|
186
199
|
end
|
187
200
|
|
@@ -1,9 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActiveRecord::Import::SQLite3Adapter
|
2
4
|
include ActiveRecord::Import::ImportSupport
|
3
5
|
include ActiveRecord::Import::OnDuplicateKeyUpdateSupport
|
4
6
|
|
5
|
-
MIN_VERSION_FOR_IMPORT = "3.7.11"
|
6
|
-
MIN_VERSION_FOR_UPSERT = "3.24.0"
|
7
|
+
MIN_VERSION_FOR_IMPORT = "3.7.11"
|
8
|
+
MIN_VERSION_FOR_UPSERT = "3.24.0"
|
7
9
|
SQLITE_LIMIT_COMPOUND_SELECT = 500
|
8
10
|
|
9
11
|
# Override our conformance to ActiveRecord::Import::ImportSupport interface
|
@@ -97,7 +99,7 @@ module ActiveRecord::Import::SQLite3Adapter
|
|
97
99
|
arg = { columns: arg } if arg.is_a?( Array ) || arg.is_a?( String )
|
98
100
|
return unless arg.is_a?( Hash )
|
99
101
|
|
100
|
-
sql = ' ON CONFLICT '
|
102
|
+
sql = ' ON CONFLICT '.dup
|
101
103
|
conflict_target = sql_for_conflict_target( arg )
|
102
104
|
|
103
105
|
columns = arg.fetch( :columns, [] )
|
@@ -150,9 +152,9 @@ module ActiveRecord::Import::SQLite3Adapter
|
|
150
152
|
conflict_target = args[:conflict_target]
|
151
153
|
index_predicate = args[:index_predicate]
|
152
154
|
if conflict_target.present?
|
153
|
-
'('
|
154
|
-
|
155
|
-
|
155
|
+
sql = '(' + Array( conflict_target ).reject( &:blank? ).join( ', ' ) + ') '
|
156
|
+
sql += "WHERE #{index_predicate} " if index_predicate
|
157
|
+
sql
|
156
158
|
end
|
157
159
|
end
|
158
160
|
|
@@ -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"
|
8
|
+
ADAPTER_PATH = "activerecord-import/active_record/adapters"
|
7
9
|
|
8
10
|
def self.base_adapter(adapter)
|
9
11
|
case adapter
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "ostruct"
|
2
4
|
|
3
5
|
module ActiveRecord::Import::ConnectionAdapters; end
|
@@ -55,7 +57,7 @@ module ActiveRecord::Import #:nodoc:
|
|
55
57
|
end
|
56
58
|
end
|
57
59
|
|
58
|
-
filter.instance_variable_set(:@attributes, attrs)
|
60
|
+
filter.instance_variable_set(:@attributes, attrs.flatten)
|
59
61
|
|
60
62
|
if @validate_callbacks.respond_to?(:chain, true)
|
61
63
|
@validate_callbacks.send(:chain).tap do |chain|
|
@@ -547,7 +549,7 @@ class ActiveRecord::Base
|
|
547
549
|
alias import! bulk_import! unless ActiveRecord::Base.respond_to? :import!
|
548
550
|
|
549
551
|
def import_helper( *args )
|
550
|
-
options = { validate: true, timestamps: true, track_validation_failures: false }
|
552
|
+
options = { model: self, validate: true, timestamps: true, track_validation_failures: false }
|
551
553
|
options.merge!( args.pop ) if args.last.is_a? Hash
|
552
554
|
# making sure that current model's primary key is used
|
553
555
|
options[:primary_key] = primary_key
|
@@ -875,19 +877,28 @@ class ActiveRecord::Base
|
|
875
877
|
end
|
876
878
|
end
|
877
879
|
|
878
|
-
|
879
|
-
|
880
|
-
|
881
|
-
|
880
|
+
set_value = lambda do |model, column, value|
|
881
|
+
val = deserialize_value.call(column, value)
|
882
|
+
if model.attribute_names.include?(column)
|
883
|
+
model.send("#{column}=", val)
|
884
|
+
else
|
885
|
+
attributes = attributes_builder.build_from_database(model.attributes.merge(column => val))
|
886
|
+
model.instance_variable_set(:@attributes, attributes)
|
887
|
+
end
|
888
|
+
end
|
889
|
+
|
890
|
+
columns = Array(options[:returning_columns])
|
891
|
+
results = Array(import_result.results)
|
892
|
+
if models.size == results.size
|
893
|
+
single_column = columns.first if columns.size == 1
|
894
|
+
results.each_with_index do |result, index|
|
882
895
|
model = models[index]
|
883
896
|
|
884
897
|
if single_column
|
885
|
-
|
886
|
-
model.send(single_column, val)
|
898
|
+
set_value.call(model, single_column, result)
|
887
899
|
else
|
888
900
|
columns.each_with_index do |column, col_index|
|
889
|
-
|
890
|
-
model.send("#{column}=", val)
|
901
|
+
set_value.call(model, column, result[col_index])
|
891
902
|
end
|
892
903
|
end
|
893
904
|
end
|
data/lib/activerecord-import.rb
CHANGED
data/test/adapters/jdbcmysql.rb
CHANGED
data/test/adapters/mysql2.rb
CHANGED
data/test/adapters/postgis.rb
CHANGED
data/test/adapters/postgresql.rb
CHANGED
data/test/adapters/spatialite.rb
CHANGED
data/test/adapters/sqlite3.rb
CHANGED
data/test/import_test.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require File.expand_path('../test_helper', __FILE__)
|
2
4
|
|
3
5
|
describe "#import" do
|
@@ -159,6 +161,25 @@ describe "#import" do
|
|
159
161
|
Tag.import columns, values, validate: false
|
160
162
|
end
|
161
163
|
end
|
164
|
+
|
165
|
+
it "should import models that are required to belong to models with composite primary keys" do
|
166
|
+
tag = Tag.create!(tag_id: 1, publisher_id: 1, tag: 'Mystery')
|
167
|
+
valid_tag_alias = TagAlias.new(tag_id: tag.tag_id, parent_id: tag.publisher_id, alias: 'Detective')
|
168
|
+
invalid_tag_aliases = [
|
169
|
+
TagAlias.new(tag_id: nil, parent_id: nil, alias: 'Detective'),
|
170
|
+
TagAlias.new(tag_id: tag.tag_id, parent_id: nil, alias: 'Detective'),
|
171
|
+
TagAlias.new(tag_id: nil, parent_id: tag.publisher_id, alias: 'Detective'),
|
172
|
+
]
|
173
|
+
|
174
|
+
assert_difference "TagAlias.count", +1 do
|
175
|
+
TagAlias.import [valid_tag_alias]
|
176
|
+
end
|
177
|
+
invalid_tag_aliases.each do |invalid_tag_alias|
|
178
|
+
assert_no_difference "TagAlias.count" do
|
179
|
+
TagAlias.import [invalid_tag_alias]
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
162
183
|
end
|
163
184
|
end
|
164
185
|
|
@@ -555,7 +576,11 @@ describe "#import" do
|
|
555
576
|
context "when the timestamps columns are present" do
|
556
577
|
setup do
|
557
578
|
@existing_book = Book.create(title: "Fell", author_name: "Curry", publisher: "Bayer", created_at: 2.years.ago.utc, created_on: 2.years.ago.utc, updated_at: 2.years.ago.utc, updated_on: 2.years.ago.utc)
|
558
|
-
ActiveRecord
|
579
|
+
if ActiveRecord.respond_to?(:default_timezone)
|
580
|
+
ActiveRecord.default_timezone = :utc
|
581
|
+
else
|
582
|
+
ActiveRecord::Base.default_timezone = :utc
|
583
|
+
end
|
559
584
|
Timecop.freeze(time) do
|
560
585
|
assert_difference "Book.count", +2 do
|
561
586
|
Book.import %w(title author_name publisher created_at created_on updated_at updated_on), [["LDAP", "Big Bird", "Del Rey", nil, nil, nil, nil], [@existing_book.title, @existing_book.author_name, @existing_book.publisher, @existing_book.created_at, @existing_book.created_on, @existing_book.updated_at, @existing_book.updated_on]]
|
data/test/models/account.rb
CHANGED
data/test/models/alarm.rb
CHANGED
data/test/models/animal.rb
CHANGED
data/test/models/bike_maker.rb
CHANGED
data/test/models/book.rb
CHANGED
data/test/models/car.rb
CHANGED
data/test/models/card.rb
CHANGED
data/test/models/chapter.rb
CHANGED
data/test/models/customer.rb
CHANGED
data/test/models/deck.rb
CHANGED
data/test/models/dictionary.rb
CHANGED
data/test/models/discount.rb
CHANGED
data/test/models/end_note.rb
CHANGED
data/test/models/group.rb
CHANGED
data/test/models/order.rb
CHANGED
data/test/models/playing_card.rb
CHANGED
data/test/models/promotion.rb
CHANGED
data/test/models/question.rb
CHANGED
data/test/models/rule.rb
CHANGED
data/test/models/tag.rb
CHANGED
data/test/models/topic.rb
CHANGED
data/test/models/user.rb
CHANGED
data/test/models/user_token.rb
CHANGED
data/test/models/vendor.rb
CHANGED
data/test/models/widget.rb
CHANGED
data/test/mysql2/import_test.rb
CHANGED