activerecord-import 1.0.2 → 1.7.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.
Files changed (141) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yaml +151 -0
  3. data/.gitignore +5 -0
  4. data/.rubocop.yml +74 -8
  5. data/.rubocop_todo.yml +10 -16
  6. data/Brewfile +3 -1
  7. data/CHANGELOG.md +138 -3
  8. data/Dockerfile +23 -0
  9. data/Gemfile +24 -14
  10. data/LICENSE +21 -56
  11. data/README.markdown +108 -60
  12. data/Rakefile +3 -0
  13. data/activerecord-import.gemspec +6 -5
  14. data/benchmarks/benchmark.rb +10 -4
  15. data/benchmarks/lib/base.rb +4 -2
  16. data/benchmarks/lib/cli_parser.rb +4 -2
  17. data/benchmarks/lib/float.rb +2 -0
  18. data/benchmarks/lib/mysql2_benchmark.rb +2 -0
  19. data/benchmarks/lib/output_to_csv.rb +2 -0
  20. data/benchmarks/lib/output_to_html.rb +4 -2
  21. data/benchmarks/models/test_innodb.rb +2 -0
  22. data/benchmarks/models/test_memory.rb +2 -0
  23. data/benchmarks/models/test_myisam.rb +2 -0
  24. data/benchmarks/schema/{mysql_schema.rb → mysql2_schema.rb} +2 -0
  25. data/docker-compose.yml +34 -0
  26. data/gemfiles/4.2.gemfile +2 -0
  27. data/gemfiles/5.0.gemfile +2 -0
  28. data/gemfiles/5.1.gemfile +2 -0
  29. data/gemfiles/5.2.gemfile +2 -0
  30. data/gemfiles/6.0.gemfile +4 -1
  31. data/gemfiles/6.1.gemfile +4 -1
  32. data/gemfiles/7.0.gemfile +4 -0
  33. data/gemfiles/7.1.gemfile +3 -0
  34. data/lib/activerecord-import/active_record/adapters/abstract_adapter.rb +2 -0
  35. data/lib/activerecord-import/active_record/adapters/jdbcmysql_adapter.rb +6 -4
  36. data/lib/activerecord-import/active_record/adapters/jdbcpostgresql_adapter.rb +2 -0
  37. data/lib/activerecord-import/active_record/adapters/jdbcsqlite3_adapter.rb +2 -0
  38. data/lib/activerecord-import/active_record/adapters/mysql2_adapter.rb +2 -0
  39. data/lib/activerecord-import/active_record/adapters/postgresql_adapter.rb +2 -0
  40. data/lib/activerecord-import/active_record/adapters/seamless_database_pool_adapter.rb +2 -0
  41. data/lib/activerecord-import/active_record/adapters/sqlite3_adapter.rb +2 -0
  42. data/lib/activerecord-import/active_record/adapters/trilogy_adapter.rb +8 -0
  43. data/lib/activerecord-import/adapters/abstract_adapter.rb +14 -5
  44. data/lib/activerecord-import/adapters/em_mysql2_adapter.rb +2 -0
  45. data/lib/activerecord-import/adapters/mysql2_adapter.rb +2 -0
  46. data/lib/activerecord-import/adapters/mysql_adapter.rb +33 -25
  47. data/lib/activerecord-import/adapters/postgresql_adapter.rb +69 -56
  48. data/lib/activerecord-import/adapters/sqlite3_adapter.rb +39 -39
  49. data/lib/activerecord-import/adapters/trilogy_adapter.rb +7 -0
  50. data/lib/activerecord-import/base.rb +10 -2
  51. data/lib/activerecord-import/import.rb +162 -65
  52. data/lib/activerecord-import/mysql2.rb +2 -0
  53. data/lib/activerecord-import/postgresql.rb +2 -0
  54. data/lib/activerecord-import/sqlite3.rb +2 -0
  55. data/lib/activerecord-import/synchronize.rb +3 -1
  56. data/lib/activerecord-import/value_sets_parser.rb +5 -0
  57. data/lib/activerecord-import/version.rb +3 -1
  58. data/lib/activerecord-import.rb +2 -1
  59. data/test/adapters/jdbcmysql.rb +2 -0
  60. data/test/adapters/jdbcpostgresql.rb +2 -0
  61. data/test/adapters/jdbcsqlite3.rb +2 -0
  62. data/test/adapters/makara_postgis.rb +2 -0
  63. data/test/adapters/mysql2.rb +2 -0
  64. data/test/adapters/mysql2_makara.rb +2 -0
  65. data/test/adapters/mysql2spatial.rb +2 -0
  66. data/test/adapters/postgis.rb +2 -0
  67. data/test/adapters/postgresql.rb +2 -0
  68. data/test/adapters/postgresql_makara.rb +2 -0
  69. data/test/adapters/seamless_database_pool.rb +2 -0
  70. data/test/adapters/spatialite.rb +2 -0
  71. data/test/adapters/sqlite3.rb +2 -0
  72. data/test/adapters/trilogy.rb +9 -0
  73. data/test/database.yml.sample +7 -0
  74. data/test/{travis → github}/database.yml +7 -1
  75. data/test/import_test.rb +93 -2
  76. data/test/jdbcmysql/import_test.rb +5 -3
  77. data/test/jdbcpostgresql/import_test.rb +4 -2
  78. data/test/jdbcsqlite3/import_test.rb +4 -2
  79. data/test/makara_postgis/import_test.rb +4 -2
  80. data/test/models/account.rb +2 -0
  81. data/test/models/alarm.rb +2 -0
  82. data/test/models/animal.rb +8 -0
  83. data/test/models/author.rb +7 -0
  84. data/test/models/bike_maker.rb +3 -0
  85. data/test/models/book.rb +7 -2
  86. data/test/models/car.rb +2 -0
  87. data/test/models/card.rb +5 -0
  88. data/test/models/chapter.rb +2 -0
  89. data/test/models/composite_book.rb +19 -0
  90. data/test/models/composite_chapter.rb +9 -0
  91. data/test/models/customer.rb +18 -0
  92. data/test/models/deck.rb +8 -0
  93. data/test/models/dictionary.rb +2 -0
  94. data/test/models/discount.rb +2 -0
  95. data/test/models/end_note.rb +2 -0
  96. data/test/models/group.rb +2 -0
  97. data/test/models/order.rb +17 -0
  98. data/test/models/playing_card.rb +4 -0
  99. data/test/models/promotion.rb +2 -0
  100. data/test/models/question.rb +2 -0
  101. data/test/models/rule.rb +2 -0
  102. data/test/models/tag.rb +9 -1
  103. data/test/models/tag_alias.rb +11 -0
  104. data/test/models/topic.rb +7 -0
  105. data/test/models/user.rb +2 -0
  106. data/test/models/user_token.rb +2 -0
  107. data/test/models/vendor.rb +2 -0
  108. data/test/models/widget.rb +2 -0
  109. data/test/mysql2/import_test.rb +5 -3
  110. data/test/mysql2_makara/import_test.rb +5 -3
  111. data/test/mysqlspatial2/import_test.rb +5 -3
  112. data/test/postgis/import_test.rb +4 -2
  113. data/test/postgresql/import_test.rb +4 -2
  114. data/test/schema/generic_schema.rb +37 -1
  115. data/test/schema/jdbcpostgresql_schema.rb +3 -1
  116. data/test/schema/mysql2_schema.rb +2 -0
  117. data/test/schema/postgis_schema.rb +3 -1
  118. data/test/schema/postgresql_schema.rb +47 -0
  119. data/test/schema/sqlite3_schema.rb +2 -0
  120. data/test/schema/version.rb +2 -0
  121. data/test/sqlite3/import_test.rb +4 -2
  122. data/test/support/active_support/test_case_extensions.rb +2 -0
  123. data/test/support/assertions.rb +2 -0
  124. data/test/support/factories.rb +2 -0
  125. data/test/support/generate.rb +4 -2
  126. data/test/support/mysql/import_examples.rb +2 -1
  127. data/test/support/postgresql/import_examples.rb +108 -2
  128. data/test/support/shared_examples/on_duplicate_key_ignore.rb +2 -0
  129. data/test/support/shared_examples/on_duplicate_key_update.rb +78 -9
  130. data/test/support/shared_examples/recursive_import.rb +98 -1
  131. data/test/support/sqlite3/import_examples.rb +2 -1
  132. data/test/synchronize_test.rb +2 -0
  133. data/test/test_helper.rb +33 -6
  134. data/test/trilogy/import_test.rb +7 -0
  135. data/test/value_sets_bytes_parser_test.rb +3 -1
  136. data/test/value_sets_records_parser_test.rb +3 -1
  137. metadata +42 -16
  138. data/.travis.yml +0 -70
  139. data/gemfiles/3.2.gemfile +0 -2
  140. data/gemfiles/4.0.gemfile +0 -2
  141. data/gemfiles/4.1.gemfile +0 -2
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Vendor < ActiveRecord::Base
2
4
  store :preferences, accessors: [:color], coder: JSON
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class CustomCoder
2
4
  def load(value)
