bulk_insert 1.6.0 → 1.9.0

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: 617835c3fcaa4df1727930d799629a1c6c327537
4
- data.tar.gz: d329fbca56f8be8dc7f79a976843774331eec70e
2
+ SHA256:
3
+ metadata.gz: 385a28475e8a07cdf1cad85aacf241734ce65ed922648417c7beda2b88a5b3c9
4
+ data.tar.gz: a14a05da23f256f3d30cf69fab032ec72c5953fe1ddb3ccc45a0673bef717266
5
5
  SHA512:
6
- metadata.gz: 249e90f483244c07d49843619ebd7248b34df29510c2382e1b5f65d3479296e6a1c768d2b00e1d9cc1d3beb58413b2396fa7a13e397ca5b826ec4d15801fde1a
7
- data.tar.gz: 3e1d944d3a19fc3b5803c44118166c96348092bfe1c5651bb1c67d090708ec54dd9c46385404cad4db7b8265e03517dbf7774e77c0cce5dfb247cbbf414a992c
6
+ metadata.gz: cbe45a91a166d6faa3e2899a4fad7ac1af8a6e050249adbc810121ddc3000f0786c277b8c240809935bbb80178bbb1df43044dda32a02cea87c0d5d956e3cee1
7
+ data.tar.gz: 165de20f26c00dfcbe605866576f731199f692b7bccb07e671b8dcb9541552e39d0940a9f6fff73701286586f909e7be1254be6df157f41f76e83b105e88b9f2
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,24 +148,72 @@ Book.bulk_insert(*destination_columns, ignore: true) do |worker|
149
148
  end
150
149
  ```
151
150
 
152
- ### Update Duplicates (MySQL)
151
+ ### Update Duplicates (MySQL, PostgreSQL)
153
152
 
154
153
  If you don't want to ignore duplicate rows but instead want to update them
155
154
  then you can use the _update_duplicates_ option. Set this option to true
156
- and when a duplicate row is found the row will be updated with your new
157
- values. Default value for this option is false.
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
158
 
159
159
  ```ruby
160
160
  destination_columns = [:title, :author]
161
161
 
162
- # Update duplicate rows
162
+ # Update duplicate rows (MySQL)
163
163
  Book.bulk_insert(*destination_columns, update_duplicates: true) do |worker|
164
164
  worker.add(...)
165
165
  worker.add(...)
166
166
  # ...
