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
data/README.md
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
# Jay API
|
2
|
+
|
3
|
+
This gem provides a set of classes and modules to access Jay functionality
|
4
|
+
while abstracting internal implementations.
|
5
|
+
|
6
|
+
## Requirements
|
7
|
+
|
8
|
+
* Ruby >= 2.7.0
|
9
|
+
* Bundler ~> 2, < 2.5.0
|
10
|
+
|
11
|
+
## Setup
|
12
|
+
|
13
|
+
Clone the repository and install the dependencies by running:
|
14
|
+
|
15
|
+
```shell
|
16
|
+
bundle install
|
17
|
+
```
|
18
|
+
|
19
|
+
## Running Tests
|
20
|
+
|
21
|
+
You can run the tests just by executing rspec.
|
22
|
+
|
23
|
+
```shell
|
24
|
+
bundle exec rspec
|
25
|
+
```
|
26
|
+
|
27
|
+
To generate a Coverage report:
|
28
|
+
|
29
|
+
```shell
|
30
|
+
export COVERAGE=true
|
31
|
+
rspec
|
32
|
+
```
|
33
|
+
|
34
|
+
*The coverage report will be written to the `/coverage` path*
|
35
|
+
|
36
|
+
## Generating Documentation
|
37
|
+
|
38
|
+
```shell
|
39
|
+
bundle exec yard
|
40
|
+
```
|
41
|
+
|
42
|
+
*The documentation will be generated in the `/doc` path*
|
43
|
+
|
44
|
+
## Contributing
|
45
|
+
|
46
|
+
* This project uses [Semantic Versioning](https://semver.org/)
|
47
|
+
* This project uses a CHANGELOG.md to keep track of the changes.
|
48
|
+
|
49
|
+
1. Add your feature.
|
50
|
+
2. While editing your code keep an eye out for Rubocop and Reek suggestions
|
51
|
+
try to keep both linters happy. 😉
|
52
|
+
3. Write unit and integration *(desirably but not required)* tests for it.
|
53
|
+
4. Run the tests with the coverage report generation enabled (Check the *Running
|
54
|
+
Tests section)*.
|
55
|
+
5. Make sure your Unit Test coverage is at least 90%
|
56
|
+
6. Run the `yard` command to generate documentation and make sure your
|
57
|
+
documentation coverage is 100% (everything should be documented)
|
58
|
+
7. Add your features to the `CHANGELOG.md` file under the *Unreleased* section.
|
59
|
+
(Check the `CHANGELOG.md`) file for info on how to properly add the changes
|
60
|
+
there.
|
61
|
+
8. Push your changes for code review
|
data/jay_api.gemspec
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lib/jay_api/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'jay_api'
|
7
|
+
spec.version = JayAPI::VERSION
|
8
|
+
spec.authors = ['Accenture-Industry X', 'ESR Labs']
|
9
|
+
|
10
|
+
spec.summary = "A collection of classes and modules to access JAY's functionality"
|
11
|
+
spec.description = "A collection of classes and modules to access JAY's functionality"
|
12
|
+
spec.homepage = 'https://github.com/esrlabs/jay_api'
|
13
|
+
spec.license = 'Apache-2.0'
|
14
|
+
|
15
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 2.7.0')
|
16
|
+
|
17
|
+
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
18
|
+
|
19
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
20
|
+
spec.metadata['source_code_uri'] = spec.homepage
|
21
|
+
spec.metadata['changelog_uri'] = 'https://github.com/esrlabs/jay_api/blob/master/CHANGELOG.md'
|
22
|
+
|
23
|
+
# Specify which files should be added to the gem when it is released.
|
24
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
25
|
+
|
26
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
27
|
+
`git ls-files -z`.split("\x0").select { |f| f.match(%r{^(CHANGELOG|README|lib/)}) } << File.basename(__FILE__)
|
28
|
+
end
|
29
|
+
|
30
|
+
spec.require_paths = ['lib']
|
31
|
+
|
32
|
+
spec.add_runtime_dependency 'activesupport', '~> 7'
|
33
|
+
spec.add_runtime_dependency 'concurrent-ruby', '~> 1'
|
34
|
+
spec.add_runtime_dependency 'elasticsearch', '~> 7', '<= 7.9.0'
|
35
|
+
spec.add_runtime_dependency 'git', '~> 1', '>= 1.8.0-1'
|
36
|
+
spec.add_runtime_dependency 'logging', '~> 2'
|
37
|
+
spec.add_runtime_dependency 'rspec', '~> 3.0'
|
38
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JayAPI
|
4
|
+
module Abstract
|
5
|
+
# A class for an abstract 'Connection'. It is responsible for yielding a block
|
6
|
+
# for +max_attempts+ times at most, or until a specified +error+ is no longer
|
7
|
+
# raised. The reason the class is specifically called 'Connection', is because
|
8
|
+
# it contains logging that describes a connection.
|
9
|
+
class Connection
|
10
|
+
attr_reader :attempts, :max_attempts, :wait_strategy, :logger
|
11
|
+
|
12
|
+
# @param [Integer] max_attempts The maximum number of connection attempts to be made.
|
13
|
+
# @param [JayAPI::Elasticsearch::WaitStrategy] wait_strategy The waiting strategy for reconnections.
|
14
|
+
# @param [Logging::Logger] logger
|
15
|
+
def initialize(max_attempts:, wait_strategy:, logger:)
|
16
|
+
@max_attempts = max_attempts
|
17
|
+
@wait_strategy = wait_strategy
|
18
|
+
@logger = logger
|
19
|
+
@attempts = 0
|
20
|
+
end
|
21
|
+
|
22
|
+
# Yields the passed block and if the specified 'error' is raised, a new
|
23
|
+
# yield attempt will be made until the +max_attempts+ limit is reached.
|
24
|
+
# @param [Class, Array<Class>] errors Some error Class, or a list of them.
|
25
|
+
# @param [Array<Class>] except An array of exceptions for which no retry
|
26
|
+
# should happen even if they are subclasses of the exception(s) passed
|
27
|
+
# in +errors+.
|
28
|
+
def retry(errors:, except: [])
|
29
|
+
self.attempts += 1
|
30
|
+
yield
|
31
|
+
rescue *errors => e
|
32
|
+
raise if except.any? { |exception| e.is_a?(exception) }
|
33
|
+
|
34
|
+
logger.info("#{e} occurred")
|
35
|
+
if attempts < max_attempts
|
36
|
+
wait_strategy.wait
|
37
|
+
logger.info("Retrying... (There are #{max_attempts - attempts} retries left)")
|
38
|
+
retry
|
39
|
+
end
|
40
|
+
|
41
|
+
logger.info('No more attempts to connect will be made')
|
42
|
+
raise
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
attr_writer :attempts
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'wait_strategy'
|
4
|
+
|
5
|
+
module JayAPI
|
6
|
+
module Abstract
|
7
|
+
# A constant wait strategy implementation of the WaitStrategy abstract class.
|
8
|
+
# This strategy uses a fixed wait interval between retries. The wait interval does not change
|
9
|
+
# regardless of the number of attempts made. It is suitable for scenarios where a constant
|
10
|
+
# delay is preferred over an increasing delay.
|
11
|
+
#
|
12
|
+
# Inherits from WaitStrategy and overrides the wait_time method to provide a linear waiting time.
|
13
|
+
class ConstantWait < WaitStrategy
|
14
|
+
alias wait_time wait_interval
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'wait_strategy'
|
4
|
+
|
5
|
+
module JayAPI
|
6
|
+
module Abstract
|
7
|
+
# A geometric wait strategy implementation of the WaitStrategy abstract class.
|
8
|
+
# This strategy uses a geometrically increasing wait interval between retries.
|
9
|
+
# The wait interval is exponentially increased based on the number of attempts made,
|
10
|
+
# making it suitable for scenarios where a rapidly increasing delay is preferred.
|
11
|
+
#
|
12
|
+
# Inherits from WaitStrategy and overrides the wait_time method to provide a geometrically increasing waiting time.
|
13
|
+
class GeometricWait < WaitStrategy
|
14
|
+
private
|
15
|
+
|
16
|
+
attr_writer :calls_count
|
17
|
+
|
18
|
+
# Determines the time to wait before the next retry in a geometric manner.
|
19
|
+
# The wait time increases exponentially with each call, calculated as wait_interval
|
20
|
+
# raised to the power of call number.
|
21
|
+
# @return [Integer] The exponentially increasing time to wait in seconds.
|
22
|
+
def wait_time
|
23
|
+
self.calls_count += 1
|
24
|
+
wait_interval**calls_count
|
25
|
+
end
|
26
|
+
|
27
|
+
# Tracks the number of calls made to the wait_time method.
|
28
|
+
# This count is used to calculate the geometrically increasing wait time.
|
29
|
+
# @return [Integer] The number of times the wait_time method has been called.
|
30
|
+
def calls_count
|
31
|
+
@calls_count ||= 0
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JayAPI
|
4
|
+
module Abstract
|
5
|
+
# Abstract base class for implementing different waiting strategies.
|
6
|
+
# This class provides a framework for implementing a strategy that dictates how long to wait
|
7
|
+
# before retrying an operation, typically used in situations where an operation might need to be
|
8
|
+
# retried multiple times (like network requests, etc.)
|
9
|
+
#
|
10
|
+
# @abstract Subclass and override {#wait_time} to implement a custom WaitStrategy.
|
11
|
+
class WaitStrategy
|
12
|
+
attr_reader :wait_interval
|
13
|
+
|
14
|
+
# @param [Integer] wait_interval The initial time to wait before retrying.
|
15
|
+
# @param [Logging::Logger] logger The logger to be used for logging wait times, defaults to stdout.
|
16
|
+
def initialize(wait_interval:, logger: nil)
|
17
|
+
@wait_interval = wait_interval
|
18
|
+
@logger = logger || Logging.logger($stdout)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Executes the wait strategy.
|
22
|
+
# Logs the waiting time and pauses the execution for the determined wait time.
|
23
|
+
def wait
|
24
|
+
wait_time.tap do |wait_time|
|
25
|
+
logger.info("Sleeping: #{format('%.2f', wait_time)} s")
|
26
|
+
Kernel.sleep(wait_time)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
attr_reader :logger
|
33
|
+
|
34
|
+
# Determines the time to wait before the next retry.
|
35
|
+
# This method must be implemented by subclasses.
|
36
|
+
# @raise [NotImplementedError] if the method is not overridden in a subclass.
|
37
|
+
# @return [Integer] The time to wait in seconds.
|
38
|
+
def wait_time
|
39
|
+
raise(NotImplementedError, "#{self.class} must implement the #{__method__} method")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_support/core_ext/hash/keys'
|
5
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
6
|
+
|
7
|
+
require 'erb'
|
8
|
+
require 'forwardable'
|
9
|
+
require 'ostruct'
|
10
|
+
require 'yaml'
|
11
|
+
|
12
|
+
require_relative 'errors/configuration_error'
|
13
|
+
|
14
|
+
module JayAPI
|
15
|
+
# Hold the configuration for Jay's API
|
16
|
+
class Configuration < OpenStruct
|
17
|
+
extend Forwardable
|
18
|
+
|
19
|
+
def_delegators :deep_to_h, :with_indifferent_access
|
20
|
+
|
21
|
+
# Loads the configuration from the given file.
|
22
|
+
# @param [String] file_name The file from which to load the configuration.
|
23
|
+
# @return [JayAPI::Configuration] The configuration for Jay's API.
|
24
|
+
# @raise [Errno::ENOENT] If the given file cannot be found.
|
25
|
+
# @raise [Psych::DisallowedClass] If the YAML contains a class other than
|
26
|
+
# Symbol
|
27
|
+
def self.from_file(file_name)
|
28
|
+
from_string(File.read(file_name))
|
29
|
+
end
|
30
|
+
|
31
|
+
# Loads the configuration from the given YAML string.
|
32
|
+
# @param [String] yaml The YAML string containing the configuration.
|
33
|
+
# @return [JayAPI::Configuration] The configuration for Jay's API
|
34
|
+
# @raise [Psych::DisallowedClass] If the YAML contains a class other than
|
35
|
+
# Symbol
|
36
|
+
def self.from_string(yaml)
|
37
|
+
yaml = ERB.new(yaml).result
|
38
|
+
config = YAML.safe_load(yaml, permitted_classes: [Symbol])
|
39
|
+
|
40
|
+
unless config.is_a?(Hash)
|
41
|
+
raise JayAPI::Errors::ConfigurationError.new(
|
42
|
+
"Jay's configuration should be a set of key-value pairs.", yaml
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
from_hash(config)
|
47
|
+
end
|
48
|
+
|
49
|
+
class << self
|
50
|
+
private
|
51
|
+
|
52
|
+
# Creates an instance of the class by parsing the given Hash.
|
53
|
+
# Nested hashes are recursively parsed an new instances of the class are
|
54
|
+
# created from them.
|
55
|
+
# @param [Hash] hash The hash with the data.
|
56
|
+
# @return [JayAPI::Configuration] An instance of the class created out of
|
57
|
+
# the given Hash.
|
58
|
+
def from_hash(hash)
|
59
|
+
new.tap do |configuration|
|
60
|
+
hash.symbolize_keys.each do |key, value|
|
61
|
+
configuration[key] = parsed_value(value)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Takes a value and parses it in accordance to its type.
|
67
|
+
# @param [Object] value The value to parse.
|
68
|
+
# @return [JayAPI::Configuration, Array, Object] The parsed value.
|
69
|
+
def parsed_value(value)
|
70
|
+
case value
|
71
|
+
when Hash
|
72
|
+
from_hash(value)
|
73
|
+
when Array
|
74
|
+
value.map { |item| parsed_value(item) }
|
75
|
+
else
|
76
|
+
value
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Recursively converts the receiver into a standard Hash
|
82
|
+
# @return [Hash] The result of the conversion.
|
83
|
+
def deep_to_h
|
84
|
+
to_h { |key, value| [key, value_for_h(value)] }
|
85
|
+
end
|
86
|
+
|
87
|
+
# @return [String] The configuration in the YAML format
|
88
|
+
def to_yaml
|
89
|
+
YAML.dump(deep_to_h.deep_stringify_keys)
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
# Takes a value and transforms it in accordance to its type.
|
95
|
+
# @param [Object] value The value to convert.
|
96
|
+
# * JayAPI::Configuration objects are transformed to hashes recursively.
|
97
|
+
# * Hashes are kept as Hashes but its values are transformed recursively.
|
98
|
+
# * Arrays are transformed recursively.
|
99
|
+
# * Any other value is left as is.
|
100
|
+
# @return [Object] The converted value (or the same value if the method
|
101
|
+
# doesn't know how to convert it).
|
102
|
+
def value_for_h(value)
|
103
|
+
case value
|
104
|
+
when self.class
|
105
|
+
value.deep_to_h
|
106
|
+
when Hash
|
107
|
+
value.to_h { |hash_key, hash_value| [hash_key, value_for_h(hash_value)] }
|
108
|
+
when Array
|
109
|
+
value.map { |element| value_for_h(element) }
|
110
|
+
else
|
111
|
+
value
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'concurrent/promise'
|
4
|
+
require 'forwardable'
|
5
|
+
|
6
|
+
require_relative 'errors/query_execution_error'
|
7
|
+
require_relative 'errors/query_execution_failure'
|
8
|
+
require_relative 'tasks'
|
9
|
+
|
10
|
+
module JayAPI
|
11
|
+
module Elasticsearch
|
12
|
+
# Provides functionality to perform asynchronous operations on an
|
13
|
+
# elasticsearch index. For more information:
|
14
|
+
# https://ruby-concurrency.github.io/concurrent-ruby/1.3.4/Concurrent
|
15
|
+
class Async
|
16
|
+
extend Forwardable
|
17
|
+
|
18
|
+
attr_reader :index
|
19
|
+
|
20
|
+
def_delegators :index, :index_name
|
21
|
+
|
22
|
+
# @param [JayAPI::Elasticsearch::Index] index The elasticsearch index on
|
23
|
+
# which to execute asynchronous operations
|
24
|
+
def initialize(index)
|
25
|
+
@index = index
|
26
|
+
end
|
27
|
+
|
28
|
+
# Deletes asynchronously the documents matching the given query from the
|
29
|
+
# Index.
|
30
|
+
# @see JayAPI::Elasticsearch::Index#delete_by_query for more info
|
31
|
+
# @param [Hash] query The delete query
|
32
|
+
# @param [Integer, String] slices Number of slices to cut the operation
|
33
|
+
# into for faster processing (i.e., run the operation in parallel). Use
|
34
|
+
# "auto" to make elasticsearch decide how many slices to divide into
|
35
|
+
# @return [Concurrent::Promise] The eventual value returned from the
|
36
|
+
# single completion of the delete operation
|
37
|
+
# @raise [Errors::QueryExecutionError] If executing the query results in
|
38
|
+
# errors
|
39
|
+
# @raise [Errors::QueryExecutionFailure] If executing the query results in
|
40
|
+
# failures
|
41
|
+
def delete_by_query(query, slices: 5)
|
42
|
+
Concurrent::Promise.execute do
|
43
|
+
async_response = index.delete_by_query(query, slices: slices, wait_for_completion: false)
|
44
|
+
result = tasks.by_id(async_response[:task])
|
45
|
+
validate_result(result)
|
46
|
+
result
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
# @param [Hash] result The operation result to be validated
|
53
|
+
# @raise [Errors::QueryExecutionError] If executing the query results in
|
54
|
+
# errors
|
55
|
+
# @raise [Errors::QueryExecutionFailure] If executing the query results in
|
56
|
+
# failures
|
57
|
+
def validate_result(result)
|
58
|
+
raise Errors::QueryExecutionError, "Errors on index '#{index_name}':\n #{result[:error]}" if result[:error]
|
59
|
+
|
60
|
+
failures = result&.dig(:response, :failures)
|
61
|
+
return if failures.nil? || failures.empty?
|
62
|
+
|
63
|
+
raise Errors::QueryExecutionFailure, "Failures on index '#{index_name}':\n #{failures}"
|
64
|
+
end
|
65
|
+
|
66
|
+
# @return [JayAPI::Elasticsearch::Tasks]
|
67
|
+
def tasks
|
68
|
+
@tasks ||= JayAPI::Elasticsearch::Tasks.new(client: index.client)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
5
|
+
|
6
|
+
module JayAPI
|
7
|
+
module Elasticsearch
|
8
|
+
# Manages and tracks the current batch within the QueryResults context. This class is responsible for
|
9
|
+
# keeping track of the current batch start position and calculating the start position for the next batch
|
10
|
+
# based on the batch size.
|
11
|
+
class BatchCounter
|
12
|
+
# The start of the batch to default to, if no other information is provided.
|
13
|
+
DEFAULT_START = 0
|
14
|
+
|
15
|
+
# @!attribute [r] batch_size
|
16
|
+
# @return [Integer] The size of each batch as determined by the query or the default size
|
17
|
+
# @!attribute [r] start_current
|
18
|
+
# @return [Integer] The starting index of the current batch
|
19
|
+
# @!attribute [r] start_next
|
20
|
+
# @return [Integer] The calculated starting index of the next batch
|
21
|
+
attr_reader :batch_size, :start_current, :start_next
|
22
|
+
|
23
|
+
# Creates a new +BatchCounter+ object by either updating a copy of the *batch* instance with new values or
|
24
|
+
# creates a new instance if none exists.
|
25
|
+
# @param [BatchCounter, nil] batch An existing BatchCounter to update or nil to create a new one
|
26
|
+
# @param [Hash] query The Elasticsearch query containing the batch information
|
27
|
+
# @param [Integer] size The size of the current batch; also serves as a default batch size
|
28
|
+
# @return [BatchCounter] A new +BatchCounter+ created out of the given parameters.
|
29
|
+
def self.create_or_update(batch, query, size)
|
30
|
+
if batch
|
31
|
+
new(query, size, batch.start_next, batch.start_next + size, batch.batch_size)
|
32
|
+
else
|
33
|
+
new(query, size)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
attr_reader :query, :size
|
40
|
+
|
41
|
+
# @param [Hash] query The Elasticsearch query which may contain :size and :from parameters
|
42
|
+
# @param [Integer] size The size of the batch; used as a default when no batch_size is provided
|
43
|
+
# @param [Integer, nil] start_current The starting index for the current batch; defaults to the query's :from
|
44
|
+
# or DEFAULT_START
|
45
|
+
# @param [Integer, nil] start_next The starting index for the next batch; calculated from start_current and size
|
46
|
+
# @param [Integer, nil] batch_size The size of the batch; taken from the query's :size or the provided size
|
47
|
+
# parameter
|
48
|
+
def initialize(query, size, start_current = nil, start_next = nil, batch_size = nil)
|
49
|
+
@query = query.symbolize_keys
|
50
|
+
@size = size
|
51
|
+
|
52
|
+
@start_current = start_current || fallback_start_current
|
53
|
+
@start_next = start_next || fallback_start_next
|
54
|
+
@batch_size = batch_size || fallback_batch_size
|
55
|
+
end
|
56
|
+
|
57
|
+
# Provides a default starting index for the next batch based on the current start index and batch size.
|
58
|
+
# @return [Integer] The calculated start index for the next batch
|
59
|
+
def fallback_start_next
|
60
|
+
start_current + size
|
61
|
+
end
|
62
|
+
|
63
|
+
# Provides a default starting index for the current batch from the query or a constant.
|
64
|
+
# @return [Integer] The starting index for the current batch
|
65
|
+
def fallback_start_current
|
66
|
+
query[:from] || DEFAULT_START
|
67
|
+
end
|
68
|
+
|
69
|
+
# Determines the batch size from the query or uses the provided size as a fallback.
|
70
|
+
# @return [Integer] The size of the batch
|
71
|
+
def fallback_batch_size
|
72
|
+
query[:size] || size
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'elasticsearch/api/namespace/tasks'
|
4
|
+
require 'elasticsearch/transport/transport/errors'
|
5
|
+
require 'faraday/error'
|
6
|
+
|
7
|
+
require_relative '../abstract/connection'
|
8
|
+
|
9
|
+
module JayAPI
|
10
|
+
module Elasticsearch
|
11
|
+
# The JayAPI wrapper class over the Elastisearch::Client object. It mirrors
|
12
|
+
# the object's API, but if one of the ERRORS is raised, this Wrapper class will
|
13
|
+
# rescue the error up to a few times and re-try the connection. This way the
|
14
|
+
# connection to Elasticsearch will be more robust.
|
15
|
+
class Client
|
16
|
+
# The errors that, if raised, must cause a retry of the connection.
|
17
|
+
ERRORS = [
|
18
|
+
::Elasticsearch::Transport::Transport::ServerError,
|
19
|
+
Faraday::TimeoutError
|
20
|
+
].freeze
|
21
|
+
|
22
|
+
# Subclasses of the +Elasticsearch::Transport::Transport::ServerError+
|
23
|
+
# for which a retry doesn't make sense.
|
24
|
+
NON_RETRIABLE_ERRORS = [
|
25
|
+
::Elasticsearch::Transport::Transport::Errors::BadRequest,
|
26
|
+
::Elasticsearch::Transport::Transport::Errors::Unauthorized,
|
27
|
+
::Elasticsearch::Transport::Transport::Errors::Forbidden,
|
28
|
+
::Elasticsearch::Transport::Transport::Errors::NotFound,
|
29
|
+
::Elasticsearch::Transport::Transport::Errors::MethodNotAllowed,
|
30
|
+
::Elasticsearch::Transport::Transport::Errors::RequestEntityTooLarge,
|
31
|
+
::Elasticsearch::Transport::Transport::Errors::NotImplemented
|
32
|
+
].freeze
|
33
|
+
|
34
|
+
attr_reader :transport_client, :logger, :max_attempts, :wait_strategy
|
35
|
+
|
36
|
+
# @param [Elasticsearch::Transport::Client] transport_client The Client
|
37
|
+
# object that will be wrapped.
|
38
|
+
# @param [Logging::Logger] logger
|
39
|
+
# @param [Integer] max_attempts The maximum number of attempts that the connection shall be retried.
|
40
|
+
# @param [JayAPI::Elasticsearch::WaitStrategy] wait_strategy The waiting strategy for reconnections.
|
41
|
+
def initialize(transport_client, logger = nil, max_attempts:, wait_strategy:)
|
42
|
+
@transport_client = transport_client
|
43
|
+
@logger = logger || Logging.logger($stdout)
|
44
|
+
@max_attempts = max_attempts
|
45
|
+
@wait_strategy = wait_strategy
|
46
|
+
end
|
47
|
+
|
48
|
+
# Calls the Elasticsearch::Client's #index method and retries the connection a few times if
|
49
|
+
# a ServerError occurs.
|
50
|
+
# @see Elasticsearch::Client#index for information about the arguments and the returned value.
|
51
|
+
def index(**args)
|
52
|
+
retry_request { transport_client.index(**args) }
|
53
|
+
end
|
54
|
+
|
55
|
+
# Calls the Elasticsearch::Client's #search method and retries the connection a few times if
|
56
|
+
# a ServerError occurs.
|
57
|
+
# @see Elasticsearch::Client#index for information about the arguments and the returned value.
|
58
|
+
def search(**args)
|
59
|
+
retry_request { transport_client.search(**args) }
|
60
|
+
end
|
61
|
+
|
62
|
+
# Calls the Elasticsearch::Client's #bulk method and retries the connection a few times if
|
63
|
+
# a ServerError occurs.
|
64
|
+
# @see Elasticsearch::Client#index for information about the arguments and the returned value.
|
65
|
+
def bulk(**args)
|
66
|
+
retry_request { transport_client.bulk(**args) }
|
67
|
+
end
|
68
|
+
|
69
|
+
# Calls the +Elasticsearch::Client+'s #delete_by_query method forwarding
|
70
|
+
# the given parameters. If the request fails additional retries will be
|
71
|
+
# performed.
|
72
|
+
# @see Elasticsearch::Client#delete_by_query for information about the
|
73
|
+
# arguments and the return value.
|
74
|
+
def delete_by_query(**args)
|
75
|
+
retry_request { transport_client.delete_by_query(**args) }
|
76
|
+
end
|
77
|
+
|
78
|
+
# Calls +Elasticsearch::Client+'s #tasks.get method forwarding the given
|
79
|
+
# parameters. If the request fails, additional retries will be performed.
|
80
|
+
# @see Elasticsearch::Client#tasks for more info about the arguments and
|
81
|
+
# the return value.
|
82
|
+
def task_by_id(**args)
|
83
|
+
retry_request { transport_client.tasks.get(**args) }
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
# @param [Proc] block The block to execute.
|
89
|
+
# @yieldreturn [Object] Whatever the block returns
|
90
|
+
def retry_request(&block)
|
91
|
+
Abstract::Connection.new(max_attempts: max_attempts, wait_strategy: wait_strategy.dup, logger: logger)
|
92
|
+
.retry(errors: ERRORS, except: NON_RETRIABLE_ERRORS, &block)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|