activerecord-import 0.22.0 → 1.4.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.
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