rsolr 2.2.1 → 2.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3fd409243f47b10c2b4c972729b19ef3b809a3feebc5c33aaa82d0cedd3ba637
4
- data.tar.gz: 970e2afe4ecc607a1236950f5346d49d598f0c5b144a2ee54760bc2e5aa3ce84
3
+ metadata.gz: 2b7e170dd7fcd0bfd504562297b93a6fb504da5e95df0d356cd0d1c70a7515c8
4
+ data.tar.gz: b4d04a24455588c421f72d29b8e9e7ab255fb4bfde9457bc6ab5f64bb64c76a4
5
5
  SHA512:
6
- metadata.gz: 2dc3d4a410d9939e3ef4e94e6248fa09aedf4e292058b0597f240faed2255c767647c593bad0152c493cd67e985ed8989420919ab430047f9b03e2841b555a63
7
- data.tar.gz: 95d31a84589b288ab7107a9559aed7eb9d897dc793e3b4cd5a277bd17f1029ab37ac9ff62b41fffff6d95bff1ec711fd1f1176edbf0b2592d98d8d3fc0305485
6
+ metadata.gz: cd27dcd3369b5d1e4cd99604426927bf83a3a4c3ef7d8a06fb3cfc45676b33c920ce256ecb7fc577dedbbe910b6d50a45fdaa7dd0324516529c5705df8698385
7
+ data.tar.gz: 5a488c3aa5e5ca93e8fe2647baf313d8b5a94229e99d6a49820a3bf80b65fe9c19fd8ab05678ee402d3674d7a043ded6110acdf4ada5a2a1752b96f4080ceec1
@@ -0,0 +1,29 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [ master ]
6
+ pull_request:
7
+ branches: [ master ]
8
+
9
+ jobs:
10
+ tests:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ ruby: [jruby-9.3.3.0, '3.0', '3.1', '3.2', '3.3']
15
+ faraday: ['~> 0.17', '~> 1', '~>2']
16
+ steps:
17
+ - uses: actions/checkout@v2
18
+ - name: Set up Ruby
19
+ uses: ruby/setup-ruby@v1
20
+ with:
21
+ ruby-version: ${{ matrix.ruby }}
22
+ - name: Install dependencies
23
+ run: bundle install
24
+ env:
25
+ FARADAY_VERSION: ${{ matrix.faraday}}
26
+ - name: Run tests
27
+ run: bundle exec rake
28
+ env:
29
+ FARADAY_VERSION: ${{ matrix.faraday}}
data/CHANGES.txt CHANGED
@@ -1,3 +1,41 @@
1
+ 2.6.0
2
+
3
+ - Stop testing on Ruby 2. https://github.com/rsolr/rsolr/pull/237
4
+ - Set solr version to 8.11.3. https://github.com/rsolr/rsolr/pull/238
5
+ - Add newer rubies to the test matrix. https://github.com/rsolr/rsolr/pull/239
6
+ - Sanitizing URIs displayed in error messages. https://github.com/rsolr/rsolr/pull/236
7
+
8
+ 2.5.0
9
+
10
+ - Sorry, not human-edited: https://github.com/rsolr/rsolr/compare/v2.4.0...v2.5.0
11
+
12
+
13
+ 2.4.0
14
+
15
+ - Raise specific timeout error for solr timeouts. https://github.com/rsolr/rsolr/pull/214
16
+ - Pass `timeout` RSolr configuration through to Faraday, deprecate `read_timeout` Rsolr configuration. https://github.com/rsolr/rsolr/pull/215
17
+ - Better visibility of Solr error message in `RSolr::Error`. https://github.com/rsolr/rsolr/pull/222
18
+ - Add soft-commit function https://github.com/rsolr/rsolr/pull/210 (thanks @giteshnandre)
19
+ - Avoid encoding exception in error message display https://github.com/rsolr/rsolr/pull/208 (thanks @expajp)
20
+ - Fix JSON generator for atomic updates of array fields https://github.com/rsolr/rsolr/pull/201 (thanks @serggl)
21
+
22
+
23
+ 2.3.0
24
+
25
+ - Sorry, not human-edited: https://github.com/rsolr/rsolr/compare/v2.2.0...v2.3.0
26
+
27
+ 2.2.0
28
+
29
+ - Sorry, not human-edited: https://github.com/rsolr/rsolr/compare/v2.1.0...v2.2.0
30
+
31
+ 2.1.0
32
+
33
+ - Sorry, not human-edited: https://github.com/rsolr/rsolr/compare/v2.0.0...v2.1.0
34
+
35
+ 2.0.0
36
+
37
+ - Sorry, not human-edited: https://github.com/rsolr/rsolr/compare/v2.0.0.pre1...v2.0.0
38
+
1
39
  2.0.0.pre1
2
40
 
3
41
  In this release, we've added many new features, including:
data/Gemfile CHANGED
@@ -3,3 +3,11 @@ source "https://rubygems.org"
3
3
  gemspec
4
4
 
5
5
  gem "builder", ">= 2.1.2"
