jay_api 27.1.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 +7 -0
- data/CHANGELOG.md +786 -0
- data/README.md +61 -0
- data/jay_api.gemspec +38 -0
- data/lib/jay_api/abstract/connection.rb +50 -0
- data/lib/jay_api/abstract/constant_wait.rb +17 -0
- data/lib/jay_api/abstract/geometric_wait.rb +35 -0
- data/lib/jay_api/abstract/wait_strategy.rb +43 -0
- data/lib/jay_api/configuration.rb +115 -0
- data/lib/jay_api/elasticsearch/async.rb +72 -0
- data/lib/jay_api/elasticsearch/batch_counter.rb +76 -0
- data/lib/jay_api/elasticsearch/client.rb +96 -0
- data/lib/jay_api/elasticsearch/client_factory.rb +100 -0
- data/lib/jay_api/elasticsearch/errors/elasticsearch_error.rb +13 -0
- data/lib/jay_api/elasticsearch/errors/end_of_query_results_error.rb +22 -0
- data/lib/jay_api/elasticsearch/errors/query_execution_error.rb +15 -0
- data/lib/jay_api/elasticsearch/errors/query_execution_failure.rb +17 -0
- data/lib/jay_api/elasticsearch/errors/query_execution_timeout.rb +13 -0
- data/lib/jay_api/elasticsearch/errors/search_after_error.rb +13 -0
- data/lib/jay_api/elasticsearch/index.rb +223 -0
- data/lib/jay_api/elasticsearch/query_builder/aggregations/aggregation.rb +66 -0
- data/lib/jay_api/elasticsearch/query_builder/aggregations/avg.rb +56 -0
- data/lib/jay_api/elasticsearch/query_builder/aggregations/errors/aggregations_error.rb +17 -0
- data/lib/jay_api/elasticsearch/query_builder/aggregations/errors.rb +14 -0
- data/lib/jay_api/elasticsearch/query_builder/aggregations/filter.rb +67 -0
- data/lib/jay_api/elasticsearch/query_builder/aggregations/max.rb +51 -0
- data/lib/jay_api/elasticsearch/query_builder/aggregations/scripted_metric.rb +72 -0
- data/lib/jay_api/elasticsearch/query_builder/aggregations/sum.rb +57 -0
- data/lib/jay_api/elasticsearch/query_builder/aggregations/terms.rb +73 -0
- data/lib/jay_api/elasticsearch/query_builder/aggregations/top_hits.rb +49 -0
- data/lib/jay_api/elasticsearch/query_builder/aggregations/value_count.rb +50 -0
- data/lib/jay_api/elasticsearch/query_builder/aggregations.rb +168 -0
- data/lib/jay_api/elasticsearch/query_builder/errors/query_builder_error.rb +16 -0
- data/lib/jay_api/elasticsearch/query_builder/query_clauses/bool.rb +179 -0
- data/lib/jay_api/elasticsearch/query_builder/query_clauses/exists.rb +33 -0
- data/lib/jay_api/elasticsearch/query_builder/query_clauses/match_all.rb +22 -0
- data/lib/jay_api/elasticsearch/query_builder/query_clauses/match_clauses.rb +140 -0
- data/lib/jay_api/elasticsearch/query_builder/query_clauses/match_none.rb +22 -0
- data/lib/jay_api/elasticsearch/query_builder/query_clauses/match_phrase.rb +35 -0
- data/lib/jay_api/elasticsearch/query_builder/query_clauses/negator.rb +42 -0
- data/lib/jay_api/elasticsearch/query_builder/query_clauses/query_clause.rb +17 -0
- data/lib/jay_api/elasticsearch/query_builder/query_clauses/query_string.rb +50 -0
- data/lib/jay_api/elasticsearch/query_builder/query_clauses/range.rb +49 -0
- data/lib/jay_api/elasticsearch/query_builder/query_clauses/regexp.rb +39 -0
- data/lib/jay_api/elasticsearch/query_builder/query_clauses/term.rb +37 -0
- data/lib/jay_api/elasticsearch/query_builder/query_clauses/terms.rb +37 -0
- data/lib/jay_api/elasticsearch/query_builder/query_clauses/wildcard.rb +37 -0
- data/lib/jay_api/elasticsearch/query_builder/query_clauses.rb +163 -0
- data/lib/jay_api/elasticsearch/query_builder/script.rb +36 -0
- data/lib/jay_api/elasticsearch/query_builder.rb +196 -0
- data/lib/jay_api/elasticsearch/query_results.rb +111 -0
- data/lib/jay_api/elasticsearch/response.rb +43 -0
- data/lib/jay_api/elasticsearch/search_after_results.rb +58 -0
- data/lib/jay_api/elasticsearch/tasks.rb +36 -0
- data/lib/jay_api/elasticsearch/time.rb +18 -0
- data/lib/jay_api/errors/configuration_error.rb +22 -0
- data/lib/jay_api/errors/error.rb +8 -0
- data/lib/jay_api/git/errors/invalid_repository_error.rb +11 -0
- data/lib/jay_api/git/errors/missing_url_error.rb +13 -0
- data/lib/jay_api/git/gerrit/gitiles_helper.rb +58 -0
- data/lib/jay_api/git/repository.rb +356 -0
- data/lib/jay_api/id_builder.rb +52 -0
- data/lib/jay_api/mergeables/merge_selector/configuration.rb +29 -0
- data/lib/jay_api/mergeables/merge_selector/merger.rb +58 -0
- data/lib/jay_api/mergeables/merge_selector.rb +15 -0
- data/lib/jay_api/prior_version_fetcher_base.rb +66 -0
- data/lib/jay_api/properties_fetcher.rb +196 -0
- data/lib/jay_api/rspec/configuration.rb +46 -0
- data/lib/jay_api/rspec/git.rb +60 -0
- data/lib/jay_api/rspec/test_data_collector.rb +189 -0
- data/lib/jay_api/rspec.rb +9 -0
- data/lib/jay_api/version.rb +6 -0
- data/lib/jay_api.rb +9 -0
- metadata +215 -0
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'elasticsearch'
|
4
|
+
require 'logging'
|
5
|
+
|
6
|
+
require 'elasticsearch/transport/transport/errors'
|
7
|
+
|
8
|
+
require_relative 'client'
|
9
|
+
require_relative '../abstract/geometric_wait'
|
10
|
+
require_relative '../abstract/constant_wait'
|
11
|
+
|
12
|
+
module JayAPI
|
13
|
+
module Elasticsearch
|
14
|
+
# A factory class that creates an Elasticsearch Client object. More specifically, a JayAPI wrapper
|
15
|
+
# object over the Elasticsearch Client object.
|
16
|
+
class ClientFactory
|
17
|
+
# The default port for the Elasticsearch cluster
|
18
|
+
DEFAULT_ELASTICSEARCH_PORT = 9200
|
19
|
+
|
20
|
+
# Classes that define the available waiting strategies, used for deciding sleep time between
|
21
|
+
# the reconnections.
|
22
|
+
WAIT_STRATEGIES = {
|
23
|
+
geometric: JayAPI::Abstract::GeometricWait,
|
24
|
+
constant: JayAPI::Abstract::ConstantWait
|
25
|
+
}.freeze
|
26
|
+
|
27
|
+
# The maximum number of connection attempts to be made.
|
28
|
+
MAX_ATTEMPTS = 4
|
29
|
+
# The default wait time to be passed to the wait strategy class.
|
30
|
+
WAIT_INTERVAL = 2
|
31
|
+
|
32
|
+
attr_reader :cluster_url, :port
|
33
|
+
|
34
|
+
# Creates a new instance of the class.
|
35
|
+
# @param [String] cluster_url The URL where the Elasticsearch service
|
36
|
+
# is exposed.
|
37
|
+
# @param [Integer] port The port to use to connect to the
|
38
|
+
# Elasticsearch instance (Needed only when different from the default
|
39
|
+
# Elasticsearch port)
|
40
|
+
# @param [Logging::Logger] logger The logger object to use, if
|
41
|
+
# none is given a new one will be created.
|
42
|
+
# @param [Hash] credentials
|
43
|
+
# @option credentials [String] :username The user name to use when
|
44
|
+
# authenticating against the Elasticsearch instance.
|
45
|
+
# @option credentials [String] :password The password to use when
|
46
|
+
# authenticating against the Elasticsearch instance.
|
47
|
+
# disabling :reek:ControlParameter
|
48
|
+
def initialize(cluster_url:, port: nil, logger: nil, **credentials)
|
49
|
+
@cluster_url = cluster_url
|
50
|
+
@port = port || DEFAULT_ELASTICSEARCH_PORT
|
51
|
+
@logger = logger || Logging.logger($stdout)
|
52
|
+
@username = credentials[:username]
|
53
|
+
@password = credentials[:password]
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns the current instance of the Elasticsearch client, or creates
|
57
|
+
# a new one.
|
58
|
+
# @param [Integer] max_attempts The maximum number of attempts that the connection
|
59
|
+
# shall be retried.
|
60
|
+
# @param [Symbol] wait_strategy The waiting strategy for reconnections (:geometric or :constant).
|
61
|
+
# @param [Integer] wait_interval The wait interval for the wait strategy. The sleep time between
|
62
|
+
# each connection will be:
|
63
|
+
# * wait_interval with :constant wait strategy
|
64
|
+
# * wait_interval**i with :geometric wait strategy (where i is the i'th re-try)
|
65
|
+
# @return [JayAPI::Elasticsearch::Client] The Elasticsearch client.
|
66
|
+
def create(max_attempts: MAX_ATTEMPTS, wait_strategy: :geometric, wait_interval: WAIT_INTERVAL)
|
67
|
+
JayAPI::Elasticsearch::Client.new(
|
68
|
+
::Elasticsearch::Client.new(
|
69
|
+
hosts: [host],
|
70
|
+
log: false
|
71
|
+
),
|
72
|
+
logger,
|
73
|
+
max_attempts: max_attempts,
|
74
|
+
wait_strategy: WAIT_STRATEGIES[wait_strategy].new(wait_interval: wait_interval, logger: logger)
|
75
|
+
)
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
attr_reader :logger, :username, :password
|
81
|
+
|
82
|
+
# @return [URI] A URI constructed by parsing the given +cluster_url+.
|
83
|
+
def cluster_uri
|
84
|
+
@cluster_uri = URI.parse(cluster_url)
|
85
|
+
end
|
86
|
+
|
87
|
+
# @return [Hash] A +Hash+ with the connection parameters for the
|
88
|
+
# Elasticsearch instance.
|
89
|
+
def host
|
90
|
+
{}.tap do |host|
|
91
|
+
host[:host] = cluster_uri.host
|
92
|
+
host[:port] = port || DEFAULT_ELASTICSEARCH_PORT # Do not use cluster_uri.port, that will ALWAYS be defined.
|
93
|
+
host[:user] = username if username
|
94
|
+
host[:password] = password if password
|
95
|
+
host[:scheme] = cluster_uri.scheme
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../errors/error'
|
4
|
+
|
5
|
+
module JayAPI
|
6
|
+
module Elasticsearch
|
7
|
+
module Errors
|
8
|
+
# An error to be raised when the ElasticSearch instance responds with
|
9
|
+
# an error.
|
10
|
+
class ElasticsearchError < JayAPI::Errors::Error; end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'elasticsearch_error'
|
4
|
+
|
5
|
+
module JayAPI
|
6
|
+
module Elasticsearch
|
7
|
+
module Errors
|
8
|
+
# An error to be raised when an attempt is made to fetch more documents
|
9
|
+
# on a QueryResults class that has reached the end of the matched
|
10
|
+
# documents.
|
11
|
+
class EndOfQueryResultsError < ElasticsearchError
|
12
|
+
# :reek:ControlParameter (want to avoid the long string in as default value)
|
13
|
+
# Creates a new instance of the class with the specified message.
|
14
|
+
# @param [String] message The message to use, if none is given
|
15
|
+
# the default message will be used.
|
16
|
+
def initialize(message = nil)
|
17
|
+
super(message || 'End of the query results reached')
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../errors/error'
|
4
|
+
|
5
|
+
module JayAPI
|
6
|
+
module Elasticsearch
|
7
|
+
module Errors
|
8
|
+
# An error to be raised when executing a query in Elasticsearch results
|
9
|
+
# in errors, i.e, when a query cannot be executed; they typically indicate
|
10
|
+
# fundamental problems with the request itself
|
11
|
+
class QueryExecutionError < JayAPI::Errors::Error
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../errors/error'
|
4
|
+
|
5
|
+
module JayAPI
|
6
|
+
module Elasticsearch
|
7
|
+
module Errors
|
8
|
+
# An error to be raised when executing a query in Elasticsearch results
|
9
|
+
# in failures, i.e., when a query can technically be processed, but
|
10
|
+
# encounters issues during execution; the query is completed but returns
|
11
|
+
# partial or problematic results. It does not necessarily stop the entire
|
12
|
+
# request from completing
|
13
|
+
class QueryExecutionFailure < JayAPI::Errors::Error
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../errors/error'
|
4
|
+
|
5
|
+
module JayAPI
|
6
|
+
module Elasticsearch
|
7
|
+
module Errors
|
8
|
+
# An error to be raised when executing a query in Elasticsearch times out.
|
9
|
+
class QueryExecutionTimeout < JayAPI::Errors::Error
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'elasticsearch_error'
|
4
|
+
|
5
|
+
module JayAPI
|
6
|
+
module Elasticsearch
|
7
|
+
module Errors
|
8
|
+
# An error to be raised when an issue arises while using Elasticsearch's
|
9
|
+
# 'search_after' parameter.
|
10
|
+
class SearchAfterError < ElasticsearchError; end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,223 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_support/json' # Needed because ActiveSupport 6 doesn't include it's own JSON module. 🤦
|
5
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
6
|
+
require 'active_support/core_ext/hash/keys'
|
7
|
+
require 'elasticsearch'
|
8
|
+
require 'logging'
|
9
|
+
|
10
|
+
require_relative 'errors/elasticsearch_error'
|
11
|
+
require_relative 'async'
|
12
|
+
require_relative 'query_results'
|
13
|
+
require_relative 'response'
|
14
|
+
require_relative 'batch_counter'
|
15
|
+
require_relative 'search_after_results'
|
16
|
+
|
17
|
+
module JayAPI
|
18
|
+
module Elasticsearch
|
19
|
+
# Represents an Elasticsearch index. Allows data to be pushed to it one
|
20
|
+
# record at a time or in batches of the specified size.
|
21
|
+
class Index
|
22
|
+
attr_reader :client, :index_name, :batch_size
|
23
|
+
|
24
|
+
# Default type for documents indexed with the #index method.
|
25
|
+
DEFAULT_DOC_TYPE = 'nested'
|
26
|
+
|
27
|
+
# Supported document types (for the #index method)
|
28
|
+
SUPPORTED_TYPES = [DEFAULT_DOC_TYPE, nil].freeze
|
29
|
+
|
30
|
+
# :reek:ControlParameter (want to avoid the creating of the logger on method definition)
|
31
|
+
# Creates a new instance of the class.
|
32
|
+
# @param [JayAPI::Elasticsearch::Client] client The Elasticsearch Client object.
|
33
|
+
# @param [String] index_name The name of the Elasticsearch index.
|
34
|
+
# @param [Integer] batch_size The size of the batch. When this number of
|
35
|
+
# items are pushed into the index they are flushed to the
|
36
|
+
# Elasticsearch instance.
|
37
|
+
# @param [Logging::Logger, nil] logger The logger object to use, if
|
38
|
+
# none is given a new one will be created.
|
39
|
+
def initialize(client:, index_name:, batch_size: 100, logger: nil)
|
40
|
+
@logger = logger || Logging.logger[self]
|
41
|
+
|
42
|
+
@client = client
|
43
|
+
@index_name = index_name
|
44
|
+
@batch_size = batch_size
|
45
|
+
|
46
|
+
@batch = []
|
47
|
+
end
|
48
|
+
|
49
|
+
# Pushes a record into the index. (This does not send the record to the
|
50
|
+
# Elasticsearch instance, only puts it into the send queue).
|
51
|
+
# @param [Hash] data The data to be pushed to the index.
|
52
|
+
def push(data)
|
53
|
+
@batch << { index: { _index: index_name, _type: 'nested', data: data } }
|
54
|
+
flush! if @batch.size >= batch_size
|
55
|
+
end
|
56
|
+
|
57
|
+
# Sends a record to the Elasticsearch instance right away.
|
58
|
+
# @param [Hash] data The data to be sent.
|
59
|
+
# @param [String, nil] type The type of the document. When set to +nil+
|
60
|
+
# the decision is left to Elasticsearch's API. Which will normally
|
61
|
+
# default to +_doc+.
|
62
|
+
# @return [Hash] A hash with information about the created document. An
|
63
|
+
# example of such Hash is:
|
64
|
+
#
|
65
|
+
# {
|
66
|
+
# "_index" => "xyz01_unit_test",
|
67
|
+
# "_type" => "nested",
|
68
|
+
# "_id" => "SVY1mJEBQ5CNFZM8Lodt",
|
69
|
+
# "_version" => 1,
|
70
|
+
# "result" => "created",
|
71
|
+
# "_shards" => { "total" => 2, "successful" => 1, "failed" => 0 },
|
72
|
+
# "_seq_no" => 0,
|
73
|
+
# "_primary_term" => 1
|
74
|
+
# }
|
75
|
+
#
|
76
|
+
# For information on the contents of this Hash please see:
|
77
|
+
# https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html#docs-index-api-response-body
|
78
|
+
def index(data, type: DEFAULT_DOC_TYPE)
|
79
|
+
raise ArgumentError, "Unsupported type: '#{type}'" unless SUPPORTED_TYPES.include?(type)
|
80
|
+
|
81
|
+
client.index index: index_name, type: type, body: data
|
82
|
+
end
|
83
|
+
|
84
|
+
# Performs a query on the index.
|
85
|
+
# For more information on how to build the query please refer to the
|
86
|
+
# Elasticsearch DSL query:
|
87
|
+
# https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html
|
88
|
+
# @param [Hash] query The query to perform.
|
89
|
+
# @param [JayAPI::Elasticsearch::BatchCounter, nil] batch_counter Object keeping track of batches.
|
90
|
+
# @param [Symbol, nil] type Type of query, at the moment either nil or :search_after.
|
91
|
+
# @return [JayAPI::Elasticsearch::QueryResults] The query results.
|
92
|
+
# @raise [Elasticsearch::Transport::Transport::ServerError] If the
|
93
|
+
# query fails.
|
94
|
+
def search(query, batch_counter: nil, type: nil)
|
95
|
+
begin
|
96
|
+
response = Response.new(client.search(index: index_name, body: query))
|
97
|
+
rescue ::Elasticsearch::Transport::Transport::Errors::BadRequest
|
98
|
+
logger.error "The 'search' query is invalid: #{JSON.pretty_generate(query)}"
|
99
|
+
raise
|
100
|
+
end
|
101
|
+
query_results(query, response, batch_counter, type)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Sends whatever is currently in the send queue to the Elasticsearch
|
105
|
+
# instance and clears the queue.
|
106
|
+
def flush
|
107
|
+
return unless @batch.any?
|
108
|
+
|
109
|
+
flush!
|
110
|
+
end
|
111
|
+
|
112
|
+
# Returns the number of elements currently on the send queue.
|
113
|
+
# @return [Integer] The number of items in the send queue.
|
114
|
+
def queue_size
|
115
|
+
@batch.size
|
116
|
+
end
|
117
|
+
|
118
|
+
# Delete the documents matching the given query from the Index.
|
119
|
+
# For more information on how to build the query please refer to the
|
120
|
+
# Elasticsearch DSL documentation:
|
121
|
+
# https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html
|
122
|
+
# https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete-by-query.html
|
123
|
+
# @param [Hash] query The delete query
|
124
|
+
# @param [Integer] slices Number of slices to cut the operation into for
|
125
|
+
# faster processing (i.e., run the operation in parallel)
|
126
|
+
# @param [Boolean] wait_for_completion False if Elasticsearch should not
|
127
|
+
# wait for completion and perform the request asynchronously, true if it
|
128
|
+
# should wait for completion (i.e., run the operation asynchronously)
|
129
|
+
# @return [Hash] A Hash that details the results of the operation
|
130
|
+
# @example Returned Hash (with `wait_for_completion: true`):
|
131
|
+
# {
|
132
|
+
# took: 103,
|
133
|
+
# timed_out: false,
|
134
|
+
# total: 76,
|
135
|
+
# deleted: 76,
|
136
|
+
# batches: 1,
|
137
|
+
# version_conflicts: 0,
|
138
|
+
# noops: 0,
|
139
|
+
# retries: { bulk: 0, search: 0 },
|
140
|
+
# throttled_millis: 0,
|
141
|
+
# requests_per_second: 1.0,
|
142
|
+
# throttled_until_millis: 0,
|
143
|
+
# failures: []
|
144
|
+
# }
|
145
|
+
# @example Returned Hash (with `wait_for_completion: false`):
|
146
|
+
# {
|
147
|
+
# task: "B5oDyEsHQu2Q-wpbaMSMTg:577388264"
|
148
|
+
# }
|
149
|
+
# @raise [Elasticsearch::Transport::Transport::ServerError] If the
|
150
|
+
# query fails.
|
151
|
+
def delete_by_query(query, slices: nil, wait_for_completion: true)
|
152
|
+
request_params = { index: index_name, body: query }.tap do |params|
|
153
|
+
params.merge!(slices: slices) if slices
|
154
|
+
params.merge!(wait_for_completion: false) unless wait_for_completion
|
155
|
+
end
|
156
|
+
|
157
|
+
client.delete_by_query(**request_params).deep_symbolize_keys
|
158
|
+
end
|
159
|
+
|
160
|
+
# Deletes asynchronously the documents matching the given query from the
|
161
|
+
# Index.
|
162
|
+
# For more information on how to build the query please refer to the
|
163
|
+
# Elasticsearch DSL documentation:
|
164
|
+
# https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html
|
165
|
+
# https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete-by-query.html
|
166
|
+
# @param [Hash] query The delete query
|
167
|
+
# @param [Integer, String] slices Number of slices to cut the operation
|
168
|
+
# into for faster processing (i.e., run the operation in parallel). Use
|
169
|
+
# "auto" to make elasticsearch decide how many slices to divide into
|
170
|
+
# @return [Concurrent::Promise] The eventual value returned from the single
|
171
|
+
# completion of the delete operation
|
172
|
+
def delete_by_query_async(query, slices: nil)
|
173
|
+
async.delete_by_query(query, slices: slices)
|
174
|
+
end
|
175
|
+
|
176
|
+
private
|
177
|
+
|
178
|
+
attr_reader :logger, :batch
|
179
|
+
|
180
|
+
# Flushes the current send queue to the Elasticsearch instance and
|
181
|
+
# clears the queue.
|
182
|
+
def flush!
|
183
|
+
logger.info "Pushing data to Elasticsearch (#{batch.size} records)..."
|
184
|
+
response = client.bulk body: batch
|
185
|
+
|
186
|
+
handle_errors(response) if response['errors']
|
187
|
+
|
188
|
+
logger.info 'Done'
|
189
|
+
@batch = []
|
190
|
+
end
|
191
|
+
|
192
|
+
# @param [Hash] query The elastic search query.
|
193
|
+
# @param [JayAPI::Elasticsearch::Response] response The response to the query.
|
194
|
+
# @param [JayAPI::Elasticsearch::BatchCounter, nil] batch_counter Object keeping track of batches.
|
195
|
+
# @param [Symbol, nil] type Type of query, at the moment either nil or :search_after.
|
196
|
+
# @return [QueryResults]
|
197
|
+
def query_results(query, response, batch_counter, type)
|
198
|
+
(type == :search_after ? SearchAfterResults : QueryResults).new(
|
199
|
+
index: self,
|
200
|
+
query: query,
|
201
|
+
response: response,
|
202
|
+
batch_counter: BatchCounter.create_or_update(batch_counter, query, response.size)
|
203
|
+
)
|
204
|
+
end
|
205
|
+
|
206
|
+
# Scans the Elasticsearch response in search for the first item that has
|
207
|
+
# en erroneous state and raises an error including the error details.
|
208
|
+
# @param [Hash] response The response returned by the Elasticsearch client.
|
209
|
+
# @raise [Errors::ElasticsearchError] Is always raised.
|
210
|
+
def handle_errors(response)
|
211
|
+
error_item = response['items'].find { |item| item['index']['error'] }
|
212
|
+
|
213
|
+
raise Errors::ElasticsearchError,
|
214
|
+
"An error occurred when pushing the data to Elasticsearch:\n#{error_item['index']['error'].inspect}"
|
215
|
+
end
|
216
|
+
|
217
|
+
# @return [JayAPI::Elasticsearch::Async]
|
218
|
+
def async
|
219
|
+
@async ||= JayAPI::Elasticsearch::Async.new(self)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'errors/aggregations_error'
|
4
|
+
|
5
|
+
module JayAPI
|
6
|
+
module Elasticsearch
|
7
|
+
class QueryBuilder
|
8
|
+
class Aggregations
|
9
|
+
# Base class for all Elasticsearch aggregation types. For more
|
10
|
+
# information on what types of aggregations are available see:
|
11
|
+
# https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html
|
12
|
+
class Aggregation
|
13
|
+
attr_reader :name
|
14
|
+
|
15
|
+
# @param [String] name The name used by Elasticsearch to identify each
|
16
|
+
# of the aggregations.
|
17
|
+
def initialize(name)
|
18
|
+
@name = name
|
19
|
+
end
|
20
|
+
|
21
|
+
# Creates nested aggregations inside the receiver.
|
22
|
+
# @yieldparam [JayAPI::Elasticsearch::QueryBuilder::Aggregations] The
|
23
|
+
# receiver's nested aggregations object.
|
24
|
+
# @return [JayAPI::Elasticsearch::QueryBuilder::Aggregations, self] If
|
25
|
+
# no block is given then the nested aggregations are returned
|
26
|
+
# instead of yielded. If a block is given then the receiver is
|
27
|
+
# returned.
|
28
|
+
def aggs(&block)
|
29
|
+
aggregations = self.aggregations ||= ::JayAPI::Elasticsearch::QueryBuilder::Aggregations.new
|
30
|
+
return aggregations unless block
|
31
|
+
|
32
|
+
block.call(aggregations)
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
# @raise [NotImplementedError] Is always raised. The child classes
|
37
|
+
# **must** override the method.
|
38
|
+
def clone
|
39
|
+
raise NotImplementedError, "Please implement #{__method__} in #{self.class}"
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
attr_accessor :aggregations
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
# @return [Hash] The Hash representation of the +Aggregation+.
|
49
|
+
# Properly formatted for Elasticsearch.
|
50
|
+
def to_h(&block)
|
51
|
+
{ name => block.call.merge(aggregations.to_h) } # nil.to_h -> {} and Aggregations#to_h = {} when empty.
|
52
|
+
end
|
53
|
+
|
54
|
+
# @param [String] aggregation The name of the aggregation that cannot
|
55
|
+
# have nested aggregations
|
56
|
+
# @raise [JayAPI::Elasticsearch::QueryBuilder::Aggregations::Errors::AggregationsError]
|
57
|
+
# Is always raised..
|
58
|
+
def no_nested_aggregations(aggregation)
|
59
|
+
raise ::JayAPI::Elasticsearch::QueryBuilder::Aggregations::Errors::AggregationsError,
|
60
|
+
"The #{aggregation} aggregation cannot have nested aggregations."
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'aggregation'
|
4
|
+
|
5
|
+
module JayAPI
|
6
|
+
module Elasticsearch
|
7
|
+
class QueryBuilder
|
8
|
+
class Aggregations
|
9
|
+
# Represents an +avg+ aggregation in Elasticsearch.
|
10
|
+
# Information on this type of aggregation can be found here:
|
11
|
+
# https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-avg-aggregation.html
|
12
|
+
class Avg < ::JayAPI::Elasticsearch::QueryBuilder::Aggregations::Aggregation
|
13
|
+
attr_reader :field, :missing
|
14
|
+
|
15
|
+
# @param [String] name The name used by Elasticsearch to identify each
|
16
|
+
# of the aggregations.
|
17
|
+
# @param [String] field The field over which the average should be
|
18
|
+
# calculated.
|
19
|
+
# @param [Float] missing The value to use for the documents where
|
20
|
+
# +field+ is missing. If no value is provided for +missing+ these
|
21
|
+
# documents are ignored,
|
22
|
+
def initialize(name, field:, missing: nil)
|
23
|
+
super(name)
|
24
|
+
|
25
|
+
@field = field
|
26
|
+
@missing = missing
|
27
|
+
end
|
28
|
+
|
29
|
+
# @raise [JayAPI::Elasticsearch::QueryBuilder::Aggregations::Errors::AggregationsError]
|
30
|
+
# Is always raised. The Avg aggregation cannot have nested aggregations.
|
31
|
+
def aggs
|
32
|
+
no_nested_aggregations('Avg')
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [self] A copy of the receiver.
|
36
|
+
def clone
|
37
|
+
self.class.new(name, field: field, missing: missing)
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return [Hash] The Hash representation of the +Aggregation+.
|
41
|
+
# Properly formatted for Elasticsearch.
|
42
|
+
def to_h
|
43
|
+
super do
|
44
|
+
{
|
45
|
+
avg: {
|
46
|
+
field: field,
|
47
|
+
missing: missing
|
48
|
+
}.compact
|
49
|
+
}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../../../errors/error'
|
4
|
+
|
5
|
+
module JayAPI
|
6
|
+
module Elasticsearch
|
7
|
+
class QueryBuilder
|
8
|
+
class Aggregations
|
9
|
+
module Errors
|
10
|
+
# An error to be raised when aggregations are used or nested in an
|
11
|
+
# unsupported way.
|
12
|
+
class AggregationsError < ::JayAPI::Errors::Error; end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'errors/aggregations_error'
|
4
|
+
|
5
|
+
module JayAPI
|
6
|
+
module Elasticsearch
|
7
|
+
class QueryBuilder
|
8
|
+
class Aggregations
|
9
|
+
# Namespace for all the error classes related to Aggregations.
|
10
|
+
module Errors; end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_support/core_ext/string/inflections'
|
5
|
+
|
6
|
+
require_relative '../query_clauses'
|
7
|
+
require_relative 'aggregation'
|
8
|
+
require_relative 'errors/aggregations_error'
|
9
|
+
|
10
|
+
module JayAPI
|
11
|
+
module Elasticsearch
|
12
|
+
class QueryBuilder
|
13
|
+
class Aggregations
|
14
|
+
# Represents a +filter+ aggregation in Elasticsearch.
|
15
|
+
# Information on this type of aggregation can be found here:
|
16
|
+
# https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-filter-aggregation.html
|
17
|
+
class Filter < ::JayAPI::Elasticsearch::QueryBuilder::Aggregations::Aggregation
|
18
|
+
# @param [String] name The name used by Elasticsearch to identify each
|
19
|
+
# of the aggregations.
|
20
|
+
# @yieldparam [JayAPI::Elasticsearch::QueryBuilder::QueryClauses] The
|
21
|
+
# subquery for the +filter+ aggregation.
|
22
|
+
def initialize(name, &block)
|
23
|
+
super(name)
|
24
|
+
|
25
|
+
unless block
|
26
|
+
raise(::JayAPI::Elasticsearch::QueryBuilder::Aggregations::Errors::AggregationsError,
|
27
|
+
"The #{self.class.name.demodulize} aggregation must be initialized with a block")
|
28
|
+
end
|
29
|
+
|
30
|
+
block.call(query)
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [self] A copy of the receiver.
|
34
|
+
def clone
|
35
|
+
# rubocop:disable Lint/EmptyBlock (The query will be assigned later)
|
36
|
+
copy = self.class.new(name) {}
|
37
|
+
# rubocop:enable Lint/EmptyBlock
|
38
|
+
|
39
|
+
copy.query = query.clone
|
40
|
+
copy.aggregations = aggregations.clone
|
41
|
+
copy
|
42
|
+
end
|
43
|
+
|
44
|
+
# @return [Hash] The Hash representation of the +Aggregation+.
|
45
|
+
# Properly formatted for Elasticsearch.
|
46
|
+
def to_h
|
47
|
+
super do
|
48
|
+
{
|
49
|
+
filter: query.to_h
|
50
|
+
}
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
protected
|
55
|
+
|
56
|
+
attr_writer :query
|
57
|
+
|
58
|
+
# @return [JayAPI::Elasticsearch::QueryBuilder::QueryClauses] The
|
59
|
+
# +filter+ aggregation's subquery.
|
60
|
+
def query
|
61
|
+
@query ||= ::JayAPI::Elasticsearch::QueryBuilder::QueryClauses.new
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|