activerecord-import 0.22.0 → 1.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (130) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/test.yaml +107 -0
  3. data/.gitignore +1 -0
  4. data/.rubocop.yml +74 -8
  5. data/Brewfile +3 -1
  6. data/CHANGELOG.md +235 -3
  7. data/Gemfile +22 -15
  8. data/LICENSE +21 -56
  9. data/README.markdown +574 -22
  10. data/Rakefile +4 -1
  11. data/activerecord-import.gemspec +6 -5
  12. data/benchmarks/benchmark.rb +7 -1
  13. data/benchmarks/lib/base.rb +2 -0
  14. data/benchmarks/lib/cli_parser.rb +3 -1
  15. data/benchmarks/lib/float.rb +2 -0
  16. data/benchmarks/lib/mysql2_benchmark.rb +2 -0
  17. data/benchmarks/lib/output_to_csv.rb +2 -0
  18. data/benchmarks/lib/output_to_html.rb +4 -2
  19. data/benchmarks/models/test_innodb.rb +2 -0
  20. data/benchmarks/models/test_memory.rb +2 -0
  21. data/benchmarks/models/test_myisam.rb +2 -0
  22. data/benchmarks/schema/{mysql_schema.rb → mysql2_schema.rb} +2 -0
  23. data/gemfiles/4.2.gemfile +2 -0
  24. data/gemfiles/5.0.gemfile +2 -0
  25. data/gemfiles/5.1.gemfile +2 -0
  26. data/gemfiles/5.2.gemfile +4 -0
  27. data/gemfiles/6.0.gemfile +4 -0
  28. data/gemfiles/6.1.gemfile +4 -0
  29. data/gemfiles/7.0.gemfile +4 -0
  30. data/lib/activerecord-import/active_record/adapters/abstract_adapter.rb +2 -0
  31. data/lib/activerecord-import/active_record/adapters/jdbcmysql_adapter.rb +6 -4
  32. data/lib/activerecord-import/active_record/adapters/jdbcpostgresql_adapter.rb +2 -0
  33. data/lib/activerecord-import/active_record/adapters/jdbcsqlite3_adapter.rb +2 -0
  34. data/lib/activerecord-import/active_record/adapters/mysql2_adapter.rb +2 -0
  35. data/lib/activerecord-import/active_record/adapters/postgresql_adapter.rb +2 -0
  36. data/lib/activerecord-import/active_record/adapters/seamless_database_pool_adapter.rb +2 -0
  37. data/lib/activerecord-import/active_record/adapters/sqlite3_adapter.rb +2 -0
  38. data/lib/activerecord-import/adapters/abstract_adapter.rb +10 -2
  39. data/lib/activerecord-import/adapters/em_mysql2_adapter.rb +2 -0
  40. data/lib/activerecord-import/adapters/mysql2_adapter.rb +2 -0
  41. data/lib/activerecord-import/adapters/mysql_adapter.rb +19 -11
  42. data/lib/activerecord-import/adapters/postgresql_adapter.rb +56 -37
  43. data/lib/activerecord-import/adapters/sqlite3_adapter.rb +128 -9
  44. data/lib/activerecord-import/base.rb +12 -2
  45. data/lib/activerecord-import/import.rb +300 -136
  46. data/lib/activerecord-import/mysql2.rb +2 -0
  47. data/lib/activerecord-import/postgresql.rb +2 -0
  48. data/lib/activerecord-import/sqlite3.rb +2 -0
  49. data/lib/activerecord-import/synchronize.rb +4 -2
  50. data/lib/activerecord-import/value_sets_parser.rb +4 -0
  51. data/lib/activerecord-import/version.rb +3 -1
  52. data/lib/activerecord-import.rb +4 -1
  53. data/test/adapters/jdbcmysql.rb +2 -0
  54. data/test/adapters/jdbcpostgresql.rb +2 -0
  55. data/test/adapters/jdbcsqlite3.rb +2 -0
  56. data/test/adapters/makara_postgis.rb +3 -0
  57. data/test/adapters/mysql2.rb +2 -0
  58. data/test/adapters/mysql2_makara.rb +2 -0
  59. data/test/adapters/mysql2spatial.rb +2 -0
  60. data/test/adapters/postgis.rb +2 -0
  61. data/test/adapters/postgresql.rb +2 -0
  62. data/test/adapters/postgresql_makara.rb +2 -0
  63. data/test/adapters/seamless_database_pool.rb +2 -0
  64. data/test/adapters/spatialite.rb +2 -0
  65. data/test/adapters/sqlite3.rb +2 -0
  66. data/test/{travis → github}/database.yml +3 -1
  67. data/test/import_test.rb +159 -8
  68. data/test/jdbcmysql/import_test.rb +2 -0
  69. data/test/jdbcpostgresql/import_test.rb +2 -0
  70. data/test/jdbcsqlite3/import_test.rb +2 -0
  71. data/test/makara_postgis/import_test.rb +10 -0
  72. data/test/models/account.rb +5 -0
  73. data/test/models/alarm.rb +2 -0
  74. data/test/models/animal.rb +8 -0
  75. data/test/models/bike_maker.rb +9 -0
  76. data/test/models/book.rb +2 -0
  77. data/test/models/car.rb +2 -0
  78. data/test/models/card.rb +5 -0
  79. data/test/models/chapter.rb +2 -0
  80. data/test/models/customer.rb +8 -0
  81. data/test/models/deck.rb +8 -0
  82. data/test/models/dictionary.rb +2 -0
  83. data/test/models/discount.rb +2 -0
  84. data/test/models/end_note.rb +2 -0
  85. data/test/models/group.rb +2 -0
  86. data/test/models/order.rb +8 -0
  87. data/test/models/playing_card.rb +4 -0
  88. data/test/models/promotion.rb +2 -0
  89. data/test/models/question.rb +2 -0
  90. data/test/models/rule.rb +2 -0
  91. data/test/models/tag.rb +3 -0
  92. data/test/models/tag_alias.rb +5 -0
  93. data/test/models/topic.rb +2 -0
  94. data/test/models/user.rb +5 -0
  95. data/test/models/user_token.rb +6 -0
  96. data/test/models/vendor.rb +2 -0
  97. data/test/models/widget.rb +2 -0
  98. data/test/mysql2/import_test.rb +2 -0
  99. data/test/mysql2_makara/import_test.rb +2 -0
  100. data/test/mysqlspatial2/import_test.rb +2 -0
  101. data/test/postgis/import_test.rb +2 -0
  102. data/test/postgresql/import_test.rb +2 -0
  103. data/test/schema/generic_schema.rb +53 -0
  104. data/test/schema/jdbcpostgresql_schema.rb +2 -0
  105. data/test/schema/mysql2_schema.rb +21 -0
  106. data/test/schema/postgis_schema.rb +2 -0
  107. data/test/schema/postgresql_schema.rb +18 -0
  108. data/test/schema/sqlite3_schema.rb +15 -0
  109. data/test/schema/version.rb +2 -0
  110. data/test/sqlite3/import_test.rb +2 -0
  111. data/test/support/active_support/test_case_extensions.rb +2 -0
  112. data/test/support/assertions.rb +2 -0
  113. data/test/support/factories.rb +10 -8
  114. data/test/support/generate.rb +10 -8
  115. data/test/support/mysql/import_examples.rb +14 -1
  116. data/test/support/postgresql/import_examples.rb +140 -3
  117. data/test/support/shared_examples/on_duplicate_key_ignore.rb +2 -0
  118. data/test/support/shared_examples/on_duplicate_key_update.rb +263 -0
  119. data/test/support/shared_examples/recursive_import.rb +76 -4
  120. data/test/support/sqlite3/import_examples.rb +191 -26
  121. data/test/synchronize_test.rb +2 -0
  122. data/test/test_helper.rb +36 -3
  123. data/test/value_sets_bytes_parser_test.rb +2 -0
  124. data/test/value_sets_records_parser_test.rb +2 -0
  125. metadata +46 -18
  126. data/.travis.yml +0 -61
  127. data/gemfiles/3.2.gemfile +0 -2
  128. data/gemfiles/4.0.gemfile +0 -2
  129. data/gemfiles/4.1.gemfile +0 -2
  130. data/test/schema/mysql_schema.rb +0 -16
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
4
 
