mwmitchell-rsolr 0.9.1 → 0.9.5
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES.txt +20 -0
- data/LICENSE +1 -55
- data/README.rdoc +61 -61
- data/Rakefile +17 -17
- data/examples/direct.rb +3 -1
- data/examples/http.rb +8 -6
- data/lib/rsolr/{adapter → connection/adapter}/direct.rb +3 -2
- data/lib/rsolr/{adapter → connection/adapter}/http.rb +5 -6
- data/lib/rsolr/connection.rb +103 -102
- data/lib/rsolr/http_client/adapter/curb.rb +2 -2
- data/lib/rsolr/http_client/adapter/net_http.rb +2 -2
- data/lib/rsolr/http_client.rb +55 -69
- data/lib/rsolr/message/{builders → adapter}/builder.rb +1 -1
- data/lib/rsolr/message/{builders → adapter}/libxml.rb +1 -1
- data/lib/rsolr/message.rb +20 -13
- data/lib/rsolr.rb +30 -23
- data/rsolr.gemspec +8 -9
- data/test/connection/direct_test.rb +3 -3
- data/test/connection/http_test.rb +1 -1
- data/test/connection/test_methods.rb +1 -1
- data/test/http_client/curb_test.rb +1 -1
- data/test/http_client/net_http_test.rb +1 -1
- data/test/http_client/test_methods.rb +1 -1
- data/test/http_client/util_test.rb +51 -8
- data/test/message_test.rb +18 -15
- metadata +8 -10
- data/lib/rsolr/adapter.rb +0 -6
- data/lib/rsolr/http_client/adapter.rb +0 -6
- data/lib/rsolr/message/builders.rb +0 -6
data/lib/rsolr/http_client.rb
CHANGED
@@ -12,9 +12,10 @@
|
|
12
12
|
# :headers
|
13
13
|
|
14
14
|
# Example:
|
15
|
-
#
|
16
|
-
#
|
17
|
-
# hclient =
|
15
|
+
# hclient = RSolr::HTTPClient.connect('http://www.google.com')
|
16
|
+
# # SAME AS
|
17
|
+
# hclient = RSolr::HTTPClient.connect(:net_http, 'http://www.google.com')
|
18
|
+
# hclient = RSolr::HTTPClient.connect(:curb, 'http://www.google.com')
|
18
19
|
# response = hclient.get('/search', :hl=>:en, :q=>:ruby, :btnG=>:Search)
|
19
20
|
# puts response[:status_code]
|
20
21
|
# puts response[:body]
|
@@ -23,53 +24,23 @@ require 'uri'
|
|
23
24
|
|
24
25
|
module RSolr::HTTPClient
|
25
26
|
|
26
|
-
|
27
|
-
|
28
|
-
|
27
|
+
module Adapter
|
28
|
+
autoload :Curb, 'rsolr/http_client/adapter/curb'
|
29
|
+
autoload :NetHTTP, 'rsolr/http_client/adapter/net_http'
|
30
|
+
end
|
29
31
|
|
30
|
-
class
|
31
|
-
|
32
|
-
attr_accessor :adapter_name
|
33
|
-
|
34
|
-
def initialize(adapter_name = :curb)
|
35
|
-
@adapter_name = adapter_name
|
36
|
-
end
|
37
|
-
|
38
|
-
# Creates and returns an instance of RSolr::HTTPClient::Adapter::*
|
39
|
-
# The "url" is a full/valid url.
|
40
|
-
# Example:
|
41
|
-
# connector = RSolr::HTTPClient::Connector.new
|
42
|
-
# client = connector.connect('http://google.com')
|
43
|
-
#
|
44
|
-
# TODO: this should be less verbose... something like RSolr:HTTPClient.connect(url, adapter=:curb)
|
45
|
-
def connect(url)
|
46
|
-
case adapter_name
|
47
|
-
when :curb
|
48
|
-
klass = 'Curb'
|
49
|
-
when :net_http
|
50
|
-
klass = 'NetHTTP'
|
51
|
-
else
|
52
|
-
raise UnkownAdapterError.new("Name: #{adapter_name}")
|
53
|
-
end
|
54
|
-
begin
|
55
|
-
RSolr::HTTPClient::Base.new RSolr::HTTPClient::Adapter.const_get(klass).new(url)
|
56
|
-
rescue ::URI::InvalidURIError
|
57
|
-
raise "#{$!} == #{url}"
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
32
|
+
class UnkownAdapterError < RuntimeError
|
61
33
|
end
|
62
34
|
|
63
|
-
# The base class for interacting with one of the HTTP client adapters
|
64
35
|
class Base
|
65
|
-
|
36
|
+
|
66
37
|
attr_reader :adapter
|
67
|
-
|
38
|
+
|
68
39
|
# requires an instace of RSolr::HTTPClient::*
|
69
40
|
def initialize(adapter)
|
70
41
|
@adapter = adapter
|
71
42
|
end
|
72
|
-
|
43
|
+
|
73
44
|
# sends a GET reqest to the "path" variable
|
74
45
|
# an optional hash of "params" can be used,
|
75
46
|
# which is later transformed into a GET query string
|
@@ -81,7 +52,7 @@ module RSolr::HTTPClient
|
|
81
52
|
end
|
82
53
|
http_context
|
83
54
|
end
|
84
|
-
|
55
|
+
|
85
56
|
# sends a POST request to the "path" variable
|
86
57
|
# "data" is required, and must be a string
|
87
58
|
# "params" is an optional hash for query string params...
|
@@ -97,13 +68,43 @@ module RSolr::HTTPClient
|
|
97
68
|
|
98
69
|
end
|
99
70
|
|
71
|
+
# Factory for creating connections.
|
72
|
+
# Can specify the connection type by
|
73
|
+
# using :net_http or :curb for the first argument.
|
74
|
+
# The ending arguments are always used for the connection adapter instance.
|
75
|
+
#
|
76
|
+
# Examples:
|
77
|
+
# # default net_http connection
|
78
|
+
# RSolr::HTTPClient.connect :url=>''
|
79
|
+
# # SAME AS
|
80
|
+
# RSolr::HTTPClient.connect :net_http, :url=>''
|
81
|
+
# # curb connection
|
82
|
+
# RSolr.connect :curb, :url=>''
|
83
|
+
def self.connect(*args)
|
84
|
+
type = args.first.is_a?(Symbol) ? args.shift : :net_http
|
85
|
+
opts = args
|
86
|
+
klass = case type
|
87
|
+
when :net_http,nil
|
88
|
+
'NetHTTP'
|
89
|
+
when :curb
|
90
|
+
'Curb'
|
91
|
+
else
|
92
|
+
raise UnkownAdapterError.new("Invalid adapter type: #{type} - use :curb or :net_http or blank for :net_http/default")
|
93
|
+
end
|
94
|
+
begin
|
95
|
+
Base.new Adapter.const_get(klass).new(*args)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
100
99
|
module Util
|
101
100
|
|
102
|
-
#
|
101
|
+
# Performs URI escaping so that you can construct proper
|
102
|
+
# query strings faster. Use this rather than the cgi.rb
|
103
|
+
# version since it's faster. (Stolen from Rack).
|
103
104
|
def escape(s)
|
104
105
|
s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
|
105
106
|
'%'+$1.unpack('H2'*$1.size).join('%').upcase
|
106
|
-
}.tr(' ', '+')
|
107
|
+
}.tr(' ', '+')
|
107
108
|
end
|
108
109
|
|
109
110
|
# creates and returns a url as a string
|
@@ -112,7 +113,7 @@ module RSolr::HTTPClient
|
|
112
113
|
# "string_query" is an extra query string that will be appended to the
|
113
114
|
# result of "url" and "params".
|
114
115
|
def build_url(url='', params={}, string_query='')
|
115
|
-
queries = [string_query,
|
116
|
+
queries = [string_query, hash_to_query(params)]
|
116
117
|
queries.delete_if{|i| i.to_s.empty?}
|
117
118
|
url += "?#{queries.join('&')}" unless queries.empty?
|
118
119
|
url
|
@@ -124,38 +125,23 @@ module RSolr::HTTPClient
|
|
124
125
|
def build_param(k,v)
|
125
126
|
"#{escape(k)}=#{escape(v)}"
|
126
127
|
end
|
127
|
-
|
128
|
+
|
128
129
|
#
|
129
130
|
# converts hash into URL query string, keys get an alpha sort
|
130
131
|
# if a value is an array, the array values get mapped to the same key:
|
131
|
-
#
|
132
|
+
# hash_to_query(:q=>'blah', :fq=>['blah', 'blah'], :facet=>{:field=>['location_facet', 'format_facet']})
|
132
133
|
# returns:
|
133
134
|
# ?q=blah&fq=blah&fq=blah&facet.field=location_facet&facet.field=format.facet
|
134
135
|
#
|
135
136
|
# if a value is empty/nil etc., the key is not added
|
136
|
-
def
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
v = params[k]
|
143
|
-
if v.is_a?(Array)
|
144
|
-
acc << v.reject{|i|i.to_s.empty?}.collect{|vv|build_param(k, vv)}
|
145
|
-
elsif v.is_a?(Hash)
|
146
|
-
# NOT USED
|
147
|
-
# creates dot based params like:
|
148
|
-
# hash_to_params(:facet=>{:field=>['one', 'two']}) == facet.field=one&facet.field=two
|
149
|
-
# TODO: should this go into a non-solr based param builder?
|
150
|
-
# - dotted syntax is special to solr only
|
151
|
-
#v.each_pair do |field,field_value|
|
152
|
-
# acc.push(hash_to_params({"#{k}.#{field}"=>field_value}))
|
153
|
-
#end
|
154
|
-
elsif ! v.to_s.empty?
|
155
|
-
acc.push(build_param(k, v))
|
137
|
+
def hash_to_query(params)
|
138
|
+
params.map { |k, v|
|
139
|
+
if v.class == Array
|
140
|
+
hash_to_query(v.map { |x| [k, x] })
|
141
|
+
else
|
142
|
+
build_param k, v
|
156
143
|
end
|
157
|
-
|
158
|
-
end.join('&')
|
144
|
+
}.join("&")
|
159
145
|
end
|
160
146
|
|
161
147
|
end
|
data/lib/rsolr/message.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
1
|
# The Solr::Message class is the XML generation module for sending updates to Solr.
|
2
2
|
|
3
|
-
|
3
|
+
module RSolr::Message
|
4
4
|
|
5
|
-
|
5
|
+
module Adapter
|
6
|
+
autoload :Builder, 'rsolr/message/adapter/builder'
|
7
|
+
autoload :Libxml, 'rsolr/message/adapter/libxml'
|
8
|
+
end
|
6
9
|
|
7
10
|
# A class that represents a "doc" xml element for a solr update
|
8
11
|
class Document
|
@@ -74,11 +77,15 @@ class RSolr::Message
|
|
74
77
|
|
75
78
|
end
|
76
79
|
|
77
|
-
class
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
80
|
+
class Builder
|
81
|
+
|
82
|
+
attr_writer :adapter
|
83
|
+
|
84
|
+
# b = Builder.new
|
85
|
+
# b.adapter = RSolr::Message::Adapter::LibXML.new
|
86
|
+
# b.optimize == '<optimize/>'
|
87
|
+
def adapter
|
88
|
+
@adapter ||= RSolr::Message::Adapter::Builder.new
|
82
89
|
end
|
83
90
|
|
84
91
|
# generates "add" xml for updating solr
|
@@ -113,34 +120,34 @@ class RSolr::Message
|
|
113
120
|
yield doc if block_given?
|
114
121
|
doc
|
115
122
|
end
|
116
|
-
|
123
|
+
adapter.add(documents, add_attrs)
|
117
124
|
end
|
118
125
|
|
119
126
|
# generates a <commit/> message
|
120
127
|
def commit(opts={})
|
121
|
-
|
128
|
+
adapter.commit(opts)
|
122
129
|
end
|
123
130
|
|
124
131
|
# generates a <optimize/> message
|
125
132
|
def optimize(opts={})
|
126
|
-
|
133
|
+
adapter.optimize(opts)
|
127
134
|
end
|
128
135
|
|
129
136
|
# generates a <rollback/> message
|
130
137
|
def rollback
|
131
|
-
|
138
|
+
adapter.rollback
|
132
139
|
end
|
133
140
|
|
134
141
|
# generates a <delete><id>ID</id></delete> message
|
135
142
|
# "ids" can be a single value or array of values
|
136
143
|
def delete_by_id(ids)
|
137
|
-
|
144
|
+
adapter.delete_by_id(ids)
|
138
145
|
end
|
139
146
|
|
140
147
|
# generates a <delete><query>ID</query></delete> message
|
141
148
|
# "queries" can be a single value or an array of values
|
142
149
|
def delete_by_query(queries)
|
143
|
-
|
150
|
+
adapter.delete_by_query(queries)
|
144
151
|
end
|
145
152
|
end
|
146
153
|
end
|
data/lib/rsolr.rb
CHANGED
@@ -1,36 +1,43 @@
|
|
1
1
|
# add this directory to the load path if it hasn't already been added
|
2
2
|
|
3
|
-
|
3
|
+
require 'rubygems'
|
4
4
|
|
5
|
-
unless
|
6
|
-
class Array
|
7
|
-
def extract_options!
|
8
|
-
last.is_a?(::Hash) ? pop : {}
|
9
|
-
end
|
10
|
-
end
|
11
|
-
end
|
5
|
+
$: << File.dirname(__FILE__) unless $:.include?(File.dirname(__FILE__))
|
12
6
|
|
13
7
|
module RSolr
|
14
8
|
|
15
|
-
VERSION = '0.
|
9
|
+
VERSION = '0.9.5'
|
16
10
|
|
17
11
|
autoload :Message, 'rsolr/message'
|
18
12
|
autoload :Connection, 'rsolr/connection'
|
19
|
-
autoload :Adapter, 'rsolr/adapter'
|
20
13
|
autoload :HTTPClient, 'rsolr/http_client'
|
21
14
|
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
15
|
+
# Factory for creating connections.
|
16
|
+
# 2 modes of argument operations:
|
17
|
+
# 1. first argument is solr-adapter type, second arg is options hash for solr-adapter instance.
|
18
|
+
# 2. options hash for solr-adapter only (no adapter type as first arg)
|
19
|
+
#
|
20
|
+
# Examples:
|
21
|
+
# # default http connection
|
22
|
+
# RSolr.connect
|
23
|
+
# # http connection with custom url
|
24
|
+
# RSolr.connect :url=>'http://solr.web100.org'
|
25
|
+
# # direct connection
|
26
|
+
# RSolr.connect :direct, :home_dir=>'solr', :dist_dir=>'solr-nightly'
|
27
|
+
def self.connect(*args)
|
28
|
+
type = args.first.is_a?(Symbol) ? args.shift : :http
|
29
|
+
opts = args
|
30
|
+
type_class = case type
|
31
|
+
when :http,nil
|
32
|
+
'HTTP'
|
33
|
+
when :direct
|
34
|
+
'Direct'
|
35
|
+
else
|
36
|
+
raise "Invalid connection type: #{type} - use :http, :direct or leave nil for :http/default"
|
37
|
+
end
|
38
|
+
adapter_class = RSolr::Connection::Adapter.const_get(type_class)
|
39
|
+
adapter = adapter_class.new(*opts)
|
40
|
+
RSolr::Connection::Base.new(adapter)
|
34
41
|
end
|
35
42
|
|
36
43
|
# A module that contains string related methods
|
@@ -48,7 +55,7 @@ module RSolr
|
|
48
55
|
# send the escape method into the Connection class ->
|
49
56
|
# solr = RSolr.connect
|
50
57
|
# solr.escape('asdf')
|
51
|
-
RSolr::Connection.send(:include, Char)
|
58
|
+
RSolr::Connection::Base.send(:include, Char)
|
52
59
|
|
53
60
|
# bring escape into this module (RSolr) -> RSolr.escape('asdf')
|
54
61
|
extend Char
|
data/rsolr.gemspec
CHANGED
@@ -1,28 +1,27 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
|
+
|
2
3
|
s.name = "rsolr"
|
3
|
-
s.version = "0.9.
|
4
|
-
s.date = "2009-
|
4
|
+
s.version = "0.9.5"
|
5
|
+
s.date = "2009-09-05"
|
5
6
|
s.summary = "A Ruby client for Apache Solr"
|
6
7
|
s.email = "goodieboy@gmail.com"
|
7
8
|
s.homepage = "http://github.com/mwmitchell/rsolr"
|
8
9
|
s.description = "RSolr is a Ruby gem for working with Apache Solr!"
|
9
10
|
s.has_rdoc = true
|
10
11
|
s.authors = ["Matt Mitchell"]
|
12
|
+
|
11
13
|
s.files = [
|
12
14
|
"examples/http.rb",
|
13
15
|
"examples/direct.rb",
|
14
16
|
"lib/rsolr.rb",
|
15
|
-
"lib/rsolr/adapter/direct.rb",
|
16
|
-
"lib/rsolr/adapter/http.rb",
|
17
|
-
"lib/rsolr/adapter.rb",
|
17
|
+
"lib/rsolr/connection/adapter/direct.rb",
|
18
|
+
"lib/rsolr/connection/adapter/http.rb",
|
18
19
|
"lib/rsolr/connection.rb",
|
19
20
|
"lib/rsolr/http_client/adapter/curb.rb",
|
20
21
|
"lib/rsolr/http_client/adapter/net_http.rb",
|
21
|
-
"lib/rsolr/http_client/adapter.rb",
|
22
22
|
"lib/rsolr/http_client.rb",
|
23
|
-
"lib/rsolr/message/
|
24
|
-
"lib/rsolr/message/
|
25
|
-
"lib/rsolr/message/builders.rb",
|
23
|
+
"lib/rsolr/message/adapter/builder.rb",
|
24
|
+
"lib/rsolr/message/adapter/libxml.rb",
|
26
25
|
"lib/rsolr/message.rb",
|
27
26
|
"LICENSE",
|
28
27
|
"Rakefile",
|
@@ -14,7 +14,7 @@ if defined?(JRUBY_VERSION)
|
|
14
14
|
base = File.expand_path( File.dirname(__FILE__) )
|
15
15
|
@dist = File.join(base, '..', '..', 'apache-solr')
|
16
16
|
@home = File.join(dist, 'example', 'solr')
|
17
|
-
@solr = RSolr.connect(:
|
17
|
+
@solr = RSolr.connect(:direct, :home_dir=>@home, :dist_dir=>@dist)
|
18
18
|
@solr.delete_by_query('*:*')
|
19
19
|
@solr.commit
|
20
20
|
end
|
@@ -26,8 +26,8 @@ if defined?(JRUBY_VERSION)
|
|
26
26
|
def test_new_connection_with_existing_core
|
27
27
|
Dir["#{@dist}/dist/*.jar"].each { |p| require p }
|
28
28
|
dc = org.apache.solr.servlet.DirectSolrConnection.new(@home, "#{@home}/data", nil)
|
29
|
-
adapter = RSolr::Adapter::Direct.new dc
|
30
|
-
s = RSolr::Connection.new(adapter)
|
29
|
+
adapter = RSolr::Connection::Adapter::Direct.new dc
|
30
|
+
s = RSolr::Connection::Base.new(adapter)
|
31
31
|
assert_equal Hash, s.request('/admin/ping').class
|
32
32
|
adapter.close
|
33
33
|
end
|
@@ -86,7 +86,7 @@ module ConnectionTestMethods
|
|
86
86
|
end
|
87
87
|
|
88
88
|
def test_admin_luke_index_info
|
89
|
-
response = @solr.
|
89
|
+
response = @solr.request('/admin/luke', :numTerms=>0)
|
90
90
|
assert response.is_a?(Hash)
|
91
91
|
# make sure the ? methods are true/false
|
92
92
|
assert [true, false].include?(response['index']['current'])
|
@@ -11,11 +11,15 @@ class HTTPUtilTest < RSolrBaseTest
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def test_build_url
|
14
|
-
|
15
|
-
assert_equal '/something',
|
16
|
-
assert_equal '/something?
|
17
|
-
|
18
|
-
|
14
|
+
assert_equal '/something', @c.build_url('/something')
|
15
|
+
assert_equal '/something?q=Testing', @c.build_url('/something', :q=>'Testing')
|
16
|
+
assert_equal '/something?array=1&array=2&array=3', @c.build_url('/something', :array=>[1, 2, 3])
|
17
|
+
result = @c.build_url('/something', :q=>'A', :array=>[1, 2, 3])
|
18
|
+
assert result=~/^\/something\?/
|
19
|
+
assert result=~/q=A/
|
20
|
+
assert result=~/array=1/
|
21
|
+
assert result=~/array=2/
|
22
|
+
assert result=~/array=3/
|
19
23
|
end
|
20
24
|
|
21
25
|
def test_escape
|
@@ -26,15 +30,54 @@ class HTTPUtilTest < RSolrBaseTest
|
|
26
30
|
assert_equal '%3A', @c.escape(':')
|
27
31
|
end
|
28
32
|
|
29
|
-
def
|
33
|
+
def test_hash_to_query
|
30
34
|
my_params = {
|
31
|
-
:z=>'should be
|
35
|
+
:z=>'should be whatever',
|
32
36
|
:q=>'test',
|
33
37
|
:d=>[1, 2, 3, 4],
|
34
38
|
:b=>:zxcv,
|
35
39
|
:x=>['!', '*', nil]
|
36
40
|
}
|
37
|
-
|
41
|
+
result = @c.hash_to_query(my_params)
|
42
|
+
assert result=~/z=should\+be\+whatever/
|
43
|
+
assert result=~/q=test/
|
44
|
+
assert result=~/d=1/
|
45
|
+
assert result=~/d=2/
|
46
|
+
assert result=~/d=3/
|
47
|
+
assert result=~/d=4/
|
48
|
+
assert result=~/b=zxcv/
|
49
|
+
assert result=~/x=%21/
|
50
|
+
assert result=~/x=*/
|
51
|
+
assert result=~/x=&?/
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_ampersand_within_query_value
|
55
|
+
my_params = {
|
56
|
+
"fq" => "building_facet:\"Green (Humanities & Social Sciences)\""
|
57
|
+
}
|
58
|
+
expected = 'fq=building_facet%3A%22Green+%28Humanities+%26+Social+Sciences%29%22'
|
59
|
+
assert_equal expected, @c.hash_to_query(my_params)
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_brackets
|
63
|
+
assert_equal '%7B', @c.escape('{')
|
64
|
+
assert_equal '%7D', @c.escape('}')
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_exclamation
|
68
|
+
assert_equal '%21', @c.escape('!')
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_complex_solr_query1
|
72
|
+
my_params = {'fq' => '{!raw f=field_name}crazy+\"field+value'}
|
73
|
+
expected = 'fq=%7B%21raw+f%3Dfield_name%7Dcrazy%2B%5C%22field%2Bvalue'
|
74
|
+
assert_equal expected, @c.hash_to_query(my_params)
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_complex_solr_query2
|
78
|
+
my_params = {'q' => '+popularity:[10 TO *] +section:0'}
|
79
|
+
expected = 'q=%2Bpopularity%3A%5B10+TO+%2A%5D+%2Bsection%3A0'
|
80
|
+
assert_equal expected, @c.hash_to_query(my_params)
|
38
81
|
end
|
39
82
|
|
40
83
|
end
|