167
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
168
192
  ```
169
193
 
194
+ ## Ruby and Rails Versions Supported
195
+
196
+ > :warning: The scope of this gem may be somehow covered natively by the `.insert_all` API
197
+ > introduced by [Rails 6](https://apidock.com/rails/v6.0.0/ActiveRecord/Persistence/ClassMethods/insert_all).
198
+ > This gem represents the state of art for rails version < 6 and it is still open to
199
+ > further developments for more recent versions.
200
+
201
+ The current CI prevents regressions on the following versions:
202
+
203
+ ruby / rails | `~>3` | `~>4` | `~>5` | `~>6`
204
+ :-----------:|-------|-------|-------|------
205
+ 2.2 | yes | yes | no | no
206
+ 2.3 | yes | yes | yes | no
207
+ 2.4 | no | yes | yes | no
208
+ 2.5 | no | no | yes | yes
209
+ 2.6 | no | no | yes | yes
210
+ 2.7 | no | no | yes | yes
211
+
212
+ The adapters covered in the CI are:
213
+ * sqlite
214
+ * mysql
215
+ * postgresql
216
+
170
217
 
171
218
  ## License
172
219
 
data/lib/bulk_insert.rb CHANGED
@@ -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, update_duplicates: 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, update_duplicates)
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,6 +1,6 @@
1
1
  module BulkInsert
2
2
  MAJOR = 1
3
- MINOR = 6
3
+ MINOR = 9
4
4
  TINY = 0
5
5
 
6
6
  VERSION = [MAJOR, MINOR, TINY].join(".")
@@ -1,3 +1,5 @@
1
+ require_relative 'statement_adapters'
2
+
1
3
  module BulkInsert
2
4
  class Worker
3
5
  attr_reader :connection
@@ -5,9 +7,11 @@ module BulkInsert
5
7
  attr_accessor :before_save_callback
6
8
  attr_accessor :after_save_callback
7
9
  attr_accessor :adapter_name
8
- attr_reader :ignore, :update_duplicates
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)
9
14
 
10
- def initialize(connection, table_name, column_names, set_size=500, ignore=false, update_duplicates=false)
11
15
  @connection = connection
12
16
  @set_size = set_size
13
17
 
@@ -15,10 +19,12 @@ module BulkInsert
15
19
  # INSERT IGNORE only fails inserts with duplicate keys or unallowed nulls not the whole set of inserts
16
20
  @ignore = ignore
17
21
  @update_duplicates = update_duplicates
22
+ @return_primary_keys = return_primary_keys
18
23
 
19
24
  columns = connection.columns(table_name)
20
25
  column_map = columns.inject({}) { |h, c| h.update(c.name => c) }
21
26
 
27
+ @primary_key = primary_key
22
28
  @columns = column_names.map { |name| column_map[name.to_s] }
23
29
  @table_name = connection.quote_table_name(table_name)
24
30
  @column_names = column_names.map { |name| connection.quote_column_name(name) }.join(",")
@@ -26,6 +32,7 @@ module BulkInsert
26
32
  @before_save_callback = nil
27
33
  @after_save_callback = nil
28
34
 
35
+ @result_sets = []
29
36
  @set = []
30
37
  end
31
38
 
@@ -76,7 +83,7 @@ module BulkInsert
76
83
  def save!
77
84
  if pending?
78
85
  @before_save_callback.(@set) if @before_save_callback
79
- compose_insert_query.tap { |query| @connection.execute(query) if query }
86
+ execute_query
80
87
  @after_save_callback.() if @after_save_callback
81
88
  @set.clear
82
89
  end
@@ -84,6 +91,24 @@ module BulkInsert
84
91
  self
85
92
  end
86
93
 
94
+ def execute_query
95
+ if query = compose_insert_query
96
+
97
+ # Return primary key support broke mysql compatibility
98
+ # with rails < 5 mysql adapter. (see issue #41)
99
+ if ActiveRecord::VERSION::STRING < "5.0.0" && @statement_adapter.is_a?(StatementAdapters::MySQLAdapter)
100
+ # raise an exception for unsupported return_primary_keys
101
+ raise ArgumentError.new("BulkInsert does not support @return_primary_keys for mysql and rails < 5") if @return_primary_keys
102
+
103
+ # restore v1.6 query execution
104
+ @connection.execute(query)
105
+ else
106
+ result_set = @connection.exec_query(query)
107
+ @result_sets.push(result_set) if @return_primary_keys
108
+ end
109
+ end
110
+ end
111
+
87
112
  def compose_insert_query
88
113
  sql = insert_sql_statement
89
114
  @now = Time.now
@@ -95,7 +120,10 @@ module BulkInsert
95
120
  value = @now if value == :__timestamp_placeholder
96
121
 
97
122
  if ActiveRecord::VERSION::STRING >= "5.0.0"
98
- value = @connection.type_cast_from_column(column, value) if column
123
+ if column
124
+ type = @connection.lookup_cast_type_from_column(column)
125
+ value = type.serialize(value)
126
+ end
99
127
  values << @connection.quote(value)
100
128
  else
101
129
  values << @connection.quote(value, column)
@@ -106,7 +134,8 @@ module BulkInsert
106
134
 
107
135
  if !rows.empty?
108
136
  sql << rows.join(",")
109
- sql << on_conflict_statement
137
+ sql << @statement_adapter.on_conflict_statement(@columns, ignore, update_duplicates)
138
+ sql << @statement_adapter.primary_key_return_statement(@primary_key) if @return_primary_keys
110
139
  sql
111
140
  else
112
141
  false
@@ -114,33 +143,8 @@ module BulkInsert
114
143
  end
115
144
 
116
145
  def insert_sql_statement
146
+ insert_ignore = @ignore ? @statement_adapter.insert_ignore_statement : ''
117
147
  "INSERT #{insert_ignore} INTO #{@table_name} (#{@column_names}) VALUES "
118
148
  end
119
-
120
- def insert_ignore
121
- if ignore
122
- case adapter_name
123
- when /^mysql/i
124
- 'IGNORE'
125
- when /\ASQLite/i # SQLite
126
- 'OR IGNORE'
127
- else
128
- '' # Not supported
129
- end
130
- end
131
- end
132
-
133
- def on_conflict_statement
134
- if (adapter_name =~ /\APost(?:greSQL|GIS)/i && ignore )
135
- ' ON CONFLICT DO NOTHING'
136
- elsif adapter_name =~ /^mysql/i && update_duplicates
137
- update_values = @columns.map do |column|
138
- "#{column.name}=VALUES(#{column.name})"
139
- end.join(', ')
140
- ' ON DUPLICATE KEY UPDATE ' + update_values
141
- else
142
- ''
143
- end
144
- end
145
149
  end
146
150
  end
@@ -1,12 +1,17 @@
1
+ require 'minitest/mock'
1
2
  require 'test_helper'
3
+ require 'connection_mocks'
2
4
 
3
5
  class BulkInsertWorkerTest < ActiveSupport::TestCase
6
+ include ConnectionMocks
7
+
4
8
  setup do
5
9
  @insert = BulkInsert::Worker.new(
6
10
  Testing.connection,
7
11
  Testing.table_name,
12
+ 'id',
8
13
  %w(greeting age happy created_at updated_at color))
9
- @now = Time.now
14
+ @now = Time.now.utc
10
15
  end
11
16
 
12
17
  test "empty insert is not pending" do
@@ -35,8 +40,8 @@ class BulkInsertWorkerTest < ActiveSupport::TestCase
35
40
  @insert.save!
36
41
 
37
42
  record = Testing.first
38
- assert_operator record.created_at, :>=, now
39
- assert_operator record.updated_at, :>=, now
43
+ assert_operator record.created_at.to_i, :>=, now.to_i
44
+ assert_operator record.updated_at.to_i, :>=, now.to_i
40
45
  end
41
46
 
42
47
  test "default timestamp columns should be equivalent for the entire batch" do
@@ -109,8 +114,8 @@ class BulkInsertWorkerTest < ActiveSupport::TestCase
109
114
  @insert.add ["Hello", 25, true, @now, @now]
110
115
  @insert.save!
111
116
 
112
- yo = Testing.find_by(greeting: 'Yo')
113
- hello = Testing.find_by(greeting: 'Hello')
117
+ yo = Testing.where(greeting: 'Yo').first
118
+ hello = Testing.where(greeting: 'Hello').first
114
119
 
115
120
  assert_not_nil yo
116
121
  assert_equal 15, yo.age
@@ -121,6 +126,57 @@ class BulkInsertWorkerTest < ActiveSupport::TestCase
121
126
  assert_equal true, hello.happy?
122
127
  end
123
128
 
129
+ test "save! does not add to result sets when not returning primary keys" do
130
+ @insert.add greeting: "first"
131
+ @insert.add greeting: "second"
132
+ @insert.save!
133
+
134
+ assert_equal 0, @insert.result_sets.count
135
+ end
136
+
137
+
138
+ test "save! adds to result sets when returning primary keys" do
139
+ worker = BulkInsert::Worker.new(
140
+ Testing.connection,
141
+ Testing.table_name,
142
+ 'id',
143
+ %w(greeting age happy created_at updated_at color),
144
+ 500,
145
+ false,
146
+ false,
147
+ true
148
+ )
149
+
150
+ # return_primary_keys is not supported for mysql and rails < 5
151
+ # skip is not supported in the minitest version used for testing rails 3
152
+ return if ActiveRecord::VERSION::STRING < "5.0.0" && worker.adapter_name =~ /^mysql/i
153
+
154
+ assert_no_difference -> { worker.result_sets.count } do
155
+ worker.save!
156
+ end
157
+
158
+ worker.add greeting: "first"
159
+ worker.add greeting: "second"
160
+ worker.save!
161
+ assert_equal 1, worker.result_sets.count
162
+
163
+ worker.add greeting: "third"
164
+ worker.add greeting: "fourth"
165
+ worker.save!
166
+ assert_equal 2, worker.result_sets.count
167
+ end
168
+
169
+ test "initialized with empty result sets array" do
170
+ new_worker = BulkInsert::Worker.new(
171
+ Testing.connection,
172
+ Testing.table_name,
173
+ 'id',
174
+ %w(greeting age happy created_at updated_at color)
175
+ )
176
+ assert_instance_of(Array, new_worker.result_sets)
177
+ assert_empty new_worker.result_sets
178
+ end
179
+
124
180
  test "save! calls the after_save handler" do
125
181
  x = 41
126
182
 
@@ -190,8 +246,8 @@ class BulkInsertWorkerTest < ActiveSupport::TestCase
190
246
  @insert.add ["Hello", 25, true, @now, @now]
191
247
  @insert.save!
192
248
 
193
- yo = Testing.find_by(greeting: 'Yo')
194
- hello = Testing.find_by(greeting: 'Hello')
249
+ yo = Testing.where(greeting: 'Yo').first
250
+ hello = Testing.where(greeting: 'Hello').first
195
251
 
196
252
  assert_nil yo
197
253
  assert_not_nil hello
@@ -206,137 +262,256 @@ class BulkInsertWorkerTest < ActiveSupport::TestCase
206
262
  @insert.add ["Hello", 25, true, @now, @now]
207
263
  @insert.save!
208
264
 
209
- yo = Testing.find_by(greeting: 'Yo')
210
- hello = Testing.find_by(greeting: 'Hello')
265
+ yo = Testing.where(greeting: 'Yo').first
266
+ hello = Testing.where(greeting: 'Hello').first
211
267
 
212
268
  assert_nil yo
213
269
  assert_nil hello
214
270
  end
215
271
 
216
- test "adapter dependent default methods" do
217
- assert_equal @insert.adapter_name, 'SQLite'
218
- assert_equal @insert.insert_sql_statement, "INSERT INTO \"testings\" (\"greeting\",\"age\",\"happy\",\"created_at\",\"updated_at\",\"color\") VALUES "
272
+ test "adapter dependent SQLite methods" do
273
+ connection = Testing.connection
274
+ stub_connection_if_needed(connection, 'SQLite') do
275
+ sqlite_worker = BulkInsert::Worker.new(
276
+ connection,
277
+ Testing.table_name,
278
+ 'id',
279
+ %w(greeting age happy created_at updated_at color),
280
+ 500 # batch size
281
+ )
282
+
283
+ assert_equal sqlite_worker.adapter_name, 'SQLite'
284
+ assert_equal sqlite_worker.insert_sql_statement, "INSERT INTO \"testings\" (\"greeting\",\"age\",\"happy\",\"created_at\",\"updated_at\",\"color\") VALUES "
219
285
 
220
- @insert.add ["Yo", 15, false, nil, nil]
221
- assert_equal @insert.compose_insert_query, "INSERT INTO \"testings\" (\"greeting\",\"age\",\"happy\",\"created_at\",\"updated_at\",\"color\") VALUES ('Yo',15,'f',NULL,NULL,'chartreuse')"
286
+ sqlite_worker.add ["Yo", 15, false, nil, nil]
287
+ assert_equal sqlite_worker.compose_insert_query, "INSERT INTO \"testings\" (\"greeting\",\"age\",\"happy\",\"created_at\",\"updated_at\",\"color\") VALUES ('Yo',15,0,NULL,NULL,'chartreuse')"
288
+ end
222
289
  end
223
290
 
224
- test "adapter dependent mysql methods" do
225
- mysql_worker = BulkInsert::Worker.new(
226
- Testing.connection,
227
- Testing.table_name,
228
- %w(greeting age happy created_at updated_at color),
229
- 500, # batch size
230
- true) # ignore
231
- mysql_worker.adapter_name = 'MySQL'
291
+ test "adapter dependent MySQL methods" do
292
+ connection = Testing.connection
293
+ stub_connection_if_needed(connection, 'mysql') do
294
+ mysql_worker = BulkInsert::Worker.new(
295
+ connection,
296
+ Testing.table_name,
297
+ 'id',
298
+ %w(greeting age happy created_at updated_at color),
299
+ 500, # batch size
300
+ true # ignore
301
+ )
232
302
 
233
- assert_equal mysql_worker.adapter_name, 'MySQL'
234
- assert_equal (mysql_worker.adapter_name == 'MySQL'), true
235
- assert_equal mysql_worker.ignore, true
236
- assert_equal ((mysql_worker.adapter_name == 'MySQL') & mysql_worker.ignore), true
303
+ assert_equal mysql_worker.adapter_name, 'mysql'
304
+ assert_equal (mysql_worker.adapter_name == 'mysql'), true
305
+ assert_equal mysql_worker.ignore, true
306
+ assert_equal ((mysql_worker.adapter_name == 'mysql') & mysql_worker.ignore), true
237
307
 
238
- mysql_worker.add ["Yo", 15, false, nil, nil]
308
+ mysql_worker.add ["Yo", 15, false, nil, nil]
239
309
 
240
- assert_equal mysql_worker.compose_insert_query, "INSERT IGNORE INTO \"testings\" (\"greeting\",\"age\",\"happy\",\"created_at\",\"updated_at\",\"color\") VALUES ('Yo',15,'f',NULL,NULL,'chartreuse')"
310
+ assert_statement_adapter mysql_worker, 'BulkInsert::StatementAdapters::MySQLAdapter'
311
+ assert_equal mysql_worker.compose_insert_query, "INSERT IGNORE INTO `testings` (`greeting`,`age`,`happy`,`created_at`,`updated_at`,`color`) VALUES ('Yo',15,FALSE,NULL,NULL,'chartreuse')"
312
+ end
241
313
  end
242
314
 
243
315
  test "adapter dependent mysql methods work for mysql2" do
244
- mysql_worker = BulkInsert::Worker.new(
245
- Testing.connection,
246
- Testing.table_name,
247
- %w(greeting age happy created_at updated_at color),
248
- 500, # batch size
249
- true, # ignore
250
- true) # update_duplicates
251
- mysql_worker.adapter_name = 'Mysql2'
252
-
253
- assert_equal mysql_worker.adapter_name, 'Mysql2'
254
- assert mysql_worker.ignore
255
-
256
- mysql_worker.add ["Yo", 15, false, nil, nil]
257
-
258
- assert_equal mysql_worker.compose_insert_query, "INSERT IGNORE INTO \"testings\" (\"greeting\",\"age\",\"happy\",\"created_at\",\"updated_at\",\"color\") VALUES ('Yo',15,'f',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)"
316
+ connection = Testing.connection
317
+ stub_connection_if_needed(connection, 'mysql2') do
318
+ mysql_worker = BulkInsert::Worker.new(
319
+ connection,
320
+ Testing.table_name,
321
+ 'id',
322
+ %w(greeting age happy created_at updated_at color),
323
+ 500, # batch size
324
+ true, # ignore
325
+ true) # update_duplicates
326
+
327
+ assert_equal mysql_worker.adapter_name, 'mysql2'
328
+ assert mysql_worker.ignore
329
+
330
+ mysql_worker.add ["Yo", 15, false, nil, nil]
331
+
332
+ assert_statement_adapter mysql_worker, 'BulkInsert::StatementAdapters::MySQLAdapter'
333
+ assert_equal mysql_worker.compose_insert_query, "INSERT IGNORE INTO `testings` (`greeting`,`age`,`happy`,`created_at`,`updated_at`,`color`) VALUES ('Yo',15,FALSE,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`)"
334
+ end
259
335
  end