3
5
  if value.nil?
@@ -1,5 +1,7 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
- require File.expand_path(File.dirname(__FILE__) + '/../support/assertions')
3
- require File.expand_path(File.dirname(__FILE__) + '/../support/mysql/import_examples')
1
+ # frozen_string_literal: true
2
+
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")
4
6
 
5
7
  should_support_mysql_import_functionality
@@ -1,6 +1,8 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
1
+ # frozen_string_literal: true
2
2
 
3
- require File.expand_path(File.dirname(__FILE__) + '/../support/assertions')
4
- require File.expand_path(File.dirname(__FILE__) + '/../support/mysql/import_examples')
3
+ require File.expand_path("#{File.dirname(__FILE__)}/../test_helper")
4
+
5
+ require File.expand_path("#{File.dirname(__FILE__)}/../support/assertions")
6
+ require File.expand_path("#{File.dirname(__FILE__)}/../support/mysql/import_examples")
5
7
 
6
8
  should_support_mysql_import_functionality
@@ -1,6 +1,8 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
1
+ # frozen_string_literal: true
2
2
 
3
- require File.expand_path(File.dirname(__FILE__) + '/../support/assertions')
4
- require File.expand_path(File.dirname(__FILE__) + '/../support/mysql/import_examples')
3
+ require File.expand_path("#{File.dirname(__FILE__)}/../test_helper")
4
+
5
+ require File.expand_path("#{File.dirname(__FILE__)}/../support/assertions")
6
+ require File.expand_path("#{File.dirname(__FILE__)}/../support/mysql/import_examples")
5
7
 
