bulk_insert 1.4.0 → 1.8.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 441f604c7dcfaa00902f46fca07f376559fed7ce
4
- data.tar.gz: 9a00313cc43ed9aa6fb704e9b0ba600015b10599
2
+ SHA256:
3
+ metadata.gz: 5e78f757829191018747a87be6b1c6aec0a8a140b248fab6e698b217d0125575
4
+ data.tar.gz: b3e5cce589f88d49820f06681c72c833f727c2743650e7da0a84cb9fffe40eed
5
5
  SHA512:
6
- metadata.gz: e8b779e68d0f0ab4fcaf9699e38ce2a56962c95edaf615d0952713e28b6d0e95ef0ffe42da46101b5b4bcbba8b8e334d7ac259f7c903770c33b30fefcab67e90
7
- data.tar.gz: c37eaf99e2ec09a9b1b9328eca84274130677b3ed6cf5a23b91abd358ef8fbff83b1cd6101481a783af2dd9542b0a5cdd4956fe6375b2f720c711ad5a5512733
6
+ metadata.gz: fa009cab807affa711ec0ac7c4618137401fa56e91651103947c444f835970e61c9a2855316bc0b46c3a669eb84b1f08ae0b211cfdac8f870f910dc203455830
7
+ data.tar.gz: d2f0a68185ebc1b788e5bb90509ab37240599b288d65ef26bc23b900545bc4766eb159757aa2b7ac441d99af5578da339ca1dfe9876ae11904f298443bdaa4c6
data/README.md CHANGED
@@ -104,7 +104,6 @@ empty the batch so that you can add more rows to it if you want. Note
104
104
  that all records saved together will have the same created_at/updated_at
105
105
  timestamp (unless one was explicitly set).
106
106
 
107
-
108
107
  ### Batch Set Size
109
108
 
110
109
  By default, the size of the insert is limited to 500 rows at a time.
@@ -149,6 +148,48 @@ Book.bulk_insert(*destination_columns, ignore: true) do |worker|
149
148
  end