3
5
  require File.expand_path(File.dirname(__FILE__) + '/../support/assertions')
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
4
 
3
5
  require File.expand_path(File.dirname(__FILE__) + '/../support/assertions')
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
4
  require File.expand_path(File.dirname(__FILE__) + '/../support/postgresql/import_examples')
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
4
  require File.expand_path(File.dirname(__FILE__) + '/../support/postgresql/import_examples')
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ActiveRecord::Schema.define do
2
4
  create_table :schema_info, force: :cascade do |t|
3
5
  t.integer :version, unique: true
@@ -52,6 +54,20 @@ ActiveRecord::Schema.define do
52
54
  t.string :name
53
55
  end
54
56
 
57
+ create_table :cards, force: :cascade do |t|
58
+ t.string :name
59
+ t.string :deck_type
60
+ t.integer :deck_id
61
+ end
62
+
63
+ create_table :decks, force: :cascade do |t|
64
+ t.string :name
65
+ end
66
+
67
+ create_table :playing_cards, force: :cascade do |t|
68
+ t.string :name
69
+ end
70
+
55
71
  create_table :books, force: :cascade do |t|
56
72
  t.string :title, null: false
57
73
  t.string :publisher, null: false, default: 'Default Publisher'
@@ -159,6 +175,26 @@ ActiveRecord::Schema.define do
159
175
  t.string :Features