6
8
  should_support_mysql_import_functionality
@@ -1,5 +1,7 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
- require File.expand_path(File.dirname(__FILE__) + '/../support/postgresql/import_examples')
1
+ # frozen_string_literal: true
2
+
3
+ require File.expand_path("#{File.dirname(__FILE__)}/../test_helper")
4
+ require File.expand_path("#{File.dirname(__FILE__)}/../support/postgresql/import_examples")
3
5
 
4
6
  should_support_postgresql_import_functionality
5
7
 
@@ -1,4 +1,6 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
- require File.expand_path(File.dirname(__FILE__) + '/../support/postgresql/import_examples')
1
+ # frozen_string_literal: true
2
+
3
+ require File.expand_path("#{File.dirname(__FILE__)}/../test_helper")
4
+ require File.expand_path("#{File.dirname(__FILE__)}/../support/postgresql/import_examples")
3
5
 
4
6
  should_support_postgresql_import_functionality
@@ -1,7 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ActiveRecord::Schema.define do
2
4
  create_table :schema_info, force: :cascade do |t|
3
- t.integer :version, unique: true
5
+ t.integer :version
4
6
  end
7
+ add_index :schema_info, :version, unique: true
8
+
5
9
  SchemaInfo.create version: SchemaInfo::VERSION
6
10
 
7
11
  create_table :group, force: :cascade do |t|
@@ -20,6 +24,7 @@ ActiveRecord::Schema.define do
20
24
  t.boolean :approved, default: '1'
21
25
  t.integer :replies_count
22
26
  t.integer :parent_id
27
+ t.integer :priority, default: 0
23
28
  t.string :type
24
29
  t.datetime :created_at
25
30
  t.datetime :created_on
@@ -52,6 +57,20 @@ ActiveRecord::Schema.define do
52
57
  t.string :name
53
58
  end
54
59
 
60
+ create_table :cards, force: :cascade do |t|
61
+ t.string :name
62
+ t.string :deck_type
63
+ t.integer :deck_id
64
+ end
65
+
66
+ create_table :decks, force: :cascade do |t|
67
+ t.string :name
68
+ end
69
+
70
+ create_table :playing_cards, force: :cascade do |t|
71
+ t.string :name
72
+ end
73
+
55
74
  create_table :books, force: :cascade do |t|