150
149
  ```
151
150
 
151
+ ### Update Duplicates (MySQL, PostgreSQL)
152
+
153
+ If you don't want to ignore duplicate rows but instead want to update them
154
+ then you can use the _update_duplicates_ option. Set this option to true
155
+ (MySQL) or list unique column names (PostgreSQL) and when a duplicate row
156
+ is found the row will be updated with your new values.
157
+ Default value for this option is false.
158
+
159
+ ```ruby
160
+ destination_columns = [:title, :author]
161
+
162
+ # Update duplicate rows (MySQL)
163
+ Book.bulk_insert(*destination_columns, update_duplicates: true) do |worker|
164
+ worker.add(...)
165
+ worker.add(...)
166
+ # ...
167
+ end
168
+
169
+ # Update duplicate rows (PostgreSQL)
170
+ Book.bulk_insert(*destination_columns, update_duplicates: %w[title]) do |worker|
171
+ worker.add(...)
172
+ # ...
173
+ end
174
+ ```
175
+
176
+ ### Return Primary Keys (PostgreSQL, PostGIS)
177
+
178
+ If you want the worker to store primary keys of inserted records, then you can
179
+ use the _return_primary_keys_ option. The worker will store a `result_sets`
180
+ array of `ActiveRecord::Result` objects. Each `ActiveRecord::Result` object
181
+ will contain the primary keys of a batch of inserted records.
182
+
183
+ ```ruby
184
+ worker = Book.bulk_insert(*destination_columns, return_primary_keys: true) do
185
+ |worker|
186
+ worker.add(...)
187
+ worker.add(...)
188
+ # ...
189
+ end
190
+
191
+ worker.result_sets
192
+ ```
152
193
 
153
194
  ## License
154
195
 
data/Rakefile CHANGED
@@ -14,11 +14,6 @@ RDoc::Task.new(:rdoc) do |rdoc|
14
14
  rdoc.rdoc_files.include('lib/**/*.rb')
15
15
  end
16
16
 
17
-
18
-
19
-
20
-
21
-
22
17
  Bundler::GemHelper.install_tasks
23
18
 
24
19
  require 'rake/testtask'
@@ -4,9 +4,9 @@ module BulkInsert
4
4
  extend ActiveSupport::Concern
5
5
 
6
6
  module ClassMethods
7
- def bulk_insert(*columns, values: nil, set_size:500, ignore: false)
7
+ def bulk_insert(*columns, values: nil, set_size:500, ignore: false, update_duplicates: false, return_primary_keys: false)
8
8
  columns = default_bulk_columns if columns.empty?
9
- worker = BulkInsert::Worker.new(connection, table_name, columns, set_size, ignore)
9
+ worker = BulkInsert::Worker.new(connection, table_name, primary_key, columns, set_size, ignore, update_duplicates, return_primary_keys)
10
10
 
11
11
  if values.present?
12
12
  transaction do
@@ -0,0 +1,22 @@
1
+ require_relative 'statement_adapters/generic_adapter'
2
+ require_relative 'statement_adapters/mysql_adapter'
3
+ require_relative 'statement_adapters/postgresql_adapter'
4
+ require_relative 'statement_adapters/sqlite_adapter'
5
+
6
+ module BulkInsert
7
+ module StatementAdapters
8
+ def adapter_for(connection)
9
+ case connection.adapter_name
10
+ when /^mysql/i
11
+ MySQLAdapter.new
12
+ when /\APost(?:greSQL|GIS)/i
13
+ PostgreSQLAdapter.new
14
+ when /\ASQLite/i
15
+ SQLiteAdapter.new
16
+ else
17
+ GenericAdapter.new
18
+ end
19
+ end
20
+ module_function :adapter_for
21
+ end
22
+ end
@@ -0,0 +1,21 @@
1
+ module BulkInsert
2
+ module StatementAdapters
3
+ class BaseAdapter
4
+ def initialize
5
+ raise "You cannot initialize base adapter" if self.class == BaseAdapter
6
+ end
7
+
8
+ def insert_ignore_statement
9
+ raise "Not implemented"
10
+ end
11
+
12
+ def on_conflict_statement(_columns, _ignore, _update_duplicates)
13
+ raise "Not implemented"
14
+ end
15
+
16
+ def primary_key_return_statement(_primary_key)
17
+ raise "Not implemented"
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,19 @@
1
+ require_relative 'base_adapter'
2
+
3
+ module BulkInsert
4
+ module StatementAdapters
5
+ class GenericAdapter < BaseAdapter
6
+ def insert_ignore_statement
7
+ ''
8
+ end
9
+
10
+ def on_conflict_statement(_columns, _ignore, _update_duplicates)
11
+ ''
12
+ end
13
+
14
+ def primary_key_return_statement(_primary_key)
15
+ ''
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,24 @@
1
+ require_relative 'base_adapter'
2
+
3
+ module BulkInsert
4
+ module StatementAdapters
5
+ class MySQLAdapter < BaseAdapter
6
+ def insert_ignore_statement
7
+ 'IGNORE'
8
+ end
9
+
10
+ def on_conflict_statement(columns, _ignore, update_duplicates)
11
+ return '' unless update_duplicates
12
+
13
+ update_values = columns.map do |column|
14
+ "`#{column.name}`=VALUES(`#{column.name}`)"
15
+ end.join(', ')
16
+ ' ON DUPLICATE KEY UPDATE ' + update_values
17
+ end
18
+
19
+ def primary_key_return_statement(_primary_key)
20
+ ''
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,28 @@
1
+ require_relative 'base_adapter'
2
+
3
+ module BulkInsert
4
+ module StatementAdapters
5
+ class PostgreSQLAdapter < BaseAdapter
6
+ def insert_ignore_statement
7
+ ''
8
+ end
9
+
10
+ def on_conflict_statement(columns, ignore, update_duplicates)
11
+ if ignore
12
+ ' ON CONFLICT DO NOTHING'
13
+ elsif update_duplicates
14
+ update_values = columns.map do |column|
15
+ "#{column.name}=EXCLUDED.#{column.name}"
16
+ end.join(', ')
17
+ ' ON CONFLICT(' + update_duplicates.join(', ') + ') DO UPDATE SET ' + update_values
18
+ else
19
+ ''
20
+ end
21
+ end
22
+
23
+ def primary_key_return_statement(primary_key)
24
+ " RETURNING #{primary_key}"
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,19 @@
1
+ require_relative 'base_adapter'
2
+
3
+ module BulkInsert
4
+ module StatementAdapters
5
+ class SQLiteAdapter < BaseAdapter
6
+ def insert_ignore_statement
7
+ 'OR IGNORE'
8
+ end
9
+
10
+ def on_conflict_statement(_columns, _ignore, _update_duplicates)
11
+ ''
12
+ end
13
+
14
+ def primary_key_return_statement(_primary_key)
15
+ ''
16
+ end
17
+ end
18
+ end
19
+ end
@@ -1,7 +1,7 @@
1
1
  module BulkInsert
