solrb 0.2.3 → 0.2.8
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/.github/workflows/reviewdog.yml +20 -0
- data/.github/workflows/tests.yaml +37 -0
- data/.gitignore +2 -0
- data/.rubocop.yml +10 -6
- data/README.md +47 -0
- data/lib/solr.rb +21 -1
- data/lib/solr/cloud/configuration.rb +2 -1
- data/lib/solr/cloud/helper_methods.rb +1 -1
- data/lib/solr/commands.rb +3 -2
- data/lib/solr/commit/request.rb +15 -5
- data/lib/solr/configuration.rb +27 -5
- data/lib/solr/connection.rb +6 -4
- data/lib/solr/data_import/request.rb +7 -1
- data/lib/solr/delete/request.rb +7 -1
- data/lib/solr/errors/solr_connection_failed_error.rb +3 -2
- data/lib/solr/helper_methods.rb +11 -0
- data/lib/solr/indexing/request.rb +7 -1
- data/lib/solr/master_slave/configuration.rb +38 -0
- data/lib/solr/master_slave/helper_methods.rb +25 -0
- data/lib/solr/master_slave/nodes_gray_list/disabled.rb +21 -0
- data/lib/solr/master_slave/nodes_gray_list/in_memory.rb +47 -0
- data/lib/solr/query/handler.rb +8 -7
- data/lib/solr/query/http_request_builder.rb +6 -12
- data/lib/solr/query/request.rb +19 -2
- data/lib/solr/query/request/boosting/dictionary_boost_function.rb +4 -3
- data/lib/solr/query/request/facet.rb +5 -2
- data/lib/solr/request/cloud/first_shard_leader_node_selection_strategy.rb +27 -0
- data/lib/solr/request/cloud/leader_node_selection_strategy.rb +21 -0
- data/lib/solr/request/default_node_selection_strategy.rb +2 -1
- data/lib/solr/request/master_slave/master_node_selection_strategy.rb +12 -0
- data/lib/solr/request/node_selection_strategy.rb +6 -0
- data/lib/solr/request/runner.rb +18 -11
- data/lib/solr/response/parser.rb +1 -1
- data/lib/solr/support/url_helper.rb +8 -3
- data/lib/solr/version.rb +1 -1
- data/solrb.gemspec +2 -3
- metadata +25 -35
- data/.circleci/config.yml +0 -69
- data/.circleci/run-with-local-config.sh +0 -7
- data/Gemfile.lock +0 -83
- data/lib/solr/request/first_shard_leader_node_selection_strategy.rb +0 -24
- data/lib/solr/request/leader_node_selection_strategy.rb +0 -18
@@ -18,7 +18,13 @@ module Solr
|
|
18
18
|
private
|
19
19
|
|
20
20
|
def default_runner_options
|
21
|
-
|
21
|
+
if Solr.cloud_enabled?
|
22
|
+
{ node_selection_strategy: Solr::Request::Cloud::LeaderNodeSelectionStrategy }
|
23
|
+
elsif Solr.master_slave_enabled?
|
24
|
+
{ node_selection_strategy: Solr::Request::MasterSlave::MasterNodeSelectionStrategy }
|
25
|
+
else
|
26
|
+
{}
|
27
|
+
end
|
22
28
|
end
|
23
29
|
|
24
30
|
def build_http_request(commit)
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'solr/master_slave/nodes_gray_list/disabled'
|
2
|
+
require 'solr/master_slave/nodes_gray_list/in_memory'
|
3
|
+
|
4
|
+
module Solr
|
5
|
+
module MasterSlave
|
6
|
+
class Configuration
|
7
|
+
attr_accessor :master_url, :slave_url, :disable_read_from_master
|
8
|
+
|
9
|
+
attr_reader :master_slave_enabled
|
10
|
+
attr_writer :nodes_gray_list
|
11
|
+
|
12
|
+
def enable_master_slave!(_)
|
13
|
+
@master_slave_enabled = true
|
14
|
+
end
|
15
|
+
|
16
|
+
def master_slave_enabled?
|
17
|
+
@master_slave_enabled
|
18
|
+
end
|
19
|
+
|
20
|
+
def active_nodes_for(collection:)
|
21
|
+
urls = []
|
22
|
+
urls.push(master_url) unless disable_read_from_master
|
23
|
+
urls.push(*slave_url) if slave_url
|
24
|
+
nodes_gray_list.select_active(urls, collection_name: collection)
|
25
|
+
end
|
26
|
+
|
27
|
+
def nodes_gray_list
|
28
|
+
@nodes_gray_list || gray_list_disabled_instance
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def gray_list_disabled_instance
|
34
|
+
@gray_list_disabled_instance ||= Solr::MasterSlave::NodesGrayList::Disabled.new
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'solr/master_slave/configuration'
|
2
|
+
|
3
|
+
module Solr
|
4
|
+
module MasterSlave
|
5
|
+
module HelperMethods
|
6
|
+
def master_slave_active_nodes_for(collection:)
|
7
|
+
master_slave_configuration.active_nodes_for(collection: collection)
|
8
|
+
end
|
9
|
+
|
10
|
+
def master_slave_enabled?
|
11
|
+
master_slave_configuration.master_slave_enabled?
|
12
|
+
end
|
13
|
+
|
14
|
+
def enable_master_slave!
|
15
|
+
master_slave_configuration.enable_master_slave!(configuration.cores.keys)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def master_slave_configuration
|
21
|
+
configuration.master_slave_configuration
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Solr
|
2
|
+
module MasterSlave
|
3
|
+
module NodesGrayList
|
4
|
+
class Disabled
|
5
|
+
def add(_)
|
6
|
+
end
|
7
|
+
|
8
|
+
def remove(_)
|
9
|
+
end
|
10
|
+
|
11
|
+
def added?(_)
|
12
|
+
true
|
13
|
+
end
|
14
|
+
|
15
|
+
def select_active(urls, collection_name:)
|
16
|
+
urls
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Solr
|
2
|
+
module MasterSlave
|
3
|
+
module NodesGrayList
|
4
|
+
class InMemory
|
5
|
+
attr_reader :gray_list, :removal_period
|
6
|
+
|
7
|
+
DEFAULT_REMOVAL_PERIOD = 5 * 60 # 5 minutes in seconds
|
8
|
+
|
9
|
+
def initialize(removal_period: DEFAULT_REMOVAL_PERIOD)
|
10
|
+
@gray_list = {}
|
11
|
+
@removal_period = removal_period
|
12
|
+
end
|
13
|
+
|
14
|
+
def add(url)
|
15
|
+
return if gray_list.has_key?(url)
|
16
|
+
::Solr.configuration.logger.info("#{url} added to a gray list")
|
17
|
+
gray_list[url] = Time.now.utc
|
18
|
+
end
|
19
|
+
|
20
|
+
def remove(url)
|
21
|
+
gray_list.delete(url)
|
22
|
+
end
|
23
|
+
|
24
|
+
def added?(url)
|
25
|
+
added_at = gray_list[url]
|
26
|
+
return false unless added_at
|
27
|
+
|
28
|
+
if added_at + removal_period > Time.now.utc
|
29
|
+
true
|
30
|
+
else
|
31
|
+
remove(url)
|
32
|
+
false
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def select_active(urls, collection_name:)
|
37
|
+
urls = Array(urls)
|
38
|
+
active_urls = urls.reject do |url|
|
39
|
+
collection_url = File.join(url, collection_name.to_s)
|
40
|
+
added?(collection_url)
|
41
|
+
end
|
42
|
+
active_urls.any? ? active_urls : urls
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/solr/query/handler.rb
CHANGED
@@ -5,21 +5,22 @@ require 'solr/query/http_request_builder'
|
|
5
5
|
module Solr
|
6
6
|
module Query
|
7
7
|
class Handler
|
8
|
-
attr_reader :query, :
|
8
|
+
attr_reader :query, :rows, :start, :runner_options
|
9
9
|
|
10
10
|
def self.call(opts)
|
11
|
-
new(opts).call
|
11
|
+
new(**opts).call
|
12
12
|
end
|
13
13
|
|
14
|
-
def initialize(query:,
|
14
|
+
def initialize(query:, rows:, start:, runner_options: {})
|
15
15
|
@query = query
|
16
|
-
@
|
17
|
-
@
|
16
|
+
@rows = rows
|
17
|
+
@start = start
|
18
|
+
@runner_options = runner_options || {}
|
18
19
|
end
|
19
20
|
|
20
21
|
def call
|
21
|
-
http_request = Solr::Query::HttpRequestBuilder.call(query: query,
|
22
|
-
solr_response = Solr::Request::Runner.call(request: http_request)
|
22
|
+
http_request = Solr::Query::HttpRequestBuilder.call(query: query, start: start, rows: rows)
|
23
|
+
solr_response = Solr::Request::Runner.call(request: http_request, **runner_options)
|
23
24
|
Solr::Query::Response::Parser.new(request: query, solr_response: solr_response.body).to_response
|
24
25
|
end
|
25
26
|
end
|
@@ -5,16 +5,16 @@ module Solr
|
|
5
5
|
class HttpRequestBuilder
|
6
6
|
PATH = '/select'.freeze
|
7
7
|
|
8
|
-
attr_reader :query, :
|
8
|
+
attr_reader :query, :start, :rows
|
9
9
|
|
10
10
|
def self.call(opts)
|
11
|
-
new(opts).call
|
11
|
+
new(**opts).call
|
12
12
|
end
|
13
13
|
|
14
|
-
def initialize(query:,
|
14
|
+
def initialize(query:, start:, rows:)
|
15
15
|
@query = query
|
16
|
-
@
|
17
|
-
@
|
16
|
+
@rows = rows
|
17
|
+
@start = start
|
18
18
|
end
|
19
19
|
|
20
20
|
def call
|
@@ -27,13 +27,7 @@ module Solr
|
|
27
27
|
|
28
28
|
# 🏋️
|
29
29
|
def build_body
|
30
|
-
@request_params ||= { params: solr_params.merge(wt: :json, rows:
|
31
|
-
end
|
32
|
-
|
33
|
-
def start
|
34
|
-
start_page = page.to_i - 1
|
35
|
-
start_page = start_page < 1 ? 0 : start_page
|
36
|
-
start_page * page_size
|
30
|
+
@request_params ||= { params: solr_params.merge(wt: :json, rows: rows, start: start) }
|
37
31
|
end
|
38
32
|
|
39
33
|
def solr_params
|
data/lib/solr/query/request.rb
CHANGED
@@ -30,8 +30,11 @@ module Solr
|
|
30
30
|
@filters = filters
|
31
31
|
end
|
32
32
|
|
33
|
-
def run(page:
|
34
|
-
|
33
|
+
def run(page: nil, start: nil, rows: nil, page_size: nil, runner_options: {})
|
34
|
+
rows ||= page_size
|
35
|
+
return run_paged(page: page, page_size: rows, runner_options: runner_options) if page && rows
|
36
|
+
return run_start(start: start, rows: rows, runner_options: runner_options) if start && rows
|
37
|
+
raise ArgumentError, 'You must specify either page/rows or start/rows arguments'
|
35
38
|
end
|
36
39
|
|
37
40
|
def grouping
|
@@ -45,6 +48,20 @@ module Solr
|
|
45
48
|
def to_h
|
46
49
|
Solr::Query::Request::EdismaxAdapter.new(self).to_h
|
47
50
|
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def run_paged(page: 1, page_size: 10, runner_options:)
|
55
|
+
start_page = page.to_i - 1
|
56
|
+
start_page = start_page < 1 ? 0 : start_page
|
57
|
+
start = start_page * page_size
|
58
|
+
|
59
|
+
Solr::Query::Handler.call(query: self, start: start, rows: page_size, runner_options: runner_options)
|
60
|
+
end
|
61
|
+
|
62
|
+
def run_start(rows: 10, start: 0, runner_options:)
|
63
|
+
Solr::Query::Handler.call(query: self, start: start, rows: rows, runner_options: runner_options)
|
64
|
+
end
|
48
65
|
end
|
49
66
|
end
|
50
67
|
end
|
@@ -5,12 +5,13 @@ module Solr
|
|
5
5
|
class DictionaryBoostFunction
|
6
6
|
include Solr::Support::SchemaHelper
|
7
7
|
using Solr::Support::StringExtensions
|
8
|
-
attr_reader :field, :dictionary
|
8
|
+
attr_reader :field, :dictionary, :default_boost
|
9
9
|
|
10
|
-
def initialize(field:, dictionary:)
|
10
|
+
def initialize(field:, dictionary:, default_boost: 1)
|
11
11
|
raise 'dictionary must be a non-empty Hash' if Hash(dictionary).empty?
|
12
12
|
@field = field
|
13
13
|
@dictionary = dictionary
|
14
|
+
@default_boost = default_boost
|
14
15
|
end
|
15
16
|
|
16
17
|
# example: given a hash (dictionary)
|
@@ -21,7 +22,7 @@ module Solr
|
|
21
22
|
# note that I added spaces for readability, real Solr query functions must always be w/out spaces
|
22
23
|
def to_solr_s
|
23
24
|
sf = solarize_field(field)
|
24
|
-
dictionary.to_a.reverse.reduce(
|
25
|
+
dictionary.to_a.reverse.reduce(default_boost) do |acc, (field_value, boost)|
|
25
26
|
if field_value.is_a?(String) || field_value.is_a?(Symbol)
|
26
27
|
"if(termfreq(#{sf},\"#{field_value.to_s.solr_escape}\"),#{boost},#{acc})"
|
27
28
|
else
|
@@ -17,7 +17,8 @@ module Solr
|
|
17
17
|
:subfacets,
|
18
18
|
:gap,
|
19
19
|
:lower_bound,
|
20
|
-
:upper_bound
|
20
|
+
:upper_bound,
|
21
|
+
:domain
|
21
22
|
|
22
23
|
def initialize(field:, type:, name: nil, value: nil, filters: [], subfacets: [], options: {})
|
23
24
|
if options[:limit].nil? && type == TERMS_TYPE
|
@@ -38,6 +39,7 @@ module Solr
|
|
38
39
|
@gap = options[:gap]
|
39
40
|
@lower_bound = options[:lower_bound]
|
40
41
|
@upper_bound = options[:upper_bound]
|
42
|
+
@domain = options[:domain]
|
41
43
|
end
|
42
44
|
|
43
45
|
def to_solr_h
|
@@ -58,7 +60,8 @@ module Solr
|
|
58
60
|
facet: subfacets.map(&:to_solr_h).reduce(&:merge),
|
59
61
|
gap: gap,
|
60
62
|
start: lower_bound,
|
61
|
-
end: upper_bound
|
63
|
+
end: upper_bound,
|
64
|
+
domain: domain
|
62
65
|
}.compact
|
63
66
|
}
|
64
67
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Solr
|
2
|
+
module Request
|
3
|
+
module Cloud
|
4
|
+
class FirstShardLeaderNodeSelectionStrategy < NodeSelectionStrategy
|
5
|
+
def call
|
6
|
+
leader = first_shard_leader_replica_node_for(collection: collection_name)
|
7
|
+
replicas = solr_cloud_active_nodes_urls.shuffle
|
8
|
+
urls = ([leader] + replicas).flatten.uniq
|
9
|
+
map_urls_to_collections(urls)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def first_shard_leader_replica_node_for(collection:)
|
15
|
+
shards = Solr.shards_for(collection: collection)
|
16
|
+
return unless shards
|
17
|
+
first_shard_name = shards.sort.first
|
18
|
+
Solr.leader_replica_node_for(collection: collection, shard: first_shard_name)
|
19
|
+
end
|
20
|
+
|
21
|
+
def solr_cloud_active_nodes_urls
|
22
|
+
Solr.active_nodes_for(collection: collection_name)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Solr
|
2
|
+
module Request
|
3
|
+
module Cloud
|
4
|
+
class LeaderNodeSelectionStrategy < NodeSelectionStrategy
|
5
|
+
def call
|
6
|
+
urls = [leader_replica_node_for(collection: collection_name)]
|
7
|
+
map_urls_to_collections(urls)
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def leader_replica_node_for(collection:)
|
13
|
+
shards = Solr.shards_for(collection: collection)
|
14
|
+
return unless shards
|
15
|
+
first_shard_name = shards.sort.first
|
16
|
+
Solr.leader_replica_node_for(collection: collection, shard: first_shard_name)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -2,7 +2,8 @@ module Solr
|
|
2
2
|
module Request
|
3
3
|
class DefaultNodeSelectionStrategy < NodeSelectionStrategy
|
4
4
|
def call
|
5
|
-
Solr.active_nodes_for(collection: collection_name)
|
5
|
+
urls = Solr.active_nodes_for(collection: collection_name)
|
6
|
+
map_urls_to_collections(urls).shuffle
|
6
7
|
end
|
7
8
|
end
|
8
9
|
end
|
data/lib/solr/request/runner.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
require 'solr/request/node_selection_strategy'
|
2
2
|
require 'solr/request/default_node_selection_strategy'
|
3
|
-
require 'solr/request/first_shard_leader_node_selection_strategy'
|
4
|
-
require 'solr/request/leader_node_selection_strategy'
|
3
|
+
require 'solr/request/cloud/first_shard_leader_node_selection_strategy'
|
4
|
+
require 'solr/request/cloud/leader_node_selection_strategy'
|
5
|
+
require 'solr/request/master_slave/master_node_selection_strategy'
|
5
6
|
require 'solr/errors/solr_query_error'
|
6
7
|
require 'solr/errors/solr_connection_failed_error'
|
7
8
|
require 'solr/errors/no_active_solr_nodes_error'
|
@@ -15,7 +16,7 @@ module Solr
|
|
15
16
|
attr_reader :request, :response_parser, :node_selection_strategy
|
16
17
|
|
17
18
|
def self.call(opts)
|
18
|
-
new(opts).call
|
19
|
+
new(**opts).call
|
19
20
|
end
|
20
21
|
|
21
22
|
def initialize(request:,
|
@@ -28,6 +29,7 @@ module Solr
|
|
28
29
|
end
|
29
30
|
|
30
31
|
def call
|
32
|
+
solr_url_errors = {}
|
31
33
|
solr_urls.each do |node_url|
|
32
34
|
request_url = build_request_url(url: node_url,
|
33
35
|
path: request.path,
|
@@ -37,19 +39,29 @@ module Solr
|
|
37
39
|
solr_response = Solr::Response::Parser.call(raw_response)
|
38
40
|
raise Solr::Errors::SolrQueryError, solr_response.error_message unless solr_response.ok?
|
39
41
|
return solr_response
|
40
|
-
rescue Faraday::ConnectionFailed, Faraday::TimeoutError, Errno::EADDRNOTAVAIL => e
|
42
|
+
rescue Faraday::ConnectionFailed, Faraday::TimeoutError, Errno::EADDRNOTAVAIL, Timeout::Error => e
|
43
|
+
if Solr.master_slave_enabled?
|
44
|
+
Solr.configuration.nodes_gray_list.add(node_url)
|
45
|
+
end
|
46
|
+
solr_url_errors[node_url] = "#{e.class.name} - #{e.message}"
|
41
47
|
# Try next node
|
42
48
|
end
|
43
49
|
end
|
44
50
|
|
45
|
-
raise Solr::Errors::SolrConnectionFailedError.new(
|
51
|
+
raise Solr::Errors::SolrConnectionFailedError.new(solr_url_errors)
|
46
52
|
end
|
47
53
|
|
48
54
|
private
|
49
55
|
|
50
56
|
def solr_urls
|
51
57
|
@solr_urls ||= begin
|
52
|
-
urls =
|
58
|
+
urls = if Solr.node_url_override
|
59
|
+
[File.join(Solr.node_url_override, collection_name.to_s)]
|
60
|
+
elsif Solr.cloud_enabled? || Solr.master_slave_enabled?
|
61
|
+
node_selection_strategy.call(collection_name)
|
62
|
+
else
|
63
|
+
[Solr.current_core_config.url]
|
64
|
+
end
|
53
65
|
unless urls && urls.any?
|
54
66
|
raise Solr::Errors::NoActiveSolrNodesError
|
55
67
|
end
|
@@ -57,11 +69,6 @@ module Solr
|
|
57
69
|
end
|
58
70
|
end
|
59
71
|
|
60
|
-
def solr_cloud_collection_urls
|
61
|
-
urls = node_selection_strategy.call(collection_name)
|
62
|
-
urls&.map { |u| File.join(u, collection_name.to_s) }
|
63
|
-
end
|
64
|
-
|
65
72
|
def collection_name
|
66
73
|
Solr.current_core_config.name
|
67
74
|
end
|