56
75
  t.string :title, null: false
57
76
  t.string :publisher, null: false, default: 'Default Publisher'
@@ -190,5 +209,22 @@ ActiveRecord::Schema.define do
190
209
  PRIMARY KEY (tag_id, publisher_id)
191
210
  );
192
211
  ).split.join(' ').strip
212
+
213
+ create_table :tag_aliases, force: :cascade do |t|
214
+ t.integer :tag_id, null: false
215
+ t.integer :parent_id, null: false
216
+ t.string :alias, null: false
217
+ end
218
+ end
219
+
220
+ create_table :customers, force: :cascade do |t|
221
+ t.integer :account_id
222
+ t.string :name
223
+ end
224
+
225
+ create_table :orders, force: :cascade do |t|
226
+ t.integer :account_id
227
+ t.integer :customer_id
228
+ t.integer :amount
193
229
  end
194
230
  end
@@ -1 +1,3 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/postgresql_schema')
1
+ # frozen_string_literal: true
2
+
3
+ require File.expand_path("#{File.dirname(__FILE__)}/postgresql_schema")
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ActiveRecord::Schema.define do
2
4
  create_table :books, force: :cascade do |t|
3
5
  t.string :title, null: false
@@ -1 +1,3 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/postgresql_schema')
1
+ # frozen_string_literal: true
2
+
3
+ require File.expand_path("#{File.dirname(__FILE__)}/postgresql_schema")
@@ -1,10 +1,21 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ActiveRecord::Schema.define do
2
4
  execute('CREATE extension IF NOT EXISTS "hstore";')
3
5
  execute('CREATE extension IF NOT EXISTS "pgcrypto";')
4
6
  execute('CREATE extension IF NOT EXISTS "uuid-ossp";')
5
7
 
8
+ # create ENUM if it does not exist yet
9
+ begin
10
+ execute('CREATE TYPE vendor_type AS ENUM (\'wholesaler\', \'retailer\');')
11
+ rescue ActiveRecord::StatementInvalid
12
+ execute('ALTER TYPE vendor_type ADD VALUE IF NOT EXISTS \'wholesaler\';')
13
+ execute('ALTER TYPE vendor_type ADD VALUE IF NOT EXISTS \'retailer\';')
14
+ end
15
+
6
16
  create_table :vendors, id: :uuid, force: :cascade do |t|
7
17
  t.string :name, null: true
18
+ t.text :hours
8
19
  t.text :preferences
9
20
 
10
21
  if t.respond_to?(:json)
@@ -29,6 +40,8 @@ ActiveRecord::Schema.define do
29
40
  t.text :json_data
30
41
  end
31
42
 
43
+ t.column :vendor_type, :vendor_type
44
+
32
45
  t.datetime :created_at
33
46
  t.datetime :updated_at
34
47
  end
@@ -44,4 +57,38 @@ ActiveRecord::Schema.define do
44
57
  end
45
58
 
46
59
  add_index :alarms, [:device_id, :alarm_type], unique: true, where: 'status <> 0'
60
+
61
+ unless ENV["SKIP_COMPOSITE_PK"]
62
+ create_table :authors, force: :cascade do |t|
63
+ t.string :name
64
+ end
65
+
66
+ execute %(
67
+ DROP SEQUENCE IF EXISTS composite_book_id_seq CASCADE;
68
+ CREATE SEQUENCE composite_book_id_seq
69
+ AS integer
70
+ START WITH 1
71
+ INCREMENT BY 1
72
+ NO MINVALUE
73
+ NO MAXVALUE
74
+ CACHE 1;
75
+
76
+ DROP TABLE IF EXISTS composite_books;
77
+ CREATE TABLE composite_books (
78
+ id bigint DEFAULT nextval('composite_book_id_seq'::regclass) NOT NULL,
79
+ title character varying,
80
+ author_id bigint
81
+ );
82
+
83
+ ALTER TABLE ONLY composite_books ADD CONSTRAINT fk_rails_040a418131 FOREIGN KEY (author_id) REFERENCES authors(id);
84
+ ).split.join(' ').strip
85
+ end
86
+
87
+ create_table :composite_chapters, force: :cascade do |t|
88
+ t.string :title
89
+ t.integer :composite_book_id, null: false
90
+ t.integer :author_id, null: false
91
+ t.datetime :created_at
92
+ t.datetime :updated_at
93
+ end
47
94
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ActiveRecord::Schema.define do
2
4
  create_table :alarms, force: true do |t|
