telvue-rsolr 2.2.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|