mdwan-rsolr 0.8.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES.txt +157 -0
- data/LICENSE +67 -0
- data/README.rdoc +126 -0
- data/Rakefile +39 -0
- data/examples/direct.rb +27 -0
- data/examples/http.rb +22 -0
- data/lib/core_ext.rb +25 -0
- data/lib/mash.rb +148 -0
- data/lib/rsolr.rb +52 -0
- data/lib/rsolr/adapter.rb +6 -0
- data/lib/rsolr/adapter/direct.rb +86 -0
- data/lib/rsolr/adapter/http.rb +44 -0
- data/lib/rsolr/connection.rb +107 -0
- data/lib/rsolr/http_client.rb +140 -0
- data/lib/rsolr/http_client/adapter.rb +6 -0
- data/lib/rsolr/http_client/adapter/curb.rb +51 -0
- data/lib/rsolr/http_client/adapter/net_http.rb +48 -0
- data/lib/rsolr/message.rb +153 -0
- data/test/connection/direct_test.rb +25 -0
- data/test/connection/http_test.rb +18 -0
- data/test/connection/test_methods.rb +97 -0
- data/test/http_client/curb_test.rb +18 -0
- data/test/http_client/net_http_test.rb +12 -0
- data/test/http_client/test_methods.rb +40 -0
- data/test/http_client/util_test.rb +40 -0
- data/test/message_test.rb +107 -0
- metadata +93 -0
data/lib/mash.rb
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
# This class has dubious semantics and we only have it so that people can write
|
2
|
+
# params[:key] instead of params['key'].
|
3
|
+
class Mash < Hash
|
4
|
+
|
5
|
+
# @param constructor<Object>
|
6
|
+
# The default value for the mash. Defaults to an empty hash.
|
7
|
+
#
|
8
|
+
# @details [Alternatives]
|
9
|
+
# If constructor is a Hash, a new mash will be created based on the keys of
|
10
|
+
# the hash and no default value will be set.
|
11
|
+
def initialize(constructor = {})
|
12
|
+
if constructor.is_a?(Hash)
|
13
|
+
super()
|
14
|
+
update(constructor)
|
15
|
+
else
|
16
|
+
super(constructor)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# @param key<Object> The default value for the mash. Defaults to nil.
|
21
|
+
#
|
22
|
+
# @details [Alternatives]
|
23
|
+
# If key is a Symbol and it is a key in the mash, then the default value will
|
24
|
+
# be set to the value matching the key.
|
25
|
+
def default(key = nil)
|
26
|
+
if key.is_a?(Symbol) && include?(key = key.to_s)
|
27
|
+
self[key]
|
28
|
+
else
|
29
|
+
super
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
|
34
|
+
alias_method :regular_update, :update unless method_defined?(:regular_update)
|
35
|
+
|
36
|
+
# @param key<Object> The key to set.
|
37
|
+
# @param value<Object>
|
38
|
+
# The value to set the key to.
|
39
|
+
#
|
40
|
+
# @see Mash#convert_key
|
41
|
+
# @see Mash#convert_value
|
42
|
+
def []=(key, value)
|
43
|
+
regular_writer(convert_key(key), convert_value(value))
|
44
|
+
end
|
45
|
+
|
46
|
+
# @param other_hash<Hash>
|
47
|
+
# A hash to update values in the mash with. The keys and the values will be
|
48
|
+
# converted to Mash format.
|
49
|
+
#
|
50
|
+
# @return <Mash> The updated mash.
|
51
|
+
def update(other_hash)
|
52
|
+
other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
|
53
|
+
self
|
54
|
+
end
|
55
|
+
|
56
|
+
alias_method :merge!, :update
|
57
|
+
|
58
|
+
# @param key<Object> The key to check for. This will be run through convert_key.
|
59
|
+
#
|
60
|
+
# @return <TrueClass, FalseClass> True if the key exists in the mash.
|
61
|
+
def key?(key)
|
62
|
+
super(convert_key(key))
|
63
|
+
end
|
64
|
+
|
65
|
+
# def include? def has_key? def member?
|
66
|
+
alias_method :include?, :key?
|
67
|
+
alias_method :has_key?, :key?
|
68
|
+
alias_method :member?, :key?
|
69
|
+
|
70
|
+
# @param key<Object> The key to fetch. This will be run through convert_key.
|
71
|
+
# @param *extras<Array> Default value.
|
72
|
+
#
|
73
|
+
# @return <Object> The value at key or the default value.
|
74
|
+
def fetch(key, *extras)
|
75
|
+
super(convert_key(key), *extras)
|
76
|
+
end
|
77
|
+
|
78
|
+
# @param *indices<Array>
|
79
|
+
# The keys to retrieve values for. These will be run through +convert_key+.
|
80
|
+
#
|
81
|
+
# @return <Array> The values at each of the provided keys
|
82
|
+
def values_at(*indices)
|
83
|
+
indices.collect {|key| self[convert_key(key)]}
|
84
|
+
end
|
85
|
+
|
86
|
+
# @param hash<Hash> The hash to merge with the mash.
|
87
|
+
#
|
88
|
+
# @return <Mash> A new mash with the hash values merged in.
|
89
|
+
def merge(hash)
|
90
|
+
self.dup.update(hash)
|
91
|
+
end
|
92
|
+
|
93
|
+
# @param key<Object>
|
94
|
+
# The key to delete from the mash.\
|
95
|
+
def delete(key)
|
96
|
+
super(convert_key(key))
|
97
|
+
end
|
98
|
+
|
99
|
+
# @param *rejected<Array[(String, Symbol)] The mash keys to exclude.
|
100
|
+
#
|
101
|
+
# @return <Mash> A new mash without the selected keys.
|
102
|
+
#
|
103
|
+
# @example
|
104
|
+
# { :one => 1, :two => 2, :three => 3 }.except(:one)
|
105
|
+
# #=> { "two" => 2, "three" => 3 }
|
106
|
+
def except(*keys)
|
107
|
+
super(*keys.map {|k| convert_key(k)})
|
108
|
+
end
|
109
|
+
|
110
|
+
# Used to provide the same interface as Hash.
|
111
|
+
#
|
112
|
+
# @return <Mash> This mash unchanged.
|
113
|
+
def stringify_keys!; self end
|
114
|
+
|
115
|
+
# @return <Hash> The mash as a Hash with string keys.
|
116
|
+
def to_hash
|
117
|
+
Hash.new(default).merge(self)
|
118
|
+
end
|
119
|
+
|
120
|
+
protected
|
121
|
+
# @param key<Object> The key to convert.
|
122
|
+
#
|
123
|
+
# @param <Object>
|
124
|
+
# The converted key. If the key was a symbol, it will be converted to a
|
125
|
+
# string.
|
126
|
+
#
|
127
|
+
# @api private
|
128
|
+
def convert_key(key)
|
129
|
+
key.kind_of?(Symbol) ? key.to_s : key
|
130
|
+
end
|
131
|
+
|
132
|
+
# @param value<Object> The value to convert.
|
133
|
+
#
|
134
|
+
# @return <Object>
|
135
|
+
# The converted value. A Hash or an Array of hashes, will be converted to
|
136
|
+
# their Mash equivalents.
|
137
|
+
#
|
138
|
+
# @api private
|
139
|
+
def convert_value(value)
|
140
|
+
if value.class == Hash
|
141
|
+
value.to_mash
|
142
|
+
elsif value.is_a?(Array)
|
143
|
+
value.collect { |e| convert_value(e) }
|
144
|
+
else
|
145
|
+
value
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
data/lib/rsolr.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# add this directory to the load path if it hasn't already been added
|
2
|
+
# load xout and rfuzz libs
|
3
|
+
proc {|base, files|
|
4
|
+
$: << base unless $:.include?(base) || $:.include?(File.expand_path(base))
|
5
|
+
files.each {|f| require f}
|
6
|
+
}.call(File.dirname(__FILE__), ['core_ext', 'mash'])
|
7
|
+
|
8
|
+
module RSolr
|
9
|
+
|
10
|
+
VERSION = '0.8.2'
|
11
|
+
|
12
|
+
autoload :Message, 'rsolr/message'
|
13
|
+
autoload :Connection, 'rsolr/connection'
|
14
|
+
autoload :Adapter, 'rsolr/adapter'
|
15
|
+
autoload :HTTPClient, 'rsolr/http_client'
|
16
|
+
|
17
|
+
# factory for creating connections
|
18
|
+
# "options" is a hash that gets used by the Connection
|
19
|
+
# object AND the adapter object.
|
20
|
+
def self.connect(options={})
|
21
|
+
adapter_name = options[:adapter] ||= :http
|
22
|
+
types = {
|
23
|
+
:http=>'HTTP',
|
24
|
+
:direct=>'Direct'
|
25
|
+
}
|
26
|
+
adapter_class = RSolr::Adapter.const_get(types[adapter_name])
|
27
|
+
adapter = adapter_class.new(options)
|
28
|
+
RSolr::Connection.new(adapter, options)
|
29
|
+
end
|
30
|
+
|
31
|
+
module Char
|
32
|
+
|
33
|
+
# escape - from the solr-ruby library
|
34
|
+
# RSolr.escape('asdf')
|
35
|
+
# backslash everything that isn't a word character
|
36
|
+
def escape(value)
|
37
|
+
value.gsub(/(\W)/, '\\\\\1')
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
# send the escape method into the Connection class ->
|
43
|
+
# solr = RSolr.connect
|
44
|
+
# solr.escape('asdf')
|
45
|
+
RSolr::Connection.send(:include, Char)
|
46
|
+
|
47
|
+
# bring escape into this module (RSolr) -> RSolr.escape('asdf')
|
48
|
+
extend Char
|
49
|
+
|
50
|
+
class RequestError < RuntimeError; end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
raise "JRuby Required" unless defined?(JRUBY_VERSION)
|
2
|
+
|
3
|
+
require 'java'
|
4
|
+
|
5
|
+
#
|
6
|
+
# Connection for JRuby + DirectSolrConnection
|
7
|
+
#
|
8
|
+
class RSolr::Adapter::Direct
|
9
|
+
|
10
|
+
include RSolr::HTTPClient::Util
|
11
|
+
|
12
|
+
attr_accessor :opts, :home_dir
|
13
|
+
|
14
|
+
# required: opts[:home_dir] is absolute path to solr home (the directory with "data", "config" etc.)
|
15
|
+
# opts must also contain either
|
16
|
+
# :dist_dir => 'absolute path to solr distribution root
|
17
|
+
# or
|
18
|
+
# :jar_paths => ['array of directories containing the solr lib/jars']
|
19
|
+
# OTHER OPTS:
|
20
|
+
# :select_path => 'the/select/handler'
|
21
|
+
# :update_path => 'the/update/handler'
|
22
|
+
def initialize(opts, &block)
|
23
|
+
@home_dir = opts[:home_dir].to_s
|
24
|
+
opts[:data_dir] ||= File.join(@home_dir, 'data')
|
25
|
+
if opts[:dist_dir] and ! opts[:jar_paths]
|
26
|
+
# add the standard lib and dist directories to the :jar_paths
|
27
|
+
opts[:jar_paths] = [File.join(opts[:dist_dir], 'lib'), File.join(opts[:dist_dir], 'dist')]
|
28
|
+
end
|
29
|
+
@opts = opts
|
30
|
+
end
|
31
|
+
|
32
|
+
# loads/imports the java dependencies
|
33
|
+
# sets the @connection instance variable
|
34
|
+
def connection
|
35
|
+
@connection ||= (
|
36
|
+
require_jars(@opts[:jar_paths]) if @opts[:jar_paths]
|
37
|
+
import_dependencies
|
38
|
+
DirectSolrConnection.new(@home_dir, @opts[:data_dir], nil)
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
def close
|
43
|
+
if @connection
|
44
|
+
@connection.close
|
45
|
+
@connection=nil
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# send a request to the connection
|
50
|
+
# request '/update', :wt=>:xml, '</commit>'
|
51
|
+
def send_request(path, params={}, data=nil)
|
52
|
+
data = data.to_xml if data.respond_to?(:to_xml)
|
53
|
+
url = build_url(path, params)
|
54
|
+
begin
|
55
|
+
body = connection.request(url, data)
|
56
|
+
rescue
|
57
|
+
raise RSolr::RequestError.new($!.message)
|
58
|
+
end
|
59
|
+
{
|
60
|
+
:status_code=>nil,
|
61
|
+
:body=>body,
|
62
|
+
:url=>url,
|
63
|
+
:path=>path,
|
64
|
+
:params=>params,
|
65
|
+
:data=>data,
|
66
|
+
:headers=>nil
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
protected
|
71
|
+
|
72
|
+
# do the java import thingy
|
73
|
+
def import_dependencies
|
74
|
+
import org.apache.solr.servlet.DirectSolrConnection
|
75
|
+
end
|
76
|
+
|
77
|
+
# require the jar files
|
78
|
+
def require_jars(paths)
|
79
|
+
paths = [paths] unless paths.is_a?(Array)
|
80
|
+
paths.each do |path|
|
81
|
+
jar_pattern = File.join(path,"**", "*.jar")
|
82
|
+
Dir[jar_pattern].each {|jar_file| require jar_file}
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
#
|
2
|
+
# Connection for standard HTTP Solr server
|
3
|
+
#
|
4
|
+
class RSolr::Adapter::HTTP
|
5
|
+
|
6
|
+
attr_reader :opts, :connector, :connection
|
7
|
+
|
8
|
+
# opts can have:
|
9
|
+
# :url => 'http://localhost:8080/solr'
|
10
|
+
# :select_path => '/the/url/path/to/the/select/handler'
|
11
|
+
# :update_path => '/the/url/path/to/the/update/handler'
|
12
|
+
# :luke_path => '/admin/luke'
|
13
|
+
#
|
14
|
+
def initialize(opts={}, &block)
|
15
|
+
opts[:url]||='http://127.0.0.1:8983/solr'
|
16
|
+
@opts = opts
|
17
|
+
@connector = RSolr::HTTPClient::Connector.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def connection
|
21
|
+
@connection ||= @connector.connect(@opts[:url])
|
22
|
+
end
|
23
|
+
|
24
|
+
# send a request to the connection
|
25
|
+
# request '/update', :wt=>:xml, '</commit>'
|
26
|
+
def send_request(path, params={}, data=nil)
|
27
|
+
data = data.to_xml if data.respond_to?(:to_xml)
|
28
|
+
if data
|
29
|
+
http_context = connection.post(path, data, params, post_headers)
|
30
|
+
else
|
31
|
+
http_context = connection.get(path, params)
|
32
|
+
end
|
33
|
+
raise RSolr::RequestError.new(http_context[:body]) unless http_context[:status_code] == 200
|
34
|
+
http_context
|
35
|
+
end
|
36
|
+
|
37
|
+
protected
|
38
|
+
|
39
|
+
# The standard post headers
|
40
|
+
def post_headers
|
41
|
+
{"Content-Type" => 'text/xml; charset=utf-8'}
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
class RSolr::Connection
|
2
|
+
|
3
|
+
attr_reader :adapter, :opts
|
4
|
+
|
5
|
+
# "adapter" is instance of:
|
6
|
+
# RSolr::Adapter::HTTP
|
7
|
+
# RSolr::Adapter::Direct (jRuby only)
|
8
|
+
def initialize(adapter, opts={})
|
9
|
+
@adapter = adapter
|
10
|
+
@opts = opts
|
11
|
+
end
|
12
|
+
|
13
|
+
# send a request to the "select" handler
|
14
|
+
def select(params, &blk)
|
15
|
+
send_request('/select', map_params(params), &blk)
|
16
|
+
end
|
17
|
+
|
18
|
+
# sends data to the update handler
|
19
|
+
# data can be a string of xml, or an object that returns xml from its #to_s method
|
20
|
+
def update(data, params={}, &blk)
|
21
|
+
send_request('/update', map_params(params), data, &blk)
|
22
|
+
end
|
23
|
+
|
24
|
+
# send request solr
|
25
|
+
# params is hash with valid solr request params (:q, :fl, :qf etc..)
|
26
|
+
# if params[:wt] is not set, the default is :ruby
|
27
|
+
# if :wt is something other than :ruby, the raw response body is used
|
28
|
+
# otherwise, a simple Hash is returned
|
29
|
+
# NOTE: to get raw ruby, use :wt=>'ruby' <- a string, not a symbol like :ruby
|
30
|
+
#
|
31
|
+
# use a block to get access to the adapter response:
|
32
|
+
# solr.send_request('/select', :q=>'blue') do |solr_response, adapter_response|
|
33
|
+
# raise 'Woops!' if adapter_response[:status] != 200
|
34
|
+
# solr_response[:response][:docs].each {|doc|}
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
def send_request(path, params={}, data=nil, &blk)
|
38
|
+
response = @adapter.send_request(path, map_params(params), data)
|
39
|
+
adapt_response(response, &blk)
|
40
|
+
end
|
41
|
+
|
42
|
+
#
|
43
|
+
# single record:
|
44
|
+
# solr.update(:id=>1, :name=>'one')
|
45
|
+
#
|
46
|
+
# update using an array
|
47
|
+
# solr.update([{:id=>1, :name=>'one'}, {:id=>2, :name=>'two'}])
|
48
|
+
#
|
49
|
+
def add(doc, &block)
|
50
|
+
update message.add(doc, &block)
|
51
|
+
end
|
52
|
+
|
53
|
+
# send </commit>
|
54
|
+
def commit
|
55
|
+
update message.commit
|
56
|
+
end
|
57
|
+
|
58
|
+
# send </optimize>
|
59
|
+
def optimize
|
60
|
+
update message.optimize
|
61
|
+
end
|
62
|
+
|
63
|
+
# send </rollback>
|
64
|
+
# NOTE: solr 1.4 only
|
65
|
+
def rollback
|
66
|
+
update message.rollback
|
67
|
+
end
|
68
|
+
|
69
|
+
# Delete one or many documents by id
|
70
|
+
# solr.delete_by_id 10
|
71
|
+
# solr.delete_by_id([12, 41, 199])
|
72
|
+
def delete_by_id(id)
|
73
|
+
update message.delete_by_id(id)
|
74
|
+
end
|
75
|
+
|
76
|
+
# delete one or many documents by query
|
77
|
+
# solr.delete_by_query 'available:0'
|
78
|
+
# solr.delete_by_query ['quantity:0', 'manu:"FQ"']
|
79
|
+
def delete_by_query(query)
|
80
|
+
update message.delete_by_query(query)
|
81
|
+
end
|
82
|
+
|
83
|
+
protected
|
84
|
+
|
85
|
+
# shortcut to solr::message
|
86
|
+
def message
|
87
|
+
RSolr::Message
|
88
|
+
end
|
89
|
+
|
90
|
+
# sets default params etc.. - could be used as a mapping hook
|
91
|
+
# type of request should be passed in here? -> map_params(:query, {})
|
92
|
+
def map_params(params)
|
93
|
+
params||={}
|
94
|
+
{:wt=>:ruby}.merge(params)
|
95
|
+
end
|
96
|
+
|
97
|
+
#
|
98
|
+
def adapt_response(adapter_response)
|
99
|
+
if adapter_response[:params][:wt] == :ruby
|
100
|
+
data = Kernel.eval(adapter_response[:body]).to_mash
|
101
|
+
else
|
102
|
+
data = adapter_response[:body]
|
103
|
+
end
|
104
|
+
block_given? ? yield(data, adapter_response) : data
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|