3
5
  t.column :device_id, :integer, null: false
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class SchemaInfo < ActiveRecord::Base
2
4
  if respond_to?(:table_name=)
3
5
  self.table_name = 'schema_info'
@@ -1,4 +1,6 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
- require File.expand_path(File.dirname(__FILE__) + '/../support/sqlite3/import_examples')
1
+ # frozen_string_literal: true
2
+
3
+ require File.expand_path("#{File.dirname(__FILE__)}/../test_helper")
4
+ require File.expand_path("#{File.dirname(__FILE__)}/../support/sqlite3/import_examples")
3
5
 
4
6
  should_support_sqlite3_import_functionality
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class ActiveSupport::TestCase
2
4
  include ActiveRecord::TestFixtures
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class ActiveSupport::TestCase
2
4
  module ImportAssertions
3
5
  def self.extended(klass)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  FactoryBot.define do
2
4
  sequence(:book_title) { |n| "Book #{n}" }
3
5
  sequence(:chapter_title) { |n| "Chapter #{n}" }
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class ActiveSupport::TestCase
2
- def Build(*args) # rubocop:disable Style/MethodName
4
+ def Build(*args) # rubocop:disable Naming/MethodName
3
5
  n = args.shift if args.first.is_a?(Numeric)
4
6
  factory = args.shift
5
7
  factory_bot_args = args.shift || {}
@@ -13,7 +15,7 @@ class ActiveSupport::TestCase
13
15
  end
14
16
  end
15
17
 
16
- def Generate(*args) # rubocop:disable Style/MethodName
18
+ def Generate(*args) # rubocop:disable Naming/MethodName
17
19
  n = args.shift if args.first.is_a?(Numeric)
18
20
  factory = args.shift
19
21
  factory_bot_args = args.shift || {}
@@ -1,4 +1,5 @@
1
- # encoding: UTF-8
1
+ # frozen_string_literal: true
2
+
2
3
  def should_support_mysql_import_functionality
3
4
  # Forcefully disable strict mode for this session.
4
5
  ActiveRecord::Base.connection.execute "set sql_mode='STRICT_ALL_TABLES'"
@@ -1,4 +1,5 @@
1
- # encoding: UTF-8
1
+ # frozen_string_literal: true
2
+
2
3
  def should_support_postgresql_import_functionality
3
4
  should_support_recursive_import
4
5
 
@@ -102,6 +103,8 @@ def should_support_postgresql_import_functionality
102
103
  books.first.id.to_s
103
104
  end
104
105
  end
106
+ let(:true_returning_value) { ENV['AR_VERSION'].to_f >= 5.0 ? true : 't' }
107
+ let(:false_returning_value) { ENV['AR_VERSION'].to_f >= 5.0 ? false : 'f' }
105
108
 
106
109
  it "creates records" do
107
110
  assert_difference("Book.count", +1) { result }
@@ -116,6 +119,26 @@ def should_support_postgresql_import_functionality
116
119
  assert_equal [%w(King It)], result.results
117
120
  end
118
121
 
122
+ context "when given an empty array" do
123
+ let(:result) { Book.import([], returning: %w(title)) }
124
+
125
+ setup { result }
126
+
127
+ it "returns empty arrays for ids and results" do
128
+ assert_equal [], result.ids
129
+ assert_equal [], result.results
130
+ end
131
+ end
132
+
133
+ context "when a returning column is a serialized attribute" do
134
+ let(:vendor) { Vendor.new(hours: { monday: '8-5' }) }
135
+ let(:result) { Vendor.import([vendor], returning: %w(hours)) }
136
+
137
+ it "creates records" do
138
+ assert_difference("Vendor.count", +1) { result }
139
+ end
140
+ end
141
+
119
142
  context "when primary key and returning overlap" do
120
143
  let(:result) { Book.import(books, returning: %w(id title)) }
121
144
 
