Skip to content

Commit fa25dd0

Browse files
committed
Option to add ignored columns on update
1 parent 3ded6b0 commit fa25dd0

7 files changed

Lines changed: 70 additions & 9 deletions

File tree

.ruby-version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
ruby-2.2.4
1+
ruby-2.4.4

.travis.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
language: ruby
22
rvm:
3-
- 2.2
4-
- 2.3
53
- 2.4
64
- 2.5
5+
- 2.6
6+
- 2.7
77
- ruby-head
88
script: bundle exec rake
9+
jobs:
10+
allow_failures:
11+
- rvm: ruby-head

Gemfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,4 +125,4 @@ DEPENDENCIES
125125
sqlite3
126126

127127
BUNDLED WITH
128-
1.14.6
128+
2.1.4

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,28 @@ Book.bulk_insert(*destination_columns, update_duplicates: %w[title]) do |worker|
174174
end
175175
```
176176

177+
### Ignored Columns On Update (MySQL, PostgreSQL)
178+
179+
When there is a conflict in _insert_ and you want to instead update those records you would be using the _update_duplicates_ option. Along with that you can also ignore certain columns which you don't want to be updated. For example you may want to ignore `created_at` field during the update.
180+
181+
```ruby
182+
destination_columns = [:title, :created_at, :updated_at]
183+
184+
# Ignored columns on update (MySQL)
185+
Book.bulk_insert(*destination_columns, update_duplicates: true, ignored_columns_on_update: %w(created_at)) do |worker|
186+
worker.add(...)
187+
# ...
188+
end
189+
190+
# Ignored columns on update (PostgreSQL)
191+
Book.bulk_insert(*destination_columns, update_duplicates: %w[title], ignored_columns_on_update: %w(created_at)) do |worker|
192+
worker.add(...)
193+
# ...
194+
end
195+
```
196+
197+
You can pass a single column name, or a list of column names to `ignored_columns_on_update`.
198+
177199
### Return Primary Keys (PostgreSQL, PostGIS)
178200

179201
If you want the worker to store primary keys of inserted records, then you can

lib/bulk_insert.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ module BulkInsert
44
extend ActiveSupport::Concern
55

66
module ClassMethods
7-
def bulk_insert(*columns, values: nil, set_size:500, ignore: false, update_duplicates: false, return_primary_keys: false)
7+
def bulk_insert(*columns, values: nil, set_size:500, ignore: false, update_duplicates: false, return_primary_keys: false, ignored_columns_on_update: nil)
88
columns = default_bulk_columns if columns.empty?
9-
worker = BulkInsert::Worker.new(connection, table_name, primary_key, columns, set_size, ignore, update_duplicates, return_primary_keys)
9+
worker = BulkInsert::Worker.new(connection, table_name, primary_key, columns, set_size, ignore, update_duplicates, return_primary_keys, ignored_columns_on_update)
1010

1111
if values.present?
1212
transaction do

lib/bulk_insert/worker.rb

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ class Worker
77
attr_accessor :adapter_name
88
attr_reader :ignore, :update_duplicates, :result_sets
99

10-
def initialize(connection, table_name, primary_key, column_names, set_size=500, ignore=false, update_duplicates=false, return_primary_keys=false)
10+
def initialize(connection, table_name, primary_key, column_names, set_size=500, ignore=false, update_duplicates=false, return_primary_keys=false, ignored_columns_on_update=nil)
1111
@connection = connection
1212
@set_size = set_size
1313

@@ -16,12 +16,14 @@ def initialize(connection, table_name, primary_key, column_names, set_size=500,
1616
@ignore = ignore
1717
@update_duplicates = update_duplicates
1818
@return_primary_keys = return_primary_keys
19+
@ignored_columns_on_update = Array(ignored_columns_on_update)
1920

2021
columns = connection.columns(table_name)
2122
column_map = columns.inject({}) { |h, c| h.update(c.name => c) }
2223

2324
@primary_key = primary_key
2425
@columns = column_names.map { |name| column_map[name.to_s] }
26+
@columns_for_update = @columns.reject { |column| @ignored_columns_on_update.include?(column.name) }
2527
@table_name = connection.quote_table_name(table_name)
2628
@column_names = column_names.map { |name| connection.quote_column_name(name) }.join(",")
2729

@@ -154,12 +156,12 @@ def on_conflict_statement
154156
if is_postgres && ignore
155157
' ON CONFLICT DO NOTHING'
156158
elsif is_postgres && update_duplicates
157-
update_values = @columns.map do |column|
159+
update_values = @columns_for_update.map do |column|
158160
"#{column.name}=EXCLUDED.#{column.name}"
159161
end.join(', ')
160162
' ON CONFLICT(' + update_duplicates.join(', ') + ') DO UPDATE SET ' + update_values
161163
elsif adapter_name =~ /^mysql/i && update_duplicates
162-
update_values = @columns.map do |column|
164+
update_values = @columns_for_update.map do |column|
163165
"`#{column.name}`=VALUES(`#{column.name}`)"
164166
end.join(', ')
165167
' ON DUPLICATE KEY UPDATE ' + update_values