160
176
  end
161
177
 
178
+ create_table :users, force: :cascade do |t|
179
+ t.string :name, null: false
180
+ t.integer :lock_version, null: false, default: 0
181
+ end
182
+
183
+ create_table :user_tokens, force: :cascade do |t|
184
+ t.string :user_name, null: false
185
+ t.string :token, null: false
186
+ end
187
+
188
+ create_table :accounts, force: :cascade do |t|
189
+ t.string :name, null: false
190
+ t.integer :lock, null: false, default: 0
191
+ end
192
+
193
+ create_table :bike_makers, force: :cascade do |t|
194
+ t.string :name, null: false
195
+ t.integer :lock_version, null: false, default: 0
196
+ end
197
+
162
198
  add_index :cars, :Name, unique: true
163
199
 
164
200
  unless ENV["SKIP_COMPOSITE_PK"]
@@ -170,5 +206,22 @@ ActiveRecord::Schema.define do
170
206
  PRIMARY KEY (tag_id, publisher_id)
171
207
  );
172
208
  ).split.join(' ').strip
209
+
210
+ create_table :tag_aliases, force: :cascade do |t|
211
+ t.integer :tag_id, null: false
212
+ t.integer :parent_id, null: false
213
+ t.string :alias, null: false
214
+ end
215
+ end
216
+
217
+ create_table :customers, force: :cascade do |t|
218
+ t.integer :account_id
219
+ t.string :name
220
+ end
221
+
222
+ create_table :orders, force: :cascade do |t|
223
+ t.integer :account_id
224
+ t.integer :customer_id
225
+ t.integer :amount
173
226
  end
