solr-client 0.0.1
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.
- data/.gitignore +17 -0
- data/Gemfile +3 -0
- data/LICENSE +22 -0
- data/README.md +39 -0
- data/Rakefile +2 -0
- data/lib/solr-client.rb +300 -0
- data/solr-client.gemspec +19 -0
- metadata +69 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2012 zzzhc
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Solr::Client
|
|
2
|
+
|
|
3
|
+
fast solr client for ruby
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add this line to your application's Gemfile:
|
|
8
|
+
|
|
9
|
+
gem 'solr-client'
|
|
10
|
+
|
|
11
|
+
And then execute:
|
|
12
|
+
|
|
13
|
+
$ bundle
|
|
14
|
+
|
|
15
|
+
Or install it yourself as:
|
|
16
|
+
|
|
17
|
+
$ gem install solr-client
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
``` ruby
|
|
22
|
+
solr_base_url = "http://localhost:8983/solr"
|
|
23
|
+
client = Solr::Client.new(solr_base_url)
|
|
24
|
+
client.core = "article"
|
|
25
|
+
rows = mysql.query("select * from article order by id asc limit 100")
|
|
26
|
+
rows.each do |row|
|
|
27
|
+
solr_client.add(row)
|
|
28
|
+
end
|
|
29
|
+
solr_client.commit
|
|
30
|
+
solr_client.stop
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Contributing
|
|
34
|
+
|
|
35
|
+
1. Fork it
|
|
36
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
37
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
|
38
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
|
39
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/lib/solr-client.rb
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
require 'curb'
|
|
2
|
+
require 'thread'
|
|
3
|
+
require 'cgi'
|
|
4
|
+
require 'time'
|
|
5
|
+
|
|
6
|
+
module Solr
|
|
7
|
+
|
|
8
|
+
module Codec
|
|
9
|
+
|
|
10
|
+
ALLOWED_ADD_ATTRS = [:overwrite, :commitWithin]
|
|
11
|
+
|
|
12
|
+
def to_docs(data, add_attrs={})
|
|
13
|
+
attrs = add_attrs.map do |k, v|
|
|
14
|
+
"#{k}=\"#{v}\"" if ALLOWED_ADD_ATTRS.include?(k) && !v.nil?
|
|
15
|
+
end.join(" ")
|
|
16
|
+
attrs = " " + attrs unless attrs.empty?
|
|
17
|
+
content = '<?xml version="1.0" encoding="UTF-8"?>';
|
|
18
|
+
content += "<add#{attrs}>"
|
|
19
|
+
|
|
20
|
+
data = [data] unless data.is_a?(Array)
|
|
21
|
+
data.each do |doc|
|
|
22
|
+
content << '<doc>'
|
|
23
|
+
doc.each do |field_key, field_values|
|
|
24
|
+
next if field_values.nil?
|
|
25
|
+
field_values = [field_values] unless field_values.is_a?(Array)
|
|
26
|
+
field_values.each do |field_value|
|
|
27
|
+
content << '<field name="' << h(field_key) << '">'
|
|
28
|
+
case field_value
|
|
29
|
+
when Time, DateTime
|
|
30
|
+
field_value = field_value.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
31
|
+
end
|
|
32
|
+
content << h(field_value)
|
|
33
|
+
content << '</field>'
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
content << '</doc>'
|
|
37
|
+
end
|
|
38
|
+
content << '</add>'
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def to_delete_by_id(*ids)
|
|
42
|
+
ids.flatten!
|
|
43
|
+
content = '<?xml version="1.0" encoding="UTF-8"?><delete>'
|
|
44
|
+
ids.each { |id| content << '<id>' << h(id) << '</id>' }
|
|
45
|
+
content << '</delete>'
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def to_delete_by_query(*queries)
|
|
49
|
+
queries.flatten!
|
|
50
|
+
content = '<?xml version="1.0" encoding="UTF-8"?><delete>'
|
|
51
|
+
queries.each { |query| content << '<query>' << h(query) << '</query>' }
|
|
52
|
+
content << '</delete>'
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
def h(s)
|
|
57
|
+
CGI.escapeHTML(s.to_s)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def escape(s)
|
|
61
|
+
CGI.escape(s.to_s)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
class Request
|
|
67
|
+
attr_reader :url, :callback, :body
|
|
68
|
+
|
|
69
|
+
def initialize(url, data = nil, &block)
|
|
70
|
+
@url, @data, @callback = url, data, block
|
|
71
|
+
@success = false
|
|
72
|
+
@finished = false
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def sync?
|
|
76
|
+
@callback.nil?
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def on_complete(curl)
|
|
80
|
+
@finished = true
|
|
81
|
+
@body = curl.body_str
|
|
82
|
+
@code = curl.response_code
|
|
83
|
+
@callback.call(self) if @callback
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def data
|
|
87
|
+
if @data.respond_to?(:call)
|
|
88
|
+
@data = @data.call
|
|
89
|
+
end
|
|
90
|
+
@data
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def finished?
|
|
94
|
+
@finished
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def success?
|
|
98
|
+
@code == 200
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def response
|
|
102
|
+
return @body unless (url =~ /wt=ruby/)
|
|
103
|
+
@response ||= Response.new(@body)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
class Response
|
|
109
|
+
|
|
110
|
+
attr_reader :status, :docs, :start, :num_found, :qtime, :params, :rows, :start
|
|
111
|
+
|
|
112
|
+
def initialize(body)
|
|
113
|
+
data = eval(body)
|
|
114
|
+
header = data["responseHeader"]
|
|
115
|
+
@status = header["status"]
|
|
116
|
+
@qtime = header["QTime"]
|
|
117
|
+
@params = header["params"]
|
|
118
|
+
@start = @params["start"].to_i
|
|
119
|
+
@rows = @params["rows"].to_i
|
|
120
|
+
response = data["response"]
|
|
121
|
+
@docs = response["docs"]
|
|
122
|
+
@start = response["start"]
|
|
123
|
+
@num_found = response["numFound"]
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def success?
|
|
127
|
+
@status == 0
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# see http://wiki.apache.org/solr/UpdateXmlMessages
|
|
133
|
+
class Client
|
|
134
|
+
|
|
135
|
+
VERSION = "0.0.1"
|
|
136
|
+
|
|
137
|
+
include Codec
|
|
138
|
+
|
|
139
|
+
attr_accessor :core
|
|
140
|
+
|
|
141
|
+
def initialize(base_url, options = {})
|
|
142
|
+
@base_url = base_url.gsub(/\/$/, '')
|
|
143
|
+
@max_connects = options[:max_connects] || 10
|
|
144
|
+
@stop = false
|
|
145
|
+
@queue = Queue.new
|
|
146
|
+
@multi = Curl::Multi.new
|
|
147
|
+
@multi.max_connects = @max_connects
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# execute a search request.
|
|
151
|
+
# [query] query parameters, keys:
|
|
152
|
+
# q: query
|
|
153
|
+
# qt: query type
|
|
154
|
+
# fq: filter query
|
|
155
|
+
# fl: specify a set of fields to return, * means all fields
|
|
156
|
+
# sort: sort method, default value is score desc
|
|
157
|
+
# start: offset in result set
|
|
158
|
+
# rows: maximum number of documents to return, default value is 10
|
|
159
|
+
# defType: specify the query parser
|
|
160
|
+
#
|
|
161
|
+
# ref:
|
|
162
|
+
# http://wiki.apache.org/solr/CoreQueryParameters
|
|
163
|
+
# http://wiki.apache.org/solr/CommonQueryParameters
|
|
164
|
+
#
|
|
165
|
+
def select(query, options={}, &block)
|
|
166
|
+
query[:wt] = "ruby"
|
|
167
|
+
options[:action] = "select"
|
|
168
|
+
url = real_url(options)
|
|
169
|
+
url += '?' unless (url =~ /\?/)
|
|
170
|
+
url += '&' if (url =~ /&/)
|
|
171
|
+
url += query.map {|k, v| "#{escape(k)}=#{escape(v)}"}.join("&")
|
|
172
|
+
|
|
173
|
+
execute Request.new(url, &block)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def commit(options = {}, &block)
|
|
177
|
+
url = real_url(options) + "?commit=true"
|
|
178
|
+
execute Request.new(url, &block)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def rollback(options = {}, &block)
|
|
182
|
+
url = real_url(options) + "?rollback=true"
|
|
183
|
+
execute Request.new(url, &block)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def optimize(options = {}, &block)
|
|
187
|
+
url = real_url(options) + "?optimize=true"
|
|
188
|
+
execute Request.new(url, &block)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def delete_all(options = {}, &block)
|
|
192
|
+
delete_by_query("*:*", options, &block)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def delete_by_id(id, options = {}, &block)
|
|
196
|
+
data = to_delete_by_id(id)
|
|
197
|
+
execute Request.new(real_url(options), data, &block)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def delete_by_query(query, options = {}, &block)
|
|
201
|
+
data = to_delete_by_query(query)
|
|
202
|
+
execute Request.new(real_url(options), data, &block)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def add(docs, options = {}, &block)
|
|
206
|
+
data = lambda { to_docs(docs, options) }
|
|
207
|
+
execute Request.new(real_url(options), data, &block)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def stop
|
|
211
|
+
@stop = true
|
|
212
|
+
if @thread
|
|
213
|
+
@queue.push nil
|
|
214
|
+
@thread.join
|
|
215
|
+
@thread = nil
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def reset
|
|
220
|
+
stop
|
|
221
|
+
@stop = false
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def full?
|
|
225
|
+
@queue.size > @max_connects
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
private
|
|
229
|
+
|
|
230
|
+
def execute(request)
|
|
231
|
+
if request.sync?
|
|
232
|
+
curl = build_curl(request)
|
|
233
|
+
curl.perform
|
|
234
|
+
request.on_complete(curl)
|
|
235
|
+
else
|
|
236
|
+
raise "please call reset before performing solr request on a stopped client!" if @stop
|
|
237
|
+
@queue.push request
|
|
238
|
+
ensure_thread_start
|
|
239
|
+
end
|
|
240
|
+
request
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def real_url(options = {})
|
|
244
|
+
return options[:url] if options[:url]
|
|
245
|
+
action = options[:action] || "update"
|
|
246
|
+
core = options[:core] || self.core
|
|
247
|
+
core ? "#{@base_url}/#{core}/#{action}" : "#{@base_url}/#{action}"
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def build_curl(request)
|
|
251
|
+
Curl::Easy.new do |curl|
|
|
252
|
+
curl.url = request.url
|
|
253
|
+
curl.headers["Content-Type"] = "application/xml; charset=UTF-8"
|
|
254
|
+
curl.headers["Expect"] = ""
|
|
255
|
+
curl.post_body = request.data if request.data
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def ensure_thread_start
|
|
260
|
+
@thread ||= Thread.new do
|
|
261
|
+
run
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def send_request(request)
|
|
266
|
+
return unless request
|
|
267
|
+
curl = build_curl(request)
|
|
268
|
+
curl.on_complete do |easy|
|
|
269
|
+
on_idle
|
|
270
|
+
request.on_complete(easy)
|
|
271
|
+
end
|
|
272
|
+
@multi.add(curl)
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def run
|
|
276
|
+
while !@stop || !empty?
|
|
277
|
+
send_request(@queue.pop)
|
|
278
|
+
@multi.perform do
|
|
279
|
+
on_idle
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def on_idle
|
|
285
|
+
if !@stop && !empty? && !busy?
|
|
286
|
+
send_request(@queue.pop)
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def empty?
|
|
291
|
+
@queue.size == 0
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def busy?
|
|
295
|
+
@multi.requests.size > @max_connects * 2
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
data/solr-client.gemspec
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
require File.expand_path('../lib/solr-client', __FILE__)
|
|
3
|
+
|
|
4
|
+
Gem::Specification.new do |gem|
|
|
5
|
+
gem.authors = ["zzzhc"]
|
|
6
|
+
gem.email = ["zzzhc.starfire@gmail.com"]
|
|
7
|
+
gem.description = %q{fast solr client for ruby}
|
|
8
|
+
gem.summary = %q{a high performance, little memory footprint solr client for ruby}
|
|
9
|
+
gem.homepage = "https://github.com/freewheel/solr-client"
|
|
10
|
+
|
|
11
|
+
gem.files = `git ls-files`.split($\)
|
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
|
14
|
+
gem.name = "solr-client"
|
|
15
|
+
gem.require_paths = ["lib"]
|
|
16
|
+
gem.version = Solr::Client::VERSION
|
|
17
|
+
|
|
18
|
+
gem.add_dependency 'curb', '>= 0.8.1'
|
|
19
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: solr-client
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
prerelease:
|
|
6
|
+
platform: ruby
|
|
7
|
+
authors:
|
|
8
|
+
- zzzhc
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: bin
|
|
11
|
+
cert_chain: []
|
|
12
|
+
date: 2012-09-03 00:00:00.000000000 Z
|
|
13
|
+
dependencies:
|
|
14
|
+
- !ruby/object:Gem::Dependency
|
|
15
|
+
name: curb
|
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
|
17
|
+
none: false
|
|
18
|
+
requirements:
|
|
19
|
+
- - ! '>='
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: 0.8.1
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
none: false
|
|
26
|
+
requirements:
|
|
27
|
+
- - ! '>='
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: 0.8.1
|
|
30
|
+
description: fast solr client for ruby
|
|
31
|
+
email:
|
|
32
|
+
- zzzhc.starfire@gmail.com
|
|
33
|
+
executables: []
|
|
34
|
+
extensions: []
|
|
35
|
+
extra_rdoc_files: []
|
|
36
|
+
files:
|
|
37
|
+
- .gitignore
|
|
38
|
+
- Gemfile
|
|
39
|
+
- LICENSE
|
|
40
|
+
- README.md
|
|
41
|
+
- Rakefile
|
|
42
|
+
- lib/solr-client.rb
|
|
43
|
+
- solr-client.gemspec
|
|
44
|
+
homepage: https://github.com/freewheel/solr-client
|
|
45
|
+
licenses: []
|
|
46
|
+
post_install_message:
|
|
47
|
+
rdoc_options: []
|
|
48
|
+
require_paths:
|
|
49
|
+
- lib
|
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
51
|
+
none: false
|
|
52
|
+
requirements:
|
|
53
|
+
- - ! '>='
|
|
54
|
+
- !ruby/object:Gem::Version
|
|
55
|
+
version: '0'
|
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
57
|
+
none: false
|
|
58
|
+
requirements:
|
|
59
|
+
- - ! '>='
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
requirements: []
|
|
63
|
+
rubyforge_project:
|
|
64
|
+
rubygems_version: 1.8.24
|
|
65
|
+
signing_key:
|
|
66
|
+
specification_version: 3
|
|
67
|
+
summary: a high performance, little memory footprint solr client for ruby
|
|
68
|
+
test_files: []
|
|
69
|
+
has_rdoc:
|