esse 0.2.0 → 0.2.3
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 +4 -4
- data/lib/esse/cli/event_listener.rb +13 -0
- data/lib/esse/cli/generate.rb +53 -14
- data/lib/esse/cli/index/base_operation.rb +5 -13
- data/lib/esse/cli/index/close.rb +1 -1
- data/lib/esse/cli/index/create.rb +1 -1
- data/lib/esse/cli/index/delete.rb +1 -1
- data/lib/esse/cli/index/import.rb +6 -2
- data/lib/esse/cli/index/open.rb +1 -1
- data/lib/esse/cli/index/reset.rb +1 -1
- data/lib/esse/cli/index/update_aliases.rb +2 -2
- data/lib/esse/cli/index/update_mapping.rb +9 -5
- data/lib/esse/cli/index/update_settings.rb +1 -1
- data/lib/esse/cli/index.rb +11 -4
- data/lib/esse/cli/templates/collection.rb.erb +29 -0
- data/lib/esse/cli/templates/config.rb.erb +13 -3
- data/lib/esse/cli/templates/document.rb.erb +34 -0
- data/lib/esse/cli/templates/index.rb.erb +63 -114
- data/lib/esse/cli/templates/mappings.json +27 -0
- data/lib/esse/cli/templates/settings.json +62 -0
- data/lib/esse/cli.rb +5 -0
- data/lib/esse/cluster.rb +93 -12
- data/lib/esse/cluster_engine.rb +42 -0
- data/lib/esse/collection.rb +18 -0
- data/lib/esse/config.rb +14 -2
- data/lib/esse/core.rb +28 -7
- data/lib/esse/deprecations/cluster.rb +27 -0
- data/lib/esse/deprecations/deprecate.rb +29 -0
- data/lib/esse/deprecations/index.rb +37 -0
- data/lib/esse/deprecations/index_backend_delegator.rb +217 -0
- data/lib/esse/deprecations/repository.rb +34 -0
- data/lib/esse/deprecations/repository_backend_delegator.rb +110 -0
- data/lib/esse/deprecations/serializer.rb +14 -0
- data/lib/esse/deprecations.rb +7 -0
- data/lib/esse/document.rb +91 -0
- data/lib/esse/dynamic_template.rb +43 -0
- data/lib/esse/errors.rb +60 -2
- data/lib/esse/events/event.rb +4 -19
- data/lib/esse/events.rb +13 -2
- data/lib/esse/hash_document.rb +38 -0
- data/lib/esse/import/bulk.rb +106 -0
- data/lib/esse/import/request_body.rb +60 -0
- data/lib/esse/index/aliases.rb +50 -0
- data/lib/esse/index/attributes.rb +107 -0
- data/lib/esse/index/base.rb +17 -53
- data/lib/esse/index/documents.rb +236 -0
- data/lib/esse/index/indices.rb +171 -0
- data/lib/esse/index/inheritance.rb +30 -0
- data/lib/esse/index/mappings.rb +6 -19
- data/lib/esse/index/object_document_mapper.rb +36 -0
- data/lib/esse/index/plugins.rb +42 -0
- data/lib/esse/index/search.rb +27 -0
- data/lib/esse/index/settings.rb +2 -2
- data/lib/esse/index/type.rb +51 -11
- data/lib/esse/index.rb +14 -9
- data/lib/esse/index_mapping.rb +10 -2
- data/lib/esse/index_setting.rb +3 -1
- data/lib/esse/null_document.rb +35 -0
- data/lib/esse/plugins.rb +12 -0
- data/lib/esse/primitives/hstring.rb +1 -1
- data/lib/esse/{index_type → repository}/actions.rb +1 -1
- data/lib/esse/repository/documents.rb +13 -0
- data/lib/esse/repository/object_document_mapper.rb +157 -0
- data/lib/esse/repository.rb +17 -0
- data/lib/esse/search/query.rb +105 -0
- data/lib/esse/search/response.rb +46 -0
- data/lib/esse/template_loader.rb +1 -1
- data/lib/esse/transport/aliases.rb +36 -0
- data/lib/esse/transport/documents.rb +199 -0
- data/lib/esse/transport/health.rb +30 -0
- data/lib/esse/transport/indices.rb +192 -0
- data/lib/esse/transport/search.rb +48 -0
- data/lib/esse/transport.rb +44 -0
- data/lib/esse/version.rb +1 -1
- data/lib/esse.rb +20 -5
- metadata +55 -50
- data/lib/esse/backend/index/aliases.rb +0 -73
- data/lib/esse/backend/index/close.rb +0 -54
- data/lib/esse/backend/index/create.rb +0 -67
- data/lib/esse/backend/index/delete.rb +0 -39
- data/lib/esse/backend/index/documents.rb +0 -23
- data/lib/esse/backend/index/existance.rb +0 -22
- data/lib/esse/backend/index/open.rb +0 -54
- data/lib/esse/backend/index/refresh.rb +0 -43
- data/lib/esse/backend/index/reset.rb +0 -33
- data/lib/esse/backend/index/update.rb +0 -143
- data/lib/esse/backend/index.rb +0 -54
- data/lib/esse/backend/index_type/documents.rb +0 -214
- data/lib/esse/backend/index_type.rb +0 -37
- data/lib/esse/cli/templates/type_collection.rb.erb +0 -41
- data/lib/esse/cli/templates/type_mappings.json +0 -6
- data/lib/esse/cli/templates/type_serializer.rb.erb +0 -23
- data/lib/esse/index/backend.rb +0 -14
- data/lib/esse/index/naming.rb +0 -64
- data/lib/esse/index_type/backend.rb +0 -14
- data/lib/esse/index_type/mappings.rb +0 -42
- data/lib/esse/index_type.rb +0 -15
- data/lib/esse/object_document_mapper.rb +0 -110
@@ -0,0 +1,106 @@
|
|
1
|
+
module Esse
|
2
|
+
module Import
|
3
|
+
class Bulk
|
4
|
+
def initialize(type: nil, index: nil, delete: nil, create: nil)
|
5
|
+
@index = Array(index).select(&method(:valid_doc?)).reject(&:ignore_on_index?).map do |doc|
|
6
|
+
value = doc.to_bulk
|
7
|
+
value[:_type] ||= type if type
|
8
|
+
{ index: value }
|
9
|
+
end
|
10
|
+
@create = Array(create).select(&method(:valid_doc?)).reject(&:ignore_on_index?).map do |doc|
|
11
|
+
value = doc.to_bulk
|
12
|
+
value[:_type] ||= type if type
|
13
|
+
{ create: value }
|
14
|
+
end
|
15
|
+
@delete = Array(delete).select(&method(:valid_doc?)).reject(&:ignore_on_delete?).map do |doc|
|
16
|
+
value = doc.to_bulk(data: false)
|
17
|
+
value[:_type] ||= type if type
|
18
|
+
{ delete: value }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Return an array of RequestBody instances
|
23
|
+
#
|
24
|
+
# In case of timeout error, will retry with an exponential backoff using the following formula:
|
25
|
+
# wait_interval = (retry_count**4) + 15 + (rand(10) * (retry_count + 1)) seconds. It will retry up to max_retries times that is default 3.
|
26
|
+
#
|
27
|
+
# Too large bulk requests will be split into multiple requests with only one attempt.
|
28
|
+
#
|
29
|
+
# @yield [RequestBody] A request body instance
|
30
|
+
def each_request(max_retries: 3)
|
31
|
+
# @TODO create indexes when by checking all the index suffixes (if mapping is not empty)
|
32
|
+
requests = [optimistic_request]
|
33
|
+
retry_count = 0
|
34
|
+
|
35
|
+
begin
|
36
|
+
requests.each do |request|
|
37
|
+
next unless request.body?
|
38
|
+
resp = yield request
|
39
|
+
if resp&.[]('errors')
|
40
|
+
raise resp&.fetch('items', [])&.select { |item| item.values.first['error'] }&.join("\n")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
rescue Faraday::TimeoutError, Esse::Transport::RequestTimeoutError => e
|
44
|
+
retry_count += 1
|
45
|
+
raise Esse::Transport::RequestTimeoutError.new(e.message) if retry_count >= max_retries
|
46
|
+
wait_interval = (retry_count**4) + 15 + (rand(10) * (retry_count + 1))
|
47
|
+
Esse.logger.warn "Timeout error, retrying in #{wait_interval} seconds"
|
48
|
+
sleep(wait_interval)
|
49
|
+
retry
|
50
|
+
rescue Esse::Transport::RequestEntityTooLargeError => e
|
51
|
+
retry_count += 1
|
52
|
+
raise e if retry_count > 1 # only retry once on this error
|
53
|
+
requests = balance_requests_size(e)
|
54
|
+
Esse.logger.warn <<~MSG
|
55
|
+
Request entity too large, retrying with a bulk with: #{requests.map(&:bytesize).join(' + ')}.
|
56
|
+
Note that this cause performance degradation, consider adjusting the batch_size of the index or increasing the bulk size.
|
57
|
+
MSG
|
58
|
+
retry
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def valid_doc?(doc)
|
65
|
+
Esse.document?(doc)
|
66
|
+
end
|
67
|
+
|
68
|
+
def optimistic_request
|
69
|
+
request = Import::RequestBodyAsJson.new
|
70
|
+
request.delete = @delete
|
71
|
+
request.create = @create
|
72
|
+
request.index = @index
|
73
|
+
request
|
74
|
+
end
|
75
|
+
|
76
|
+
# @return [Array<RequestBody>]
|
77
|
+
def balance_requests_size(err)
|
78
|
+
if (bulk_size = err.message.scan(/exceeded.(\d+).bytes/).dig(0, 0).to_i) > 0
|
79
|
+
requests = (@delete + @create + @index).each_with_object([Import::RequestBodyRaw.new]) do |as_json, result|
|
80
|
+
operation, meta = as_json.to_a.first
|
81
|
+
meta = meta.dup
|
82
|
+
data = meta.delete(:data)
|
83
|
+
piece = MultiJson.dump(operation => meta)
|
84
|
+
piece << "\n" << MultiJson.dump(data) if data
|
85
|
+
if piece.bytesize > bulk_size
|
86
|
+
Esse.logger.warn <<~MSG
|
87
|
+
The document #{meta.inspect} size is #{piece.bytesize} bytes, which exceeds the maximum bulk size of #{bulk_size} bytes.
|
88
|
+
Consider increasing the bulk size or reducing the document size. The document will be ignored during this import.
|
89
|
+
MSG
|
90
|
+
next
|
91
|
+
end
|
92
|
+
|
93
|
+
if result.last.body.bytesize + piece.bytesize > bulk_size
|
94
|
+
result.push(Import::RequestBodyRaw.new.tap { |r| r.add(operation, piece) })
|
95
|
+
else
|
96
|
+
result[-1].add(operation, piece)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
requests.each(&:finalize)
|
100
|
+
else
|
101
|
+
raise err
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Esse
|
2
|
+
module Import
|
3
|
+
class RequestBody
|
4
|
+
attr_reader :body, :stats
|
5
|
+
|
6
|
+
def initialize(body:)
|
7
|
+
@body = body # body may be String or Array<Hash>
|
8
|
+
@stats = { index: 0, create: 0, delete: 0 }
|
9
|
+
end
|
10
|
+
|
11
|
+
def body?
|
12
|
+
!body.empty?
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class RequestBodyRaw < RequestBody
|
17
|
+
def initialize
|
18
|
+
super(body: '')
|
19
|
+
end
|
20
|
+
|
21
|
+
def bytesize
|
22
|
+
body.bytesize
|
23
|
+
end
|
24
|
+
|
25
|
+
def add(operation, payload)
|
26
|
+
stats[operation] += 1
|
27
|
+
if @body.empty?
|
28
|
+
@body = payload
|
29
|
+
else
|
30
|
+
@body << "\n" << payload
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def finalize
|
35
|
+
@body << "\n"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class RequestBodyAsJson < RequestBody
|
40
|
+
def initialize
|
41
|
+
super(body: [])
|
42
|
+
end
|
43
|
+
|
44
|
+
def index=(docs)
|
45
|
+
@body += docs
|
46
|
+
@stats[:index] += docs.size
|
47
|
+
end
|
48
|
+
|
49
|
+
def create=(docs)
|
50
|
+
@body += docs
|
51
|
+
@stats[:create] += docs.size
|
52
|
+
end
|
53
|
+
|
54
|
+
def delete=(docs)
|
55
|
+
@body += docs
|
56
|
+
@stats[:delete] += docs.size
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Esse
|
4
|
+
class Index
|
5
|
+
module ClassMethods
|
6
|
+
# Get the aliases for the index.
|
7
|
+
def aliases(**options)
|
8
|
+
response = cluster.api.aliases(**options, index: index_name, name: '*')
|
9
|
+
idx_name = response.keys.find { |idx| idx.start_with?(index_name) }
|
10
|
+
return [] unless idx_name
|
11
|
+
|
12
|
+
response.dig(idx_name, 'aliases')&.keys || []
|
13
|
+
rescue Esse::Transport::NotFoundError
|
14
|
+
[]
|
15
|
+
end
|
16
|
+
|
17
|
+
# Return list of real index names for the virtual index name(alias)
|
18
|
+
def indices_pointing_to_alias(**options)
|
19
|
+
cluster.api.aliases(**options, name: index_name).keys
|
20
|
+
rescue Esse::Transport::NotFoundError
|
21
|
+
[]
|
22
|
+
end
|
23
|
+
|
24
|
+
# Replaces all existing aliases by the respective suffixed index from argument.
|
25
|
+
#
|
26
|
+
# @param options [Hash] Hash of paramenters that will be passed along to elasticsearch request
|
27
|
+
# @option [Array<String>] :suffix One or more index suffixes to point the alias to.
|
28
|
+
# @raise [Esse::Transport::ServerError] in case of failure
|
29
|
+
# @return [Hash] the elasticsearch response
|
30
|
+
def update_aliases(suffix:, **options)
|
31
|
+
cluster.throw_error_when_readonly!
|
32
|
+
raise(ArgumentError, 'index suffix cannot be nil') if suffix.nil?
|
33
|
+
|
34
|
+
options[:body] = {
|
35
|
+
actions: [
|
36
|
+
*indices_pointing_to_alias.map do |index|
|
37
|
+
{ remove: { index: index, alias: index_name } }
|
38
|
+
end,
|
39
|
+
*Array(suffix).map do |value|
|
40
|
+
{ add: { index: build_real_index_name(value), alias: index_name } }
|
41
|
+
end,
|
42
|
+
],
|
43
|
+
}
|
44
|
+
cluster.api.update_aliases(**options)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
extend ClassMethods
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Esse
|
4
|
+
class Index
|
5
|
+
module ClassMethods
|
6
|
+
TEMPLATE_DIRS = [
|
7
|
+
'%<dirname>s/templates',
|
8
|
+
'%<dirname>s'
|
9
|
+
].freeze
|
10
|
+
|
11
|
+
def index_name=(value)
|
12
|
+
@index_name = Hstring.new(value.to_s).underscore.presence
|
13
|
+
end
|
14
|
+
|
15
|
+
def index_name(suffix: nil)
|
16
|
+
iname = index_prefixed_name(@index_name || normalized_name)
|
17
|
+
suffix = Hstring.new(suffix).underscore.presence
|
18
|
+
return iname if !iname || !suffix
|
19
|
+
|
20
|
+
[iname, suffix].join('_')
|
21
|
+
end
|
22
|
+
|
23
|
+
def index_name?
|
24
|
+
!index_name.nil?
|
25
|
+
end
|
26
|
+
|
27
|
+
def index_prefix
|
28
|
+
return @index_prefix if defined? @index_prefix
|
29
|
+
|
30
|
+
cluster.index_prefix
|
31
|
+
end
|
32
|
+
|
33
|
+
def index_prefix=(value)
|
34
|
+
if value == false
|
35
|
+
@index_prefix = nil
|
36
|
+
return
|
37
|
+
end
|
38
|
+
|
39
|
+
@index_prefix = Hstring.new(value.to_s).underscore.presence
|
40
|
+
end
|
41
|
+
|
42
|
+
def index_suffix=(value)
|
43
|
+
@index_suffix = Hstring.new(value.to_s).underscore.presence
|
44
|
+
end
|
45
|
+
|
46
|
+
def index_suffix
|
47
|
+
@index_suffix
|
48
|
+
end
|
49
|
+
|
50
|
+
def uname
|
51
|
+
Hstring.new(name).underscore.presence
|
52
|
+
end
|
53
|
+
|
54
|
+
def index_directory
|
55
|
+
return unless uname
|
56
|
+
return if uname == 'Esse::Index'
|
57
|
+
|
58
|
+
Esse.config.indices_directory.join(uname).to_s
|
59
|
+
end
|
60
|
+
|
61
|
+
def template_dirs
|
62
|
+
return [] unless index_directory
|
63
|
+
|
64
|
+
TEMPLATE_DIRS.map { |term| format(term, dirname: index_directory) }
|
65
|
+
end
|
66
|
+
|
67
|
+
def bulk_wait_interval
|
68
|
+
@bulk_wait_interval || Esse.config.bulk_wait_interval
|
69
|
+
end
|
70
|
+
|
71
|
+
def bulk_wait_interval=(value)
|
72
|
+
@bulk_wait_interval = value.to_f
|
73
|
+
end
|
74
|
+
|
75
|
+
def mapping_single_type=(value)
|
76
|
+
@mapping_single_type = !!value
|
77
|
+
end
|
78
|
+
|
79
|
+
def mapping_single_type?
|
80
|
+
return @mapping_single_type if defined? @mapping_single_type
|
81
|
+
|
82
|
+
@mapping_single_type = cluster.engine.mapping_single_type?
|
83
|
+
end
|
84
|
+
|
85
|
+
protected
|
86
|
+
|
87
|
+
def index_prefixed_name(value)
|
88
|
+
return if value == '' || value.nil?
|
89
|
+
return value.to_s unless index_prefix
|
90
|
+
|
91
|
+
[index_prefix, value].join('_')
|
92
|
+
end
|
93
|
+
|
94
|
+
def normalized_name
|
95
|
+
Hstring.new(name).underscore.tr('/', '_').sub(/_(index)$/, '')
|
96
|
+
end
|
97
|
+
|
98
|
+
def build_real_index_name(suffix = nil)
|
99
|
+
suffix = Hstring.new(suffix).underscore.presence || index_suffix || Esse.timestamp
|
100
|
+
|
101
|
+
index_name(suffix: suffix)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
extend ClassMethods
|
106
|
+
end
|
107
|
+
end
|
data/lib/esse/index/base.rb
CHANGED
@@ -3,48 +3,23 @@
|
|
3
3
|
module Esse
|
4
4
|
class Index
|
5
5
|
module ClassMethods
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
#
|
20
|
-
# class Baz < EsIndex(:v1)
|
21
|
-
# # Uses :v1 elasticsearch client connection
|
22
|
-
# end
|
23
|
-
# end
|
24
|
-
def def_Index(index_module) # rubocop:disable Naming/MethodName
|
25
|
-
tap do |model|
|
26
|
-
index_module.define_singleton_method(:Index) do |source|
|
27
|
-
model.Index(source)
|
28
|
-
end
|
6
|
+
# Sets the client_id associated with the Index class.
|
7
|
+
# This can be used directly on Esse::Index to set the :default es cluster
|
8
|
+
# to be used by subclasses, or to override the es client used for specific indices:
|
9
|
+
# Esse::Index.cluster_id = :v1
|
10
|
+
# ArtistIndex = Class.new(Esse::Index)
|
11
|
+
# ArtistIndex.cluster_id = :v2
|
12
|
+
# @param [Symbol, Esse::Cluster, NilClass] source the cluster id or the cluster instance
|
13
|
+
# @return [Symbol] the cluster id
|
14
|
+
# @raise [ArgumentError] if the cluster id is not defined in the Esse.config
|
15
|
+
def cluster_id=(source)
|
16
|
+
if source.nil?
|
17
|
+
@cluster_id = nil
|
18
|
+
return
|
29
19
|
end
|
30
|
-
end
|
31
|
-
|
32
|
-
# Lets you create a Index subclass with its elasticsearch cluster
|
33
|
-
#
|
34
|
-
# Example:
|
35
|
-
# # Using a custom cluster
|
36
|
-
# Esse.config.cluster(:v1).client = Elasticsearch::Client.new
|
37
|
-
# class UsersIndex < Esse::Index(:v1)
|
38
|
-
# end
|
39
|
-
#
|
40
|
-
# # Using :default cluster
|
41
|
-
# class UsersIndex < Esse::Index
|
42
|
-
# end
|
43
|
-
def Index(source) # rubocop:disable Naming/MethodName
|
44
|
-
klass = Class.new(self)
|
45
20
|
|
46
21
|
valid_ids = Esse.config.cluster_ids
|
47
|
-
|
22
|
+
new_id = \
|
48
23
|
case source
|
49
24
|
when Esse::Cluster
|
50
25
|
source.id
|
@@ -55,7 +30,7 @@ module Esse
|
|
55
30
|
|
56
31
|
msg = <<~MSG
|
57
32
|
We could not resolve the index cluster using the argument %<arg>p. \n
|
58
|
-
It must be previously defined in the `Esse.config' settings. \n
|
33
|
+
It must be previously defined in the `Esse.config.cluster(%<arg>p) { ... }' settings. \n
|
59
34
|
Here is the list of cluster ids we have configured: %<ids>s\n
|
60
35
|
|
61
36
|
You can ignore this cluster id entirely. That way the :default id will be used.\n
|
@@ -63,22 +38,11 @@ module Esse
|
|
63
38
|
class UsersIndex < Esse::Index\n
|
64
39
|
end\n
|
65
40
|
MSG
|
66
|
-
unless
|
41
|
+
unless new_id
|
67
42
|
raise ArgumentError.new, format(msg, arg: source, ids: valid_ids.map(&:inspect).join(', '))
|
68
43
|
end
|
69
44
|
|
70
|
-
|
71
|
-
klass
|
72
|
-
end
|
73
|
-
|
74
|
-
# Sets the client_id associated with the Index class.
|
75
|
-
# This can be used directly on Esse::Index to set the :default es cluster
|
76
|
-
# to be used by subclasses, or to override the es client used for specific indices:
|
77
|
-
# Esse::Index.cluster_id = :v1
|
78
|
-
# ArtistIndex = Class.new(Esse::Index)
|
79
|
-
# ArtistIndex.cluster_id = :v2
|
80
|
-
def cluster_id=(cluster_id)
|
81
|
-
@cluster_id = cluster_id
|
45
|
+
@cluster_id = new_id
|
82
46
|
end
|
83
47
|
|
84
48
|
# @return [Symbol] reads the @cluster_id instance variable or :default
|
@@ -0,0 +1,236 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Esse
|
4
|
+
class Index
|
5
|
+
module ClassMethods
|
6
|
+
# Retrieves the specified JSON document from an index.
|
7
|
+
#
|
8
|
+
# UsersIndex.get(id: 1) # { '_id' => 1, ... }
|
9
|
+
# UsersIndex.get(id: 'missing') # raise Esse::Transport::NotFoundError
|
10
|
+
#
|
11
|
+
# @param doc [Esse::Document] the document to retrieve
|
12
|
+
# @param options [Hash] Hash of paramenters that will be passed along to elasticsearch request
|
13
|
+
# @option [String, Integer] :id The `_id` of the elasticsearch document
|
14
|
+
# @option [String, NilClass] :type The type of the document (Optional for elasticsearch >= 7)
|
15
|
+
# @option [String, nil] :suffix The index suffix. Defaults to the nil.
|
16
|
+
# @raise [Esse::Transport::NotFoundError] when the doc does not exist
|
17
|
+
# @return [Hash] The elasticsearch document.
|
18
|
+
#
|
19
|
+
# @see https://www.elastic.co/guide/en/elasticsearch/reference/7.5/docs-get.html
|
20
|
+
def get(doc = nil, suffix: nil, **options)
|
21
|
+
if document?(doc)
|
22
|
+
options[:id] = doc.id
|
23
|
+
options[:type] = doc.type if doc.type?
|
24
|
+
options[:routing] = doc.routing if doc.routing?
|
25
|
+
end
|
26
|
+
require_kwargs!(options, :id)
|
27
|
+
options[:index] = index_name(suffix: suffix)
|
28
|
+
cluster.may_update_type!(options)
|
29
|
+
cluster.api.get(**options)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Check if a JSON document exists
|
33
|
+
#
|
34
|
+
# UsersIndex.exist?(id: 1) # true
|
35
|
+
# UsersIndex.exist?(id: 'missing') # false
|
36
|
+
#
|
37
|
+
# @param doc [Esse::Document] the document to retrieve
|
38
|
+
# @param options [Hash] Hash of paramenters that will be passed along to elasticsearch request
|
39
|
+
# @option [String, Integer] :id The `_id` of the elasticsearch document
|
40
|
+
# @option [String, NilClass] :type The type of the document (Optional for elasticsearch >= 7)
|
41
|
+
# @option [String, nil] :suffix The index suffix. Defaults to the nil.
|
42
|
+
# @return [Boolean] true if the document exists
|
43
|
+
def exist?(doc = nil, suffix: nil, **options)
|
44
|
+
if document?(doc)
|
45
|
+
options[:id] = doc.id
|
46
|
+
options[:type] = doc.type if doc.type?
|
47
|
+
options[:routing] = doc.routing if doc.routing?
|
48
|
+
end
|
49
|
+
require_kwargs!(options, :id)
|
50
|
+
options[:index] = index_name(suffix: suffix)
|
51
|
+
cluster.may_update_type!(options)
|
52
|
+
cluster.api.exist?(**options)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Gets the number of matches for a search query.
|
56
|
+
#
|
57
|
+
# UsersIndex.count # 999
|
58
|
+
# UsersIndex.count(body: { ... }) # 32
|
59
|
+
#
|
60
|
+
# @param options [Hash] Hash of paramenters that will be passed along to elasticsearch request
|
61
|
+
# @option [Hash] :body A query to restrict the results specified with the Query DSL (optional)
|
62
|
+
# @option [String, NilClass] :type The type of the document (Optional for elasticsearch >= 7)
|
63
|
+
# @option [String, nil] :suffix The index suffix. Defaults to the nil.
|
64
|
+
# @return [Integer] amount of documents found
|
65
|
+
#
|
66
|
+
# @see https://www.elastic.co/guide/en/elasticsearch/reference/7.5/search-count.html
|
67
|
+
def count(type: nil, suffix: nil, **options)
|
68
|
+
params = {
|
69
|
+
index: index_name(suffix: suffix),
|
70
|
+
type: type,
|
71
|
+
}
|
72
|
+
cluster.may_update_type!(params)
|
73
|
+
cluster.api.count(**options, **params)['count']
|
74
|
+
end
|
75
|
+
|
76
|
+
# Removes a JSON document from the specified index.
|
77
|
+
#
|
78
|
+
# UsersIndex.delete(id: 1) # true
|
79
|
+
# UsersIndex.delete(id: 'missing') # false
|
80
|
+
#
|
81
|
+
# @param doc [Esse::Document] the document to retrieve
|
82
|
+
# @param options [Hash] Hash of paramenters that will be passed along to elasticsearch request
|
83
|
+
# @option [String, Integer] :id The `_id` of the elasticsearch document
|
84
|
+
# @option [String, NilClass] :type The type of the document (Optional for elasticsearch >= 7)
|
85
|
+
# @option [String, nil] :suffix The index suffix. Defaults to the nil.
|
86
|
+
# @raise [Esse::Transport::NotFoundError] when the doc does not exist
|
87
|
+
#
|
88
|
+
# @see https://www.elastic.co/guide/en/elasticsearch/reference/7.5/docs-delete.html
|
89
|
+
def delete(doc = nil, suffix: nil, **options)
|
90
|
+
if document?(doc)
|
91
|
+
options[:id] = doc.id
|
92
|
+
options[:type] = doc.type if doc.type?
|
93
|
+
options[:routing] = doc.routing if doc.routing?
|
94
|
+
end
|
95
|
+
require_kwargs!(options, :id)
|
96
|
+
options[:index] = index_name(suffix: suffix)
|
97
|
+
cluster.may_update_type!(options)
|
98
|
+
cluster.api.delete(**options)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Updates a document using the specified script.
|
102
|
+
#
|
103
|
+
# UsersIndex.update(id: 1, body: { doc: { ... } }) # { '_id' => 1, ...}
|
104
|
+
#
|
105
|
+
# @param options [Hash] Hash of paramenters that will be passed along to elasticsearch request
|
106
|
+
# @option [String, Integer] :id The `_id` of the elasticsearch document
|
107
|
+
# @option [Hash] :body the body of the request
|
108
|
+
# @option [String, NilClass] :type The type of the document (Optional for elasticsearch >= 7)
|
109
|
+
# @option [String, nil] :suffix The index suffix. Defaults to the nil.
|
110
|
+
# @raise [Esse::Transport::NotFoundError] when the doc does not exist
|
111
|
+
# @return [Hash] elasticsearch response hash
|
112
|
+
#
|
113
|
+
# @see https://www.elastic.co/guide/en/elasticsearch/reference/7.5/docs-update.html
|
114
|
+
def update(doc = nil, suffix: nil, **options)
|
115
|
+
if document?(doc)
|
116
|
+
options[:id] = doc.id
|
117
|
+
options[:body] = { doc: doc.source }
|
118
|
+
options[:type] = doc.type if doc.type?
|
119
|
+
options[:routing] = doc.routing if doc.routing?
|
120
|
+
end
|
121
|
+
require_kwargs!(options, :id, :body)
|
122
|
+
options[:index] = index_name(suffix: suffix)
|
123
|
+
cluster.may_update_type!(options)
|
124
|
+
cluster.api.update(**options)
|
125
|
+
end
|
126
|
+
|
127
|
+
# Adds a JSON document to the specified index and makes it searchable. If the document
|
128
|
+
# already exists, updates the document and increments its version.
|
129
|
+
#
|
130
|
+
# UsersIndex::User.index(id: 1, body: { name: 'name' }) # { '_id' => 1, ...}
|
131
|
+
#
|
132
|
+
# @param options [Hash] Hash of paramenters that will be passed along to elasticsearch request
|
133
|
+
# @option [String, Integer] :id The `_id` of the elasticsearch document
|
134
|
+
# @option [Hash] :body The JSON document that will be indexed (Required)
|
135
|
+
# @option [String, NilClass] :type The type of the document (Optional for elasticsearch >= 7)
|
136
|
+
# @option [String, nil] :suffix The index suffix. Defaults to the nil.
|
137
|
+
# @return [Hash] the elasticsearch response Hash
|
138
|
+
#
|
139
|
+
# @see https://www.elastic.co/guide/en/elasticsearch/reference/7.5/docs-index_.html
|
140
|
+
def index(doc = nil, suffix: nil, **options)
|
141
|
+
if document?(doc)
|
142
|
+
options[:id] = doc.id
|
143
|
+
options[:body] = doc.source
|
144
|
+
options[:type] = doc.type if doc.type?
|
145
|
+
options[:routing] = doc.routing if doc.routing?
|
146
|
+
end
|
147
|
+
require_kwargs!(options, :id, :body)
|
148
|
+
options[:index] = index_name(suffix: suffix)
|
149
|
+
cluster.may_update_type!(options)
|
150
|
+
cluster.api.index(**options)
|
151
|
+
end
|
152
|
+
|
153
|
+
# Performs multiple indexing or delete operations in a single API call.
|
154
|
+
# This reduces overhead and can greatly increase indexing speed.
|
155
|
+
#
|
156
|
+
# @param options [Hash] Hash of paramenters that will be passed along to elasticsearch request
|
157
|
+
# @option [String, nil] :suffix The index suffix. Defaults to the nil.
|
158
|
+
# @option [Array<Esse::Document>] :index list of documents to be indexed(Optional)
|
159
|
+
# @option [Array<Esse::Document>] :delete list of documents to be deleted(Optional)
|
160
|
+
# @option [Array<Esse::Document>] :create list of documents to be created(Optional)
|
161
|
+
# @option [String, NilClass] :type The type of the document (Optional for elasticsearch >= 7)
|
162
|
+
# @return [Array<Esse::Import::RequestBody>] The list of request bodies. @TODO Change this to a Stats object
|
163
|
+
#
|
164
|
+
# @see https://www.elastic.co/guide/en/elasticsearch/reference/7.5/docs-bulk.html
|
165
|
+
# @see https://github.com/elastic/elasticsearch-ruby/blob/main/elasticsearch-api/lib/elasticsearch/api/utils.rb
|
166
|
+
# @see https://github.com/elastic/elasticsearch-ruby/blob/main/elasticsearch-api/lib/elasticsearch/api/actions/bulk.rb
|
167
|
+
def bulk(index: nil, delete: nil, create: nil, type: nil, suffix: nil, **options)
|
168
|
+
definition = {
|
169
|
+
index: index_name(suffix: suffix),
|
170
|
+
type: type,
|
171
|
+
}.merge(options)
|
172
|
+
cluster.may_update_type!(definition)
|
173
|
+
|
174
|
+
# @TODO Wrap the return in a some other Stats object with more information
|
175
|
+
Esse::Import::Bulk.new(
|
176
|
+
**definition.slice(:type),
|
177
|
+
index: index,
|
178
|
+
delete: delete,
|
179
|
+
create: create,
|
180
|
+
).each_request do |request_body|
|
181
|
+
cluster.api.bulk(**definition, body: request_body.body) do |event_payload|
|
182
|
+
event_payload[:body_stats] = request_body.stats
|
183
|
+
if bulk_wait_interval > 0
|
184
|
+
event_payload[:wait_interval] = bulk_wait_interval
|
185
|
+
sleep(bulk_wait_interval)
|
186
|
+
else
|
187
|
+
event_payload[:wait_interval] = 0.0
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# Resolve collection and index data
|
194
|
+
#
|
195
|
+
# @param repos [Array<String>] List of repo types. Defaults to all types.
|
196
|
+
# @param options [Hash] Hash of paramenters that will be passed along to elasticsearch request
|
197
|
+
# @option [String, nil] :suffix The index suffix. Defaults to the nil.
|
198
|
+
# @option [Hash] :context The collection context. This value will be passed as argument to the collection
|
199
|
+
# May be SQL condition or any other filter you have defined on the collection.
|
200
|
+
# @return [Numeric] The number of documents imported
|
201
|
+
def import(*repo_types, context: {}, suffix: nil, **options)
|
202
|
+
repo_types = repo_hash.keys if repo_types.empty?
|
203
|
+
count = 0
|
204
|
+
repo_hash.slice(*repo_types).each do |repo_name, repo|
|
205
|
+
repo.each_serialized_batch(**(context || {})) do |batch|
|
206
|
+
# Elasticsearch 6.x and older have multiple types per index.
|
207
|
+
# This gem supports multiple types per index for backward compatibility, but we recommend to update
|
208
|
+
# your elasticsearch to a at least 7.x version and use a single type per index.
|
209
|
+
#
|
210
|
+
# Note that the repository name will be used as the document type.
|
211
|
+
# mapping_default_type
|
212
|
+
kwargs = { index: batch, suffix: suffix, type: repo_name, **options }
|
213
|
+
cluster.may_update_type!(kwargs)
|
214
|
+
bulk(**kwargs)
|
215
|
+
count += batch.size
|
216
|
+
end
|
217
|
+
end
|
218
|
+
count
|
219
|
+
end
|
220
|
+
|
221
|
+
protected
|
222
|
+
|
223
|
+
def document?(doc)
|
224
|
+
Esse.document?(doc)
|
225
|
+
end
|
226
|
+
|
227
|
+
def require_kwargs!(options, *keys)
|
228
|
+
keys.each do |key|
|
229
|
+
raise ArgumentError, "missing keyword: #{key}" unless options.key?(key)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
extend ClassMethods
|
235
|
+
end
|
236
|
+
end
|