@@ -130,6 +153,34 @@ def should_support_postgresql_import_functionality
130
153
  end
131
154
  end
132
155
 
156
+ context "when returning is raw sql" do
157
+ let(:result) { Book.import(books, returning: "title, (xmax = '0') AS inserted") }
158
+
159
+ setup { result }
160
+
161
+ it "returns ids" do
162
+ assert_equal [book_id], result.ids
163
+ end
164
+
165
+ it "returns specified columns" do
166
+ assert_equal [['It', true_returning_value]], result.results
167
+ end
168
+ end
169
+
170
+ context "when returning contains raw sql" do
171
+ let(:result) { Book.import(books, returning: [:title, "id, (xmax = '0') AS inserted"]) }
172
+
173
+ setup { result }
174
+
175
+ it "returns ids" do
176
+ assert_equal [book_id], result.ids
177
+ end
178
+
179
+ it "returns specified columns" do
180
+ assert_equal [['It', book_id, true_returning_value]], result.results
181
+ end
182
+ end
183
+
133
184
  context "setting model attributes" do
134
185
  let(:code) { 'abc' }
135
186
  let(:discount) { 0.10 }
@@ -159,6 +210,14 @@ def should_support_postgresql_import_functionality
159
210
  assert_equal updated_promotion.discount, discount
160
211
  end
161
212
  end
213
+
214
+ context 'returning raw sql' do
215
+ let(:returning_columns) { [:discount, "(xmax = '0') AS inserted"] }
216
+
217
+ it "sets custom model attributes" do
218
+ assert_equal updated_promotion.inserted, false_returning_value
219
+ end
220
+ end
162
221
  end
163
222
  end
164
223
  end
@@ -249,8 +308,19 @@ def should_support_postgresql_import_functionality
249
308
  end
250
309
  end
251
310
 
311
+ describe "with enum field" do
312
+ let(:vendor_type) { "retailer" }
313
+ it "imports the correct values for enum fields" do
314
+ vendor = Vendor.new(name: 'Vendor 1', vendor_type: vendor_type)
315
+ assert_difference "Vendor.count", +1 do
316
+ Vendor.import [vendor]
317
+ end
318
+ assert_equal(vendor_type, Vendor.first.vendor_type)
319
+ end
320
+ end
321
+
252
322
  describe "with binary field" do
253
- let(:binary_value) { "\xE0'c\xB2\xB0\xB3Bh\\\xC2M\xB1m\\I\xC4r".force_encoding('ASCII-8BIT') }
323
+ let(:binary_value) { "\xE0'c\xB2\xB0\xB3Bh\\\xC2M\xB1m\\I\xC4r".dup.force_encoding('ASCII-8BIT') }
254
324
  it "imports the correct values for binary fields" do
255
325
  alarms = [Alarm.new(device_id: 1, alarm_type: 1, status: 1, secret_key: binary_value)]
256
326
  assert_difference "Alarm.count", +1 do
@@ -259,6 +329,42 @@ def should_support_postgresql_import_functionality
259
329
  assert_equal(binary_value, Alarm.first.secret_key)
260
330
  end
261
331
  end
332
+
333
+ unless ENV["SKIP_COMPOSITE_PK"]
334
+ describe "with composite foreign keys" do
335
+ let(:account_id) { 555 }
336
+ let(:customer) { Customer.new(account_id: account_id, name: "foo") }
337
+ let(:order) { Order.new(account_id: account_id, amount: 100, customer: customer) }
338
+
339
+ it "imports and correctly maps foreign keys" do
340
+ assert_difference "Customer.count", +1 do
341
+ Customer.import [customer]
342
+ end
343
+
344
+ assert_difference "Order.count", +1 do
345
+ Order.import [order]
346
+ end
347
+
348
+ db_customer = Customer.last
349
+ db_order = Order.last
350
+
351
+ assert_equal db_customer.orders.last, db_order
352
+ assert_not_equal db_order.customer_id, nil
353
+ end
354
+
355
+ it "should import models with auto-incrementing ID successfully" do
356
+ author = Author.create!(name: "Foo Barson")
357
+
358
+ books = []
359
+ 2.times do |i|
360
+ books << CompositeBook.new(author_id: author.id, title: "book #{i}")
361
+ end
362
+ assert_difference "CompositeBook.count", +2 do
363
+ CompositeBook.import books
364
+ end
365
+ end
366
+ end
367
+ end
262
368
  end
