activerecord-import 1.0.3 → 1.0.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|