174
227
  end
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require File.expand_path(File.dirname(__FILE__) + '/postgresql_schema')
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ ActiveRecord::Schema.define do
4
+ create_table :books, force: :cascade do |t|
5
+ t.string :title, null: false
6
+ t.virtual :upper_title, type: :string, as: "upper(`title`)" if t.respond_to?(:virtual)
7
+ t.string :publisher, null: false, default: 'Default Publisher'
8
+ t.string :author_name, null: false
9
+ t.datetime :created_at
10
+ t.datetime :created_on
11
+ t.datetime :updated_at
12
+ t.datetime :updated_on
13
+ t.date :publish_date
14
+ t.integer :topic_id
15
+ t.integer :tag_id
16
+ t.integer :publisher_id
17
+ t.boolean :for_sale, default: true
18
+ t.integer :status, default: 0
19
+ t.string :type
20
+ end
21
+ end
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require File.expand_path(File.dirname(__FILE__) + '/postgresql_schema')
@@ -1,13 +1,28 @@
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 => e
12
+ # since PostgreSQL does not support IF NOT EXISTS when creating a TYPE,
13
+ # rescue the error and check the error class
14
+ raise unless e.cause.is_a? PG::DuplicateObject
15
+ execute('ALTER TYPE vendor_type ADD VALUE IF NOT EXISTS \'wholesaler\';')
16
+ execute('ALTER TYPE vendor_type ADD VALUE IF NOT EXISTS \'retailer\';')
17
+ end
18
+
6
19
  create_table :vendors, id: :uuid, force: :cascade do |t|
7
20
  t.string :name, null: true
21
+ t.text :hours
8
22
  t.text :preferences
9
23
 
10
24
  if t.respond_to?(:json)
25
+ t.json :pure_json_data
11
26
  t.json :data
12
27
  else
13
28
  t.text :data
@@ -20,6 +35,7 @@ ActiveRecord::Schema.define do
20
35
  end
21
36
 
22
37
  if t.respond_to?(:jsonb)
38
+ t.jsonb :pure_jsonb_data
23
39
  t.jsonb :settings
24
40
  t.jsonb :json_data, null: false, default: {}
25
41
  else
@@ -27,6 +43,8 @@ ActiveRecord::Schema.define do
27
43
  t.text :json_data
28
44
  end
29
45
 
46
+ t.column :vendor_type, :vendor_type
47
+
30
48
  t.datetime :created_at
31
49
  t.datetime :updated_at
32
50
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ ActiveRecord::Schema.define do
4
+ create_table :alarms, force: true do |t|
5
+ t.column :device_id, :integer, null: false
6
+ t.column :alarm_type, :integer, null: false
7
+ t.column :status, :integer, null: false
8
+ t.column :metadata, :text
9
+ t.column :secret_key, :binary
10
+ t.datetime :created_at
11
+ t.datetime :updated_at
12
+ end
13
+
14
+ add_index :alarms, [:device_id, :alarm_type], unique: true, where: 'status <> 0'
15
+ end
@@ -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,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
4
  require File.expand_path(File.dirname(__FILE__) + '/../support/sqlite3/import_examples')
3
5
 
@@ -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,4 +1,6 @@
1
- FactoryGirl.define do
1
+ # frozen_string_literal: true
2
+
3
+ FactoryBot.define do
2
4
  sequence(:book_title) { |n| "Book #{n}" }
3
5
  sequence(:chapter_title) { |n| "Chapter #{n}" }
4
6
  sequence(:end_note) { |n| "Endnote #{n}" }
@@ -9,7 +11,7 @@ FactoryGirl.define do
9
11
 
10
12
  factory :invalid_topic, class: "Topic" do
11
13
  sequence(:title) { |n| "Title #{n}" }
12
- author_name nil
14
+ author_name { nil }
13
15
  end
14
16
 
15
17
  factory :topic do
@@ -27,7 +29,7 @@ FactoryGirl.define do
27
29
 
28
30
  trait :with_rule do
29
31
  after(:build) do |question|
30
- question.build_rule(FactoryGirl.attributes_for(:rule))
32
+ question.build_rule(FactoryBot.attributes_for(:rule))
31
33
  end
32
34
  end
33
35
  end
@@ -40,21 +42,21 @@ FactoryGirl.define do
40
42
  factory :topic_with_book, parent: :topic do
41
43
  after(:build) do |topic|
42
44
  2.times do
43
- book = topic.books.build(title: FactoryGirl.generate(:book_title), author_name: 'Stephen King')
45
+ book = topic.books.build(title: FactoryBot.generate(:book_title), author_name: 'Stephen King')
44
46
  3.times do
