mwmitchell-solr 0.5.3 → 0.5.4
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/CHANGES.txt +28 -0
- data/README.rdoc +50 -13
- data/Rakefile +1 -1
- data/examples/direct.rb +6 -4
- data/examples/http.rb +7 -2
- data/lib/solr/{adapter → connection/adapter}/common_methods.rb +1 -44
- data/lib/solr/{adapter → connection/adapter}/direct.rb +28 -13
- data/lib/solr/connection/adapter/http.rb +51 -0
- data/lib/solr/connection/adapter.rb +7 -0
- data/lib/solr/connection/search_ext.rb +28 -24
- data/lib/solr/connection.rb +1 -1
- data/lib/solr/http_client/adapter/curb.rb +51 -0
- data/lib/solr/http_client/adapter/net_http.rb +48 -0
- data/lib/solr/http_client/adapter.rb +6 -0
- data/lib/solr/http_client.rb +115 -0
- data/lib/solr/mapper/rss.rb +2 -0
- data/lib/solr/message.rb +5 -1
- data/lib/solr/response/base.rb +32 -0
- data/lib/solr/response/index_info.rb +22 -0
- data/lib/solr/response/query.rb +93 -0
- data/lib/solr/response/update.rb +4 -0
- data/lib/solr.rb +3 -4
- data/test/{direct_test.rb → connection/direct_test.rb} +4 -4
- data/test/connection/http_test.rb +19 -0
- data/test/{connection_test_methods.rb → connection/test_methods.rb} +14 -4
- data/test/http_client/curb_test.rb +19 -0
- data/test/http_client/net_http_test.rb +13 -0
- data/test/http_client/test_methods.rb +40 -0
- data/test/{adapter_common_methods_test.rb → http_client/util_test.rb} +3 -12
- data/test/message_test.rb +10 -0
- data/test/{ext_pagination_test.rb → pagination_test.rb} +8 -8
- data/test/test_helpers.rb +8 -0
- metadata +29 -17
- data/lib/solr/adapter/http.rb +0 -55
- data/lib/solr/adapter.rb +0 -7
- data/test/ext_search_test.rb +0 -9
- data/test/http_test.rb +0 -15
- data/test/indexer_test.rb +0 -14
data/lib/solr/connection.rb
CHANGED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'curb'
|
|
3
|
+
|
|
4
|
+
class Solr::HTTPClient::Adapter::Curb
|
|
5
|
+
|
|
6
|
+
include Solr::HTTPClient::Util
|
|
7
|
+
|
|
8
|
+
attr :uri
|
|
9
|
+
attr :c
|
|
10
|
+
|
|
11
|
+
def initialize(url)
|
|
12
|
+
@uri = URI.parse(url)
|
|
13
|
+
@c = ::Curl::Easy.new
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def get(path, params={})
|
|
17
|
+
@c.url = _build_url(path, params)
|
|
18
|
+
@c.multipart_form_post = false
|
|
19
|
+
@c.perform
|
|
20
|
+
create_http_context(path, params)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def post(path, data, params={}, headers={})
|
|
24
|
+
@c.url = _build_url(path, params)
|
|
25
|
+
@c.headers = headers
|
|
26
|
+
@c.http_post(data)
|
|
27
|
+
create_http_context(path, params, data, headers)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
protected
|
|
31
|
+
|
|
32
|
+
def create_http_context(path, params, data=nil, headers={})
|
|
33
|
+
{
|
|
34
|
+
:status_code=>@c.response_code.to_i,
|
|
35
|
+
:url=>@c.url,
|
|
36
|
+
:body=>@c.body_str,
|
|
37
|
+
:path=>path,
|
|
38
|
+
:params=>params,
|
|
39
|
+
:data=>data,
|
|
40
|
+
:headers=>headers
|
|
41
|
+
}
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def _build_url(path, params={})
|
|
45
|
+
url = @uri.scheme + '://' + @uri.host
|
|
46
|
+
url += ':' + @uri.port.to_s if @uri.port
|
|
47
|
+
url += @uri.path + path
|
|
48
|
+
build_url(url, params, @uri.query)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
require 'net/http'
|
|
2
|
+
|
|
3
|
+
class Solr::HTTPClient::Adapter::NetHTTP
|
|
4
|
+
|
|
5
|
+
include Solr::HTTPClient::Util
|
|
6
|
+
|
|
7
|
+
attr :uri
|
|
8
|
+
attr :c
|
|
9
|
+
|
|
10
|
+
def initialize(url)
|
|
11
|
+
@uri = URI.parse(url)
|
|
12
|
+
@c = Net::HTTP.new(@uri.host, @uri.port)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def get(path, params={})
|
|
16
|
+
url = _build_url(path, params)
|
|
17
|
+
net_http_response = @c.get(url)
|
|
18
|
+
create_http_context(net_http_response, url, path, params)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def post(path, data, params={}, headers={})
|
|
22
|
+
url = _build_url(path, params)
|
|
23
|
+
net_http_response = @c.post(url, data, headers)
|
|
24
|
+
create_http_context(net_http_response, url, path, params, data, headers)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
protected
|
|
28
|
+
|
|
29
|
+
def create_http_context(net_http_response, url, path, params, data=nil, headers={})
|
|
30
|
+
full_url = "#{@uri.scheme}://#{@uri.host}"
|
|
31
|
+
full_url += @uri.port ? ":#{@uri.port}" : ''
|
|
32
|
+
full_url += url
|
|
33
|
+
{
|
|
34
|
+
:status_code=>net_http_response.code.to_i,
|
|
35
|
+
:body=>net_http_response.body,
|
|
36
|
+
:url=>full_url,
|
|
37
|
+
:path=>path,
|
|
38
|
+
:params=>params,
|
|
39
|
+
:data=>data,
|
|
40
|
+
:headers=>headers
|
|
41
|
+
}
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def _build_url(path, params={})
|
|
45
|
+
build_url(@uri.path + path, params, @uri.query)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
require 'uri'
|
|
2
|
+
|
|
3
|
+
# A simple wrapper for different http client implementations
|
|
4
|
+
# that supports #get and #post
|
|
5
|
+
# This was motivated by: http://apocryph.org/2008/11/09/more_indepth_analysis_ruby_http_client_performance/
|
|
6
|
+
# Net::HTTP is the default adapter
|
|
7
|
+
|
|
8
|
+
# Each adapter response should be a hash with the following keys:
|
|
9
|
+
# :status_code
|
|
10
|
+
# :url
|
|
11
|
+
# :body
|
|
12
|
+
# :path
|
|
13
|
+
# :params
|
|
14
|
+
# :data
|
|
15
|
+
# :headers
|
|
16
|
+
|
|
17
|
+
# Example:
|
|
18
|
+
#hclient = Solr::HTTPClient.connect('http://www.google.com', :net_http)
|
|
19
|
+
#response = hclient.get('/search', :hl=>:en, :q=>:ruby, :btnG=>:Search)
|
|
20
|
+
#puts response[:status_code]
|
|
21
|
+
#puts response[:body]
|
|
22
|
+
|
|
23
|
+
module Solr::HTTPClient
|
|
24
|
+
|
|
25
|
+
autoload :Adapter, 'solr/http_client/adapter'
|
|
26
|
+
|
|
27
|
+
class UnkownAdapterError < RuntimeError; end
|
|
28
|
+
|
|
29
|
+
def self.connect(url, adapter_name=:net_http)
|
|
30
|
+
case adapter_name
|
|
31
|
+
when :curb
|
|
32
|
+
klass = 'Curb'
|
|
33
|
+
when :net_http
|
|
34
|
+
klass = 'NetHTTP'
|
|
35
|
+
else
|
|
36
|
+
raise UnkownAdapterError.new("Name: #{adapter_name}")
|
|
37
|
+
end
|
|
38
|
+
Base.new Solr::HTTPClient::Adapter.const_get(klass).new(url)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
class Base
|
|
42
|
+
|
|
43
|
+
attr_reader :adapter
|
|
44
|
+
|
|
45
|
+
def initialize(adapter)
|
|
46
|
+
@adapter = adapter
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def get(path, params={})
|
|
50
|
+
begin
|
|
51
|
+
http_context = @adapter.get(path, params)
|
|
52
|
+
rescue
|
|
53
|
+
raise Solr::RequestError.new($!)
|
|
54
|
+
end
|
|
55
|
+
http_context
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def post(path, data, params={}, headers={})
|
|
59
|
+
begin
|
|
60
|
+
http_context = @adapter.post(path, data, params, headers)
|
|
61
|
+
rescue
|
|
62
|
+
raise Solr::RequestError.new($!)
|
|
63
|
+
end
|
|
64
|
+
http_context
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
module Util
|
|
70
|
+
|
|
71
|
+
# escapes a query key/value for http
|
|
72
|
+
def escape(s)
|
|
73
|
+
s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
|
|
74
|
+
'%'+$1.unpack('H2'*$1.size).join('%').upcase
|
|
75
|
+
}.tr(' ', '+')
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def build_url(url='', params={}, string_query='')
|
|
79
|
+
queries = [string_query, hash_to_params(params)]
|
|
80
|
+
queries.delete_if{|i| i.to_s.empty?}
|
|
81
|
+
url += "?#{queries.join('&')}" unless queries.empty?
|
|
82
|
+
url
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def build_param(k,v)
|
|
86
|
+
"#{escape(k)}=#{escape(v)}"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
#
|
|
90
|
+
# converts hash into URL query string, keys get an alpha sort
|
|
91
|
+
# if a value is an array, the array values get mapped to the same key:
|
|
92
|
+
# hash_to_params(:q=>'blah', 'facet.field'=>['location_facet', 'format_facet'])
|
|
93
|
+
# returns:
|
|
94
|
+
# ?q=blah&facet.field=location_facet&facet.field=format.facet
|
|
95
|
+
#
|
|
96
|
+
# if a value is empty/nil etc., the key is not added
|
|
97
|
+
def hash_to_params(params)
|
|
98
|
+
return unless params.is_a?(Hash)
|
|
99
|
+
# copy params and convert keys to strings
|
|
100
|
+
params = params.inject({}){|acc,(k,v)| acc.merge({k.to_s, v}) }
|
|
101
|
+
# get sorted keys
|
|
102
|
+
params.keys.sort.inject([]) do |acc,k|
|
|
103
|
+
v = params[k]
|
|
104
|
+
if v.is_a?(Array)
|
|
105
|
+
acc << v.reject{|i|i.to_s.empty?}.collect{|vv|build_param(k, vv)}
|
|
106
|
+
elsif ! v.to_s.empty?
|
|
107
|
+
acc.push(build_param(k, v))
|
|
108
|
+
end
|
|
109
|
+
acc
|
|
110
|
+
end.join('&')
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
end
|
data/lib/solr/mapper/rss.rb
CHANGED
data/lib/solr/message.rb
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
require 'rubygems'
|
|
4
4
|
require 'builder'
|
|
5
5
|
|
|
6
|
+
# The Solr::Message class is the XML generation module for sending updates to Solr.
|
|
7
|
+
|
|
6
8
|
class Solr::Message
|
|
7
9
|
|
|
8
10
|
class << self
|
|
@@ -27,7 +29,9 @@ class Solr::Message
|
|
|
27
29
|
sorted_items.keys.sort.each do |k|
|
|
28
30
|
doc_attrs = {:name=>k}
|
|
29
31
|
yield doc_attrs if block_given?
|
|
30
|
-
|
|
32
|
+
[sorted_items[k]].flatten.each do |v| # multiValued attributes
|
|
33
|
+
doc_xml.field(v, doc_attrs)
|
|
34
|
+
end
|
|
31
35
|
end
|
|
32
36
|
end
|
|
33
37
|
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# default/base response object
|
|
2
|
+
# This is where the ruby "eval" happens
|
|
3
|
+
# So far, all response classes extend this
|
|
4
|
+
class Solr::Response::Base
|
|
5
|
+
|
|
6
|
+
attr_reader :source
|
|
7
|
+
|
|
8
|
+
attr_reader :raw_response, :data, :header, :params, :status, :query_time
|
|
9
|
+
|
|
10
|
+
def initialize(data)
|
|
11
|
+
if data.is_a?(Hash) and data.has_key?(:body)
|
|
12
|
+
@data = Kernel.eval(data[:body])
|
|
13
|
+
@source = data
|
|
14
|
+
else
|
|
15
|
+
if data.is_a?(String)
|
|
16
|
+
@raw_response = data
|
|
17
|
+
@data = Kernel.eval(@raw_response)
|
|
18
|
+
elsif data.is_a?(Hash)
|
|
19
|
+
@data = data
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
@header = @data['responseHeader']
|
|
23
|
+
@params = @header['params']
|
|
24
|
+
@status = @header['status']
|
|
25
|
+
@query_time = @header['QTime']
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def ok?
|
|
29
|
+
self.status==0
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# response for /admin/luke
|
|
2
|
+
class Solr::Response::IndexInfo < Solr::Response::Base
|
|
3
|
+
|
|
4
|
+
attr_reader :index, :directory, :has_deletions, :optimized, :current, :max_doc, :num_docs, :version
|
|
5
|
+
|
|
6
|
+
alias :has_deletions? :has_deletions
|
|
7
|
+
alias :optimized? :optimized
|
|
8
|
+
alias :current? :current
|
|
9
|
+
|
|
10
|
+
def initialize(data)
|
|
11
|
+
super(data)
|
|
12
|
+
@index = @data['index']
|
|
13
|
+
@directory = @data['directory']
|
|
14
|
+
@has_deletions = @index['hasDeletions']
|
|
15
|
+
@optimized = @index['optimized']
|
|
16
|
+
@current = @index['current']
|
|
17
|
+
@max_doc = @index['maxDoc']
|
|
18
|
+
@num_docs = @index['numDocs']
|
|
19
|
+
@version = @index['version']
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# response module for queries
|
|
2
|
+
module Solr::Response::Query
|
|
3
|
+
|
|
4
|
+
# module for adding some helper methods for each document
|
|
5
|
+
module DocExt
|
|
6
|
+
|
|
7
|
+
# Provide "method accessors" to the data.
|
|
8
|
+
# This might be better implemented using instance_eval
|
|
9
|
+
# to create the methods?
|
|
10
|
+
def method_missing(k, *args)
|
|
11
|
+
has_key?(k) ? self[k] : super(k, *args)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Helper method to check if value/values exist for a given key.
|
|
15
|
+
# The value can be a string, or a RegExp
|
|
16
|
+
# Example:
|
|
17
|
+
# doc.has?(:location_facet, 'Clemons')
|
|
18
|
+
# doc.has?(:id, 'h009', /^u/i)
|
|
19
|
+
def has?(k, *values)
|
|
20
|
+
return if self[k].nil?
|
|
21
|
+
target = self[k]
|
|
22
|
+
if target.is_a?(Array)
|
|
23
|
+
values.each do |val|
|
|
24
|
+
return target.any?{|tv| val.is_a?(Regexp) ? (tv =~ val) : (tv==val)}
|
|
25
|
+
end
|
|
26
|
+
else
|
|
27
|
+
return values.any? {|val| val.is_a?(Regexp) ? (target =~ val) : (target == val)}
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
module Pagination
|
|
34
|
+
|
|
35
|
+
# alias to the Solr param, 'rows'
|
|
36
|
+
def per_page
|
|
37
|
+
@per_page = params['rows'].to_s.to_i
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Returns the current page calculated from 'rows' and 'start'
|
|
41
|
+
# WillPaginate hook
|
|
42
|
+
def current_page
|
|
43
|
+
@current_page = self.per_page > 0 ? ((self.start / self.per_page).ceil) : 1
|
|
44
|
+
@current_page == 0 ? 1 : @current_page
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Calcuates the total pages from 'numFound' and 'rows'
|
|
48
|
+
# WillPaginate hook
|
|
49
|
+
def total_pages
|
|
50
|
+
self.per_page > 0 ? (self.total / self.per_page.to_f).ceil : 1
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# returns the previous page number or 1
|
|
54
|
+
# WillPaginate hook
|
|
55
|
+
def previous_page
|
|
56
|
+
(current_page > 1) ? current_page - 1 : 1
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# returns the next page number or the last
|
|
60
|
+
# WillPaginate hook
|
|
61
|
+
def next_page
|
|
62
|
+
(current_page < total_pages) ? current_page + 1 : total_pages
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# The base query response class
|
|
68
|
+
# adds to the Solr::Response::Base class by defining a few more attributes,
|
|
69
|
+
# includes the Pagination module, and extends each of the doc hashes
|
|
70
|
+
# with Solr::Response::Query::DocExt
|
|
71
|
+
class Base < Solr::Response::Base
|
|
72
|
+
|
|
73
|
+
include Solr::Response::Query::Pagination
|
|
74
|
+
|
|
75
|
+
attr_reader :response, :docs, :num_found, :start
|
|
76
|
+
|
|
77
|
+
alias :total :num_found
|
|
78
|
+
alias :offset :start
|
|
79
|
+
|
|
80
|
+
def initialize(data)
|
|
81
|
+
super(data)
|
|
82
|
+
@response = @data['response']
|
|
83
|
+
@docs = @response['docs']
|
|
84
|
+
@docs.each do |d|
|
|
85
|
+
d.extend Solr::Response::Query::DocExt
|
|
86
|
+
end
|
|
87
|
+
@num_found = @response['numFound']
|
|
88
|
+
@start = @response['start']
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
end
|
data/lib/solr.rb
CHANGED
|
@@ -7,15 +7,15 @@ proc {|base, files|
|
|
|
7
7
|
|
|
8
8
|
module Solr
|
|
9
9
|
|
|
10
|
-
VERSION = '0.5.
|
|
10
|
+
VERSION = '0.5.4'
|
|
11
11
|
|
|
12
|
-
autoload :Adapter, 'solr/adapter'
|
|
13
12
|
autoload :Message, 'solr/message'
|
|
14
13
|
autoload :Response, 'solr/response'
|
|
15
14
|
autoload :Connection, 'solr/connection'
|
|
16
15
|
autoload :Ext, 'solr/ext'
|
|
17
16
|
autoload :Mapper, 'solr/mapper'
|
|
18
17
|
autoload :Indexer, 'solr/indexer'
|
|
18
|
+
autoload :HTTPClient, 'solr/http_client'
|
|
19
19
|
|
|
20
20
|
# factory for creating connections
|
|
21
21
|
# adapter name is either :http or :direct
|
|
@@ -26,8 +26,7 @@ module Solr
|
|
|
26
26
|
:http=>'HTTP',
|
|
27
27
|
:direct=>'Direct'
|
|
28
28
|
}
|
|
29
|
-
|
|
30
|
-
adapter_class = Kernel.eval adapter_class_name
|
|
29
|
+
adapter_class = Solr::Connection::Adapter.const_get(types[adapter_name])
|
|
31
30
|
Solr::Connection::Base.new(adapter_class.new(opts), opts)
|
|
32
31
|
end
|
|
33
32
|
|