260
336
 
261
337
  test "adapter dependent Mysql2Spatial methods" do
262
- mysql_worker = BulkInsert::Worker.new(
263
- Testing.connection,
264
- Testing.table_name,
265
- %w(greeting age happy created_at updated_at color),
266
- 500, # batch size
267
- true) # ignore
268
- mysql_worker.adapter_name = 'Mysql2Spatial'
269
-
270
- assert_equal mysql_worker.adapter_name, 'Mysql2Spatial'
271
-
272
- mysql_worker.add ["Yo", 15, false, nil, nil]
273
-
274
- assert_equal mysql_worker.compose_insert_query, "INSERT IGNORE INTO \"testings\" (\"greeting\",\"age\",\"happy\",\"created_at\",\"updated_at\",\"color\") VALUES ('Yo',15,'f',NULL,NULL,'chartreuse')"
338
+ connection = Testing.connection
339
+ stub_connection_if_needed(connection, 'mysql2spatial') do
340
+ mysql_worker = BulkInsert::Worker.new(
341
+ connection,
342
+ Testing.table_name,
343
+ 'id',
344
+ %w(greeting age happy created_at updated_at color),
345
+ 500, # batch size
346
+ true) # ignore
347
+
348
+ assert_equal mysql_worker.adapter_name, 'mysql2spatial'
349
+
350
+ mysql_worker.add ["Yo", 15, false, nil, nil]
351
+
352
+ assert_statement_adapter mysql_worker, 'BulkInsert::StatementAdapters::MySQLAdapter'
353
+ assert_equal mysql_worker.compose_insert_query, "INSERT IGNORE INTO `testings` (`greeting`,`age`,`happy`,`created_at`,`updated_at`,`color`) VALUES ('Yo',15,FALSE,NULL,NULL,'chartreuse')"
354
+ end
275
355
  end