45
- book.chapters.build(title: FactoryGirl.generate(:chapter_title))
47
+ book.chapters.build(title: FactoryBot.generate(:chapter_title))
46
48
  end
47
49
 
48
50
  4.times do
49
- book.end_notes.build(note: FactoryGirl.generate(:end_note))
51
+ book.end_notes.build(note: FactoryBot.generate(:end_note))
50
52
  end
51
53
  end
52
54
  end
53
55
  end
54
56
 
55
57
  factory :book do
56
- title 'Tortilla Flat'
57
- author_name 'John Steinbeck'
58
+ title { 'Tortilla Flat' }
59
+ author_name { 'John Steinbeck' }
58
60
  end
59
61
 
60
62
  factory :car do
@@ -1,29 +1,31 @@
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
- factory_girl_args = args.shift || {}
7
+ factory_bot_args = args.shift || {}
6
8
 
7
9
  if n
8
10
  [].tap do |collection|
9
- n.times.each { collection << FactoryGirl.build(factory.to_s.singularize.to_sym, factory_girl_args) }
11
+ n.times.each { collection << FactoryBot.build(factory.to_s.singularize.to_sym, factory_bot_args) }
10
12
  end
11
13
  else
12
- FactoryGirl.build(factory.to_s.singularize.to_sym, factory_girl_args)
14
+ FactoryBot.build(factory.to_s.singularize.to_sym, factory_bot_args)
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
- factory_girl_args = args.shift || {}
21
+ factory_bot_args = args.shift || {}
20
22
 
21
23
  if n
22
24
  [].tap do |collection|
23
- n.times.each { collection << FactoryGirl.create(factory.to_s.singularize.to_sym, factory_girl_args) }
25
+ n.times.each { collection << FactoryBot.create(factory.to_s.singularize.to_sym, factory_bot_args) }
24
26
  end
25
27
  else
26
- FactoryGirl.create(factory.to_s.singularize.to_sym, factory_girl_args)
28
+ FactoryBot.create(factory.to_s.singularize.to_sym, factory_bot_args)
27
29
  end
28
30
  end
29
31
  end
@@ -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'"
@@ -82,5 +83,17 @@ def should_support_mysql_import_functionality
82
83
  assert_equal "Chad Fowler", topics.last.author_name, "wrong author!"
83
84
  end
84
85
  end
86
+
87
+ if ENV['AR_VERSION'].to_f >= 5.1
88
+ context "with virtual columns" do
89
+ let(:books) { [Book.new(author_name: "foo", title: "bar")] }
90
+
91
+ it "ignores virtual columns and creates record" do
92
+ assert_difference "Book.count", +1 do
93
+ Book.import books
94
+ end
95
+ end
96
+ end
97
+ end
85
98
  end
86
99
  end
@@ -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
 
@@ -37,6 +38,12 @@ def should_support_postgresql_import_functionality
37
38
  assert !topic.changed?
38
39
  end
39
40
 
41
+ if ENV['AR_VERSION'].to_f > 4.1
42
+ it "moves the dirty changes to previous_changes" do
43
+ assert topic.previous_changes.present?
44
+ end
45
+ end
46
+
40
47
  it "marks models as persisted" do
41
48
  assert !topic.new_record?
42
49
  assert topic.persisted?
@@ -96,6 +103,8 @@ def should_support_postgresql_import_functionality
96
103
  books.first.id.to_s
97
104
  end
98
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' }
99
108
 
100
109
  it "creates records" do
101
110
  assert_difference("Book.count", +1) { result }
@@ -110,6 +119,26 @@ def should_support_postgresql_import_functionality
110
119
  assert_equal [%w(King It)], result.results
111
120
  end
112
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
+
113
142
  context "when primary key and returning overlap" do
114
143
  let(:result) { Book.import(books, returning: %w(id title)) }
115
144
 