263
369
 
264
370
  def should_support_postgresql_upsert_functionality
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  def should_support_on_duplicate_key_ignore
2
4
  describe "#import" do
3
5
  extend ActiveSupport::TestCase::ImportAssertions
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  def should_support_basic_on_duplicate_key_update
2
4
  describe "#import" do
3
5
  extend ActiveSupport::TestCase::ImportAssertions
@@ -24,7 +26,7 @@ def should_support_basic_on_duplicate_key_update
24
26
  User.import(updated_users, on_duplicate_key_update: [:name])
25
27
  assert User.count == updated_users.length
26
28
  User.all.each_with_index do |user, i|
27
- assert_equal user.name, users[i].name + ' Rothschild'
29
+ assert_equal user.name, "#{users[i].name} Rothschild"
28
30
  assert_equal 1, user.lock_version
29
31
  end
30
32
  end
@@ -48,7 +50,7 @@ def should_support_basic_on_duplicate_key_update
48
50
  User.import(columns, updated_values, on_duplicate_key_update: [:name])
49
51
  assert User.count == updated_values.length
50
52
  User.all.each_with_index do |user, i|
51
- assert_equal user.name, users[i].name + ' Rothschild'
53
+ assert_equal user.name, "#{users[i].name} Rothschild"
52
54
  assert_equal 1, user.lock_version
53
55
  end
54
56
  end
@@ -70,9 +72,19 @@ def should_support_basic_on_duplicate_key_update
70
72
  User.import(updated_values, on_duplicate_key_update: [:name])
71
73
  assert User.count == updated_values.length
72
74
  User.all.each_with_index do |user, i|
73
- assert_equal user.name, users[i].name + ' Rothschild'
75
+ assert_equal user.name, "#{users[i].name} Rothschild"
74
76
  assert_equal 1, user.lock_version
75
77
  end
78
+ updated_values2 = User.all.map do |user|
79
+ user.name += ' jr.'
80
+ { id: user.id, name: user.name }
81
+ end
82
+ User.import(updated_values2, on_duplicate_key_update: [:name])
83
+ assert User.count == updated_values2.length
84
+ User.all.each_with_index do |user, i|
85
+ assert_equal user.name, "#{users[i].name} Rothschild jr."
86
+ assert_equal 2, user.lock_version
87
+ end
76
88
  end
77
89
 
78
90
  it 'upsert optimistic lock columns other than lock_version by model' do
@@ -92,7 +104,7 @@ def should_support_basic_on_duplicate_key_update
92
104
  Account.import(updated_accounts, on_duplicate_key_update: [:id, :name])
93
105
  assert Account.count == updated_accounts.length
94
106
  Account.all.each_with_index do |user, i|
95
- assert_equal user.name, accounts[i].name + ' Rothschild'
107
+ assert_equal user.name, "#{accounts[i].name} Rothschild"
96
108
  assert_equal 1, user.lock
97
109
  end
98
110
  end
@@ -116,7 +128,7 @@ def should_support_basic_on_duplicate_key_update
116
128
  Account.import(columns, updated_values, on_duplicate_key_update: [:name])
117
129
  assert Account.count == updated_values.length
118
130
  Account.all.each_with_index do |user, i|
119
- assert_equal user.name, accounts[i].name + ' Rothschild'
131
+ assert_equal user.name, "#{accounts[i].name} Rothschild"
120
132
  assert_equal 1, user.lock
121
133
  end
122
134
  end
@@ -138,7 +150,7 @@ def should_support_basic_on_duplicate_key_update
138
150
  Account.import(updated_values, on_duplicate_key_update: [:name])
139
151
  assert Account.count == updated_values.length
140
152
  Account.all.each_with_index do |user, i|
141
- assert_equal user.name, accounts[i].name + ' Rothschild'
153
+ assert_equal user.name, "#{accounts[i].name} Rothschild"
142
154
  assert_equal 1, user.lock
143
155
  end
144
156
  end
@@ -160,10 +172,11 @@ def should_support_basic_on_duplicate_key_update
160
172
  Bike::Maker.import(updated_makers, on_duplicate_key_update: [:name])
161
173
  assert Bike::Maker.count == updated_makers.length