276
356
 
277
357
  test "adapter dependent postgresql methods" do
278
- pgsql_worker = BulkInsert::Worker.new(
279
- Testing.connection,
280
- Testing.table_name,
281
- %w(greeting age happy created_at updated_at color),
282
- 500, # batch size
283
- true) # ignore
284
- pgsql_worker.adapter_name = 'PostgreSQL'
285
- pgsql_worker.add ["Yo", 15, false, nil, nil]
358
+ connection = Testing.connection
359
+ stub_connection_if_needed(connection, 'PostgreSQL') do
360
+ pgsql_worker = BulkInsert::Worker.new(
361
+ connection,
362
+ Testing.table_name,
363
+ 'id',
364
+ %w(greeting age happy created_at updated_at color),
365
+ 500, # batch size
366
+ true, # ignore
367
+ false, # update duplicates
368
+ true # return primary keys
369
+ )
370
+
371
+ pgsql_worker.add ["Yo", 15, false, nil, nil]
372
+
373
+ assert_statement_adapter pgsql_worker, 'BulkInsert::StatementAdapters::PostgreSQLAdapter'
374
+
375
+ if ActiveRecord::VERSION::STRING >= "5.0.0"
376
+ assert_equal pgsql_worker.compose_insert_query, "INSERT INTO \"testings\" (\"greeting\",\"age\",\"happy\",\"created_at\",\"updated_at\",\"color\") VALUES ('Yo',15,FALSE,NULL,NULL,'chartreuse') ON CONFLICT DO NOTHING RETURNING id"
377
+ else
378
+ assert_equal pgsql_worker.compose_insert_query, "INSERT INTO \"testings\" (\"greeting\",\"age\",\"happy\",\"created_at\",\"updated_at\",\"color\") VALUES ('Yo',15,'f',NULL,NULL,'chartreuse') ON CONFLICT DO NOTHING RETURNING id"
379
+ end
380
+ end
381
+ end
286
382
 
