telvue-rsolr 2.2.2
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/.gitignore +13 -0
- data/.rspec +2 -0
- data/.travis.yml +14 -0
- data/CHANGES.txt +47 -0
- data/Gemfile +5 -0
- data/LICENSE +13 -0
- data/README.rdoc +229 -0
- data/Rakefile +19 -0
- data/lib/rsolr.rb +52 -0
- data/lib/rsolr/char.rb +6 -0
- data/lib/rsolr/client.rb +342 -0
- data/lib/rsolr/document.rb +59 -0
- data/lib/rsolr/error.rb +136 -0
- data/lib/rsolr/field.rb +87 -0
- data/lib/rsolr/generator.rb +5 -0
- data/lib/rsolr/json.rb +60 -0
- data/lib/rsolr/response.rb +95 -0
- data/lib/rsolr/uri.rb +25 -0
- data/lib/rsolr/version.rb +7 -0
- data/lib/rsolr/xml.rb +150 -0
- data/rsolr.gemspec +46 -0
- data/spec/api/client_spec.rb +355 -0
- data/spec/api/document_spec.rb +48 -0
- data/spec/api/error_spec.rb +47 -0
- data/spec/api/json_spec.rb +198 -0
- data/spec/api/pagination_spec.rb +31 -0
- data/spec/api/rsolr_spec.rb +31 -0
- data/spec/api/uri_spec.rb +37 -0
- data/spec/api/xml_spec.rb +255 -0
- data/spec/fixtures/basic_configs/_rest_managed.json +1 -0
- data/spec/fixtures/basic_configs/currency.xml +67 -0
- data/spec/fixtures/basic_configs/lang/stopwords_en.txt +54 -0
- data/spec/fixtures/basic_configs/protwords.txt +21 -0
- data/spec/fixtures/basic_configs/schema.xml +530 -0
- data/spec/fixtures/basic_configs/solrconfig.xml +572 -0
- data/spec/fixtures/basic_configs/stopwords.txt +14 -0
- data/spec/fixtures/basic_configs/synonyms.txt +29 -0
- data/spec/integration/solr5_spec.rb +34 -0
- data/spec/spec_helper.rb +94 -0
- metadata +232 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 8206df30839209477712ca57220df5bc89ffef4502b343b7160a08ead5e18328
|
4
|
+
data.tar.gz: 173b7730bb208f49a3fa4745c06b087e9047437ca42393c48f41001b872c4582
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 841eb4744fb76c3cf57490b90cf27532191a3bf3400c4dd1c39fa28261ea46810f23843aecb0a95ae870b96afc080f36e15e79f27d21dbb1e2dc3a807e46fc28
|
7
|
+
data.tar.gz: a02c86754b9bda6a4469dc22b3d8f0b5074e04dc4602380efea3dd78a2e8856f2b9e9c807b5a9695e5826163c74e3074ee7709a48a298515bdf34542e4f8a62e
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CHANGES.txt
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
2.0.0.pre1
|
2
|
+
|
3
|
+
In this release, we've added many new features, including:
|
4
|
+
|
5
|
+
- a new JSON request generator (enabled by default, replacing the XML-based requests) (@mootprinter)
|
6
|
+
- using Faraday for added flexibility for HTTP configuration
|
7
|
+
- native support for nested child documents and atomic updates in RSolr::Document and RSolr::Client.add
|
8
|
+
- better support for custom field value converters (@solenko)
|
9
|
+
- removing code deprecated in RSolr 1.x (@vipulnsward, and others)
|
10
|
+
|
11
|
+
|
12
|
+
1.0.12
|
13
|
+
- Fix bug where specifying the wt property as a string, would add the supplied value to the default ('ruby') rather than overriding it.
|
14
|
+
|
15
|
+
1.0.11
|
16
|
+
- add RSolr.solr_escape method and add deprecation messages to RSolr.escape (ndushay)
|
17
|
+
- use stdlib URI.escape methods instead of homegrown in RSolr::URI (ndushay)
|
18
|
+
- fix bug with Rsolr::Uri.create adding trailing slash if query params (ndushay)
|
19
|
+
- update rake tasks (cbeer)
|
20
|
+
- add Ruby 2.2.0 to travis ci build (ndushay)
|
21
|
+
- Housekeeping (badges to README, license in gemspec, correct url in gemspec ...) (ndushay)
|
22
|
+
- Improve rdoc styling (udaykadaboina)
|
23
|
+
- Support setting default_wt per connection via its options. (jcoleman)
|
24
|
+
- eliminates the usage of per-instance `extend` (jcoleman)
|
25
|
+
- Upgrade to RSpec 3 (blackwinter, adamjonas, cbeer)
|
26
|
+
- Fixed RSolr::Error to_s (PofMagicfingers)
|
27
|
+
1.0.10 xxx
|
28
|
+
|
29
|
+
1.0.8
|
30
|
+
- Fix connection refused errors in specs + add basic auth support (Denis Goeury)
|
31
|
+
- Ability to set :retry_503, the number of retry attempts for a 503 response
|
32
|
+
with a Retry-After header.
|
33
|
+
1.0.7
|
34
|
+
- Response body encoding is set to response charset in Ruby >= 1.9
|
35
|
+
- Ability to set :read_timeout and :open_timeout when creating new instance of RSolr
|
36
|
+
1.0.6
|
37
|
+
- More dependency fixups
|
38
|
+
1.0.5
|
39
|
+
- Dependency fixups
|
40
|
+
1.0.4
|
41
|
+
- The "builder" gem dependency is less strict: ~> 2.1.2
|
42
|
+
- RSolr.version is no longer read from a file
|
43
|
+
- Gemspec updated -- less strict dev/test versions
|
44
|
+
- Jeweler is no longer used for building the gemspec
|
45
|
+
1.0.3
|
46
|
+
- Proper encodings in Ruby 1.9
|
47
|
+
- Applied pull request from https://github.com/mwmitchell/rsolr/pull/20
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright 2008-2010 Matt Mitchell
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
you may not use this file except in compliance with the License.
|
5
|
+
You may obtain a copy of the License at
|
6
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
See the License for the specific language governing permissions and
|
13
|
+
limitations under the License.
|
data/README.rdoc
ADDED
@@ -0,0 +1,229 @@
|
|
1
|
+
== This is a TelVue Corp fork of the rsolr gem
|
2
|
+
|
3
|
+
=RSolr
|
4
|
+
{<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]
|
5
|
+
|
6
|
+
|
7
|
+
A simple, extensible Ruby client for Apache Solr.
|
8
|
+
|
9
|
+
==Documentation
|
10
|
+
The code docs http://www.rubydoc.info/gems/rsolr
|
11
|
+
|
12
|
+
== Installation:
|
13
|
+
gem install telvue-rsolr
|
14
|
+
|
15
|
+
== Example:
|
16
|
+
require 'rsolr'
|
17
|
+
|
18
|
+
# Direct connection
|
19
|
+
solr = RSolr.connect :url => 'http://solrserver.com'
|
20
|
+
|
21
|
+
# Connecting over a proxy server
|
22
|
+
solr = RSolr.connect :url => 'http://solrserver.com', :proxy=>'http://user:pass@proxy.example.com:8080'
|
23
|
+
|
24
|
+
# Using an alternate Faraday adapter
|
25
|
+
solr = RSolr.connect :url => 'http://solrserver.com', :adapter => :em_http
|
26
|
+
|
27
|
+
# Using a custom Faraday connection
|
28
|
+
conn = Faraday.new do |faraday|
|
29
|
+
faraday.response :logger # log requests to STDOUT
|
30
|
+
faraday.adapter Faraday.default_adapter # make requests with Net::HTTP
|
31
|
+
end
|
32
|
+
solr = RSolr.connect conn, :url => 'http://solrserver.com'
|
33
|
+
|
34
|
+
# send a request to /select
|
35
|
+
response = solr.get 'select', :params => {:q => '*:*'}
|
36
|
+
|
37
|
+
# send a request to /catalog
|
38
|
+
response = solr.get 'catalog', :params => {:q => '*:*'}
|
39
|
+
|
40
|
+
When the Solr +:wt+ is +:ruby+, then the response will be a Hash. This Hash is the same object returned by Solr, but evaluated as Ruby. If the +:wt+ is not +:ruby+, then the response will be a String.
|
41
|
+
|
42
|
+
The response also exposes 2 attribute readers (for any +:wt+ value), +:request+ and +:response+. Both are Hash objects with symbolized keys.
|
43
|
+
|
44
|
+
The +:request+ attribute contains the original request context. You can use this for debugging or logging. Some of the keys this object contains are +:uri+, +:query+, +:method+ etc..
|
45
|
+
|
46
|
+
The +:response+ attribute contains the original response. This object contains the +:status+, +:body+ and +:headers+ keys.
|
47
|
+
|
48
|
+
== Request formats
|
49
|
+
|
50
|
+
By default, RSolr uses the Solr JSON command format for all requests.
|
51
|
+
|
52
|
+
RSolr.connect :url => 'http://solrserver.com', update_format: :json # the default
|
53
|
+
# or
|
54
|
+
RSolr.connect :url => 'http://solrserver.com', update_format: :xml
|
55
|
+
|
56
|
+
== Timeouts
|
57
|
+
The read and connect timeout settings can be set when creating a new instance of RSolr:
|
58
|
+
solr = RSolr.connect(:read_timeout => 120, :open_timeout => 120)
|
59
|
+
|
60
|
+
== Retry 503s
|
61
|
+
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.
|
62
|
+
|
63
|
+
Only requests which specify a Retry-After header will be retried, after waiting the indicated retry interval, otherwise RSolr will treat the request as a 500. You may specify a maximum Retry-After interval to wait with the +:retry_after_limit+ option (default: one second).
|
64
|
+
solr = RSolr.connect(:retry_503 => 1, :retry_after_limit => 1)
|
65
|
+
|
66
|
+
For additional control, consider using a custom Faraday connection (see above) using its `retry` middleware.
|
67
|
+
|
68
|
+
== Querying
|
69
|
+
Use the #get / #post method to send search requests to the /select handler:
|
70
|
+
response = solr.get 'select', :params => {
|
71
|
+
:q=>'washington',
|
72
|
+
:start=>0,
|
73
|
+
:rows=>10
|
74
|
+
}
|
75
|
+
response["response"]["docs"].each{|doc| puts doc["id"] }
|
76
|
+
|
77
|
+
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.
|
78
|
+
When an array is used, multiple parameters *with the same name* are generated for the Solr query. Example:
|
79
|
+
|
80
|
+
solr.get 'select', :params => {:q=>'roses', :fq=>['red', 'violet']}
|
81
|
+
|
82
|
+
The above statement generates this Solr query:
|
83
|
+
|
84
|
+
select?q=roses&fq=red&fq=violet
|
85
|
+
|
86
|
+
===Pagination
|
87
|
+
To paginate through a set of Solr documents, use the paginate method:
|
88
|
+
solr.paginate 1, 10, "select", :params => {:q => "test"}
|
89
|
+
|
90
|
+
The first argument is the current page, the second is how many documents to return for each page. In other words, "page" is the "start" Solr param and "per-page" is the "rows" Solr param.
|
91
|
+
|
92
|
+
The paginate method returns WillPaginate ready "docs" objects, so for example in a Rails application, paginating is as simple as:
|
93
|
+
<%= will_paginate @solr_response["response"]["docs"] %>
|
94
|
+
|
95
|
+
===Method Missing
|
96
|
+
The +RSolr::Client+ class also uses +method_missing+ for setting the request handler/path:
|
97
|
+
|
98
|
+
solr.paintings :params => {:q=>'roses', :fq=>['red', 'violet']}
|
99
|
+
|
100
|
+
This is sent to Solr as:
|
101
|
+
paintings?q=roses&fq=red&fq=violet
|
102
|
+
|
103
|
+
This works with pagination as well:
|
104
|
+
|
105
|
+
solr.paginate_paintings 1, 10, {:q=>'roses', :fq=>['red', 'violet']}
|
106
|
+
|
107
|
+
===Using POST for Search Queries
|
108
|
+
There may be cases where the query string is too long for a GET request. RSolr solves this issue by converting hash objects into form-encoded strings:
|
109
|
+
response = solr.music :data => {:q => "*:*"}
|
110
|
+
|
111
|
+
The +:data+ hash is serialized as a form-encoded query string, and the correct content-type headers are sent along to Solr.
|
112
|
+
|
113
|
+
===Sending HEAD Requests
|
114
|
+
There may be cases where you'd like to send a HEAD request to Solr:
|
115
|
+
solr.head("admin/ping").response[:status] == 200
|
116
|
+
|
117
|
+
==Sending HTTP Headers
|
118
|
+
Solr responds to the request headers listed here: http://wiki.apache.org/solr/SolrAndHTTPCaches
|
119
|
+
To send header information to Solr using RSolr, just use the +:headers+ option:
|
120
|
+
response = solr.head "admin/ping", :headers => {"Cache-Control" => "If-None-Match"}
|
121
|
+
|
122
|
+
===Building a Request
|
123
|
+
+RSolr::Client+ provides a method for building a request context, which can be useful for debugging or logging etc.:
|
124
|
+
request_context = solr.build_request "select", :data => {:q => "*:*"}, :method => :post, :headers => {}
|
125
|
+
|
126
|
+
To build a paginated request use build_paginated_request:
|
127
|
+
request_context = solr.build_paginated_request 1, 10, "select", ...
|
128
|
+
|
129
|
+
== Updating Solr
|
130
|
+
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.
|
131
|
+
|
132
|
+
Single document via #add
|
133
|
+
solr.add :id=>1, :price=>1.00
|
134
|
+
|
135
|
+
Multiple documents via #add
|
136
|
+
documents = [{:id=>1, :price=>1.00}, {:id=>2, :price=>10.50}]
|
137
|
+
solr.add documents
|
138
|
+
|
139
|
+
The optional +:add_attributes+ hash can also be used to set Solr "add" document attributes:
|
140
|
+
solr.add documents, :add_attributes => {:commitWithin => 10}
|
141
|
+
|
142
|
+
Raw commands via #update
|
143
|
+
solr.update data: '<commit/>', headers: { 'Content-Type' => 'text/xml' }
|
144
|
+
solr.update data: { optimize: true }.to_json, headers: { 'Content-Type' => 'application/json' }
|
145
|
+
|
146
|
+
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:
|
147
|
+
|
148
|
+
doc = {:id=>1, :price=>1.00}
|
149
|
+
add_attributes = {:allowDups=>false, :commitWithin=>10}
|
150
|
+
add_xml = solr.xml.add(doc, add_attributes) do |doc|
|
151
|
+
# boost each document
|
152
|
+
doc.attrs[:boost] = 1.5
|
153
|
+
# boost the price field:
|
154
|
+
doc.field_by_name(:price).attrs[:boost] = 2.0
|
155
|
+
end
|
156
|
+
|
157
|
+
Now the "add_xml" object can be sent to Solr like:
|
158
|
+
solr.update :data => add_xml
|
159
|
+
|
160
|
+
===Deleting
|
161
|
+
Delete by id
|
162
|
+
solr.delete_by_id 1
|
163
|
+
or an array of ids
|
164
|
+
solr.delete_by_id [1, 2, 3, 4]
|
165
|
+
|
166
|
+
Delete by query:
|
167
|
+
solr.delete_by_query 'price:1.00'
|
168
|
+
Delete by array of queries
|
169
|
+
solr.delete_by_query ['price:1.00', 'price:10.00']
|
170
|
+
|
171
|
+
===Commit / Optimize
|
172
|
+
solr.commit, :commit_attributes => {}
|
173
|
+
solr.optimize, :optimize_attributes => {}
|
174
|
+
|
175
|
+
== Response Formats
|
176
|
+
The default response format is Ruby. When the +:wt+ param is set to +:ruby+, the response is eval'd resulting in a Hash. You can get a raw response by setting the +:wt+ to +"ruby"+ - notice, the string -- not a symbol. RSolr will eval the Ruby string ONLY if the :wt value is :ruby. All other response formats are available as expected, +:wt=>'xml'+ etc..
|
177
|
+
|
178
|
+
===Evaluated Ruby:
|
179
|
+
solr.get 'select', :params => {:wt => :ruby} # notice :ruby is a Symbol
|
180
|
+
===Raw Ruby:
|
181
|
+
solr.get 'select', :params => {:wt => 'ruby'} # notice 'ruby' is a String
|
182
|
+
===XML:
|
183
|
+
solr.get 'select', :params => {:wt => :xml}
|
184
|
+
===JSON (default):
|
185
|
+
solr.get 'select', :params => {:wt => :json}
|
186
|
+
|
187
|
+
==Related Resources & Projects
|
188
|
+
* {RSolr Google Group}[http://groups.google.com/group/rsolr] -- The RSolr discussion group
|
189
|
+
* {rsolr-ext}[http://github.com/mwmitchell/rsolr-ext] -- An extension kit for RSolr
|
190
|
+
* {rsolr-direct}[http://github.com/mwmitchell/rsolr-direct] -- JRuby direct connection for RSolr
|
191
|
+
* {rsolr-nokogiri}[http://github.com/mwmitchell/rsolr-nokogiri] -- Gives RSolr Nokogiri for XML generation.
|
192
|
+
* {SunSpot}[http://github.com/sunspot/sunspot] -- An awesome Solr DSL, built with RSolr
|
193
|
+
* {Blacklight}[http://blacklightopac.org] -- A "next generation" Library OPAC, built with RSolr
|
194
|
+
* {java_bin}[http://github.com/kennyj/java_bin] -- Provides javabin/binary parsing for RSolr
|
195
|
+
* {Solr}[http://lucene.apache.org/solr/] -- The Apache Solr project
|
196
|
+
* {solr-ruby}[http://wiki.apache.org/solr/solr-ruby] -- The original Solr Ruby Gem!
|
197
|
+
|
198
|
+
== Note on Patches/Pull Requests
|
199
|
+
* Fork the project.
|
200
|
+
* Make your feature addition or bug fix.
|
201
|
+
* Add tests for it. This is important so I don't break it in a future version unintentionally.
|
202
|
+
* Commit, do not mess with rakefile, version, or history
|
203
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
204
|
+
* Send me a pull request. Bonus points for topic branches.
|
205
|
+
|
206
|
+
==Contributors
|
207
|
+
* Nathan Witmer
|
208
|
+
* Magnus Bergmark
|
209
|
+
* shima
|
210
|
+
* Randy Souza
|
211
|
+
* Mat Brown
|
212
|
+
* Jeremy Hinegardner
|
213
|
+
* Denis Goeury
|
214
|
+
* shairon toledo
|
215
|
+
* Rob Di Marco
|
216
|
+
* Peter Kieltyka
|
217
|
+
* Mike Perham
|
218
|
+
* Lucas Souza
|
219
|
+
* Dmitry Lihachev
|
220
|
+
* Antoine Latter
|
221
|
+
* Naomi Dushay
|
222
|
+
|
223
|
+
==Author
|
224
|
+
|
225
|
+
Matt Mitchell <mailto:goodieboy@gmail.com>
|
226
|
+
|
227
|
+
==Copyright
|
228
|
+
|
229
|
+
Copyright (c) 2008-2010 Matt Mitchell. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
|
3
|
+
task default: ['spec']
|
4
|
+
|
5
|
+
require 'rspec/core/rake_task'
|
6
|
+
|
7
|
+
RSpec::Core::RakeTask.new(:spec)
|
8
|
+
|
9
|
+
# Rdoc
|
10
|
+
require 'rdoc/task'
|
11
|
+
|
12
|
+
desc 'Generate documentation for the rsolr gem.'
|
13
|
+
RDoc::Task.new(:doc) do |rdoc|
|
14
|
+
rdoc.rdoc_dir = 'doc'
|
15
|
+
rdoc.title = 'RSolr'
|
16
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
17
|
+
rdoc.rdoc_files.include('README.rdoc')
|
18
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
19
|
+
end
|
data/lib/rsolr.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
module RSolr
|
2
|
+
require 'rsolr/version'
|
3
|
+
|
4
|
+
autoload :Char, 'rsolr/char'
|
5
|
+
autoload :Client, 'rsolr/client'
|
6
|
+
autoload :Document, 'rsolr/document'
|
7
|
+
autoload :Error, 'rsolr/error'
|
8
|
+
autoload :Field, 'rsolr/field'
|
9
|
+
autoload :Generator, 'rsolr/generator'
|
10
|
+
autoload :HashWithResponse, 'rsolr/response'
|
11
|
+
autoload :JSON, 'rsolr/json'
|
12
|
+
autoload :Response, 'rsolr/response'
|
13
|
+
autoload :Uri, 'rsolr/uri'
|
14
|
+
autoload :Xml, 'rsolr/xml'
|
15
|
+
|
16
|
+
def self.connect *args
|
17
|
+
opts = args.pop if args.last.is_a?(::Hash)
|
18
|
+
opts ||= {}
|
19
|
+
|
20
|
+
connection = args.first
|
21
|
+
|
22
|
+
Client.new connection, opts
|
23
|
+
end
|
24
|
+
|
25
|
+
# backslash escape characters that have special meaning to Solr query parser
|
26
|
+
# per http://lucene.apache.org/core/4_0_0/queryparser/org/apache/lucene/queryparser/classic/package-summary.html#Escaping_Special_Characters
|
27
|
+
# + - & | ! ( ) { } [ ] ^ " ~ * ? : \ /
|
28
|
+
# see also http://svn.apache.org/repos/asf/lucene/dev/tags/lucene_solr_4_9_1/solr/solrj/src/java/org/apache/solr/client/solrj/util/ClientUtils.java
|
29
|
+
# escapeQueryChars method
|
30
|
+
# @return [String] str with special chars preceded by a backslash
|
31
|
+
def self.solr_escape(str)
|
32
|
+
# note that the gsub will parse the escaped backslashes, as will the ruby code sending the query to Solr
|
33
|
+
# so the result sent to Solr is ultimately a single backslash in front of the particular character
|
34
|
+
str.gsub(/([+\-&|!\(\)\{\}\[\]\^"~\*\?:\\\/])/, '\\\\\1')
|
35
|
+
end
|
36
|
+
|
37
|
+
module Array
|
38
|
+
def self.wrap(object)
|
39
|
+
if object.nil?
|
40
|
+
[nil]
|
41
|
+
elsif object.respond_to?(:to_ary)
|
42
|
+
object.to_ary || [object]
|
43
|
+
elsif object.is_a? Hash
|
44
|
+
[object]
|
45
|
+
elsif object.is_a? Enumerable
|
46
|
+
object
|
47
|
+
else
|
48
|
+
[object]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/rsolr/char.rb
ADDED
data/lib/rsolr/client.rb
ADDED
@@ -0,0 +1,342 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'faraday'
|
5
|
+
require 'uri'
|
6
|
+
|
7
|
+
class RSolr::Client
|
8
|
+
DEFAULT_URL = 'http://127.0.0.1:8983/solr/'
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def default_wt
|
12
|
+
@default_wt ||= :json
|
13
|
+
end
|
14
|
+
|
15
|
+
def default_wt= value
|
16
|
+
@default_wt = value
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :uri, :proxy, :update_format, :options, :update_path
|
21
|
+
|
22
|
+
def initialize connection, options = {}
|
23
|
+
@proxy = @uri = nil
|
24
|
+
@connection = connection
|
25
|
+
unless false === options[:url]
|
26
|
+
@uri = extract_url_from_options(options)
|
27
|
+
if options[:proxy]
|
28
|
+
proxy_url = options[:proxy].dup
|
29
|
+
proxy_url << "/" unless proxy_url.nil? or proxy_url[-1] == ?/
|
30
|
+
@proxy = ::URI.parse proxy_url if proxy_url
|
31
|
+
elsif options[:proxy] == false
|
32
|
+
@proxy = false # used to avoid setting the proxy from the environment.
|
33
|
+
end
|
34
|
+
end
|
35
|
+
@update_format = options.delete(:update_format) || RSolr::JSON::Generator
|
36
|
+
@update_path = options.fetch(:update_path, 'update')
|
37
|
+
@options = options
|
38
|
+
end
|
39
|
+
|
40
|
+
def extract_url_from_options(options)
|
41
|
+
url = options[:url] ? options[:url].dup : DEFAULT_URL
|
42
|
+
url << "/" unless url[-1] == ?/
|
43
|
+
uri = ::URI.parse(url)
|
44
|
+
# URI::HTTPS is a subclass of URI::HTTP, so this check accepts HTTP(S)
|
45
|
+
raise ArgumentError, "You must provide an HTTP(S) url." unless uri.kind_of?(URI::HTTP)
|
46
|
+
uri
|
47
|
+
end
|
48
|
+
|
49
|
+
# returns the request uri object.
|
50
|
+
def base_request_uri
|
51
|
+
base_uri.request_uri if base_uri
|
52
|
+
end
|
53
|
+
|
54
|
+
# returns the URI uri object.
|
55
|
+
def base_uri
|
56
|
+
@uri
|
57
|
+
end
|
58
|
+
|
59
|
+
# Create the get, post, and head methods
|
60
|
+
%W(get post head).each do |meth|
|
61
|
+
class_eval <<-RUBY
|
62
|
+
def #{meth} path, opts = {}, &block
|
63
|
+
send_and_receive path, opts.merge(:method => :#{meth}), &block
|
64
|
+
end
|
65
|
+
RUBY
|
66
|
+
end
|
67
|
+
|
68
|
+
# A paginated request method.
|
69
|
+
# Converts the page and per_page
|
70
|
+
# arguments into "rows" and "start".
|
71
|
+
def paginate page, per_page, path, opts = nil
|
72
|
+
opts ||= {}
|
73
|
+
opts[:params] ||= {}
|
74
|
+
raise "'rows' or 'start' params should not be set when using +paginate+" if ["start", "rows"].include?(opts[:params].keys)
|
75
|
+
execute build_paginated_request(page, per_page, path, opts)
|
76
|
+
end
|
77
|
+
|
78
|
+
# POST XML messages to /update with optional params.
|
79
|
+
#
|
80
|
+
# http://wiki.apache.org/solr/UpdateXmlMessages#add.2BAC8-update
|
81
|
+
#
|
82
|
+
# If not set, opts[:headers] will be set to a hash with the key
|
83
|
+
# 'Content-Type' set to 'text/xml'
|
84
|
+
#
|
85
|
+
# +opts+ can/should contain:
|
86
|
+
#
|
87
|
+
# :data - posted data
|
88
|
+
# :headers - http headers
|
89
|
+
# :params - solr query parameter hash
|
90
|
+
#
|
91
|
+
def update opts = {}
|
92
|
+
opts[:headers] ||= {}
|
93
|
+
opts[:headers]['Content-Type'] ||= builder.content_type
|
94
|
+
post opts.fetch(:path, update_path), opts
|
95
|
+
end
|
96
|
+
|
97
|
+
# +add+ creates xml "add" documents and sends the xml data to the +update+ method
|
98
|
+
#
|
99
|
+
# http://wiki.apache.org/solr/UpdateXmlMessages#add.2BAC8-update
|
100
|
+
#
|
101
|
+
# single record:
|
102
|
+
# solr.add(:id=>1, :name=>'one')
|
103
|
+
#
|
104
|
+
# add using an array
|
105
|
+
#
|
106
|
+
# solr.add(
|
107
|
+
# [{:id=>1, :name=>'one'}, {:id=>2, :name=>'two'}],
|
108
|
+
# :add_attributes => {:boost=>5.0, :commitWithin=>10}
|
109
|
+
# )
|
110
|
+
#
|
111
|
+
def add doc, opts = {}
|
112
|
+
add_attributes = opts.delete :add_attributes
|
113
|
+
update opts.merge(:data => builder.add(doc, add_attributes))
|
114
|
+
end
|
115
|
+
|
116
|
+
# send "commit" xml with opts
|
117
|
+
#
|
118
|
+
# http://wiki.apache.org/solr/UpdateXmlMessages#A.22commit.22_and_.22optimize.22
|
119
|
+
#
|
120
|
+
def commit opts = {}
|
121
|
+
commit_attrs = opts.delete :commit_attributes
|
122
|
+
update opts.merge(:data => builder.commit( commit_attrs ))
|
123
|
+
end
|
124
|
+
|
125
|
+
# send "optimize" xml with opts.
|
126
|
+
#
|
127
|
+
# http://wiki.apache.org/solr/UpdateXmlMessages#A.22commit.22_and_.22optimize.22
|
128
|
+
#
|
129
|
+
def optimize opts = {}
|
130
|
+
optimize_attributes = opts.delete :optimize_attributes
|
131
|
+
update opts.merge(:data => builder.optimize(optimize_attributes))
|
132
|
+
end
|
133
|
+
|
134
|
+
# send </rollback>
|
135
|
+
#
|
136
|
+
# http://wiki.apache.org/solr/UpdateXmlMessages#A.22rollback.22
|
137
|
+
#
|
138
|
+
# NOTE: solr 1.4 only
|
139
|
+
def rollback opts = {}
|
140
|
+
update opts.merge(:data => builder.rollback)
|
141
|
+
end
|
142
|
+
|
143
|
+
# Delete one or many documents by id
|
144
|
+
# solr.delete_by_id 10
|
145
|
+
# solr.delete_by_id([12, 41, 199])
|
146
|
+
def delete_by_id id, opts = {}
|
147
|
+
update opts.merge(:data => builder.delete_by_id(id))
|
148
|
+
end
|
149
|
+
|
150
|
+
# delete one or many documents by query.
|
151
|
+
#
|
152
|
+
# http://wiki.apache.org/solr/UpdateXmlMessages#A.22delete.22_by_ID_and_by_Query
|
153
|
+
#
|
154
|
+
# solr.delete_by_query 'available:0'
|
155
|
+
# solr.delete_by_query ['quantity:0', 'manu:"FQ"']
|
156
|
+
def delete_by_query query, opts = {}
|
157
|
+
update opts.merge(:data => builder.delete_by_query(query))
|
158
|
+
end
|
159
|
+
|
160
|
+
def builder
|
161
|
+
@builder ||= if update_format.is_a? Class
|
162
|
+
update_format.new
|
163
|
+
elsif update_format == :json
|
164
|
+
RSolr::JSON::Generator.new
|
165
|
+
elsif update_format == :xml
|
166
|
+
RSolr::Xml::Generator.new
|
167
|
+
else
|
168
|
+
update_format
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# +send_and_receive+ is the main request method responsible for sending requests to the +connection+ object.
|
173
|
+
#
|
174
|
+
# "path" : A string value that usually represents a solr request handler
|
175
|
+
# "opts" : A hash, which can contain the following keys:
|
176
|
+
# :method : required - the http method (:get, :post or :head)
|
177
|
+
# :params : optional - the query string params in hash form
|
178
|
+
# :data : optional - post data -- if a hash is given, it's sent as "application/x-www-form-urlencoded; charset=UTF-8"
|
179
|
+
# :headers : optional - hash of request headers
|
180
|
+
# All other options are passed right along to the connection's +send_and_receive+ method (:get, :post, or :head)
|
181
|
+
#
|
182
|
+
# +send_and_receive+ returns either a string or hash on a successful ruby request.
|
183
|
+
# When the :params[:wt] => :ruby, the response will be a hash, else a string.
|
184
|
+
#
|
185
|
+
# creates a request context hash,
|
186
|
+
# sends it to the connection's +execute+ method
|
187
|
+
# which returns a simple hash,
|
188
|
+
# then passes the request/response into +adapt_response+.
|
189
|
+
def send_and_receive path, opts
|
190
|
+
request_context = build_request path, opts
|
191
|
+
execute request_context
|
192
|
+
end
|
193
|
+
|
194
|
+
#
|
195
|
+
def execute request_context
|
196
|
+
raw_response = begin
|
197
|
+
response = connection.send(request_context[:method], request_context[:uri].to_s) do |req|
|
198
|
+
req.body = request_context[:data] if request_context[:method] == :post and request_context[:data]
|
199
|
+
req.headers.merge!(request_context[:headers]) if request_context[:headers]
|
200
|
+
end
|
201
|
+
|
202
|
+
{ status: response.status.to_i, headers: response.headers, body: response.body.dup.force_encoding('utf-8') }
|
203
|
+
rescue Errno::ECONNREFUSED, Faraday::Error::ConnectionFailed
|
204
|
+
raise RSolr::Error::ConnectionRefused, request_context.inspect
|
205
|
+
rescue Faraday::Error => e
|
206
|
+
raise RSolr::Error::Http.new(request_context, e.response)
|
207
|
+
end
|
208
|
+
adapt_response(request_context, raw_response) unless raw_response.nil?
|
209
|
+
end
|
210
|
+
|
211
|
+
# +build_request+ accepts a path and options hash,
|
212
|
+
# then prepares a normalized hash to return for sending
|
213
|
+
# to a solr connection driver.
|
214
|
+
# +build_request+ sets up the uri/query string
|
215
|
+
# and converts the +data+ arg to form-urlencoded,
|
216
|
+
# if the +data+ arg is a hash.
|
217
|
+
# returns a hash with the following keys:
|
218
|
+
# :method
|
219
|
+
# :params
|
220
|
+
# :headers
|
221
|
+
# :data
|
222
|
+
# :uri
|
223
|
+
# :path
|
224
|
+
# :query
|
225
|
+
def build_request path, opts
|
226
|
+
raise "path must be a string or symbol, not #{path.inspect}" unless [String,Symbol].include?(path.class)
|
227
|
+
path = path.to_s
|
228
|
+
opts[:proxy] = proxy unless proxy.nil?
|
229
|
+
opts[:method] ||= :get
|
230
|
+
raise "The :data option can only be used if :method => :post" if opts[:method] != :post and opts[:data]
|
231
|
+
opts[:params] = params_with_wt(opts[:params])
|
232
|
+
query = RSolr::Uri.params_to_solr(opts[:params]) unless opts[:params].empty?
|
233
|
+
opts[:query] = query
|
234
|
+
if opts[:data].is_a? Hash
|
235
|
+
opts[:data] = RSolr::Uri.params_to_solr opts[:data]
|
236
|
+
opts[:headers] ||= {}
|
237
|
+
opts[:headers]['Content-Type'] ||= 'application/x-www-form-urlencoded; charset=UTF-8'
|
238
|
+
end
|
239
|
+
opts[:path] = path
|
240
|
+
opts[:uri] = base_uri.merge(path.to_s + (query ? "?#{query}" : "")) if base_uri
|
241
|
+
|
242
|
+
opts
|
243
|
+
end
|
244
|
+
|
245
|
+
def params_with_wt(params)
|
246
|
+
return { wt: default_wt } if params.nil?
|
247
|
+
return params if params.key?(:wt) || params.key?('wt')
|
248
|
+
{ wt: default_wt }.merge(params)
|
249
|
+
end
|
250
|
+
|
251
|
+
def build_paginated_request page, per_page, path, opts
|
252
|
+
per_page = per_page.to_s.to_i
|
253
|
+
page = page.to_s.to_i-1
|
254
|
+
page = page < 1 ? 0 : page
|
255
|
+
opts[:params]["start"] = page * per_page
|
256
|
+
opts[:params]["rows"] = per_page
|
257
|
+
build_request path, opts
|
258
|
+
end
|
259
|
+
|
260
|
+
# This method will evaluate the :body value
|
261
|
+
# if the params[:uri].params[:wt] == :ruby
|
262
|
+
# ... otherwise, the body is returned as is.
|
263
|
+
# The return object has methods attached, :request and :response.
|
264
|
+
# These methods give you access to the original
|
265
|
+
# request and response from the connection.
|
266
|
+
#
|
267
|
+
# +adapt_response+ will raise an InvalidRubyResponse
|
268
|
+
# if :wt == :ruby and the body
|
269
|
+
# couldn't be evaluated.
|
270
|
+
def adapt_response request, response
|
271
|
+
raise "The response does not have the correct keys => :body, :headers, :status" unless
|
272
|
+
%W(body headers status) == response.keys.map{|k|k.to_s}.sort
|
273
|
+
|
274
|
+
result = if respond_to? "evaluate_#{request[:params][:wt]}_response", true
|
275
|
+
send "evaluate_#{request[:params][:wt]}_response", request, response
|
276
|
+
else
|
277
|
+
response[:body]
|
278
|
+
end
|
279
|
+
|
280
|
+
if result.is_a?(Hash) || request[:method] == :head
|
281
|
+
result = RSolr::HashWithResponse.new(request, response, result)
|
282
|
+
end
|
283
|
+
|
284
|
+
result
|
285
|
+
end
|
286
|
+
|
287
|
+
def connection
|
288
|
+
@connection ||= begin
|
289
|
+
conn_opts = { request: {} }
|
290
|
+
conn_opts[:url] = uri.to_s
|
291
|
+
conn_opts[:proxy] = proxy if proxy
|
292
|
+
conn_opts[:request][:open_timeout] = options[:open_timeout] if options[:open_timeout]
|
293
|
+
conn_opts[:request][:timeout] = options[:read_timeout] if options[:read_timeout]
|
294
|
+
conn_opts[:request][:params_encoder] = Faraday::FlatParamsEncoder
|
295
|
+
|
296
|
+
Faraday.new(conn_opts) do |conn|
|
297
|
+
conn.basic_auth(uri.user, uri.password) if uri.user && uri.password
|
298
|
+
conn.response :raise_error
|
299
|
+
conn.request :retry, max: options[:retry_after_limit], interval: 0.05,
|
300
|
+
interval_randomness: 0.5, backoff_factor: 2,
|
301
|
+
exceptions: ['Faraday::Error', 'Timeout::Error'] if options[:retry_503]
|
302
|
+
conn.adapter options[:adapter] || Faraday.default_adapter
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
protected
|
308
|
+
|
309
|
+
# converts the method name for the solr request handler path.
|
310
|
+
def method_missing name, *args
|
311
|
+
if name.to_s =~ /^paginated?_(.+)$/
|
312
|
+
paginate args[0], args[1], $1, *args[2..-1]
|
313
|
+
else
|
314
|
+
send_and_receive name, *args
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
# evaluates the response[:body],
|
319
|
+
# attempts to bring the ruby string to life.
|
320
|
+
# If a SyntaxError is raised, then
|
321
|
+
# this method intercepts and raises a
|
322
|
+
# RSolr::Error::InvalidRubyResponse
|
323
|
+
# instead, giving full access to the
|
324
|
+
# request/response objects.
|
325
|
+
def evaluate_ruby_response request, response
|
326
|
+
Kernel.eval response[:body].to_s
|
327
|
+
rescue SyntaxError
|
328
|
+
raise RSolr::Error::InvalidRubyResponse.new request, response
|
329
|
+
end
|
330
|
+
|
331
|
+
def evaluate_json_response request, response
|
332
|
+
return if response[:body].nil? || response[:body].empty?
|
333
|
+
|
334
|
+
JSON.parse response[:body].to_s
|
335
|
+
rescue JSON::ParserError
|
336
|
+
raise RSolr::Error::InvalidJsonResponse.new request, response
|
337
|
+
end
|
338
|
+
|
339
|
+
def default_wt
|
340
|
+
self.options[:default_wt] || self.class.default_wt
|
341
|
+
end
|
342
|
+
end
|