activerecord-import 1.0.3 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/README.markdown +3 -2
- data/lib/activerecord-import/adapters/abstract_adapter.rb +7 -1
- data/lib/activerecord-import/adapters/mysql_adapter.rb +4 -5
- data/lib/activerecord-import/adapters/postgresql_adapter.rb +2 -9
- data/lib/activerecord-import/adapters/sqlite3_adapter.rb +7 -14
- data/lib/activerecord-import/import.rb +9 -15
- data/lib/activerecord-import/version.rb +1 -1
- data/test/schema/postgresql_schema.rb +13 -0
- data/test/support/postgresql/import_examples.rb +11 -0
- data/test/support/shared_examples/on_duplicate_key_update.rb +10 -0
- metadata +3 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cbf4c01d6a3f043ed541f7331d6a19e651cbc035fcc3030b17f1e741a6d4e8b7
|
4
|
+
data.tar.gz: a740491de16c78d4d94ccb428c69342fd8a809f235ce9d42fc41237a4d15c7e6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 48e62637f6493cd5446cd78aee3db3b4a58e646fb0bc07d3583a662f89f52b03c2a079c83fc2cae807c5577f922e1e3219354cf36dff8ae8cc81ae68266e711f
|
7
|
+
data.tar.gz: 2f40f5fd61975589f8af932a3dbf5b6d14d2cfc6884ebe7019c85956c8c380d3faebffb1b600a7fa04536fe262ed40688946c8d35b8cf5b1bfedd1ddff20af5a
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
## Changes in 1.0.4
|
2
|
+
|
3
|
+
### Fixes
|
4
|
+
|
5
|
+
* Use prepend pattern for ActiveRecord::Base#establish_connection patching. Thanks to @dombesz via \#648.
|
6
|
+
* Fix NoMethodError when using PostgreSQL ENUM types. Thanks to @sebcoetzee via \#651.
|
7
|
+
* Fix issue updating optimistic lock in Postgres. Thanks to @timanovsky
|
8
|
+
via \#656.
|
9
|
+
|
1
10
|
## Changes in 1.0.3
|
2
11
|
|
3
12
|
### New Features
|
data/README.markdown
CHANGED
@@ -251,6 +251,7 @@ Key | Options | Default | Descripti
|
|
251
251
|
----------------------- | --------------------- | ------------------ | -----------
|
252
252
|
:validate | `true`/`false` | `true` | Whether or not to run `ActiveRecord` validations (uniqueness skipped). This option will always be true when using `import!`.
|
253
253
|
:validate_uniqueness | `true`/`false` | `false` | Whether or not to run uniqueness validations, has potential pitfalls, use with caution (requires `>= v0.27.0`).
|
254
|
+
:validate_with_context | `Symbol` |`:create`/`:update` | Allows passing an ActiveModel validation context for each model. Default is `:create` for new records and `:update` for existing ones.
|
254
255
|
:on_duplicate_key_ignore| `true`/`false` | `false` | Allows skipping records with duplicate keys. See [here](https://github.com/zdennis/activerecord-import/#duplicate-key-ignore) for more details.
|
255
256
|
:ignore | `true`/`false` | `false` | Alias for :on_duplicate_key_ignore.
|
256
257
|
:on_duplicate_key_update| :all, `Array`, `Hash` | N/A | Allows upsert logic to be used. See [here](https://github.com/zdennis/activerecord-import/#duplicate-key-update) for more details.
|
@@ -258,8 +259,8 @@ Key | Options | Default | Descripti
|
|
258
259
|
:timestamps | `true`/`false` | `true` | Enables/disables timestamps on imported records.
|
259
260
|
:recursive | `true`/`false` | `false` | Imports has_many/has_one associations (PostgreSQL only).
|
260
261
|
:batch_size | `Integer` | total # of records | Max number of records to insert per import
|
261
|
-
:raise_error | `true`/`false` | `false` |
|
262
|
-
|
262
|
+
:raise_error | `true`/`false` | `false` | Raises an exception at the first invalid record. This means there will not be a result object returned. The `import!` method is a shortcut for this.
|
263
|
+
:all_or_none | `true`/`false` | `false` | Will not import any records if there is a record with validation errors.
|
263
264
|
|
264
265
|
#### Duplicate Key Ignore
|
265
266
|
|
@@ -59,8 +59,14 @@ module ActiveRecord::Import::AbstractAdapter
|
|
59
59
|
post_sql_statements
|
60
60
|
end
|
61
61
|
|
62
|
+
def increment_locking_column!(table_name, results, locking_column)
|
63
|
+
if locking_column.present?
|
64
|
+
results << "\"#{locking_column}\"=#{table_name}.\"#{locking_column}\"+1"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
62
68
|
def supports_on_duplicate_key_update?
|
63
|
-
|
69
|
+
true
|
64
70
|
end
|
65
71
|
end
|
66
72
|
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
module ActiveRecord::Import::MysqlAdapter
|
2
2
|
include ActiveRecord::Import::ImportSupport
|
3
|
-
include ActiveRecord::Import::OnDuplicateKeyUpdateSupport
|
4
3
|
|
5
4
|
NO_MAX_PACKET = 0
|
6
5
|
QUERY_OVERHEAD = 8 # This was shown to be true for MySQL, but it's not clear where the overhead is from.
|
@@ -102,7 +101,7 @@ module ActiveRecord::Import::MysqlAdapter
|
|
102
101
|
qc = quote_column_name( column )
|
103
102
|
"#{table_name}.#{qc}=VALUES(#{qc})"
|
104
103
|
end
|
105
|
-
increment_locking_column!(
|
104
|
+
increment_locking_column!(table_name, results, locking_column)
|
106
105
|
results.join( ',' )
|
107
106
|
end
|
108
107
|
|
@@ -112,7 +111,7 @@ module ActiveRecord::Import::MysqlAdapter
|
|
112
111
|
qc2 = quote_column_name( column2 )
|
113
112
|
"#{table_name}.#{qc1}=VALUES( #{qc2} )"
|
114
113
|
end
|
115
|
-
increment_locking_column!(
|
114
|
+
increment_locking_column!(table_name, results, locking_column)
|
116
115
|
results.join( ',')
|
117
116
|
end
|
118
117
|
|
@@ -121,9 +120,9 @@ module ActiveRecord::Import::MysqlAdapter
|
|
121
120
|
exception.is_a?(ActiveRecord::StatementInvalid) && exception.to_s.include?('Duplicate entry')
|
122
121
|
end
|
123
122
|
|
124
|
-
def increment_locking_column!(
|
123
|
+
def increment_locking_column!(table_name, results, locking_column)
|
125
124
|
if locking_column.present?
|
126
|
-
results << "
|
125
|
+
results << "`#{locking_column}`=#{table_name}.`#{locking_column}`+1"
|
127
126
|
end
|
128
127
|
end
|
129
128
|
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
module ActiveRecord::Import::PostgreSQLAdapter
|
2
2
|
include ActiveRecord::Import::ImportSupport
|
3
|
-
include ActiveRecord::Import::OnDuplicateKeyUpdateSupport
|
4
3
|
|
5
4
|
MIN_VERSION_FOR_UPSERT = 90_500
|
6
5
|
|
@@ -158,7 +157,7 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
158
157
|
qc = quote_column_name( column )
|
159
158
|
"#{qc}=EXCLUDED.#{qc}"
|
160
159
|
end
|
161
|
-
increment_locking_column!(results, locking_column)
|
160
|
+
increment_locking_column!(table_name, results, locking_column)
|
162
161
|
results.join( ',' )
|
163
162
|
end
|
164
163
|
|
@@ -168,7 +167,7 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
168
167
|
qc2 = quote_column_name( column2 )
|
169
168
|
"#{qc1}=EXCLUDED.#{qc2}"
|
170
169
|
end
|
171
|
-
increment_locking_column!(results, locking_column)
|
170
|
+
increment_locking_column!(table_name, results, locking_column)
|
172
171
|
results.join( ',' )
|
173
172
|
end
|
174
173
|
|
@@ -203,12 +202,6 @@ module ActiveRecord::Import::PostgreSQLAdapter
|
|
203
202
|
true
|
204
203
|
end
|
205
204
|
|
206
|
-
def increment_locking_column!(results, locking_column)
|
207
|
-
if locking_column.present?
|
208
|
-
results << "\"#{locking_column}\"=EXCLUDED.\"#{locking_column}\"+1"
|
209
|
-
end
|
210
|
-
end
|
211
|
-
|
212
205
|
private
|
213
206
|
|
214
207
|
def database_version
|
@@ -1,6 +1,5 @@
|
|
1
1
|
module ActiveRecord::Import::SQLite3Adapter
|
2
2
|
include ActiveRecord::Import::ImportSupport
|
3
|
-
include ActiveRecord::Import::OnDuplicateKeyUpdateSupport
|
4
3
|
|
5
4
|
MIN_VERSION_FOR_IMPORT = "3.7.11".freeze
|
6
5
|
MIN_VERSION_FOR_UPSERT = "3.24.0".freeze
|
@@ -92,7 +91,7 @@ module ActiveRecord::Import::SQLite3Adapter
|
|
92
91
|
|
93
92
|
# Returns a generated ON CONFLICT DO UPDATE statement given the passed
|
94
93
|
# in +args+.
|
95
|
-
def sql_for_on_duplicate_key_update(
|
94
|
+
def sql_for_on_duplicate_key_update( table_name, *args ) # :nodoc:
|
96
95
|
arg, primary_key, locking_column = args
|
97
96
|
arg = { columns: arg } if arg.is_a?( Array ) || arg.is_a?( String )
|
98
97
|
return unless arg.is_a?( Hash )
|
@@ -113,9 +112,9 @@ module ActiveRecord::Import::SQLite3Adapter
|
|
113
112
|
|
114
113
|
sql << "#{conflict_target}DO UPDATE SET "
|
115
114
|
if columns.is_a?( Array )
|
116
|
-
sql << sql_for_on_duplicate_key_update_as_array( locking_column, columns )
|
115
|
+
sql << sql_for_on_duplicate_key_update_as_array( table_name, locking_column, columns )
|
117
116
|
elsif columns.is_a?( Hash )
|
118
|
-
sql << sql_for_on_duplicate_key_update_as_hash( locking_column, columns )
|
117
|
+
sql << sql_for_on_duplicate_key_update_as_hash( table_name, locking_column, columns )
|
119
118
|
elsif columns.is_a?( String )
|
120
119
|
sql << columns
|
121
120
|
else
|
@@ -127,22 +126,22 @@ module ActiveRecord::Import::SQLite3Adapter
|
|
127
126
|
sql
|
128
127
|
end
|
129
128
|
|
130
|
-
def sql_for_on_duplicate_key_update_as_array( locking_column, arr ) # :nodoc:
|
129
|
+
def sql_for_on_duplicate_key_update_as_array( table_name, locking_column, arr ) # :nodoc:
|
131
130
|
results = arr.map do |column|
|
132
131
|
qc = quote_column_name( column )
|
133
132
|
"#{qc}=EXCLUDED.#{qc}"
|
134
133
|
end
|
135
|
-
increment_locking_column!(results, locking_column)
|
134
|
+
increment_locking_column!(table_name, results, locking_column)
|
136
135
|
results.join( ',' )
|
137
136
|
end
|
138
137
|
|
139
|
-
def sql_for_on_duplicate_key_update_as_hash( locking_column, hsh ) # :nodoc:
|
138
|
+
def sql_for_on_duplicate_key_update_as_hash( table_name, locking_column, hsh ) # :nodoc:
|
140
139
|
results = hsh.map do |column1, column2|
|
141
140
|
qc1 = quote_column_name( column1 )
|
142
141
|
qc2 = quote_column_name( column2 )
|
143
142
|
"#{qc1}=EXCLUDED.#{qc2}"
|
144
143
|
end
|
145
|
-
increment_locking_column!(results, locking_column)
|
144
|
+
increment_locking_column!(table_name, results, locking_column)
|
146
145
|
results.join( ',' )
|
147
146
|
end
|
148
147
|
|
@@ -166,12 +165,6 @@ module ActiveRecord::Import::SQLite3Adapter
|
|
166
165
|
exception.is_a?(ActiveRecord::StatementInvalid) && exception.to_s.include?('duplicate key')
|
167
166
|
end
|
168
167
|
|
169
|
-
def increment_locking_column!(results, locking_column)
|
170
|
-
if locking_column.present?
|
171
|
-
results << "\"#{locking_column}\"=EXCLUDED.\"#{locking_column}\"+1"
|
172
|
-
end
|
173
|
-
end
|
174
|
-
|
175
168
|
private
|
176
169
|
|
177
170
|
def database_version
|
@@ -11,12 +11,6 @@ module ActiveRecord::Import #:nodoc:
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
-
module OnDuplicateKeyUpdateSupport #:nodoc:
|
15
|
-
def supports_on_duplicate_key_update? #:nodoc:
|
16
|
-
true
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
14
|
class MissingColumnError < StandardError
|
21
15
|
def initialize(name, index)
|
22
16
|
super "Missing column for value <#{name}> at index #{index}"
|
@@ -245,16 +239,16 @@ class ActiveRecord::Associations::CollectionAssociation
|
|
245
239
|
alias import bulk_import unless respond_to? :import
|
246
240
|
end
|
247
241
|
|
242
|
+
module ActiveRecord::Import::Connection
|
243
|
+
def establish_connection(args = nil)
|
244
|
+
super(args)
|
245
|
+
ActiveRecord::Import.load_from_connection_pool connection_pool
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
248
249
|
class ActiveRecord::Base
|
249
250
|
class << self
|
250
|
-
|
251
|
-
conn = establish_connection_without_activerecord_import(*args)
|
252
|
-
ActiveRecord::Import.load_from_connection_pool connection_pool
|
253
|
-
conn
|
254
|
-
end
|
255
|
-
|
256
|
-
alias establish_connection_without_activerecord_import establish_connection
|
257
|
-
alias establish_connection establish_connection_with_activerecord_import
|
251
|
+
prepend ActiveRecord::Import::Connection
|
258
252
|
|
259
253
|
# Returns true if the current database connection adapter
|
260
254
|
# supports import functionality, otherwise returns false.
|
@@ -964,7 +958,7 @@ class ActiveRecord::Base
|
|
964
958
|
val = serialized_attributes[column.name].dump(val)
|
965
959
|
end
|
966
960
|
# Fixes #443 to support binary (i.e. bytea) columns on PG
|
967
|
-
val = column.type_cast(val) unless column.type.to_sym == :binary
|
961
|
+
val = column.type_cast(val) unless column.type && column.type.to_sym == :binary
|
968
962
|
connection_memo.quote(val, column)
|
969
963
|
end
|
970
964
|
else
|
@@ -3,6 +3,17 @@ ActiveRecord::Schema.define do
|
|
3
3
|
execute('CREATE extension IF NOT EXISTS "pgcrypto";')
|
4
4
|
execute('CREATE extension IF NOT EXISTS "uuid-ossp";')
|
5
5
|
|
6
|
+
# create ENUM if it does not exist yet
|
7
|
+
begin
|
8
|
+
execute('CREATE TYPE vendor_type AS ENUM (\'wholesaler\', \'retailer\');')
|
9
|
+
rescue ActiveRecord::StatementInvalid => e
|
10
|
+
# since PostgreSQL does not support IF NOT EXISTS when creating a TYPE,
|
11
|
+
# rescue the error and check the error class
|
12
|
+
raise unless e.cause.is_a? PG::DuplicateObject
|
13
|
+
execute('ALTER TYPE vendor_type ADD VALUE IF NOT EXISTS \'wholesaler\';')
|
14
|
+
execute('ALTER TYPE vendor_type ADD VALUE IF NOT EXISTS \'retailer\';')
|
15
|
+
end
|
16
|
+
|
6
17
|
create_table :vendors, id: :uuid, force: :cascade do |t|
|
7
18
|
t.string :name, null: true
|
8
19
|
t.text :preferences
|
@@ -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
|
@@ -260,6 +260,17 @@ def should_support_postgresql_import_functionality
|
|
260
260
|
end
|
261
261
|
end
|
262
262
|
|
263
|
+
describe "with enum field" do
|
264
|
+
let(:vendor_type) { "retailer" }
|
265
|
+
it "imports the correct values for enum fields" do
|
266
|
+
vendor = Vendor.new(name: 'Vendor 1', vendor_type: vendor_type)
|
267
|
+
assert_difference "Vendor.count", +1 do
|
268
|
+
Vendor.import [vendor]
|
269
|
+
end
|
270
|
+
assert_equal(vendor_type, Vendor.first.vendor_type)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
263
274
|
describe "with binary field" do
|
264
275
|
let(:binary_value) { "\xE0'c\xB2\xB0\xB3Bh\\\xC2M\xB1m\\I\xC4r".force_encoding('ASCII-8BIT') }
|
265
276
|
it "imports the correct values for binary fields" do
|
@@ -73,6 +73,16 @@ def should_support_basic_on_duplicate_key_update
|
|
73
73
|
assert_equal user.name, users[i].name + ' Rothschild'
|
74
74
|
assert_equal 1, user.lock_version
|
75
75
|
end
|
76
|
+
updated_values2 = User.all.map do |user|
|
77
|
+
user.name += ' jr.'
|
78
|
+
{ id: user.id, name: user.name }
|
79
|
+
end
|
80
|
+
User.import(updated_values2, on_duplicate_key_update: [:name])
|
81
|
+
assert User.count == updated_values2.length
|
82
|
+
User.all.each_with_index do |user, i|
|
83
|
+
assert_equal user.name, users[i].name + ' Rothschild jr.'
|
84
|
+
assert_equal 2, user.lock_version
|
85
|
+
end
|
76
86
|
end
|
77
87
|
|
78
88
|
it 'upsert optimistic lock columns other than lock_version by model' do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activerecord-import
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Zach Dennis
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-12-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -185,8 +185,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
185
185
|
- !ruby/object:Gem::Version
|
186
186
|
version: '0'
|
187
187
|
requirements: []
|
188
|
-
|
189
|
-
rubygems_version: 2.7.7
|
188
|
+
rubygems_version: 3.0.6
|
190
189
|
signing_key:
|
191
190
|
specification_version: 4
|
192
191
|
summary: Bulk insert extension for ActiveRecord
|