2
2
  MAJOR = 1
3
- MINOR = 4
4
- TINY = 0
3
+ MINOR = 8
4
+ TINY = 1
5
5
 
6
6
  VERSION = [MAJOR, MINOR, TINY].join(".")
7
7
  end
@@ -1,24 +1,38 @@
1
+ require_relative 'statement_adapters'
2
+
1
3
  module BulkInsert
2
4
  class Worker
3
5
  attr_reader :connection
4
6
  attr_accessor :set_size
7
+ attr_accessor :before_save_callback
5
8
  attr_accessor :after_save_callback
9
+ attr_accessor :adapter_name
10
+ attr_reader :ignore, :update_duplicates, :result_sets
11
+
12
+ def initialize(connection, table_name, primary_key, column_names, set_size=500, ignore=false, update_duplicates=false, return_primary_keys=false)
13
+ @statement_adapter = StatementAdapters.adapter_for(connection)
6
14
 
7
- def initialize(connection, table_name, column_names, set_size=500, ignore=false)
8
15
  @connection = connection
9
16
  @set_size = set_size
17
+
18
+ @adapter_name = connection.adapter_name
10
19
  # INSERT IGNORE only fails inserts with duplicate keys or unallowed nulls not the whole set of inserts
11
- @ignore = ignore ? "IGNORE" : nil
20
+ @ignore = ignore
21
+ @update_duplicates = update_duplicates
22
+ @return_primary_keys = return_primary_keys
12
23
 
13
24
  columns = connection.columns(table_name)
14
25
  column_map = columns.inject({}) { |h, c| h.update(c.name => c) }
15
26
 
27
+ @primary_key = primary_key
16
28
  @columns = column_names.map { |name| column_map[name.to_s] }
17
29
  @table_name = connection.quote_table_name(table_name)
18
30
  @column_names = column_names.map { |name| connection.quote_column_name(name) }.join(",")
19
31
 
32
+ @before_save_callback = nil
20
33
  @after_save_callback = nil
21
34
 
35
+ @result_sets = []
22
36
  @set = []
23
37
  end
24
38
 
@@ -58,40 +72,65 @@ module BulkInsert
58
72
  self
59
73
  end
60
74
 
75
+ def before_save(&block)
76
+ @before_save_callback = block
77
+ end
78
+
61
79
  def after_save(&block)
62
80
  @after_save_callback = block
63
81
  end
64
82
 
65
83
  def save!
66
84
  if pending?
