Skip to content

Commit 8c3f148

Browse files
authored
Add SearchFlip::Connection#bulk (#30)
1 parent 4d977f8 commit 8c3f148

File tree

7 files changed

+144
-3
lines changed

7 files changed

+144
-3
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11

22
# CHANGELOG
33

4+
## v3.7.0
5+
6+
* Add `SearchFlip::Connection#bulk` to allow more clean bulk indexing to
7+
multiple indices at once
8+
49
## v3.6.0
510

611
* Support Elasticsearch v8

lib/search_flip/connection.rb

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,51 @@ def index_exists?(index_name)
355355
raise e
356356
end
357357

358+
# Initiates and yields a bulk object, such that index, import, create,
359+
# update and delete requests can be appended to the bulk request. Please
360+
# note that you need to manually pass the desired index name as well as
361+
# type name (depending on the Elasticsearch version) when using #bulk on a
362+
# connection object or Elasticsearch will return an error. After the bulk
363+
# requests are successfully processed all existing indices will
364+
# subsequently be refreshed when auto_refresh is enabled.
365+
#
366+
# @see SearchFlip::Config See SearchFlip::Config for auto_refresh
367+
#
368+
# @example
369+
# connection = SearchFlip::Connection.new
370+
#
371+
# connection.bulk ignore_errors: [409] do |bulk|
372+
# bulk.create comment.id, CommentIndex.serialize(comment),
373+
# _index: CommentIndex.index_name, version: comment.version, version_type: "external_gte"
374+
#
375+
# bulk.delete product.id, _index: ProductIndex.index_name, routing: product.user_id
376+
#
377+
# # ...
378+
# end
379+
#
380+
# @param options [Hash] Specifies options regarding the bulk indexing
381+
# @option options ignore_errors [Array] Specifies an array of http status
382+
# codes that shouldn't raise any exceptions, like eg 409 for conflicts,
383+
# ie when optimistic concurrency control is used.
384+
# @option options raise [Boolean] Prevents any exceptions from being
385+
# raised. Please note that this only applies to the bulk response, not to
386+
# the request in general, such that connection errors, etc will still
387+
# raise.
388+
389+
def bulk(options = {})
390+
default_options = {
391+
http_client: http_client,
392+
bulk_limit: bulk_limit,
393+
bulk_max_mb: bulk_max_mb
394+
}
395+
396+
SearchFlip::Bulk.new("#{base_url}/_bulk", default_options.merge(options)) do |indexer|
397+
yield indexer
398+
end
399+
400+
refresh if SearchFlip::Config[:auto_refresh]
401+
end
402+
358403
# Returns the full Elasticsearch type URL, ie base URL, index name with
359404
# prefix and type name.
360405
#

lib/search_flip/index.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -601,7 +601,7 @@ def delete(scope, options = {}, additional_index_options = {})
601601
scope
602602
end
603603

604-
# Initiates and yields the bulk object, such that index, import, create,
604+
# Initiates and yields a bulk object, such that index, import, create,
605605
# update and delete requests can be appended to the bulk request. Sends a
606606
# refresh request afterwards if auto_refresh is enabled.
607607
#

lib/search_flip/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module SearchFlip
2-
VERSION = "3.6.0"
2+
VERSION = "3.7.0"
33
end

search_flip.gemspec

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ Gem::Specification.new do |spec|
1515

1616
spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
1717
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18-
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
1918
spec.require_paths = ["lib"]
2019

2120
spec.post_install_message = <<~MESSAGE

spec/search_flip/connection_spec.rb

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,96 @@
315315
end
316316
end
317317