test/bulk_insert/worker_test.rb

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,4 +418,38 @@ class BulkInsertWorkerTest < ActiveSupport::TestCase
418418

419419
assert_equal mysql_worker.compose_insert_query, "INSERT INTO \"testings\" (\"greeting\",\"age\",\"happy\",\"created_at\",\"updated_at\",\"color\") VALUES ('Yo',15,0,NULL,NULL,'chartreuse') ON DUPLICATE KEY UPDATE `greeting`=VALUES(`greeting`), `age`=VALUES(`age`), `happy`=VALUES(`happy`), `created_at`=VALUES(`created_at`), `updated_at`=VALUES(`updated_at`), `color`=VALUES(`color`)"
420420
end
421+
422+
test "mysql adapter can ignore specific columns when updating duplicates" do
423+
mysql_worker = BulkInsert::Worker.new(
424+
Testing.connection,
425+
Testing.table_name,
426+
'id',
427+
%w(greeting age happy created_at updated_at color),
428+
500, # batch size
429+
false, # ignore
430+
true, # update_duplicates
431+
false, # return_primary_keys
432+
%w(created_at)) # ignored_columns_on_update
433+
mysql_worker.adapter_name = 'MySQL'
434+
mysql_worker.add ["Yo", 15, false, nil, nil]
435+
436+
assert_equal mysql_worker.compose_insert_query, "INSERT INTO \"testings\" (\"greeting\",\"age\",\"happy\",\"created_at\",\"updated_at\",\"color\") VALUES ('Yo',15,0,NULL,NULL,'chartreuse') ON DUPLICATE KEY UPDATE `greeting`=VALUES(`greeting`), `age`=VALUES(`age`), `happy`=VALUES(`happy`), `updated_at`=VALUES(`updated_at`), `color`=VALUES(`color`)"
437+
end
438+
439+
test "pgsql_worker adapter can ignore specific columns when updating duplicates" do
440+
pgsql_worker = BulkInsert::Worker.new(
441+
Testing.connection,
442+
Testing.table_name,
443+
'id',
444+
%w(greeting age happy created_at updated_at color),
445+
500, # batch size
446+
false, # ignore
447+
%w(greeting), # update_duplicates
448+
false, # return_primary_keys
449+
%w(created_at)) # ignored_columns_on_update
450+
pgsql_worker.adapter_name = 'PostgreSQL'
451+
pgsql_worker.add ["Yo", 15, false, nil, nil]
452+
453+
assert_equal pgsql_worker.compose_insert_query, "INSERT INTO \"testings\" (\"greeting\",\"age\",\"happy\",\"created_at\",\"updated_at\",\"color\") VALUES ('Yo',15,0,NULL,NULL,'chartreuse') ON CONFLICT(greeting) DO UPDATE SET greeting=EXCLUDED.greeting, age=EXCLUDED.age, happy=EXCLUDED.happy, updated_at=EXCLUDED.updated_at, color=EXCLUDED.color"
454+
end
421455
end

0 commit comments

Comments
 (0)