67
- sql = "INSERT #{@ignore} INTO #{@table_name} (#{@column_names}) VALUES "
68
- @now = Time.now
69
-
70
- rows = []
71
- @set.each do |row|
72
- values = []
73
- @columns.zip(row) do |column, value|
74
- value = @now if value == :__timestamp_placeholder
75
-
76
- if ActiveRecord::VERSION::STRING >= "5.0.0"
77
- value = @connection.type_cast_from_column(column, value) if column
78
- values << @connection.quote(value)
79
- else
80
- values << @connection.quote(value, column)
81
- end
85
+ @before_save_callback.(@set) if @before_save_callback
86
+ execute_query
87
+ @after_save_callback.() if @after_save_callback
88
+ @set.clear
89
+ end
90
+
91
+ self
92
+ end
93
+
94
+ def execute_query
95
+ if query = compose_insert_query
96
+ result_set = @connection.exec_query(query)
97
+ @result_sets.push(result_set) if @return_primary_keys
98
+ end
99
+ end
100
+
101
+ def compose_insert_query
102
+ sql = insert_sql_statement
103
+ @now = Time.now
104
+ rows = []
105
+
106
+ @set.each do |row|
107
+ values = []
108
+ @columns.zip(row) do |column, value|
109
+ value = @now if value == :__timestamp_placeholder
110
+
111
+ if ActiveRecord::VERSION::STRING >= "5.0.0"
112
+ value = @connection.type_cast_from_column(column, value) if column
113
+ values << @connection.quote(value)
114
+ else
115
+ values << @connection.quote(value, column)
82
116
  end
83
- rows << "(#{values.join(',')})"
84
117
  end
118
+ rows << "(#{values.join(',')})"
119
+ end
85
120
 
121
+ if !rows.empty?
86
122
  sql << rows.join(",")
87
- @connection.execute(sql)
88
-
89
- @after_save_callback.() if @after_save_callback
90
-
91
- @set.clear
123
+ sql << @statement_adapter.on_conflict_statement(@columns, ignore, update_duplicates)
124
+ sql << @statement_adapter.primary_key_return_statement(@primary_key) if @return_primary_keys
125
+ sql
126
+ else
127
+ false
92
128
  end
129
+ end
93
130
 
94
- self
131
+ def insert_sql_statement
132
+ insert_ignore = @ignore ? @statement_adapter.insert_ignore_statement : ''
133
+ "INSERT #{insert_ignore} INTO #{@table_name} (#{@column_names}) VALUES "
95
134
  end
96
135
  end
97
136
  end
@@ -1,3 +1,4 @@
1
+ require 'minitest/mock'
1
2
  require 'test_helper'
2
3
 
3
4
  class BulkInsertWorkerTest < ActiveSupport::TestCase
@@ -5,6 +6,7 @@ class BulkInsertWorkerTest < ActiveSupport::TestCase
5
6
  @insert = BulkInsert::Worker.new(
6
7
  Testing.connection,
7
8
  Testing.table_name,
9
+ 'id',
8
10
  %w(greeting age happy created_at updated_at color))
9
11
  @now = Time.now
10
12
  end
@@ -121,6 +123,53 @@ class BulkInsertWorkerTest < ActiveSupport::TestCase
121
123
  assert_equal true, hello.happy?
122
124
  end
123
125
 
126
+ test "save! does not add to result sets when not returning primary keys" do
127
+ @insert.add greeting: "first"
128
+ @insert.add greeting: "second"
129
+ @insert.save!
130
+
131
+ assert_equal 0, @insert.result_sets.count
132
+ end
133
+
134
+
135
+ test "save! adds to result sets when returning primary keys" do
136
+ worker = BulkInsert::Worker.new(
137
+ Testing.connection,
138
+ Testing.table_name,
139
+ 'id',
140
+ %w(greeting age happy created_at updated_at color),
141
+ 500,
142
+ false,
143
+ false,
144
+ true
145
+ )
146
+
147
+ assert_no_difference -> { worker.result_sets.count } do
148
+ worker.save!
149
+ end
150
+
151
+ worker.add greeting: "first"
152
+ worker.add greeting: "second"
153
+ worker.save!
154
+ assert_equal 1, worker.result_sets.count
155
+
156
+ worker.add greeting: "third"
157
+ worker.add greeting: "fourth"
158
+ worker.save!
159
+ assert_equal 2, worker.result_sets.count
160
+ end
161
+
162
+ test "initialized with empty result sets array" do
163
+ new_worker = BulkInsert::Worker.new(
164
+ Testing.connection,
165
+ Testing.table_name,
166
+ 'id',
167
+ %w(greeting age happy created_at updated_at color)
168
+ )
169
+ assert_instance_of(Array, new_worker.result_sets)
170
+ assert_empty new_worker.result_sets
171
+ end
172
+
124
173
  test "save! calls the after_save handler" do