@@ -124,6 +153,34 @@ def should_support_postgresql_import_functionality
124
153
  end
125
154
  end
126
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
+
127
184
  context "setting model attributes" do
128
185
  let(:code) { 'abc' }
129
186
  let(:discount) { 0.10 }
@@ -153,6 +210,14 @@ def should_support_postgresql_import_functionality
153
210
  assert_equal updated_promotion.discount, discount
154
211
  end
155
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
156
221
  end
157
222
  end
158
223
  end
@@ -228,10 +293,34 @@ def should_support_postgresql_import_functionality
228
293
  assert_equal({}, Vendor.first.json_data)
229
294
  end
230
295
  end
296
+
297
+ %w(json jsonb).each do |json_type|
298
+ describe "with pure #{json_type} fields" do
299
+ let(:data) { { a: :b } }
300
+ let(:json_field_name) { "pure_#{json_type}_data" }
301
+ it "imports the values from saved records" do
302
+ vendor = Vendor.create!(name: 'Vendor 1', json_field_name => data)
303
+
304
+ Vendor.import [vendor], on_duplicate_key_update: [json_field_name]
305
+ assert_equal(data.as_json, vendor.reload[json_field_name])
306
+ end
307
+ end
308
+ end
309
+ end
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
231
320
  end
232
321
 
233
322
  describe "with binary field" do
234
- 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') }
235
324
  it "imports the correct values for binary fields" do
236
325
  alarms = [Alarm.new(device_id: 1, alarm_type: 1, status: 1, secret_key: binary_value)]
237
326
  assert_difference "Alarm.count", +1 do
@@ -240,6 +329,30 @@ def should_support_postgresql_import_functionality
240
329
  assert_equal(binary_value, Alarm.first.secret_key)
241
330
  end
242
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
+ end
355
+ end
243
356
  end
244
357
 
245
358
  def should_support_postgresql_upsert_functionality
@@ -295,6 +408,30 @@ def should_support_postgresql_upsert_functionality
295
408
  end
296
409
 
297
410
  context "using a hash" do
411
+ context "with :columns :all" do
412
+ let(:columns) { %w( id title author_name author_email_address parent_id ) }
413
+ let(:updated_values) { [[99, "Book - 2nd Edition", "Jane Doe", "janedoe@example.com", 57]] }
414
+
415
+ macro(:perform_import) do |*opts|
416
+ Topic.import columns, updated_values, opts.extract_options!.merge(on_duplicate_key_update: { conflict_target: :id, columns: :all }, validate: false)
417
+ end
418
+
419
+ setup do
420
+ values = [[99, "Book", "John Doe", "john@doe.com", 17, 3]]
421
+ Topic.import columns + ['replies_count'], values, validate: false
422
+ end
423
+
424
+ it "should update all specified columns" do
425
+ perform_import
426
+ updated_topic = Topic.find(99)
427
+ assert_equal 'Book - 2nd Edition', updated_topic.title
428
+ assert_equal 'Jane Doe', updated_topic.author_name
429
+ assert_equal 'janedoe@example.com', updated_topic.author_email_address
430
+ assert_equal 57, updated_topic.parent_id
431
+ assert_equal 3, updated_topic.replies_count
432
+ end
433
+ end
434
+
298
435
  context "with :columns a hash" do
299
436
  let(:columns) { %w( id title author_name author_email_address parent_id ) }
300
437
  let(:values) { [[99, "Book", "John Doe", "john@doe.com", 17]] }
@@ -312,7 +449,7 @@ def should_support_postgresql_upsert_functionality
312
449
  it "should not modify the passed in :on_duplicate_key_update columns array" do
313
450
  assert_nothing_raised do
314
451
  columns = %w(title author_name).freeze
315
- Topic.import columns, [%w(foo, bar)], on_duplicate_key_update: { columns: columns }
452
+ Topic.import columns, [%w(foo, bar)], { on_duplicate_key_update: { columns: columns }.freeze }.freeze
316
453
  end
317
454
  end
318
455
 
@@ -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