287
- assert_equal pgsql_worker.compose_insert_query, "INSERT INTO \"testings\" (\"greeting\",\"age\",\"happy\",\"created_at\",\"updated_at\",\"color\") VALUES ('Yo',15,'f',NULL,NULL,'chartreuse') ON CONFLICT DO NOTHING"
383
+ test "adapter dependent postgresql methods (no ignore, no update_duplicates)" do
384
+ connection = Testing.connection
385
+ stub_connection_if_needed(connection, 'PostgreSQL') do
386
+ pgsql_worker = BulkInsert::Worker.new(
387
+ connection,
388
+ Testing.table_name,
389
+ 'id',
390
+ %w(greeting age happy created_at updated_at color),
391
+ 500, # batch size
392
+ false, # ignore
393
+ false, # update duplicates
394
+ true # return primary keys
395
+ )
396
+
397
+ pgsql_worker.add ["Yo", 15, false, nil, nil]
398
+
399
+ assert_statement_adapter pgsql_worker, 'BulkInsert::StatementAdapters::PostgreSQLAdapter'
400
+
401
+ if ActiveRecord::VERSION::STRING >= "5.0.0"
402
+ assert_equal pgsql_worker.compose_insert_query, "INSERT INTO \"testings\" (\"greeting\",\"age\",\"happy\",\"created_at\",\"updated_at\",\"color\") VALUES ('Yo',15,FALSE,NULL,NULL,'chartreuse') RETURNING id"
403
+ else
404
+ assert_equal pgsql_worker.compose_insert_query, "INSERT INTO \"testings\" (\"greeting\",\"age\",\"happy\",\"created_at\",\"updated_at\",\"color\") VALUES ('Yo',15,'f',NULL,NULL,'chartreuse') RETURNING id"
405
+ end
406
+ end
288
407
  end