125
174
  x = 41
126
175
 
@@ -150,5 +199,261 @@ class BulkInsertWorkerTest < ActiveSupport::TestCase
150
199
 
151
200
  assert_equal "hello", @insert.after_save_callback.()
152
201
  end
153
- end
154
202
 
203
+ test "save! calls the before_save handler" do
204
+ x = 41
205
+
206
+ @insert.before_save do
207
+ x += 1
208
+ end
209
+
210
+ @insert.add ["Yo", 15, false, @now, @now]
211
+ @insert.add ["Hello", 25, true, @now, @now]
212
+ @insert.save!
213
+
214
+ assert_equal 42, x
215
+ end
216
+
217
+ test "before_save stores a block as a proc" do
218
+ @insert.before_save do
219
+ "hello"
220
+ end
221
+
222
+ assert_equal "hello", @insert.before_save_callback.()
223
+ end
224
+
225
+ test "before_save_callback can be set as a proc" do
226
+ @insert.before_save_callback = -> do
227
+ "hello"
228
+ end
229
+
230
+ assert_equal "hello", @insert.before_save_callback.()
231
+ end
232
+
233
+ test "before_save can manipulate the set" do
234
+ @insert.before_save do |set|
235
+ set.reject!{|row| row[0] == "Yo"}
236
+ end
237
+
238
+ @insert.add ["Yo", 15, false, @now, @now]
239
+ @insert.add ["Hello", 25, true, @now, @now]
240
+ @insert.save!
241
+
242
+ yo = Testing.find_by(greeting: 'Yo')
243
+ hello = Testing.find_by(greeting: 'Hello')
244
+
245
+ assert_nil yo
246
+ assert_not_nil hello
247
+ end
248
+
249
+ test "save! doesn't blow up if before_save emptying the set" do
250
+ @insert.before_save do |set|
251
+ set.clear
252
+ end
253
+
254
+ @insert.add ["Yo", 15, false, @now, @now]
255
+ @insert.add ["Hello", 25, true, @now, @now]
256
+ @insert.save!
257
+
258
+ yo = Testing.find_by(greeting: 'Yo')
259
+ hello = Testing.find_by(greeting: 'Hello')
260
+
261
+ assert_nil yo
262
+ assert_nil hello
263
+ end
264
+
265
+ test "adapter dependent default methods" do
266
+ assert_equal @insert.adapter_name, 'SQLite'
267
+ assert_equal @insert.insert_sql_statement, "INSERT INTO \"testings\" (\"greeting\",\"age\",\"happy\",\"created_at\",\"updated_at\",\"color\") VALUES "
268
+
269
+ @insert.add ["Yo", 15, false, nil, nil]
270
+ assert_equal @insert.compose_insert_query, "INSERT INTO \"testings\" (\"greeting\",\"age\",\"happy\",\"created_at\",\"updated_at\",\"color\") VALUES ('Yo',15,0,NULL,NULL,'chartreuse')"
271
+ end
272
+
273
+ test "adapter dependent mysql methods" do
274
+ connection = Testing.connection
275
+ connection.stub :adapter_name, 'MySQL' do
276
+ mysql_worker = BulkInsert::Worker.new(
277
+ connection,
278
+ Testing.table_name,
279
+ 'id',
280
+ %w(greeting age happy created_at updated_at color),
281
+ 500, # batch size
282
+ true # ignore
283
+ )
284
+
285
+ assert_equal mysql_worker.adapter_name, 'MySQL'
286
+ assert_equal (mysql_worker.adapter_name == 'MySQL'), true
287
+ assert_equal mysql_worker.ignore, true
288
+ assert_equal ((mysql_worker.adapter_name == 'MySQL') & mysql_worker.ignore), true
289
+
290
+ mysql_worker.add ["Yo", 15, false, nil, nil]
291
+
292
+ assert_equal mysql_worker.compose_insert_query, "INSERT IGNORE INTO \"testings\" (\"greeting\",\"age\",\"happy\",\"created_at\",\"updated_at\",\"color\") VALUES ('Yo',15,0,NULL,NULL,'chartreuse')"
293
+ end
294
+ end
295
+
296
+ test "adapter dependent mysql methods work for mysql2" do
297
+ connection = Testing.connection
298
+ connection.stub :adapter_name, 'Mysql2' do
299
+ mysql_worker = BulkInsert::Worker.new(
300
+ connection,
301
+ Testing.table_name,
302
+ 'id',
303
+ %w(greeting age happy created_at updated_at color),
304
+ 500, # batch size
305
+ true, # ignore
306
+ true) # update_duplicates
307
+
308
+ assert_equal mysql_worker.adapter_name, 'Mysql2'
309
+ assert mysql_worker.ignore
310
+
311
+ mysql_worker.add ["Yo", 15, false, nil, nil]
312
+
313
+ assert_equal mysql_worker.compose_insert_query, "INSERT IGNORE 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`)"
314
+ end
315
+ end
316
+
317
+ test "adapter dependent Mysql2Spatial methods" do
318
+ connection = Testing.connection
319
+ connection.stub :adapter_name, 'Mysql2Spatial' do
320
+ mysql_worker = BulkInsert::Worker.new(
321
+ connection,
322
+ Testing.table_name,
323
+ 'id',
324
+ %w(greeting age happy created_at updated_at color),
325
+ 500, # batch size
326
+ true) # ignore
327
+
328
+ assert_equal mysql_worker.adapter_name, 'Mysql2Spatial'
329
+
330
+ mysql_worker.add ["Yo", 15, false, nil, nil]
331
+
332
+ assert_equal mysql_worker.compose_insert_query, "INSERT IGNORE INTO \"testings\" (\"greeting\",\"age\",\"happy\",\"created_at\",\"updated_at\",\"color\") VALUES ('Yo',15,0,NULL,NULL,'chartreuse')"
333
+ end
334
+ end
335
+
336
+ test "adapter dependent postgresql methods" do
337
+ connection = Testing.connection
338
+ connection.stub :adapter_name, 'PostgreSQL' do
339
+ pgsql_worker = BulkInsert::Worker.new(
340
+ connection,
341
+ Testing.table_name,
342
+ 'id',
343
+ %w(greeting age happy created_at updated_at color),
344
+ 500, # batch size
345
+ true, # ignore
346
+ false, # update duplicates
347
+ true # return primary keys
348
+ )
349
+
350
+ pgsql_worker.add ["Yo", 15, false, nil, nil]
351
+
352
+ 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 DO NOTHING RETURNING id"
353
+ end
354
+ end
355
+
356
+ test "adapter dependent postgresql methods (no ignore, no update_duplicates)" do
357
+ connection = Testing.connection
358
+ connection.stub :adapter_name, 'PostgreSQL' do
359
+ pgsql_worker = BulkInsert::Worker.new(
360
+ connection,
361
+ Testing.table_name,
362
+ 'id',
363
+ %w(greeting age happy created_at updated_at color),
364
+ 500, # batch size
365
+ false, # ignore
366
+ false, # update duplicates
367
+ true # return primary keys
368
+ )
369
+
370
+ pgsql_worker.add ["Yo", 15, false, nil, nil]
371
+
372
+ 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') RETURNING id"
373
+ end
374
+ end
375
+
376
+ test "adapter dependent postgresql methods (with update_duplicates)" do
377
+ connection = Testing.connection
378
+ connection.stub :adapter_name, 'PostgreSQL' do
379
+ pgsql_worker = BulkInsert::Worker.new(
380
+ connection,
381
+ Testing.table_name,
382
+ 'id',
383
+ %w(greeting age happy created_at updated_at color),
384
+ 500, # batch size
385
+ false, # ignore
386
+ %w(greeting age happy), # update duplicates
387
+ true # return primary keys
388
+ )
389
+ pgsql_worker.add ["Yo", 15, false, nil, nil]
390
+
391
+ 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, age, happy) DO UPDATE SET greeting=EXCLUDED.greeting, age=EXCLUDED.age, happy=EXCLUDED.happy, created_at=EXCLUDED.created_at, updated_at=EXCLUDED.updated_at, color=EXCLUDED.color RETURNING id"
392
+ end
393
+ end
394
+
395
+ test "adapter dependent PostGIS methods" do
396
+ connection = Testing.connection
397
+ connection.stub :adapter_name, 'PostGIS' do
398
+ pgsql_worker = BulkInsert::Worker.new(
399
+ connection,
400
+ Testing.table_name,
401
+ 'id',
402
+ %w(greeting age happy created_at updated_at color),
403
+ 500, # batch size
404
+ true, # ignore
405
+ false, # update duplicates
406
+ true # return primary keys
407
+ )
408
+ pgsql_worker.add ["Yo", 15, false, nil, nil]
409
+
410
+ 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 DO NOTHING RETURNING id"
411
+ end
412
+ end
413
+
414
+ test "adapter dependent sqlite3 methods (with lowercase adapter name)" do
415
+ sqlite_worker = BulkInsert::Worker.new(
416
+ Testing.connection,
417
+ Testing.table_name,
418
+ 'id',
419
+ %w(greeting age happy created_at updated_at color),
420
+ 500, # batch size
421
+ true) # ignore
422
+ sqlite_worker.adapter_name = 'sqlite3'
423
+ sqlite_worker.add ["Yo", 15, false, nil, nil]
424
+
425
+ assert_equal sqlite_worker.compose_insert_query, "INSERT OR IGNORE INTO \"testings\" (\"greeting\",\"age\",\"happy\",\"created_at\",\"updated_at\",\"color\") VALUES ('Yo',15,0,NULL,NULL,'chartreuse')"
426
+ end
427
+
428
+ test "adapter dependent sqlite3 methods (with stylecase adapter name)" do
429
+ sqlite_worker = BulkInsert::Worker.new(
430
+ Testing.connection,
431
+ Testing.table_name,
432
+ 'id',
433
+ %w(greeting age happy created_at updated_at color),
434
+ 500, # batch size
435
+ true) # ignore
436
+ sqlite_worker.adapter_name = 'SQLite'
437
+ sqlite_worker.add ["Yo", 15, false, nil, nil]
438
+
439
+ assert_equal sqlite_worker.compose_insert_query, "INSERT OR IGNORE INTO \"testings\" (\"greeting\",\"age\",\"happy\",\"created_at\",\"updated_at\",\"color\") VALUES ('Yo',15,0,NULL,NULL,'chartreuse')"
440
+ end
441
+
442
+ test "mysql adapter can update duplicates" do
443
+ connection = Testing.connection
444
+ connection.stub :adapter_name, 'MySQL' do
445
+ mysql_worker = BulkInsert::Worker.new(
446
+ connection,
447
+ Testing.table_name,
448
+ 'id',
449
+ %w(greeting age happy created_at updated_at color),
450
+ 500, # batch size
451
+ false, # ignore
452
+ true # update_duplicates
453
+ )
454
+ mysql_worker.add ["Yo", 15, false, nil, nil]
455
+
456
+ 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`)"
457
+ end
458
+ end
459
+ end