318+
describe "#bulk" do
319+
it "imports objects to the specified indices" do
320+
connection = SearchFlip::Connection.new
321+
322+
bulk = proc do
323+
connection.bulk do |indexer|
324+
indexer.index 1, { id: 1 }, _index: ProductIndex.index_name, ** connection.version.to_i < 8 ? { _type: ProductIndex.type_name } : {}
325+
indexer.index 2, { id: 2 }, _index: ProductIndex.index_name, ** connection.version.to_i < 8 ? { _type: ProductIndex.type_name } : {}
326+
indexer.index 1, { id: 1 }, _index: CommentIndex.index_name, ** connection.version.to_i < 8 ? { _type: CommentIndex.type_name } : {}
327+
end
328+
end
329+
330+
expect(&bulk).to(change { CommentIndex.total_count }.by(1).and(change { CommentIndex.total_count }.by(1)))
331+
end
332+
333+
it "raises when no index is given" do
334+
connection = SearchFlip::Connection.new
335+
336+
bulk = proc do
337+
connection.bulk do |indexer|
338+
indexer.index 1, id: 1
339+
end
340+
end
341+
342+
expect(&bulk).to raise_error(SearchFlip::ResponseError)
343+
end
344+
345+
it "respects options" do
346+
connection = SearchFlip::Connection.new
347+
348+
connection.bulk do |indexer|
349+
indexer.index 1, { id: 1 }, _index: ProductIndex.index_name, ** connection.version.to_i < 8 ? { _type: ProductIndex.type_name } : {}
350+
indexer.index 2, { id: 2 }, _index: ProductIndex.index_name, ** connection.version.to_i < 8 ? { _type: ProductIndex.type_name } : {}
351+
end
352+
353+
bulk = proc do
354+
connection.bulk do |indexer|
355+
indexer.index 1, { id: 1 }, _index: ProductIndex.index_name, version: 1, version_type: "external", ** connection.version.to_i < 8 ? { _type: ProductIndex.type_name } : {}
356+
indexer.index 2, { id: 2 }, _index: ProductIndex.index_name, version: 1, version_type: "external", ** connection.version.to_i < 8 ? { _type: ProductIndex.type_name } : {}
357+
end
358+
end
359+
360+
expect(&bulk).to raise_error(SearchFlip::Bulk::Error)
361+
362+
bulk = proc do
363+
connection.bulk ignore_errors: [409] do |indexer|
364+
indexer.index 1, { id: 1 }, _index: ProductIndex.index_name, version: 1, version_type: "external", ** connection.version.to_i < 8 ? { _type: ProductIndex.type_name } : {}
365+
indexer.index 2, { id: 2 }, _index: ProductIndex.index_name, version: 1, version_type: "external", ** connection.version.to_i < 8 ? { _type: ProductIndex.type_name } : {}
366+
end
367+
end
368+
369+
expect(&bulk).not_to(change { ProductIndex.total_count })
370+
end
371+
372+
it "passes default options" do
373+
allow(SearchFlip::Bulk).to receive(:new)
374+
375+
connection = SearchFlip::Connection.new
376+
377+
connection.bulk do |indexer|
378+
indexer.index 1, { id: 1 }, _index: ProductIndex.index_name, ** connection.version.to_i < 8 ? { _type: ProductIndex.type_name } : {}
379+
end
380+
381+
expect(SearchFlip::Bulk).to have_received(:new).with(
382+
anything,
383+
http_client: connection.http_client,
384+
bulk_limit: connection.bulk_limit,
385+
bulk_max_mb: connection.bulk_max_mb
386+
)
387+
end
388+
389+
it "passes custom options" do
390+
allow(SearchFlip::Bulk).to receive(:new)
391+
392+
connection = SearchFlip::Connection.new
393+
394+
options = {
395+
bulk_limit: "bulk limit",
396+
bulk_max_mb: "bulk max mb",
397+
http_client: "http client"
398+
}
399+
400+
connection.bulk(options) do |indexer|
401+
indexer.index 1, { id: 1 }, _index: ProductIndex.index_name, ** connection.version.to_i < 8 ? { _type: ProductIndex.type_name } : {}
402+
end
403+
404+
expect(SearchFlip::Bulk).to have_received(:new).with(anything, options)
405+
end
406+
end
407+
318408
describe "#index_url" do
319409
it "returns the index url for the specified index" do
320410
connection = SearchFlip::Connection.new(base_url: "base_url")

spec/spec_helper.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
TestIndex.delete_index if TestIndex.index_exists?
1717
ProductIndex.match_all.delete
1818
Product.delete_all
19+
CommentIndex.match_all.delete
20+
Comment.delete_all
1921
end
2022
end
2123

0 commit comments

Comments
 (0)