289
408
 
290
- test "adapter dependent PostGIS methods" do
291
- pgsql_worker = BulkInsert::Worker.new(
292
- Testing.connection,
293
- Testing.table_name,
294
- %w(greeting age happy created_at updated_at color),
295
- 500, # batch size
296
- true) # ignore
297
- pgsql_worker.adapter_name = 'PostGIS'
298
- pgsql_worker.add ["Yo", 15, false, nil, nil]
409
+ test "adapter dependent postgresql methods (with update_duplicates)" do
410
+ connection = Testing.connection
411
+ stub_connection_if_needed(connection, 'PostgreSQL') do
412
+ pgsql_worker = BulkInsert::Worker.new(
413
+ connection,
414
+ Testing.table_name,
415
+ 'id',
416
+ %w(greeting age happy created_at updated_at color),
417
+ 500, # batch size
418
+ false, # ignore
419
+ %w(greeting age happy), # update duplicates
420
+ true # return primary keys
421
+ )
422
+ pgsql_worker.add ["Yo", 15, false, nil, nil]
423
+
424
+ assert_statement_adapter pgsql_worker, 'BulkInsert::StatementAdapters::PostgreSQLAdapter'
425
+
426
+ if ActiveRecord::VERSION::STRING >= "5.0.0"
427
+ assert_equal pgsql_worker.compose_insert_query, "INSERT INTO \"testings\" (\"greeting\",\"age\",\"happy\",\"created_at\",\"updated_at\",\"color\") VALUES ('Yo',15,FALSE,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"
428
+ else
429
+ assert_equal pgsql_worker.compose_insert_query, "INSERT INTO \"testings\" (\"greeting\",\"age\",\"happy\",\"created_at\",\"updated_at\",\"color\") VALUES ('Yo',15,'f',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"
430
+ end
431
+ end
432
+ end
299
433
 
