rsolr 1.0.8 → 1.1.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.
- checksums.yaml +7 -0
- data/.travis.yml +21 -0
- data/CHANGES.txt +20 -1
- data/Gemfile +3 -9
- data/README.rdoc +34 -31
- data/Rakefile +1 -10
- data/lib/rsolr.rb +14 -10
- data/lib/rsolr/char.rb +4 -1
- data/lib/rsolr/client.rb +133 -54
- data/lib/rsolr/connection.rb +21 -17
- data/lib/rsolr/error.rb +39 -20
- data/lib/rsolr/response.rb +57 -13
- data/lib/rsolr/uri.rb +44 -27
- data/lib/rsolr/version.rb +7 -0
- data/lib/rsolr/xml.rb +69 -15
- data/rsolr.gemspec +14 -7
- data/spec/api/char_spec.rb +8 -3
- data/spec/api/client_spec.rb +221 -87
- data/spec/api/connection_spec.rb +93 -29
- data/spec/api/error_spec.rb +22 -32
- data/spec/api/pagination_spec.rb +12 -5
- data/spec/api/rsolr_spec.rb +28 -8
- data/spec/api/uri_spec.rb +108 -50
- data/spec/api/xml_spec.rb +196 -96
- data/spec/fixtures/basic_configs/_rest_managed.json +1 -0
- data/spec/fixtures/basic_configs/currency.xml +67 -0
- data/spec/fixtures/basic_configs/lang/stopwords_en.txt +54 -0
- data/spec/fixtures/basic_configs/protwords.txt +21 -0
- data/spec/fixtures/basic_configs/schema.xml +530 -0
- data/spec/fixtures/basic_configs/solrconfig.xml +572 -0
- data/spec/fixtures/basic_configs/stopwords.txt +14 -0
- data/spec/fixtures/basic_configs/synonyms.txt +29 -0
- data/spec/integration/solr5_spec.rb +26 -0
- data/spec/spec_helper.rb +8 -1
- data/tasks/spec.rake +1 -38
- metadata +98 -34
- data/tasks/rcov.rake +0 -25
data/lib/rsolr/connection.rb
CHANGED
@@ -15,11 +15,13 @@ class RSolr::Connection
|
|
15
15
|
response = h.request request
|
16
16
|
charset = response.type_params["charset"]
|
17
17
|
{:status => response.code.to_i, :headers => response.to_hash, :body => force_charset(response.body, charset)}
|
18
|
+
rescue Errno::ECONNREFUSED
|
19
|
+
raise RSolr::Error::ConnectionRefused, request_context.inspect
|
18
20
|
# catch the undefined closed? exception -- this is a confirmed ruby bug
|
19
|
-
rescue NoMethodError
|
20
|
-
|
21
|
-
raise(
|
22
|
-
raise(
|
21
|
+
rescue NoMethodError => e
|
22
|
+
e.message == "undefined method `closed?' for nil:NilClass" ?
|
23
|
+
raise(RSolr::Error::ConnectionRefused, request_context.inspect) :
|
24
|
+
raise(e)
|
23
25
|
end
|
24
26
|
end
|
25
27
|
|
@@ -27,18 +29,20 @@ class RSolr::Connection
|
|
27
29
|
|
28
30
|
# This returns a singleton of a Net::HTTP or Net::HTTP.Proxy request object.
|
29
31
|
def http uri, proxy = nil, read_timeout = nil, open_timeout = nil
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
32
|
+
http = if proxy
|
33
|
+
proxy_user, proxy_pass = proxy.userinfo.split(/:/) if proxy.userinfo
|
34
|
+
Net::HTTP.Proxy(proxy.host, proxy.port, proxy_user, proxy_pass).new uri.host, uri.port
|
35
|
+
elsif proxy == false
|
36
|
+
# If explicitly passing in false, make sure we set proxy_addr to nil
|
37
|
+
# to tell Net::HTTP to *not* use the environment proxy variables.
|
38
|
+
Net::HTTP.new uri.host, uri.port, nil
|
39
|
+
else
|
40
|
+
Net::HTTP.new uri.host, uri.port
|
41
|
+
end
|
42
|
+
http.use_ssl = uri.port == 443 || uri.instance_of?(URI::HTTPS)
|
43
|
+
http.read_timeout = read_timeout if read_timeout
|
44
|
+
http.open_timeout = open_timeout if open_timeout
|
45
|
+
http
|
42
46
|
end
|
43
47
|
|
44
48
|
#
|
@@ -67,4 +71,4 @@ class RSolr::Connection
|
|
67
71
|
body.force_encoding(charset)
|
68
72
|
end
|
69
73
|
|
70
|
-
end
|
74
|
+
end
|
data/lib/rsolr/error.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
module RSolr::Error
|
2
|
-
|
2
|
+
|
3
3
|
module SolrContext
|
4
|
-
|
4
|
+
|
5
5
|
attr_accessor :request, :response
|
6
|
-
|
6
|
+
|
7
7
|
def to_s
|
8
8
|
m = "#{super.to_s}"
|
9
9
|
if response
|
@@ -12,22 +12,25 @@ module RSolr::Error
|
|
12
12
|
m << "\nError: #{details}\n" if details
|
13
13
|
end
|
14
14
|
p = "\nURI: #{request[:uri].to_s}"
|
15
|
-
p
|
16
|
-
p
|
15
|
+
p << "\nRequest Headers: #{request[:headers].inspect}" if request[:headers]
|
16
|
+
p << "\nRequest Data: #{request[:data].inspect}" if request[:data]
|
17
17
|
p << "\n"
|
18
18
|
p << "\nBacktrace: " + self.backtrace[0..10].join("\n")
|
19
19
|
m << p
|
20
20
|
m
|
21
21
|
end
|
22
|
-
|
22
|
+
|
23
23
|
protected
|
24
|
-
|
24
|
+
|
25
25
|
def parse_solr_error_response body
|
26
26
|
begin
|
27
|
-
|
27
|
+
if body =~ /<pre>/
|
28
|
+
info = body.scan(/<pre>(.*)<\/pre>/mi)[0]
|
29
|
+
elsif body =~ /'msg'=>/
|
30
|
+
info = body.scan(/'msg'=>(.*)/)[0]
|
31
|
+
end
|
28
32
|
info = info.join if info.respond_to? :join
|
29
|
-
|
30
|
-
info ||= body # body may not contain <pre> elements
|
33
|
+
info ||= body # body might not contain <pre> or msg elements
|
31
34
|
|
32
35
|
partial = info.to_s.split("\n")[0..10]
|
33
36
|
partial.join("\n").gsub(">", ">").gsub("<", "<")
|
@@ -35,13 +38,17 @@ module RSolr::Error
|
|
35
38
|
nil
|
36
39
|
end
|
37
40
|
end
|
38
|
-
|
41
|
+
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
class ConnectionRefused < ::Errno::ECONNREFUSED
|
39
46
|
end
|
40
|
-
|
47
|
+
|
41
48
|
class Http < RuntimeError
|
42
|
-
|
49
|
+
|
43
50
|
include SolrContext
|
44
|
-
|
51
|
+
|
45
52
|
# ripped right from ActionPack
|
46
53
|
# Defines the standard HTTP status codes, by integer, with their
|
47
54
|
# corresponding default message texts.
|
@@ -101,17 +108,29 @@ module RSolr::Error
|
|
101
108
|
507 => "Insufficient Storage",
|
102
109
|
510 => "Not Extended"
|
103
110
|
}
|
104
|
-
|
111
|
+
|
105
112
|
def initialize request, response
|
106
113
|
@request, @response = request, response
|
107
114
|
end
|
108
|
-
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
# Thrown if the :wt is :ruby
|
119
|
+
# but the body wasn't succesfully parsed/evaluated
|
120
|
+
class InvalidResponse < Http
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
# Thrown if the :wt is :ruby
|
125
|
+
# but the body wasn't succesfully parsed/evaluated
|
126
|
+
class InvalidJsonResponse < InvalidResponse
|
127
|
+
|
109
128
|
end
|
110
|
-
|
129
|
+
|
111
130
|
# Thrown if the :wt is :ruby
|
112
131
|
# but the body wasn't succesfully parsed/evaluated
|
113
|
-
class InvalidRubyResponse <
|
114
|
-
|
132
|
+
class InvalidRubyResponse < InvalidResponse
|
133
|
+
|
115
134
|
end
|
116
|
-
|
135
|
+
|
117
136
|
end
|
data/lib/rsolr/response.rb
CHANGED
@@ -1,20 +1,55 @@
|
|
1
1
|
module RSolr::Response
|
2
|
-
|
3
|
-
def self.
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
2
|
+
|
3
|
+
def self.included(base)
|
4
|
+
unless base < Hash
|
5
|
+
raise ArgumentError, "RSolr::Response expects to included only in (sub)classes of Hash; got included in '#{base}' instead."
|
6
|
+
end
|
7
|
+
base.send(:attr_reader, :request, :response)
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize_rsolr_response(request, response, result)
|
11
|
+
@request = request
|
12
|
+
@response = response
|
13
|
+
self.merge!(result)
|
14
|
+
if self["response"] && self["response"]["docs"].is_a?(Array)
|
15
|
+
docs = PaginatedDocSet.new(self["response"]["docs"])
|
16
|
+
docs.per_page = request[:params]["rows"]
|
17
|
+
docs.page_start = request[:params]["start"]
|
18
|
+
docs.page_total = self["response"]["numFound"].to_s.to_i
|
19
|
+
self["response"]["docs"] = docs
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def with_indifferent_access
|
24
|
+
if defined?(::RSolr::HashWithIndifferentAccessWithResponse)
|
25
|
+
::RSolr::HashWithIndifferentAccessWithResponse.new(request, response, self)
|
26
|
+
else
|
27
|
+
if defined?(ActiveSupport::HashWithIndifferentAccess)
|
28
|
+
RSolr.const_set("HashWithIndifferentAccessWithResponse", Class.new(ActiveSupport::HashWithIndifferentAccess))
|
29
|
+
RSolr::HashWithIndifferentAccessWithResponse.class_eval <<-eos
|
30
|
+
include RSolr::Response
|
31
|
+
def initialize(request, response, result)
|
32
|
+
super()
|
33
|
+
initialize_rsolr_response(request, response, result)
|
34
|
+
end
|
35
|
+
eos
|
36
|
+
::RSolr::HashWithIndifferentAccessWithResponse.new(request, response, self)
|
37
|
+
else
|
38
|
+
raise RuntimeError, "HashWithIndifferentAccess is not currently defined"
|
10
39
|
end
|
11
40
|
end
|
12
41
|
end
|
13
|
-
|
42
|
+
|
14
43
|
# A response module which gets mixed into the solr ["response"]["docs"] array.
|
15
|
-
|
44
|
+
class PaginatedDocSet < Array
|
16
45
|
|
17
|
-
attr_accessor :
|
46
|
+
attr_accessor :page_start, :per_page, :page_total
|
47
|
+
if not (Object.const_defined?("RUBY_ENGINE") and Object::RUBY_ENGINE=='rbx')
|
48
|
+
alias_method(:start,:page_start)
|
49
|
+
alias_method(:start=,:page_start=)
|
50
|
+
alias_method(:total,:page_total)
|
51
|
+
alias_method(:total=,:page_total=)
|
52
|
+
end
|
18
53
|
|
19
54
|
# Returns the current page calculated from 'rows' and 'start'
|
20
55
|
def current_page
|
@@ -47,5 +82,14 @@ module RSolr::Response
|
|
47
82
|
end
|
48
83
|
|
49
84
|
end
|
50
|
-
|
51
|
-
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
class RSolr::HashWithResponse < Hash
|
89
|
+
include RSolr::Response
|
90
|
+
|
91
|
+
def initialize(request, response, result)
|
92
|
+
super()
|
93
|
+
initialize_rsolr_response(request, response, result || {})
|
94
|
+
end
|
95
|
+
end
|
data/lib/rsolr/uri.rb
CHANGED
@@ -3,56 +3,73 @@ require 'uri'
|
|
3
3
|
module RSolr::Uri
|
4
4
|
|
5
5
|
def create url
|
6
|
-
::URI.parse url[-1] ==
|
6
|
+
::URI.parse (url[-1] == '/' || URI.parse(url).query) ? url : "#{url}/"
|
7
7
|
end
|
8
8
|
|
9
|
-
# Returns a query string param pair as a string.
|
10
|
-
# Both key and value are escaped.
|
11
|
-
def build_param(k,v, escape = true)
|
12
|
-
escape ?
|
13
|
-
"#{escape_query_value(k)}=#{escape_query_value(v)}" :
|
14
|
-
"#{k}=#{v}"
|
15
|
-
end
|
16
|
-
|
17
|
-
# Return the bytesize of String; uses String#size under Ruby 1.8 and
|
18
|
-
# String#bytesize under 1.9.
|
19
|
-
if ''.respond_to?(:bytesize)
|
20
|
-
def bytesize(string)
|
21
|
-
string.bytesize
|
22
|
-
end
|
23
|
-
else
|
24
|
-
def bytesize(string)
|
25
|
-
string.size
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
9
|
# Creates a Solr based query string.
|
30
10
|
# Keys that have arrays values are set multiple times:
|
31
11
|
# params_to_solr(:q => 'query', :fq => ['a', 'b'])
|
32
12
|
# is converted to:
|
33
13
|
# ?q=query&fq=a&fq=b
|
14
|
+
# @param [boolean] escape false if no URI escaping is to be performed. Default true.
|
15
|
+
# @return [String] Solr query params as a String, suitable for use in a url
|
34
16
|
def params_to_solr(params, escape = true)
|
17
|
+
return URI.encode_www_form(params.reject{|k,v| k.to_s.empty? || v.to_s.empty?}) if escape
|
18
|
+
|
19
|
+
# escape = false if we are here
|
35
20
|
mapped = params.map do |k, v|
|
36
21
|
next if v.to_s.empty?
|
37
22
|
if v.class == Array
|
38
|
-
params_to_solr(v.map { |x| [k, x] },
|
23
|
+
params_to_solr(v.map { |x| [k, x] }, false)
|
39
24
|
else
|
40
|
-
|
25
|
+
"#{k}=#{v}"
|
41
26
|
end
|
42
27
|
end
|
43
28
|
mapped.compact.join("&")
|
44
29
|
end
|
45
30
|
|
31
|
+
# Returns a query string param pair as a string.
|
32
|
+
# Both key and value are URI escaped, unless third param is false
|
33
|
+
# @param [boolean] escape false if no URI escaping is to be performed. Default true.
|
34
|
+
# @deprecated - used to be called from params_to_solr before 2015-02-25
|
35
|
+
def build_param(k, v, escape = true)
|
36
|
+
warn "[DEPRECATION] `RSolr::Uri.build_param` is deprecated. Use `URI.encode_www_form_component` or k=v instead."
|
37
|
+
escape ?
|
38
|
+
"#{URI.encode_www_form_component(k)}=#{URI.encode_www_form_component(v)}" :
|
39
|
+
"#{k}=#{v}"
|
40
|
+
end
|
41
|
+
|
42
|
+
# 2015-02 Deprecated: use URI.encode_www_form_component(s)
|
43
|
+
#
|
46
44
|
# Performs URI escaping so that you can construct proper
|
47
45
|
# query strings faster. Use this rather than the cgi.rb
|
48
46
|
# version since it's faster.
|
49
47
|
# (Stolen from Rack).
|
48
|
+
# http://www.rubydoc.info/github/rack/rack/URI.encode_www_form_component
|
49
|
+
# @deprecated
|
50
50
|
def escape_query_value(s)
|
51
|
-
|
52
|
-
|
53
|
-
|
51
|
+
warn "[DEPRECATION] `RSolr::Uri.escape_query_value` is deprecated. Use `URI.encode_www_form_component` instead."
|
52
|
+
URI.encode_www_form_component(s)
|
53
|
+
# s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/u) {
|
54
|
+
# '%'+$1.unpack('H2'*bytesize($1)).join('%').upcase
|
55
|
+
# }.tr(' ', '+')
|
54
56
|
end
|
55
|
-
|
57
|
+
|
58
|
+
# Return the bytesize of String; uses String#size under Ruby 1.8 and
|
59
|
+
# String#bytesize under 1.9.
|
60
|
+
# @deprecated as bytesize was only used by escape_query_value which is itself deprecated
|
61
|
+
if ''.respond_to?(:bytesize)
|
62
|
+
def bytesize(string)
|
63
|
+
warn "[DEPRECATION] `RSolr::Uri.bytesize` is deprecated. Use String.bytesize"
|
64
|
+
string.bytesize
|
65
|
+
end
|
66
|
+
else
|
67
|
+
def bytesize(string)
|
68
|
+
warn "[DEPRECATION] `RSolr::Uri.bytesize` is deprecated. Use String.size"
|
69
|
+
string.size
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
56
73
|
extend self
|
57
74
|
|
58
75
|
end
|
data/lib/rsolr/xml.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
require '
|
1
|
+
begin; require 'nokogiri'; rescue LoadError; end
|
2
|
+
require 'time'
|
2
3
|
|
3
4
|
module RSolr::Xml
|
4
5
|
|
@@ -15,11 +16,10 @@ module RSolr::Xml
|
|
15
16
|
@fields = []
|
16
17
|
doc_hash.each_pair do |field,values|
|
17
18
|
# create a new field for each value (multi-valued)
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
@fields << RSolr::Xml::Field.new({:name=>field}, v.to_s)
|
19
|
+
wrap(values).each do |v|
|
20
|
+
v = format_value(v)
|
21
|
+
next if v.empty?
|
22
|
+
@fields << RSolr::Xml::Field.new({:name=>field}, v)
|
23
23
|
end
|
24
24
|
end
|
25
25
|
@attrs={}
|
@@ -47,9 +47,35 @@ module RSolr::Xml
|
|
47
47
|
def add_field(name, value, options = {})
|
48
48
|
@fields << RSolr::Xml::Field.new(options.merge({:name=>name}), value)
|
49
49
|
end
|
50
|
-
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def format_value(v)
|
54
|
+
case v
|
55
|
+
when Time
|
56
|
+
v.getutc.iso8601
|
57
|
+
when DateTime
|
58
|
+
v.to_time.getutc.iso8601
|
59
|
+
when Date
|
60
|
+
Time.utc(v.year, v.mon, v.mday).iso8601
|
61
|
+
else
|
62
|
+
v.to_s
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def wrap(object)
|
67
|
+
if object.nil?
|
68
|
+
[]
|
69
|
+
elsif object.respond_to?(:to_ary)
|
70
|
+
object.to_ary || [object]
|
71
|
+
elsif object.is_a? Enumerable
|
72
|
+
object
|
73
|
+
else
|
74
|
+
[object]
|
75
|
+
end
|
76
|
+
end
|
51
77
|
end
|
52
|
-
|
78
|
+
|
53
79
|
class Field
|
54
80
|
|
55
81
|
# "attrs" is a hash for setting the "doc" xml attributes
|
@@ -71,13 +97,40 @@ module RSolr::Xml
|
|
71
97
|
end
|
72
98
|
|
73
99
|
class Generator
|
100
|
+
class << self
|
101
|
+
attr_accessor :use_nokogiri
|
102
|
+
|
103
|
+
def builder_proc
|
104
|
+
if use_nokogiri
|
105
|
+
require 'nokogiri' unless defined?(::Nokogiri::XML::Builder)
|
106
|
+
:nokogiri_build
|
107
|
+
else
|
108
|
+
require 'builder' unless defined?(::Builder::XmlMarkup)
|
109
|
+
:builder_build
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
self.use_nokogiri = (defined?(::Nokogiri::XML::Builder) and not defined?(JRuby)) ? true : false
|
114
|
+
|
115
|
+
def nokogiri_build &block
|
116
|
+
b = ::Nokogiri::XML::Builder.new do |xml|
|
117
|
+
block_given? ? yield(xml) : xml
|
118
|
+
end
|
119
|
+
'<?xml version="1.0" encoding="UTF-8"?>'+b.to_xml(:indent => 0, :encoding => 'UTF-8', :save_with => ::Nokogiri::XML::Node::SaveOptions::AS_XML | ::Nokogiri::XML::Node::SaveOptions::NO_DECLARATION).strip
|
120
|
+
end
|
121
|
+
protected :nokogiri_build
|
74
122
|
|
75
|
-
def
|
123
|
+
def builder_build &block
|
76
124
|
b = ::Builder::XmlMarkup.new(:indent => 0, :margin => 0, :encoding => 'UTF-8')
|
77
125
|
b.instruct!
|
78
126
|
block_given? ? yield(b) : b
|
79
127
|
end
|
80
|
-
|
128
|
+
protected :builder_build
|
129
|
+
|
130
|
+
def build &block
|
131
|
+
self.send(self.class.builder_proc,&block)
|
132
|
+
end
|
133
|
+
|
81
134
|
# generates "add" xml for updating solr
|
82
135
|
# "data" can be a hash or an array of hashes.
|
83
136
|
# - each hash should be a simple key=>value pair representing a solr doc.
|
@@ -111,11 +164,12 @@ module RSolr::Xml
|
|
111
164
|
data.each do |doc|
|
112
165
|
doc = RSolr::Xml::Document.new(doc) if doc.respond_to?(:each_pair)
|
113
166
|
yield doc if block_given?
|
114
|
-
|
167
|
+
doc_node_builder = lambda do |doc_node|
|
115
168
|
doc.fields.each do |field_obj|
|
116
169
|
doc_node.field field_obj.value, field_obj.attrs
|
117
170
|
end
|
118
171
|
end
|
172
|
+
self.class.use_nokogiri ? add_node.doc_(doc.attrs,&doc_node_builder) : add_node.doc(doc.attrs,&doc_node_builder)
|
119
173
|
end
|
120
174
|
end
|
121
175
|
end
|
@@ -144,7 +198,9 @@ module RSolr::Xml
|
|
144
198
|
ids = [ids] unless ids.is_a?(Array)
|
145
199
|
build do |xml|
|
146
200
|
xml.delete do |delete_node|
|
147
|
-
ids.each
|
201
|
+
ids.each do |id|
|
202
|
+
self.class.use_nokogiri ? delete_node.id_(id) : delete_node.id(id)
|
203
|
+
end
|
148
204
|
end
|
149
205
|
end
|
150
206
|
end
|
@@ -159,7 +215,5 @@ module RSolr::Xml
|
|
159
215
|
end
|
160
216
|
end
|
161
217
|
end
|
162
|
-
|
163
218
|
end
|
164
|
-
|
165
|
-
end
|
219
|
+
end
|