6
+
7
+ if defined? JRUBY_VERSION
8
+ # HTTP.rb (used by solr_wrapper to download solr for integration testing) fails
9
+ # to download the full contents of files (under jruby)?
10
+ gem "http", '< 5', platforms: :jruby
11
+ end
12
+
13
+ gem 'faraday', ENV['FARADAY_VERSION'] if ENV['FARADAY_VERSION']
data/README.rdoc CHANGED
@@ -1,6 +1,4 @@
1
1
  =RSolr
2
- {<img src="https://travis-ci.org/rsolr/rsolr.svg?branch=master" alt="Build Status" />}[https://travis-ci.org/rsolr/rsolr] {<img src="https://badge.fury.io/rb/rsolr.svg" alt="Gem Version" />}[http://badge.fury.io/rb/rsolr]
3
-
4
2
 
5
3
  A simple, extensible Ruby client for Apache Solr.
6
4
 
@@ -12,26 +10,26 @@ The code docs http://www.rubydoc.info/gems/rsolr
12
10
 
13
11
  == Example:
14
12
  require 'rsolr'
15
-
13
+
16
14
  # Direct connection
17
15
  solr = RSolr.connect :url => 'http://solrserver.com'
18
-
16
+
19
17
  # Connecting over a proxy server
20
18
  solr = RSolr.connect :url => 'http://solrserver.com', :proxy=>'http://user:pass@proxy.example.com:8080'
21
19
 
22
20
  # Using an alternate Faraday adapter
23
21
  solr = RSolr.connect :url => 'http://solrserver.com', :adapter => :em_http
24
-
22
+
25
23
  # Using a custom Faraday connection
26
24
  conn = Faraday.new do |faraday|
27
25
  faraday.response :logger # log requests to STDOUT
28
26
  faraday.adapter Faraday.default_adapter # make requests with Net::HTTP
29
27
  end
30
28
  solr = RSolr.connect conn, :url => 'http://solrserver.com'
31
-
29
+
32
30
  # send a request to /select
33
31
  response = solr.get 'select', :params => {:q => '*:*'}
34
-
32
+
35
33
  # send a request to /catalog
36
34
  response = solr.get 'catalog', :params => {:q => '*:*'}
37
35
 
@@ -52,8 +50,10 @@ By default, RSolr uses the Solr JSON command format for all requests.
52
50
  RSolr.connect :url => 'http://solrserver.com', update_format: :xml
53
51
 
54
52
  == Timeouts
55
- The read and connect timeout settings can be set when creating a new instance of RSolr:
56
- solr = RSolr.connect(:read_timeout => 120, :open_timeout => 120)
53
+ The read and connect timeout settings can be set when creating a new instance of RSolr, and will
54
+ be passed on to underlying Faraday instance:
55
+
56
+ solr = RSolr.connect(:timeout => 120, :open_timeout => 120)
57
57
 
58
58
  == Retry 503s
59
59
  A 503 is usually a temporary error which RSolr may retry if requested. You may specify the number of retry attempts with the +:retry_503+ option.
@@ -74,11 +74,11 @@ Use the #get / #post method to send search requests to the /select handler:
74
74
 
75
75
  The +:params+ sent into the method are sent to Solr as-is, which is to say they are converted to Solr url style, but no special mapping is used.
76
76
  When an array is used, multiple parameters *with the same name* are generated for the Solr query. Example:
77
-
77
+
78
78
  solr.get 'select', :params => {:q=>'roses', :fq=>['red', 'violet']}
79
79
 
80
80
  The above statement generates this Solr query:
81
-
81
+
82
82
  select?q=roses&fq=red&fq=violet
83
83
 
84
84
  ===Pagination
@@ -92,14 +92,14 @@ The paginate method returns WillPaginate ready "docs" objects, so for example in
92
92
 
93
93
  ===Method Missing
94
94
  The +RSolr::Client+ class also uses +method_missing+ for setting the request handler/path:
95
-
95
+
96
96
  solr.paintings :params => {:q=>'roses', :fq=>['red', 'violet']}
97
-
97
+
98
98
  This is sent to Solr as:
99
99
  paintings?q=roses&fq=red&fq=violet
100
100
 
101
101
  This works with pagination as well:
102
-
102
+
103
103
  solr.paginate_paintings 1, 10, {:q=>'roses', :fq=>['red', 'violet']}
104
104
 
105
105
  ===Using POST for Search Queries
@@ -120,10 +120,10 @@ To send header information to Solr using RSolr, just use the +:headers+ option:
120
120
  ===Building a Request
121
121
  +RSolr::Client+ provides a method for building a request context, which can be useful for debugging or logging etc.:
122
122
  request_context = solr.build_request "select", :data => {:q => "*:*"}, :method => :post, :headers => {}
123
-
123
+
124
124
  To build a paginated request use build_paginated_request:
125
125
  request_context = solr.build_paginated_request 1, 10, "select", ...
126
-
126
+
127
127
  == Updating Solr
128
128
  Updating is done using native Ruby objects. Hashes are used for single documents and arrays are used for a collection of documents (hashes). These objects get turned into simple XML "messages". Raw XML strings can also be used.
129
129
 
@@ -142,7 +142,7 @@ Raw commands via #update
142
142
  solr.update data: { optimize: true }.to_json, headers: { 'Content-Type' => 'application/json' }
143
143
 
144
144
  When adding, you can also supply "add" xml element attributes and/or a block for manipulating other "add" related elements (docs and fields) by calling the +xml+ method directly:
145
-
145
+
146
146
  doc = {:id=>1, :price=>1.00}
147
147
  add_attributes = {:allowDups=>false, :commitWithin=>10}
148
148
  add_xml = solr.xml.add(doc, add_attributes) do |doc|
data/lib/rsolr/client.rb CHANGED
@@ -35,6 +35,10 @@ class RSolr::Client
35
35
  @update_format = options.delete(:update_format) || RSolr::JSON::Generator
36
36
  @update_path = options.fetch(:update_path, 'update')
37
37
  @options = options
38
+
39
+ if options[:read_timeout]
40
+ warn "DEPRECATION: Rsolr.new/connect option `read_timeout` is deprecated and will be removed in Rsolr 3. `timeout` is currently a synonym, use that instead."
41
+ end
38
42
  end
39
43
 
40
44
  def extract_url_from_options(options)
@@ -122,6 +126,14 @@ class RSolr::Client
122
126
  update opts.merge(:data => builder.commit( commit_attrs ))
123
127
  end
124
128
 
129
+ # soft commit
130
+ #
131
+ # https://lucene.apache.org/solr/guide/updatehandlers-in-solrconfig.html#commit-and-softcommit
132
+ #
133
+ def soft_commit opts = {}
134
+ commit(opts.merge params: { softCommit: true })
135
+ end
136
+
125
137
  # send "optimize" xml with opts.
126
138
  #
127
139
  # http://wiki.apache.org/solr/UpdateXmlMessages#A.22commit.22_and_.22optimize.22
@@ -200,8 +212,10 @@ class RSolr::Client
200
212
  end
201
213
 
202
214
  { status: response.status.to_i, headers: response.headers, body: response.body.force_encoding('utf-8') }
203
- rescue Errno::ECONNREFUSED, Faraday::Error::ConnectionFailed
204
- raise RSolr::Error::ConnectionRefused, request_context.inspect
215
+ rescue Faraday::TimeoutError => e
216
+ raise RSolr::Error::Timeout.new(request_context, e.response)
217
+ rescue Errno::ECONNREFUSED, defined?(Faraday::ConnectionFailed) ? Faraday::ConnectionFailed : Faraday::Error::ConnectionFailed
218
+ raise RSolr::Error::ConnectionRefused.new(request_context)
205
219
  rescue Faraday::Error => e
206
220
  raise RSolr::Error::Http.new(request_context, e.response)
207
221
  end
@@ -283,23 +297,39 @@ class RSolr::Client
283
297
 
284
298
  result
285
299
  end
286
-
300
+
287
301
  def connection
288
302
  @connection ||= begin
289
303
  conn_opts = { request: {} }
290
304
  conn_opts[:url] = uri.to_s
291
305
  conn_opts[:proxy] = proxy if proxy
292
306
  conn_opts[:request][:open_timeout] = options[:open_timeout] if options[:open_timeout]
293
- conn_opts[:request][:timeout] = options[:read_timeout] if options[:read_timeout]
307
+
308
+ if options[:read_timeout] || options[:timeout]
309
+ # read_timeout was being passed to faraday as timeout since Rsolr 2.0,
310
+ # it's now deprecated, just use `timeout` directly.
311
+ conn_opts[:request][:timeout] = options[:timeout] || options[:read_timeout]
312
+ end
313
+
294
314
  conn_opts[:request][:params_encoder] = Faraday::FlatParamsEncoder
295
315
 
296
316
  Faraday.new(conn_opts) do |conn|
297
- conn.basic_auth(uri.user, uri.password) if uri.user && uri.password
317
+ if uri.user && uri.password
318
+ case Faraday::VERSION
319
+ when /^0/
320
+ conn.basic_auth uri.user, uri.password
321
+ when /^1/
322
+ conn.request :basic_auth, uri.user, uri.password
323
+ else
324
+ conn.request :authorization, :basic_auth, uri.user, uri.password
325
+ end
326
+ end
327
+
298
328
  conn.response :raise_error
299
329
  conn.request :retry, max: options[:retry_after_limit], interval: 0.05,
300
330
  interval_randomness: 0.5, backoff_factor: 2,
301
331
  exceptions: ['Faraday::Error', 'Timeout::Error'] if options[:retry_503]
302
- conn.adapter options[:adapter] || Faraday.default_adapter
332
+ conn.adapter options[:adapter] || Faraday.default_adapter || :net_http
303
333
  end
304
334
  end
305
335
  end
@@ -1,6 +1,7 @@
1
1
  module RSolr
2
2
  class Document
3
3
  CHILD_DOCUMENT_KEY = '_childDocuments_'.freeze
4
+ ATOMIC_MULTI_VALUE_OPERATIONS = %i[set add add-distinct remove]
4
5
 
5
6
  # "attrs" is a hash for setting the "doc" xml attributes
6
7
  # "fields" is an array of Field objects
@@ -48,8 +49,14 @@ module RSolr
48
49
  def as_json
49
50
  @fields.group_by(&:name).each_with_object({}) do |(field, values), result|
50
51
  v = values.map(&:as_json)
51
- if v.length > 1 && v.first.is_a?(Hash) && v.first.key?(:value)
52
- v = v.first.merge(value: v.map { |single| single[:value] })
52
+ if v.length > 1 && v.first.is_a?(Hash)
53
+ if v.first.key?(:value)
54
+ v = v.first.merge(value: v.map { |single| single[:value] })
55
+ else
56
+ (v.first.keys & ATOMIC_MULTI_VALUE_OPERATIONS).each do |op|
57
+ v = [{ op => v.map { |single| single[op] } }]
58
+ end
59
+ end
53
60
  end
54
61
  v = v.first if v.length == 1 && field.to_s != CHILD_DOCUMENT_KEY
55
62
  result[field] = v
data/lib/rsolr/error.rb CHANGED
@@ -1,6 +1,19 @@
1
+ require 'json'
2
+
1
3
  module RSolr::Error
2
4
 
5
+ module URICleanup
6
+ # Removes username and password from URI object.
7
+ def clean_uri(uri)
8
+ uri = uri.dup
9
+ uri.password = "REDACTED" if uri.password
10
+ uri.user = "REDACTED" if uri.user
11
+ uri
12
+ end
13
+ end
14
+
3
15
  module SolrContext
16
+ include URICleanup
4
17
 
5
18
  attr_accessor :request, :response
6
19
 
@@ -11,7 +24,7 @@ module RSolr::Error
11
24
  details = parse_solr_error_response response[:body]
12
25
  m << "\nError: #{details}\n" if details
13
26
  end
14
- p = "\nURI: #{request[:uri].to_s}"
27
+ p = "\nURI: #{clean_uri(request[:uri]).to_s}"
15
28
  p << "\nRequest Headers: #{request[:headers].inspect}" if request[:headers]
16
29
  p << "\nRequest Data: #{request[:data].inspect}" if request[:data]
17
30
  p << "\n"
@@ -24,6 +37,18 @@ module RSolr::Error
24
37
 
25
38
  def parse_solr_error_response body
26
39
  begin
40
+ # Default JSON response, try to parse and retrieve error message
41
+ if response[:headers] && response[:headers]["content-type"].start_with?("application/json")
42
+ begin
43
+ parsed_body = JSON.parse(body)
44
+ info = parsed_body && parsed_body["error"] && parsed_body["error"]["msg"]
45
+ rescue JSON::ParserError
46
+ end
47
+ end
48
+ return info if info
49
+
50
+ # legacy analysis, I think trying to handle wt=ruby responses without
51
+ # a full parse?
27
52
  if body =~ /<pre>/
28
53
  info = body.scan(/<pre>(.*)<\/pre>/mi)[0]
29
54
  elsif body =~ /'msg'=>/
@@ -38,11 +63,16 @@ module RSolr::Error
38
63
  nil
39
64
  end
40
65
  end
41
-
42
-
43
66
  end
44
67
 
45
68
  class ConnectionRefused < ::Errno::ECONNREFUSED
69
+ include URICleanup
70
+
71
+ def initialize(request)
72
+ request[:uri] = clean_uri(request[:uri])
73
+
74
+ super(request.inspect)
75
+ end
46
76
  end
47
77
 
48
78
  class Http < RuntimeError
@@ -110,9 +140,16 @@ module RSolr::Error
110
140
  }