300
- assert_equal pgsql_worker.compose_insert_query, "INSERT INTO \"testings\" (\"greeting\",\"age\",\"happy\",\"created_at\",\"updated_at\",\"color\") VALUES ('Yo',15,'f',NULL,NULL,'chartreuse') ON CONFLICT DO NOTHING"
434
+ test "adapter dependent PostGIS methods" do
435
+ connection = Testing.connection
436
+ stub_connection_if_needed(connection, 'postgis') do
437
+ pgsql_worker = BulkInsert::Worker.new(
438
+ connection,
439
+ Testing.table_name,
440
+ 'id',
441
+ %w(greeting age happy created_at updated_at color),
442
+ 500, # batch size
443
+ true, # ignore
444
+ false, # update duplicates
445
+ true # return primary keys
446
+ )
447
+ pgsql_worker.add ["Yo", 15, false, nil, nil]
448
+
449
+ assert_statement_adapter pgsql_worker, 'BulkInsert::StatementAdapters::PostgreSQLAdapter'
450
+
451
+ if ActiveRecord::VERSION::STRING >= "5.0.0"
452
+ assert_equal pgsql_worker.compose_insert_query, "INSERT INTO \"testings\" (\"greeting\",\"age\",\"happy\",\"created_at\",\"updated_at\",\"color\") VALUES ('Yo',15,FALSE,NULL,NULL,'chartreuse') ON CONFLICT DO NOTHING RETURNING id"
453
+ else
454
+ assert_equal pgsql_worker.compose_insert_query, "INSERT INTO \"testings\" (\"greeting\",\"age\",\"happy\",\"created_at\",\"updated_at\",\"color\") VALUES ('Yo',15,'f',NULL,NULL,'chartreuse') ON CONFLICT DO NOTHING RETURNING id"
455
+ end
456
+ end
301
457
  end
302
458
 
303
459
  test "adapter dependent sqlite3 methods (with lowercase adapter name)" do
