rubberband 0.0.2 → 0.0.4
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/VERSION +1 -1
- data/lib/elasticsearch/client/hits.rb +4 -3
- data/lib/elasticsearch/client/index.rb +11 -5
- data/lib/elasticsearch/client/retrying_client.rb +5 -4
- data/lib/elasticsearch/encoding.rb +6 -1
- data/lib/elasticsearch/transport.rb +12 -1
- data/lib/elasticsearch/transport/base.rb +7 -2
- data/lib/elasticsearch/transport/base_protocol.rb +171 -34
- data/lib/elasticsearch/transport/http.rb +6 -219
- data/lib/elasticsearch/transport/thrift.rb +101 -0
- data/lib/elasticsearch/transport/thrift/elasticsearch_constants.rb +12 -0
- data/lib/elasticsearch/transport/thrift/elasticsearch_types.rb +124 -0
- data/lib/elasticsearch/transport/thrift/rest.rb +83 -0
- data/lib/rubberband.rb +1 -0
- data/rubberband.gemspec +83 -0
- data/test/elasticsearch_test.rb +1 -1
- data/test/hits_test.rb +72 -5
- data/vendor/elasticsearch/elasticsearch.thrift +81 -0
- metadata +15 -14
- data/.gitignore +0 -2
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.4
|
@@ -3,11 +3,11 @@ require 'ostruct'
|
|
3
3
|
module ElasticSearch
|
4
4
|
module Api
|
5
5
|
class Hit < OpenStruct
|
6
|
-
undef_method :id
|
6
|
+
undef_method :id if method_defined?(:id)
|
7
7
|
|
8
8
|
def initialize(hit)
|
9
9
|
hit = hit.dup
|
10
|
-
hit.merge!(hit
|
10
|
+
hit.merge!(hit["_source"]) if hit["_source"]
|
11
11
|
hit["id"] ||= hit["_id"]
|
12
12
|
super(hit)
|
13
13
|
@table.freeze
|
@@ -44,7 +44,7 @@ module ElasticSearch
|
|
44
44
|
|
45
45
|
class Hits
|
46
46
|
include Pagination
|
47
|
-
attr_reader :hits, :total_entries, :_shards, :response, :facets
|
47
|
+
attr_reader :hits, :total_entries, :_shards, :response, :facets, :scroll_id
|
48
48
|
|
49
49
|
def initialize(response, options={})
|
50
50
|
@response = response
|
@@ -52,6 +52,7 @@ module ElasticSearch
|
|
52
52
|
@total_entries = response["hits"]["total"]
|
53
53
|
@_shards = response["_shards"]
|
54
54
|
@facets = response["facets"]
|
55
|
+
@scroll_id = response["_scrollId"]
|
55
56
|
populate(@options[:ids_only])
|
56
57
|
end
|
57
58
|
|
@@ -52,17 +52,18 @@ module ElasticSearch
|
|
52
52
|
#from The starting from index of the hits to return. Defaults to 0.
|
53
53
|
#size The number of hits to return. Defaults to 10.
|
54
54
|
#search_type The type of the search operation to perform. Can be dfs_query_then_fetch, dfs_query_and_fetch, query_then_fetch, query_and_fetch. Defaults to query_then_fetch.
|
55
|
+
#scroll Get a scroll id to continue paging through the search results. Value is the time to keep a scroll request around, e.g. 5m
|
56
|
+
#ids_only Return ids instead of hits
|
55
57
|
def search(query, options={})
|
56
58
|
set_default_scope!(options)
|
57
59
|
|
58
60
|
#TODO this doesn't work for facets, because they have a valid query key as element. need a list of valid toplevel keys in the search dsl
|
59
61
|
#query = {:query => query} if query.is_a?(Hash) && !query[:query] # if there is no query element, wrap query in one
|
60
62
|
|
61
|
-
search_options = slice_hash(options, :df, :analyzer, :default_operator, :explain, :fields, :field, :sort, :from, :size, :search_type, :limit, :per_page, :page, :offset)
|
63
|
+
search_options = slice_hash(options, :df, :analyzer, :default_operator, :explain, :fields, :field, :sort, :from, :size, :search_type, :limit, :per_page, :page, :offset, :scroll)
|
62
64
|
|
63
|
-
search_options[:size] ||= search_options[:per_page]
|
64
|
-
search_options[:
|
65
|
-
search_options[:from] ||= search_options[:size] * (search_options[:page]-1) if search_options[:page] && search_options[:page] > 1
|
65
|
+
search_options[:size] ||= (search_options[:per_page] || search_options[:limit] || 10)
|
66
|
+
search_options[:from] ||= search_options[:size] * (search_options[:page].to_i-1) if search_options[:page] && search_options[:page].to_i > 1
|
66
67
|
search_options[:from] ||= search_options[:offset] if search_options[:offset]
|
67
68
|
|
68
69
|
search_options[:fields] = "_id" if options[:ids_only]
|
@@ -71,6 +72,12 @@ module ElasticSearch
|
|
71
72
|
Hits.new(response, slice_hash(options, :per_page, :page, :ids_only)).freeze #ids_only returns array of ids instead of hits
|
72
73
|
end
|
73
74
|
|
75
|
+
#ids_only Return ids instead of hits
|
76
|
+
def scroll(scroll_id, options={})
|
77
|
+
response = execute(:scroll, scroll_id)
|
78
|
+
Hits.new(response, slice_hash(options, :ids_only)).freeze
|
79
|
+
end
|
80
|
+
|
74
81
|
#df The default field to use when no field prefix is defined within the query.
|
75
82
|
#analyzer The analyzer name to be used when analyzing the query string.
|
76
83
|
#default_operator The default operator to be used, can be AND or OR. Defaults to OR.
|
@@ -82,7 +89,6 @@ module ElasticSearch
|
|
82
89
|
response["count"].to_i #TODO check if count is nil
|
83
90
|
end
|
84
91
|
|
85
|
-
|
86
92
|
private
|
87
93
|
|
88
94
|
def slice_hash(hash, *keys)
|
@@ -1,9 +1,9 @@
|
|
1
1
|
# mostly ripped from thrift_client
|
2
2
|
|
3
3
|
module ElasticSearch
|
4
|
-
|
4
|
+
class NoServersAvailable < StandardError; end
|
5
5
|
|
6
|
-
|
6
|
+
module RetryingClient
|
7
7
|
|
8
8
|
RETRYING_DEFAULTS = {
|
9
9
|
:randomize_server_list => true,
|
@@ -27,7 +27,7 @@ module ElasticSearch
|
|
27
27
|
def connect!
|
28
28
|
@current_server = next_server
|
29
29
|
super
|
30
|
-
rescue ElasticSearch::
|
30
|
+
rescue ElasticSearch::RetryableError
|
31
31
|
retry
|
32
32
|
end
|
33
33
|
|
@@ -42,6 +42,7 @@ module ElasticSearch
|
|
42
42
|
@request_count = 0
|
43
43
|
end
|
44
44
|
|
45
|
+
#TODO this can spin indefinitely if timeout > retry_period
|
45
46
|
def next_server
|
46
47
|
if @retry_period
|
47
48
|
rebuild_live_server_list! if Time.now > @last_rebuild + @retry_period
|
@@ -66,7 +67,7 @@ module ElasticSearch
|
|
66
67
|
@request_count += 1
|
67
68
|
begin
|
68
69
|
super
|
69
|
-
rescue ElasticSearch::
|
70
|
+
rescue ElasticSearch::RetryableError
|
70
71
|
disconnect!
|
71
72
|
retry
|
72
73
|
end
|
@@ -1,3 +1,14 @@
|
|
1
1
|
require "transport/base_protocol"
|
2
2
|
require "transport/base"
|
3
|
-
|
3
|
+
|
4
|
+
module ElasticSearch
|
5
|
+
class ConnectionFailed < RetryableError; end
|
6
|
+
class HostResolutionError < RetryableError; end
|
7
|
+
class TimeoutError < RetryableError; end
|
8
|
+
class RequestError < FatalError; end
|
9
|
+
|
10
|
+
module Transport
|
11
|
+
autoload :HTTP, 'transport/http'
|
12
|
+
autoload :Thrift, 'transport/thrift'
|
13
|
+
end
|
14
|
+
end
|
@@ -1,7 +1,8 @@
|
|
1
1
|
module ElasticSearch
|
2
|
+
class RetryableError < StandardError; end
|
3
|
+
class FatalError < StandardError; end
|
4
|
+
|
2
5
|
module Transport
|
3
|
-
class RetryableError < StandardError; end
|
4
|
-
class FatalError < StandardError; end
|
5
6
|
|
6
7
|
DEFAULTS = {
|
7
8
|
:encoder => ElasticSearch::Encoding::JSON
|
@@ -27,6 +28,10 @@ module ElasticSearch
|
|
27
28
|
def encoder
|
28
29
|
@encoder ||= @options[:encoder].new
|
29
30
|
end
|
31
|
+
|
32
|
+
def request(method, operation, params={}, body=nil, headers={})
|
33
|
+
raise NotImplementedError
|
34
|
+
end
|
30
35
|
end
|
31
36
|
end
|
32
37
|
end
|
@@ -1,98 +1,235 @@
|
|
1
1
|
module ElasticSearch
|
2
2
|
module Transport
|
3
|
-
module
|
4
|
-
|
5
|
-
# index api
|
6
|
-
|
3
|
+
module IndexProtocol
|
7
4
|
def index(index, type, id, document, options={})
|
8
|
-
|
5
|
+
body = encoder.is_encoded?(document) ? document : encoder.encode(document)
|
6
|
+
if id.nil?
|
7
|
+
response = request(:post, {:index => index, :type => type}, {}, body)
|
8
|
+
else
|
9
|
+
response = request(:put, {:index => index, :type => type, :id => id}, {}, body)
|
10
|
+
end
|
11
|
+
handle_error(response) unless response.status == 200
|
12
|
+
encoder.decode(response.body)
|
9
13
|
end
|
10
14
|
|
11
15
|
def get(index, type, id, options={})
|
12
|
-
|
16
|
+
response = request(:get, {:index => index, :type => type, :id => id})
|
17
|
+
return nil if response.status == 404
|
18
|
+
|
19
|
+
handle_error(response) unless response.status == 200
|
20
|
+
hit = encoder.decode(response.body)
|
21
|
+
unescape_id!(hit) #TODO extract these two calls from here and search
|
22
|
+
set_encoding!(hit)
|
23
|
+
hit # { "_id", "_index", "_type", "_source" }
|
13
24
|
end
|
14
25
|
|
15
|
-
def delete(options)
|
16
|
-
|
26
|
+
def delete(index, type, id, options={})
|
27
|
+
response = request(:delete,{:index => index, :type => type, :id => id})
|
28
|
+
handle_error(response) unless response.status == 200 # ElasticSearch always returns 200 on delete, even if the object doesn't exist
|
29
|
+
encoder.decode(response.body)
|
17
30
|
end
|
18
31
|
|
19
32
|
def search(index, type, query, options={})
|
20
|
-
|
33
|
+
if query.is_a?(Hash)
|
34
|
+
# patron cannot submit get requests with content, so if query is a hash, post it instead (assume a query hash is using the query dsl)
|
35
|
+
response = request(:post, {:index => index, :type => type, :op => "_search"}, options, encoder.encode(query))
|
36
|
+
else
|
37
|
+
response = request(:get, {:index => index, :type => type, :op => "_search"}, options.merge(:q => query))
|
38
|
+
end
|
39
|
+
handle_error(response) unless response.status == 200
|
40
|
+
results = encoder.decode(response.body)
|
41
|
+
# unescape ids
|
42
|
+
results["hits"]["hits"].each do |hit|
|
43
|
+
unescape_id!(hit)
|
44
|
+
set_encoding!(hit)
|
45
|
+
end
|
46
|
+
results # {"hits"=>{"hits"=>[{"_id", "_type", "_source", "_index", "_score"}], "total"}, "_shards"=>{"failed", "total", "successful"}}
|
47
|
+
end
|
48
|
+
|
49
|
+
def scroll(scroll_id)
|
50
|
+
response = request(:get, {:op => "_search/scroll"}, {:scroll_id => scroll_id })
|
51
|
+
handle_error(response) unless response.status == 200
|
52
|
+
results = encoder.decode(response.body)
|
53
|
+
# unescape ids
|
54
|
+
results["hits"]["hits"].each do |hit|
|
55
|
+
unescape_id!(hit)
|
56
|
+
set_encoding!(hit)
|
57
|
+
end
|
58
|
+
results # {"hits"=>{"hits"=>[{"_id", "_type", "_source", "_index", "_score"}], "total"}, "_shards"=>{"failed", "total", "successful"}, "_scrollId"}
|
21
59
|
end
|
22
60
|
|
23
61
|
def count(index, type, query, options={})
|
24
|
-
|
62
|
+
if query.is_a?(Hash)
|
63
|
+
# patron cannot submit get requests with content, so if query is a hash, post it instead (assume a query hash is using the query dsl)
|
64
|
+
response = request(:post, {:index => index, :type => type, :op => "_count"}, options, encoder.encode(query))
|
65
|
+
else
|
66
|
+
response = request(:get, {:index => index, :type => type, :op => "_count"}, options.merge(:q => query))
|
67
|
+
end
|
68
|
+
handle_error(response) unless response.status == 200
|
69
|
+
encoder.decode(response.body) # {"count", "_shards"=>{"failed", "total", "successful"}}
|
25
70
|
end
|
71
|
+
end
|
26
72
|
|
27
|
-
|
73
|
+
module IndexAdminProtocol
|
28
74
|
def index_status(index_list, options={})
|
29
|
-
|
75
|
+
standard_request(:get, {:index => index_list, :op => "_status"})
|
30
76
|
end
|
31
77
|
|
32
78
|
def create_index(index, create_options={}, options={})
|
33
|
-
|
79
|
+
standard_request(:put, {:index => index}, {}, encoder.encode(create_options))
|
34
80
|
end
|
35
81
|
|
36
82
|
def delete_index(index, options={})
|
37
|
-
|
83
|
+
standard_request(:delete, {:index => index})
|
38
84
|
end
|
39
85
|
|
40
86
|
def alias_index(operations, options={})
|
41
|
-
|
87
|
+
standard_request(:post, {:op => "_aliases"}, {}, encoder.encode(operations))
|
42
88
|
end
|
43
89
|
|
44
90
|
def update_mapping(index, type, mapping, options)
|
45
|
-
|
91
|
+
standard_request(:put, {:index => index, :type => type, :op => "_mapping"}, options, encoder.encode(mapping))
|
46
92
|
end
|
47
93
|
|
48
94
|
def flush(index_list, options={})
|
49
|
-
|
95
|
+
standard_request(:post, {:index => index_list, :op => "_flush"}, options, "")
|
50
96
|
end
|
51
|
-
|
97
|
+
|
52
98
|
def refresh(index_list, options={})
|
53
|
-
|
99
|
+
standard_request(:post, {:index => index_list, :op => "_refresh"}, {}, "")
|
54
100
|
end
|
55
|
-
|
101
|
+
|
56
102
|
def snapshot(index_list, options={})
|
57
|
-
|
103
|
+
standard_request(:post, {:index => index_list, :type => "_gateway", :op => "snapshot"}, {}, "")
|
58
104
|
end
|
59
|
-
|
105
|
+
|
60
106
|
def optimize(index_list, options={})
|
61
|
-
|
107
|
+
standard_request(:post, {:index => index_list, :op => "_optimize"}, options, {})
|
62
108
|
end
|
109
|
+
end
|
63
110
|
|
64
|
-
|
65
|
-
# admin cluster api
|
111
|
+
module ClusterAdminProtocol
|
66
112
|
def cluster_health(index_list, options={})
|
67
|
-
|
113
|
+
standard_request(:get, {:index => "_cluster", :type => "health", :id => index_list}, options)
|
68
114
|
end
|
69
115
|
|
70
116
|
def cluster_state(options={})
|
71
|
-
|
117
|
+
standard_request(:get, {:index => "_cluster", :op => "state"})
|
72
118
|
end
|
73
119
|
|
74
120
|
def nodes_info(node_list, options={})
|
75
|
-
|
121
|
+
standard_request(:get, {:index => "_cluster", :type => "nodes", :id => node_list})
|
76
122
|
end
|
77
123
|
|
78
124
|
def nodes_stats(node_list, options={})
|
79
|
-
|
125
|
+
standard_request(:get, {:index => "_cluster", :type => "nodes", :id => node_list, :op => "stats"})
|
80
126
|
end
|
81
127
|
|
82
128
|
def shutdown_nodes(node_list, options={})
|
83
|
-
|
129
|
+
standard_request(:post, {:index => "_cluster", :type => "nodes", :id => node_list, :op => "_shutdown"}, options, "")
|
84
130
|
end
|
85
131
|
|
86
132
|
def restart_nodes(node_list, options={})
|
87
|
-
|
133
|
+
standard_request(:post, {:index => "_cluster", :type => "nodes", :id => node_list, :op => "_restart"}, options, "")
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
module ProtocolHelpers
|
138
|
+
private
|
139
|
+
|
140
|
+
def standard_request(*args)
|
141
|
+
response = request(*args)
|
142
|
+
handle_error(response) unless response.status == 200
|
143
|
+
encoder.decode(response.body)
|
88
144
|
end
|
89
145
|
|
90
|
-
|
146
|
+
def handle_error(response)
|
147
|
+
raise RequestError, "(#{response.status}) #{response.body}"
|
148
|
+
end
|
149
|
+
|
150
|
+
# :index - one or many index names
|
151
|
+
# :type - one or many types
|
152
|
+
# :id - one id
|
153
|
+
# :op - optional operation
|
154
|
+
def generate_uri(options)
|
155
|
+
path = ""
|
156
|
+
path << "/#{Array(options[:index]).collect { |i| escape(i.downcase) }.join(",")}" if options[:index] && !options[:index].empty?
|
157
|
+
path << "/#{Array(options[:type]).collect { |t| escape(t) }.join(",")}" if options[:type] && !options[:type].empty?
|
158
|
+
path << "/#{Array(options[:id]).collect { |id| escape(id) }.join(",")}" if options[:id] && !options[:id].empty?
|
159
|
+
path << "/#{options[:op]}" if options[:op]
|
160
|
+
path
|
161
|
+
end
|
162
|
+
|
163
|
+
#doesn't handle arrays or hashes or what have you
|
164
|
+
def generate_query_string(params)
|
165
|
+
params.collect { |k,v| "#{escape(k.to_s)}=#{escape(v.to_s)}" }.join("&")
|
166
|
+
end
|
167
|
+
|
168
|
+
def unescape_id!(hit)
|
169
|
+
hit["_id"] = unescape(hit["_id"])
|
170
|
+
nil
|
171
|
+
end
|
172
|
+
|
173
|
+
def set_encoding!(hit)
|
174
|
+
encode_utf8(hit["_source"]) if hit["_source"].is_a?(String)
|
175
|
+
nil
|
176
|
+
end
|
177
|
+
|
178
|
+
# faster than CGI.escape
|
179
|
+
# stolen from RSolr, who stole it from Rack
|
180
|
+
def escape(string)
|
181
|
+
string.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
|
182
|
+
#'%'+$1.unpack('H2'*$1.size).join('%').upcase
|
183
|
+
'%'+$1.unpack('H2'*bytesize($1)).join('%').upcase
|
184
|
+
}.tr(' ', '+')
|
185
|
+
end
|
186
|
+
|
187
|
+
def unescape(string)
|
188
|
+
CGI.unescape(string)
|
189
|
+
end
|
190
|
+
|
191
|
+
if ''.respond_to?(:force_encoding) && ''.respond_to?(:encoding)
|
192
|
+
# encodes the string as utf-8 in Ruby 1.9
|
193
|
+
def encode_utf8(string)
|
194
|
+
# ElasticSearch only ever returns json in UTF-8 (per the JSON spec) so we can use force_encoding here (#TODO what about ids? can we assume those are always ascii?)
|
195
|
+
string.force_encoding(::Encoding::UTF_8)
|
196
|
+
end
|
197
|
+
else
|
198
|
+
# returns the unaltered string in Ruby 1.8
|
199
|
+
def encode_utf8(string)
|
200
|
+
string
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# Return the bytesize of String; uses String#size under Ruby 1.8 and
|
205
|
+
# String#bytesize under 1.9.
|
206
|
+
if ''.respond_to?(:bytesize)
|
207
|
+
def bytesize(string)
|
208
|
+
string.bytesize
|
209
|
+
end
|
210
|
+
else
|
211
|
+
def bytesize(string)
|
212
|
+
string.size
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
module BaseProtocol
|
218
|
+
include IndexProtocol
|
219
|
+
include IndexAdminProtocol
|
220
|
+
include ClusterAdminProtocol
|
221
|
+
include ProtocolHelpers
|
222
|
+
|
91
223
|
def all_nodes
|
92
|
-
|
224
|
+
http_addresses = nodes_info([])["nodes"].collect { |id, node| node["http_address"] }
|
225
|
+
http_addresses.collect! do |a|
|
226
|
+
if a =~ /inet\[.*\/([\d.:]+)\]/
|
227
|
+
$1
|
228
|
+
end
|
229
|
+
end.compact!
|
230
|
+
http_addresses
|
93
231
|
end
|
94
232
|
|
95
233
|
end
|
96
234
|
end
|
97
235
|
end
|
98
|
-
|
@@ -5,11 +5,6 @@ module ElasticSearch
|
|
5
5
|
module Transport
|
6
6
|
class HTTP < Base
|
7
7
|
|
8
|
-
class ConnectionFailed < RetryableError; end
|
9
|
-
class HostResolutionError < RetryableError; end
|
10
|
-
class TimeoutError < RetryableError; end
|
11
|
-
class RequestError < FatalError; end
|
12
|
-
|
13
8
|
DEFAULTS = {
|
14
9
|
:timeout => 5
|
15
10
|
}.freeze
|
@@ -24,220 +19,16 @@ module ElasticSearch
|
|
24
19
|
@session.base_url = @server
|
25
20
|
@session.timeout = @options[:timeout]
|
26
21
|
@session.headers['User-Agent'] = 'ElasticSearch.rb v0.1'
|
27
|
-
request(:get, "/") # try a get to see if the server responds
|
28
|
-
end
|
29
|
-
|
30
|
-
# index api (TODO modulize)
|
31
|
-
|
32
|
-
def index(index, type, id, document, options={})
|
33
|
-
body = encoder.is_encoded?(document) ? document : encoder.encode(document)
|
34
|
-
if id.nil?
|
35
|
-
response = request(:post, generate_path(:index => index, :type => type), body)
|
36
|
-
else
|
37
|
-
response = request(:put, generate_path(:index => index, :type => type, :id => id), body)
|
38
|
-
end
|
39
|
-
handle_error(response) unless response.status == 200
|
40
|
-
encoder.decode(response.body)
|
41
|
-
end
|
42
|
-
|
43
|
-
def get(index, type, id, options={})
|
44
|
-
response = request(:get, generate_path(:index => index, :type => type, :id => id))
|
45
|
-
return nil if response.status == 404
|
46
|
-
|
47
|
-
handle_error(response) unless response.status == 200
|
48
|
-
hit = encoder.decode(response.body)
|
49
|
-
unescape_id!(hit) #TODO extract these two calls from here and search
|
50
|
-
set_encoding!(hit)
|
51
|
-
hit # { "_id", "_index", "_type", "_source" }
|
52
|
-
end
|
53
|
-
|
54
|
-
def delete(index, type, id, options={})
|
55
|
-
response = request(:delete, generate_path(:index => index, :type => type, :id => id))
|
56
|
-
handle_error(response) unless response.status == 200 # ElasticSearch always returns 200 on delete, even if the object doesn't exist
|
57
|
-
encoder.decode(response.body)
|
58
|
-
end
|
59
|
-
|
60
|
-
def search(index, type, query, options={})
|
61
|
-
if query.is_a?(Hash)
|
62
|
-
# patron cannot submit get requests with content, so if query is a hash, post it instead (assume a query hash is using the query dsl)
|
63
|
-
response = request(:post, generate_path(:index => index, :type => type, :op => "_search", :params => options), encoder.encode(query))
|
64
|
-
else
|
65
|
-
response = request(:get, generate_path(:index => index, :type => type, :op => "_search", :params => options.merge(:q => query)))
|
66
|
-
end
|
67
|
-
handle_error(response) unless response.status == 200
|
68
|
-
results = encoder.decode(response.body)
|
69
|
-
# unescape ids
|
70
|
-
results["hits"]["hits"].each do |hit|
|
71
|
-
unescape_id!(hit)
|
72
|
-
set_encoding!(hit)
|
73
|
-
end
|
74
|
-
results # {"hits"=>{"hits"=>[{"_id", "_type", "_source", "_index"}], "total"}, "_shards"=>{"failed", "total", "successful"}}
|
75
|
-
end
|
76
|
-
|
77
|
-
def count(index, type, query, options={})
|
78
|
-
if query.is_a?(Hash)
|
79
|
-
# patron cannot submit get requests with content, so if query is a hash, post it instead (assume a query hash is using the query dsl)
|
80
|
-
response = request(:post, generate_path(:index => index, :type => type, :op => "_count", :params => options), encoder.encode(query))
|
81
|
-
else
|
82
|
-
response = request(:get, generate_path(:index => index, :type => type, :op => "_count", :params => options.merge(:q => query)))
|
83
|
-
end
|
84
|
-
handle_error(response) unless response.status == 200
|
85
|
-
encoder.decode(response.body) # {"count", "_shards"=>{"failed", "total", "successful"}}
|
86
|
-
end
|
87
|
-
|
88
|
-
# admin index api (TODO modulize)
|
89
|
-
#
|
90
|
-
def index_status(index_list, options={})
|
91
|
-
standard_request(:get, generate_path(:index => index_list, :op => "_status"))
|
92
|
-
end
|
93
|
-
|
94
|
-
def create_index(index, create_options={}, options={})
|
95
|
-
standard_request(:put, generate_path(:index => index), encoder.encode(create_options))
|
96
|
-
end
|
97
|
-
|
98
|
-
def delete_index(index, options={})
|
99
|
-
standard_request(:delete, generate_path(:index => index))
|
100
|
-
end
|
101
|
-
|
102
|
-
def alias_index(operations, options={})
|
103
|
-
standard_request(:post, generate_path(:op => "_aliases"), encoder.encode(operations))
|
104
|
-
end
|
105
|
-
|
106
|
-
def update_mapping(index, type, mapping, options)
|
107
|
-
standard_request(:put, generate_path(:index => index, :type => type, :op => "_mapping", :params => options), encoder.encode(mapping))
|
108
|
-
end
|
109
|
-
|
110
|
-
def flush(index_list, options={})
|
111
|
-
standard_request(:post, generate_path(:index => index_list, :op => "_flush", :params => options), "")
|
112
|
-
end
|
113
|
-
|
114
|
-
def refresh(index_list, options={})
|
115
|
-
standard_request(:post, generate_path(:index => index_list, :op => "_refresh"), "")
|
116
|
-
end
|
117
|
-
|
118
|
-
def snapshot(index_list, options={})
|
119
|
-
standard_request(:post, generate_path(:index => index_list, :type => "_gateway", :op => "snapshot"), "")
|
120
|
-
end
|
121
|
-
|
122
|
-
def optimize(index_list, options={})
|
123
|
-
standard_request(:post, generate_path(:index => index_list, :op => "_optimize", :params => options), "")
|
124
|
-
end
|
125
|
-
|
126
|
-
# admin cluster api (TODO modulize)
|
127
|
-
|
128
|
-
def cluster_health(index_list, options={})
|
129
|
-
standard_request(:get, generate_path(:index => "_cluster", :type => "health", :id => index_list, :params => options))
|
130
|
-
end
|
131
|
-
|
132
|
-
def cluster_state(options={})
|
133
|
-
standard_request(:get, generate_path(:index => "_cluster", :op => "state"))
|
134
|
-
end
|
135
|
-
|
136
|
-
def nodes_info(node_list, options={})
|
137
|
-
standard_request(:get, generate_path(:index => "_cluster", :type => "nodes", :id => node_list))
|
138
|
-
end
|
139
|
-
|
140
|
-
def nodes_stats(node_list, options={})
|
141
|
-
standard_request(:get, generate_path(:index => "_cluster", :type => "nodes", :id => node_list, :op => "stats"))
|
142
|
-
end
|
143
|
-
|
144
|
-
def shutdown_nodes(node_list, options={})
|
145
|
-
standard_request(:post, generate_path(:index => "_cluster", :type => "nodes", :id => node_list, :op => "_shutdown", :params => options), "")
|
146
|
-
end
|
147
|
-
|
148
|
-
def restart_nodes(node_list, options={})
|
149
|
-
standard_request(:post, generate_path(:index => "_cluster", :type => "nodes", :id => node_list, :op => "_restart", :params => options), "")
|
150
|
-
end
|
151
|
-
|
152
|
-
# misc helper methods (TODO modulize)
|
153
|
-
def all_nodes
|
154
|
-
http_addresses = nodes_info([])["nodes"].collect { |id, node| node["http_address"] }
|
155
|
-
http_addresses.collect! do |a|
|
156
|
-
if a =~ /inet\[.*\/([\d.:]+)\]/
|
157
|
-
$1
|
158
|
-
end
|
159
|
-
end.compact!
|
160
|
-
http_addresses
|
161
22
|
end
|
162
23
|
|
163
24
|
private
|
164
25
|
|
165
|
-
def
|
166
|
-
response = request(*args)
|
167
|
-
handle_error(response) unless response.status == 200
|
168
|
-
encoder.decode(response.body)
|
169
|
-
end
|
170
|
-
|
171
|
-
# :index - one or many index names
|
172
|
-
# :type - one or many types
|
173
|
-
# :id - one id
|
174
|
-
# :params - hash of query params
|
175
|
-
def generate_path(options)
|
176
|
-
path = ""
|
177
|
-
path << "/#{Array(options[:index]).collect { |i| escape(i.downcase) }.join(",")}" if options[:index] && !options[:index].empty?
|
178
|
-
path << "/#{Array(options[:type]).collect { |t| escape(t) }.join(",")}" if options[:type] && !options[:type].empty?
|
179
|
-
path << "/#{Array(options[:id]).collect { |id| escape(id) }.join(",")}" if options[:id] && !options[:id].empty?
|
180
|
-
path << "/#{options[:op]}" if options[:op]
|
181
|
-
path << "?" << query_string(options[:params]) if options[:params] && !options[:params].empty?
|
182
|
-
path
|
183
|
-
end
|
184
|
-
|
185
|
-
def unescape_id!(hit)
|
186
|
-
hit["_id"] = unescape(hit["_id"])
|
187
|
-
nil
|
188
|
-
end
|
189
|
-
|
190
|
-
def set_encoding!(hit)
|
191
|
-
encode_utf8(hit["_source"])
|
192
|
-
nil
|
193
|
-
end
|
194
|
-
|
195
|
-
# faster than CGI.escape
|
196
|
-
# stolen from RSolr, which stole it from Rack
|
197
|
-
def escape(string)
|
198
|
-
string.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
|
199
|
-
#'%'+$1.unpack('H2'*$1.size).join('%').upcase
|
200
|
-
'%'+$1.unpack('H2'*bytesize($1)).join('%').upcase
|
201
|
-
}.tr(' ', '+')
|
202
|
-
end
|
203
|
-
|
204
|
-
def unescape(string)
|
205
|
-
CGI.unescape(string)
|
206
|
-
end
|
207
|
-
|
208
|
-
if ''.respond_to?(:force_encoding) && ''.respond_to?(:encoding)
|
209
|
-
# encodes the string as utf-8 in Ruby 1.9
|
210
|
-
def encode_utf8(string)
|
211
|
-
# ElasticSearch only ever returns json in UTF-8 (per the JSON spec) so we can use force_encoding here (#TODO what about ids? can we assume those are always ascii?)
|
212
|
-
string.force_encoding(Encoding::UTF_8)
|
213
|
-
end
|
214
|
-
else
|
215
|
-
# returns the unaltered string in Ruby 1.8
|
216
|
-
def encode_utf8(string)
|
217
|
-
string
|
218
|
-
end
|
219
|
-
end
|
220
|
-
|
221
|
-
# Return the bytesize of String; uses String#size under Ruby 1.8 and
|
222
|
-
# String#bytesize under 1.9.
|
223
|
-
if ''.respond_to?(:bytesize)
|
224
|
-
def bytesize(string)
|
225
|
-
string.bytesize
|
226
|
-
end
|
227
|
-
else
|
228
|
-
def bytesize(string)
|
229
|
-
string.size
|
230
|
-
end
|
231
|
-
end
|
232
|
-
|
233
|
-
#doesn't handle arrays or hashes or what have you
|
234
|
-
def query_string(params)
|
235
|
-
params.collect { |k,v| "#{escape(k.to_s)}=#{escape(v.to_s)}" }.join("&")
|
236
|
-
end
|
237
|
-
|
238
|
-
def request(method, path, body=nil, headers={})
|
26
|
+
def request(method, operation, params={}, body=nil, headers={})
|
239
27
|
begin
|
240
|
-
|
28
|
+
uri = generate_uri(operation)
|
29
|
+
query = generate_query_string(params)
|
30
|
+
path = [uri, query].join("?")
|
31
|
+
#puts "request: #{@server} #{path} #{body}"
|
241
32
|
response = @session.request(method, path, headers, :data => body)
|
242
33
|
handle_error(response) if response.status >= 500
|
243
34
|
response
|
@@ -247,17 +38,13 @@ module ElasticSearch
|
|
247
38
|
raise ConnectionFailed
|
248
39
|
when Patron::HostResolutionError
|
249
40
|
raise HostResolutionError
|
250
|
-
when TimeoutError
|
41
|
+
when Patron::TimeoutError
|
251
42
|
raise TimeoutError
|
252
43
|
else
|
253
44
|
raise e
|
254
45
|
end
|
255
46
|
end
|
256
47
|
end
|
257
|
-
|
258
|
-
def handle_error(response)
|
259
|
-
raise RequestError, "(#{response.status}) #{response.body}"
|
260
|
-
end
|
261
48
|
end
|
262
49
|
end
|
263
50
|
end
|