111
141
 
112
142
  def initialize request, response
143
+ response = response_with_force_encoded_body(response)
113
144
  @request, @response = request, response
114
145
  end
115
146
 
147
+ private
148
+
149
+ def response_with_force_encoded_body(response)
150
+ response[:body] = response[:body].force_encoding('UTF-8') if response
151
+ response
152
+ end
116
153
  end
117
154
 
118
155
  # Thrown if the :wt is :ruby
@@ -121,6 +158,15 @@ module RSolr::Error
121
158
 
122
159
  end
123
160
 
161
+ # Subclasses Rsolr::Error::Http for legacy backwards compatibility
162
+ # purposes, because earlier RSolr 2 didn't distinguish these
163
+ # from Http errors.
164
+ #
165
+ # In RSolr 3, it could make sense to `< Timeout::Error` instead,
166
+ # analagous to ConnectionRefused above
167
+ class Timeout < Http
168
+ end
169
+
124
170
  # Thrown if the :wt is :ruby
125
171
  # but the body wasn't succesfully parsed/evaluated
126
172
  class InvalidJsonResponse < InvalidResponse
data/lib/rsolr/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module RSolr
2
- VERSION = "2.2.1"
2
+ VERSION = "2.6.0"
3
3
 
4
4
  def self.version
5
5
  VERSION