162
174
  Bike::Maker.all.each_with_index do |maker, i|
163
- assert_equal maker.name, makers[i].name + ' bikes'
175
+ assert_equal maker.name, "#{makers[i].name} bikes"
164
176
  assert_equal 1, maker.lock_version
165
177
  end
166
178
  end
179
+
167
180
  it 'update the lock_version of models separated by namespaces by array' do
168
181
  makers = [
169
182
  Bike::Maker.new(name: 'Yamaha'),
@@ -183,7 +196,7 @@ def should_support_basic_on_duplicate_key_update
183
196
  Bike::Maker.import(columns, updated_values, on_duplicate_key_update: [:name])
184
197
  assert Bike::Maker.count == updated_values.length
185
198
  Bike::Maker.all.each_with_index do |maker, i|
186
- assert_equal maker.name, makers[i].name + ' bikes'
199
+ assert_equal maker.name, "#{makers[i].name} bikes"
187
200
  assert_equal 1, maker.lock_version
188
201
  end
189
202
  end
@@ -205,11 +218,39 @@ def should_support_basic_on_duplicate_key_update
205
218
  Bike::Maker.import(updated_values, on_duplicate_key_update: [:name])
206
219
  assert Bike::Maker.count == updated_values.length
207
220
  Bike::Maker.all.each_with_index do |maker, i|
208
- assert_equal maker.name, makers[i].name + ' bikes'
221
+ assert_equal maker.name, "#{makers[i].name} bikes"
209
222
  assert_equal 1, maker.lock_version
210
223
  end
211
224
  end
212
225
  end
226
+
227
+ context 'with locking disabled' do
228
+ it 'does not update the lock_version' do
229
+ users = [
230
+ User.new(name: 'Salomon'),
231
+ User.new(name: 'Nathan')
232
+ ]
233
+ User.import(users)
234
+ assert User.count == users.length
235
+ User.all.each do |user|
236
+ assert_equal 0, user.lock_version
237
+ end
238
+ updated_users = User.all.map do |user|
239
+ user.name += ' Rothschild'
240
+ user
241
+ end
242
+
243
+ ActiveRecord::Base.lock_optimistically = false # Disable locking
244
+ User.import(updated_users, on_duplicate_key_update: [:name])
245
+ ActiveRecord::Base.lock_optimistically = true # Enable locking
246
+
247
+ assert User.count == updated_users.length
248
+ User.all.each_with_index do |user, i|
249
+ assert_equal user.name, "#{users[i].name} Rothschild"
250
+ assert_equal 0, user.lock_version
251
+ end
252
+ end
253
+ end
213
254
  end
214
255
 
215
256
  context "with :on_duplicate_key_update" do
@@ -304,6 +345,34 @@ def should_support_basic_on_duplicate_key_update
304
345
  should_support_on_duplicate_key_update
305
346
  should_update_fields_mentioned
306
347
  end
348
+
349
+ context "using column aliases" do
350
+ let(:columns) { %w( id title author_name author_email_address parent_id ) }
351
+ let(:update_columns) { %w(title author_email_address parent_id) }
352
+
353
+ context "with column aliases in column list" do
354
+ let(:columns) { %w( id name author_name author_email_address parent_id ) }
355
+ should_support_on_duplicate_key_update
356
+ should_update_fields_mentioned
357
+ end
358
+
359
+ context "with column aliases in update columns list" do
360
+ let(:update_columns) { %w(name author_email_address parent_id) }
361
+ should_support_on_duplicate_key_update
362
+ should_update_fields_mentioned
363
+ end
364
+ end
365
+
366
+ if ENV['AR_VERSION'].to_i >= 6.0
367
+ context "using ignored columns" do
368
+ let(:columns) { %w( id title author_name author_email_address parent_id priority ) }
369
+ let(:values) { [[99, "Book", "John Doe", "john@doe.com", 17, 1]] }
370
+ let(:update_columns) { %w(name author_email_address parent_id priority) }
371
+ let(:updated_values) { [[99, "Book - 2nd Edition", "Author Should Not Change", "johndoe@example.com", 57, 2]] }
372
+ should_support_on_duplicate_key_update
373
+ should_update_fields_mentioned
374
+ end
375
+ end
307
376
  end
308
377
 
309
378
  context "with a table that has a non-standard primary key" do