mwmitchell-rsolr 0.9.1 → 0.9.5
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 +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
|