data/rsolr.gemspec CHANGED
@@ -23,18 +23,17 @@ Gem::Specification.new do |s|
23
23
  s.email = ["goodieboy@gmail.com"]
24
24
  s.license = 'Apache-2.0'
25
25
  s.homepage = "https://github.com/rsolr/rsolr"
26
- s.rubyforge_project = "rsolr"
27
26
  s.files = `git ls-files`.split("\n")
28
27
  s.test_files = `git ls-files -- {spec}/*`.split("\n")
29
28
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
30
29
  s.require_paths = ["lib"]
31
-
30
+
32
31
  s.required_ruby_version = '>= 1.9.3'
33
-
32
+
34
33
  s.requirements << 'Apache Solr'
35
34
 
36
35
  s.add_dependency 'builder', '>= 2.1.2'
37
- s.add_dependency 'faraday', '>= 0.9.0'
36
+ s.add_dependency 'faraday', '>= 0.9', '!= 2.0.0', '< 3'
38
37
 
39
38
  s.add_development_dependency 'activesupport'
40
39
  s.add_development_dependency 'nokogiri', '>= 1.4.0'
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
  RSpec.describe RSolr::Client do
4
4
  let(:connection) { nil }
5
5
  let(:url) { "http://localhost:9999/solr" }
6
- let(:connection_options) { { url: url, read_timeout: 42, open_timeout: 43, update_format: :xml } }
6
+ let(:connection_options) { { url: url, update_format: :xml } }
7
7
 
