mdwan-rsolr 0.8.2
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 +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
|