304
- sqlite_worker = BulkInsert::Worker.new(
305
- Testing.connection,
306
- Testing.table_name,
307
- %w(greeting age happy created_at updated_at color),
308
- 500, # batch size
309
- true) # ignore
310
- sqlite_worker.adapter_name = 'sqlite3'
311
- sqlite_worker.add ["Yo", 15, false, nil, nil]
312
-
313
- assert_equal sqlite_worker.compose_insert_query, "INSERT OR IGNORE INTO \"testings\" (\"greeting\",\"age\",\"happy\",\"created_at\",\"updated_at\",\"color\") VALUES ('Yo',15,'f',NULL,NULL,'chartreuse')"
460
+ connection = Testing.connection
461
+ stub_connection_if_needed(connection, 'sqlite3') do
462
+ sqlite_worker = BulkInsert::Worker.new(
463
+ Testing.connection,
464
+ Testing.table_name,
465
+ 'id',
466
+ %w(greeting age happy created_at updated_at color),
467
+ 500, # batch size
468
+ true) # ignore
469
+ sqlite_worker.adapter_name = 'sqlite3'
470
+ sqlite_worker.add ["Yo", 15, false, nil, nil]
471
+
472
+ assert_statement_adapter sqlite_worker, 'BulkInsert::StatementAdapters::SQLiteAdapter'
473
+ 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')"
474
+ end
314
475
  end
315
476
 
316
477
  test "adapter dependent sqlite3 methods (with stylecase adapter name)" do
317
- sqlite_worker = BulkInsert::Worker.new(
318
- Testing.connection,
319
- Testing.table_name,
320
- %w(greeting age happy created_at updated_at color),
321
- 500, # batch size
322
- true) # ignore
323
- sqlite_worker.adapter_name = 'SQLite'
324
- sqlite_worker.add ["Yo", 15, false, nil, nil]
325
-
326
- assert_equal sqlite_worker.compose_insert_query, "INSERT OR IGNORE INTO \"testings\" (\"greeting\",\"age\",\"happy\",\"created_at\",\"updated_at\",\"color\") VALUES ('Yo',15,'f',NULL,NULL,'chartreuse')"
478
+ connection = Testing.connection
479
+ stub_connection_if_needed(connection, 'SQLite') do
480
+ sqlite_worker = BulkInsert::Worker.new(
481
+ connection,
482
+ Testing.table_name,
483
+ 'id',
484
+ %w(greeting age happy created_at updated_at color),
485
+ 500, # batch size
486
+ true) # ignore
487
+ sqlite_worker.adapter_name = 'SQLite'
488
+ sqlite_worker.add ["Yo", 15, false, nil, nil]
489
+
490
+ assert_statement_adapter sqlite_worker, 'BulkInsert::StatementAdapters::SQLiteAdapter'
491
+ 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')"
492
+ end
327
493
  end
328
494
 
329
495
  test "mysql adapter can update duplicates" do
330
- mysql_worker = BulkInsert::Worker.new(
331
- Testing.connection,
332
- Testing.table_name,
333
- %w(greeting age happy created_at updated_at color),
334
- 500, # batch size
335
- false, # ignore
336
- true) # update_duplicates
337
- mysql_worker.adapter_name = 'MySQL'
338
- mysql_worker.add ["Yo", 15, false, nil, nil]
496
+ connection = Testing.connection
497
+ stub_connection_if_needed(connection, 'mysql') do
498
+ mysql_worker = BulkInsert::Worker.new(
499
+ connection,
500
+ Testing.table_name,
501
+ 'id',
502
+ %w(greeting age happy created_at updated_at color),
503
+ 500, # batch size
504
+ false, # ignore
505
+ true # update_duplicates
506
+ )
507
+ mysql_worker.add ["Yo", 15, false, nil, nil]
508
+
509
+ assert_statement_adapter mysql_worker, 'BulkInsert::StatementAdapters::MySQLAdapter'
510
+ assert_equal mysql_worker.compose_insert_query, "INSERT INTO `testings` (`greeting`,`age`,`happy`,`created_at`,`updated_at`,`color`) VALUES ('Yo',15,FALSE,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`)"
511
+ end
512
+ end
339
513
 
340
- assert_equal mysql_worker.compose_insert_query, "INSERT INTO \"testings\" (\"greeting\",\"age\",\"happy\",\"created_at\",\"updated_at\",\"color\") VALUES ('Yo',15,'f',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)"
514
+ def assert_statement_adapter(worker, adapter_name)
515
+ assert_equal worker.instance_variable_get(:@statement_adapter).class.to_s, adapter_name
341
516
  end
342
517
  end