8
8
  let(:client) do
9
9
  RSolr::Client.new connection, connection_options
@@ -71,6 +71,32 @@ RSpec.describe RSolr::Client do
71
71
  end
72
72
  end
73
73
 
74
+ context "execute" do
75
+ it "maps Faraday::TimeoutError to an RSolr::Error::Timeout" do
76
+ allow(client.connection).to receive(:send).and_raise(Faraday::TimeoutError)
77
+
78
+ expect{ client.execute({}) }.to raise_error RSolr::Error::Timeout
79
+ end
80
+
81
+ context 'when an Errno::ECONNREFUSED error is raised' do
82
+ let(:uri) { URI.parse('http://admin:secret@hostname.local:8983/solr/admin/update?wt=json&q=test') }
83
+
84
+ before do
85
+ allow(client.connection).to receive(:send).and_raise(Errno::ECONNREFUSED)
86
+ end
87
+
88
+ it "maps error to RSolr::Error:ConnectionRefused" do
89
+ expect { client.execute({ uri: uri }) }.to raise_error RSolr::Error::ConnectionRefused
90
+ end
91
+
92
+ it "removes credentials from uri" do
93
+ expect {
94
+ client.execute({ uri: uri })
95
+ }.to raise_error(RSolr::Error::ConnectionRefused, /http:\/\/REDACTED:REDACTED@hostname\.local:8983/)
96
+ end
97
+ end
98
+ end
99
+
74
100
  context "post" do
75
101
  it "should pass the expected params to the connection's #execute method" do
76
102
  request_opts = {:data => "the data", :method=>:post, :headers => {"Content-Type" => "text/plain"}}
@@ -109,7 +135,7 @@ RSpec.describe RSolr::Client do
109
135
 
110
136
  context 'when the client is configured for json updates' do
111
137
  let(:client) do
112
- RSolr::Client.new nil, :url => "http://localhost:9999/solr", :read_timeout => 42, :open_timeout=>43, :update_format => :json
138
+ RSolr::Client.new nil, :url => "http://localhost:9999/solr", :update_format => :json
113
139
  end
114
140
  it "should send json to the connection's #post method" do
115
141
  expect(client).to receive(:execute).
@@ -279,6 +305,48 @@ RSpec.describe RSolr::Client do
279
305
 
280
306
  end
281
307
 
308
+ context "commit" do
309
+ it "should add hard commit params for hard commit request" do
310
+ expect(client).to receive(:execute).
311
+ with(
312
+ hash_including({
313
+ :path => "update",
314
+ :headers => {"Content-Type"=>"text/xml"},
315
+ :method => :post,
316
+ :data => "<?xml version=\"1.0\" encoding=\"UTF-8\"?><commit/>",
317
+ :params => {:wt=>:json},
318
+ :query => "wt=json"
319
+ })
320
+ ).
321
+ and_return(
322
+ :body => "",
323
+ :status => 200,
324
+ :headers => {"Content-Type"=>"text/xml"}
325
+ )
326
+ client.commit
327
+ end
328
+
329
+ it "should add soft commit params for soft commit request" do
330
+ expect(client).to receive(:execute).
331
+ with(
332
+ hash_including({
333
+ :path => "update",
334
+ :headers => {"Content-Type"=>"text/xml"},
335
+ :method => :post,
336
+ :data => "<?xml version=\"1.0\" encoding=\"UTF-8\"?><commit/>",
337
+ :params => {:softCommit=>true, :wt=>:json},
338
+ :query => "wt=json&softCommit=true"
339
+ })
340
+ ).
341
+ and_return(
342
+ :body => "",
343
+ :status => 200,
344
+ :headers => {"Content-Type"=>"text/xml"}
345
+ )
346
+ client.soft_commit
347
+ end
348
+ end
349
+
282
350
  context "indifferent access" do
283
351
  it "should raise a RuntimeError if the #with_indifferent_access extension isn't loaded" do
284
352
  hide_const("::RSolr::HashWithIndifferentAccessWithResponse")
@@ -342,7 +410,7 @@ RSpec.describe RSolr::Client do
342
410
  expect(subject[:headers]).to eq({"Content-Type" => "application/x-www-form-urlencoded; charset=UTF-8"})
343
411
  end
344
412
  end
345
-
413
+
346
414
  it "should properly handle proxy configuration" do
347
415
  result = client_with_proxy.build_request('select',
348
416
  :method => :post,
@@ -7,7 +7,7 @@ RSpec.describe RSolr::Error do
7
7
  exception
8
8
  end
9
9
  let (:response_lines) { (1..15).to_a.map { |i| "line #{i}" } }
10
- let(:request) { double :[] => "mocked" }
10
+ let(:request) { { uri: URI.parse('http://hostname.local:8983/solr/admin/update?wt=json&q=test') } }
11
11
  let(:response_body) { response_lines.join("\n") }
12
12
  let(:response) {{
13
13
  :body => response_body,
@@ -43,5 +43,116 @@ RSpec.describe RSolr::Error do
43
43
  let(:response_body) { (response_lines << "'error'=>{'msg'=> #{msg}").join("\n") }
44
44
  it { should include msg }
45
45
  end
46
+
47
+ context "when the response body is made of multi-byte chars and encoded by ASCII-8bit" do
48
+ let (:response_lines) { (1..15).to_a.map { |i| "レスポンス #{i}".b } }
49
+
50
+ it "encodes errorlogs by UTF-8" do
51
+ expect(subject.encoding.to_s).to eq 'UTF-8'
52
+ end
53
+ end
54
+ end
55
+
56
+ context "when response is JSON" do
57
+ let(:response) {{
58
+ :body => response_body,
59
+ :status => 500,
60
+ :headers => {
61
+ "content-type" => "application/json;charset=utf-8"
62
+ }
63
+
64
+ }}
65
+
66
+ context "and contains a msg key" do
67
+ let(:msg) { "field 'description_text4_tesim' was indexed without offsets, cannot highlight" }
68
+ let(:response_body) {<<~EOS
69
+ {
70
+ "responseHeader":{
71
+ "status":500,
72
+ "QTime":11,
73
+ "params":{
74
+ "q":"supercali",
75
+ "hl":"true",
76
+ "hl:fl":"description_text4_tesim",
77
+ "hl.method":"unified",
78
+ "hl.offsetSource":"postings"
79
+ }
80
+ },
81
+ "response":{"numFound":0,"start":0,"maxScore":127.32743,"numFoundExact":true,"docs":[]},
82
+ "facet_counts":{
83
+ "facet_queries":{},
84
+ "facet_fields":{}
85
+ },
86
+ "error":{
87
+ "msg":"#{msg}",
88
+ "trace":"java.lang.IllegalArgumentException: field 'description_text4_tesim' was indexed without offsets, cannot highlight\\n\\tat org.apache.lucene.search.uhighlight.FieldHighlighter.highlightOffsetsEnums(FieldHighlighter.java:149)\\n\\tat org.apache.lucene.search.uhighlight.FieldHighlighter.highlightFieldForDoc(FieldHighlighter.java:79)\\n\\tat org.apache.lucene.search.uhighlight.UnifiedHighlighter.highlightFieldsAsObjects(UnifiedHighlighter.java:641)\\n\\tat org.apache.lucene.search.uhighlight.UnifiedHighlighter.highlightFields(UnifiedHighlighter.java:510)\\n\\tat org.apache.solr.highlight.UnifiedSolrHighlighter.doHighlighting(UnifiedSolrHighlighter.java:149)\\n\\tat org.apache.solr.handler.component.HighlightComponent.process(HighlightComponent.java:172)\\n\\tat org.apache.solr.handler.component.SearchHandler.handleRequestBody(SearchHandler.java:331)\\n\\tat org.apache.solr.handler.RequestHandlerBase.handleRequest(RequestHandlerBase.java:214)\\n\\tat org.apache.solr.core.SolrCore.execute(SolrCore.java:2606)\\n\\tat org.apache.solr.servlet.HttpSolrCall.execute(HttpSolrCall.java:815)\\n\\tat org.apache.solr.servlet.HttpSolrCall.call(HttpSolrCall.java:588)\\n\\tat org.apache.solr.servlet.SolrDispatchFilter.doFilter(SolrDispatchFilter.java:415)\\n\\tat org.apache.solr.servlet.SolrDispatchFilter.doFilter(SolrDispatchFilter.java:345)\\n\\tat org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1596)\\n\\tat org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:545)\\n\\tat org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)\\n\\tat org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:590)\\n\\tat org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)\\n\\tat org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:235)\\n\\tat org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1610)\\n\\tat org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:233)\\n\\tat org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1300)\\n\\tat org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:188)\\n\\tat org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:485)\\n\\tat org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1580)\\n\\tat org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:186)\\n\\tat org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1215)\\n\\tat org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)\\n\\tat org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:221)\\n\\tat org.eclipse.jetty.server.handler.InetAccessHandler.handle(InetAccessHandler.java:177)\\n\\tat org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:146)\\n\\tat org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)\\n\\tat org.eclipse.jetty.rewrite.handler.RewriteHandler.handle(RewriteHandler.java:322)\\n\\tat org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)\\n\\tat org.eclipse.jetty.server.Server.handle(Server.java:500)\\n\\tat org.eclipse.jetty.server.HttpChannel.lambda$handle$1(HttpChannel.java:383)\\n\\tat org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:547)\\n\\tat org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:375)\\n\\tat org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:273)\\n\\tat org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:311)\\n\\tat org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:103)\\n\\tat org.eclipse.jetty.io.ChannelEndPoint$2.run(ChannelEndPoint.java:117)\\n\\tat org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.runTask(EatWhatYouKill.java:336)\\n\\tat org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:313)\\n\\tat org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.tryProduce(EatWhatYouKill.java:171)\\n\\tat org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.run(EatWhatYouKill.java:129)\\n\\tat org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:375)\\n\\tat org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:806)\\n\\tat org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:938)\\n\\tat java.base/java.lang.Thread.run(Thread.java:834)\\n",
89
+ "code":500
90
+ }
91
+ }
92
+ EOS
93
+ }
94
+ it {
95
+ should include msg
96
+ }
97
+ end
98
+
99
+ context "and does not contain a msg key" do
100
+ let(:response_body) {<<~EOS
101
+ {
102
+ "responseHeader":{
103
+ "status":500,
104
+ "QTime":11,
105
+ "params":{
106
+ "q":"supercali",
107
+ "hl":"true",
108
+ "hl:fl":"description_text4_tesim",
109
+ "hl.method":"unified",
110
+ "hl.offsetSource":"postings"
111
+ }
112
+ },
113
+ "response":{"numFound":0,"start":0,"maxScore":127.32743,"numFoundExact":true,"docs":[]},
114
+ "facet_counts":{
115
+ "facet_queries":{},
116
+ "facet_fields":{}
117
+ },
118
+ }
119
+ EOS
120
+ }
121
+ it "shows the first eleven lines of the response" do
122
+ expect(subject).to include(response_body.split("\n")[0..10].join("\n"))
123
+ expect(subject).not_to include(response_body.split("\n")[11])
124
+ end
125
+ end
126
+
127
+ context "and is not parseable json" do
128
+ let(:response_body) {<<~EOS
129
+ one
130
+ two
131
+ three
132
+ four
133
+ five
134
+ six
135
+ seven
136
+ eight
137
+ nine
138
+ ten
139
+ eleven
140
+ twelve
141
+ EOS
142
+ }
143
+ end
144
+ it "shows the first eleven lines of the response" do
145
+ expect(subject).to include(response_body.split("\n")[0..10].join("\n"))
146
+ expect(subject).not_to include(response_body.split("\n")[11])
147
+ end
148
+ end
149
+
150
+ context "when request uri contains credentials" do
151
+ let(:request) { { uri: URI.parse('http://admin:admin@hostname.local:8983/solr/admin/update?wt=json&q=test') } }
152
+
153
+
154
+ it 'includes redacted url' do
155
+ expect(subject).to include 'http://REDACTED:REDACTED@hostname.local:8983/solr/admin/update?wt=json&q=test'
156
+ end
46
157
  end
47
158
  end
@@ -158,6 +158,56 @@ RSpec.describe RSolr::JSON do
158
158
  expect(message).to eq [{ id: '1', name: { boost: 3, value: test_values } }]
159
159
  end
160
160
 
161
+ context 'for atomic updates with arrays' do
162
+ let(:test_values) { %w[value1 value2] }
163
+
164
+ it 'creates single field from array values on SET' do
165
+ expect(
166
+ JSON.parse(
167
+ generator.add(id: 'set-id') { |doc| doc.add_field(:name, test_values, update: :set) },
168
+ symbolize_names: true
169
+ )
170
+ ).to eq [{ id: 'set-id', name: { set: test_values } }]
171
+ end
172
+
173
+ it 'creates single field from array values on ADD' do
174
+ expect(
175
+ JSON.parse(
176
+ generator.add(id: 'add-id') { |doc| doc.add_field(:name, test_values, update: :add) },
177
+ symbolize_names: true
178
+ )
179
+ ).to eq [{ id: 'add-id', name: { add: test_values } }]
180
+ end
181
+
182
+ it 'creates single field from array values on ADD-DISTINCT' do
183
+ expect(
184
+ JSON.parse(
185
+ generator.add(id: 'add-distinct-id') { |doc| doc.add_field(:name, test_values, update: :'add-distinct') },
186
+ symbolize_names: true
187
+ )
188
+ ).to eq [{ id: 'add-distinct-id', name: { 'add-distinct': test_values } }]
189
+ end
190
+
191
+ it 'creates single field from array values on REMOVE' do
192
+ expect(
193
+ JSON.parse(
194
+ generator.add(id: 'remove-id') { |doc| doc.add_field(:name, test_values, update: :remove) },
195
+ symbolize_names: true
196
+ )
197
+ ).to eq [{ id: 'remove-id', name: { remove: test_values } }]
198
+ end
199
+
200
+ it 'creates single field from array values for child document update' do
201
+ test_nested_values = [{id: 1, name: 'value1'}, {id: 1, name: 'value2'}]
202
+ expect(
203
+ JSON.parse(
204
+ generator.add(id: 'set-id') { |doc| doc.add_field(:child_documents, test_nested_values, update: :set) },
205
+ symbolize_names: true
206
+ )
207
+ ).to eq [{ id: 'set-id', child_documents: { set: test_nested_values } }]
208
+ end
209
+ end
210
+
161
211
  describe '#commit' do
162
212
  it 'generates a commit command' do
163
213
  expect(JSON.parse(generator.commit, symbolize_names: true)).to eq(commit: {})
@@ -2,6 +2,10 @@ require 'spec_helper'
2
2
  require 'solr_wrapper'
3
3
 
4
4
  RSpec.describe "Solr basic_configs" do
5
+ SolrWrapper.default_instance_options = {
6
+ port: SolrWrapper.default_solr_port,
7
+ version: '8.11.3'
8
+ }
5
9
  SOLR_INSTANCE = SolrWrapper.default_instance({})
6
10
  before(:all) { SOLR_INSTANCE.start }
7
11
  after(:all) { SOLR_INSTANCE.stop }
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe RSolr::Client do
4
+ describe "#connection" do
5
+ it "accepts a timeout parameter it passes to Faraday" do
6
+ client = described_class.new(nil, timeout: 1000)
7
+
8
+ expect(client.connection.options[:timeout]).to eq 1000
9
+ end
10
+ it "accepts a deprecated read_timeout" do
11
+ client = nil
12
+ expect do
13
+ client = described_class.new(nil, read_timeout: 1000)
14
+ end.to output(/`read_timeout` is deprecated/).to_stderr
15
+
16
+ expect(client.connection.options[:timeout]).to eq 1000
17
+ end
18
+ end
19
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rsolr
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.1
4
+ version: 2.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Antoine Latter
@@ -26,10 +26,10 @@ authors:
26
26
  - Nathan Witmer
27
27
  - Naomi Dushay
28
28
  - '"shima"'
29
- autorequire:
29
+ autorequire:
30
30
  bindir: bin
31
31
  cert_chain: []
32
- date: 2018-05-14 00:00:00.000000000 Z
32
+ date: 2024-03-25 00:00:00.000000000 Z
33
33
  dependencies:
34
34
  - !ruby/object:Gem::Dependency
35
35
  name: builder
@@ -51,14 +51,26 @@ dependencies:
51
51
  requirements:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: 0.9.0
54
+ version: '0.9'
55
+ - - "!="
56
+ - !ruby/object:Gem::Version
57
+ version: 2.0.0
58
+ - - "<"
59
+ - !ruby/object:Gem::Version
60
+ version: '3'
55
61
  type: :runtime
56
62
  prerelease: false
57
63
  version_requirements: !ruby/object:Gem::Requirement
58
64
  requirements:
59
65
  - - ">="
60
66
  - !ruby/object:Gem::Version
61
- version: 0.9.0
67
+ version: '0.9'
68
+ - - "!="
69
+ - !ruby/object:Gem::Version
70
+ version: 2.0.0
71
+ - - "<"
72
+ - !ruby/object:Gem::Version
73
+ version: '3'
62
74
  - !ruby/object:Gem::Dependency
63
75
  name: activesupport
64
76
  requirement: !ruby/object:Gem::Requirement
@@ -165,9 +177,9 @@ executables: []
165
177
  extensions: []
166
178
  extra_rdoc_files: []
167
179
  files:
180
+ - ".github/workflows/ruby.yml"
168
181
  - ".gitignore"
169
182
  - ".rspec"
170
- - ".travis.yml"
171
183
  - CHANGES.txt
172
184
  - Gemfile
173
185
  - LICENSE
@@ -203,12 +215,13 @@ files:
203
215
  - spec/fixtures/basic_configs/stopwords.txt
204
216
  - spec/fixtures/basic_configs/synonyms.txt
205
217
  - spec/integration/solr5_spec.rb
218
+ - spec/lib/rsolr/client_spec.rb
206
219
  - spec/spec_helper.rb
207
220
  homepage: https://github.com/rsolr/rsolr
208
221
  licenses:
209
222
  - Apache-2.0
210
223
  metadata: {}
211
- post_install_message:
224
+ post_install_message:
212
225
  rdoc_options: []
213
226
  require_paths:
214
227
  - lib
@@ -224,9 +237,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
224
237
  version: '0'
225
238
  requirements:
226
239
  - Apache Solr
227
- rubyforge_project: rsolr
228
- rubygems_version: 2.7.6
229
- signing_key:
240
+ rubygems_version: 3.4.10
241
+ signing_key:
230
242
  specification_version: 4
231
243
  summary: A Ruby client for Apache Solr
232
244
  test_files: []
data/.travis.yml DELETED
@@ -1,14 +0,0 @@
1
- language: ruby
2
- sudo: false
3
- rvm:
4
- - 2.4.0
5
- - 2.3.3
6
- - 2.2.6
7
- - jruby-9.1.7.0
8
-
9
- env:
10
- global:
11
- - JRUBY_OPTS="-J-Xms512m -J-Xmx1024m"
12
- - NOKOGIRI_USE_SYSTEM_LIBRARIES=true
13
-
14
- jdk: oraclejdk8