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 CHANGED
@@ -1 +1 @@
1
- 0.0.2
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.delete("_source"))
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] if search_options[:per_page]
64
- search_options[:size] ||= search_options[:limit] if search_options[:limit]
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
- module RetryingClient
4
+ class NoServersAvailable < StandardError; end
5
5
 
6
- class NoServersAvailable < StandardError; end
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::Transport::RetryableError
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::Transport::RetryableError
70
+ rescue ElasticSearch::RetryableError
70
71
  disconnect!
71
72
  retry
72
73
  end
@@ -1,2 +1,7 @@
1
1
  require 'encoding/base'
2
- require 'encoding/json'
2
+
3
+ module ElasticSearch
4
+ module Encoding
5
+ autoload :JSON, 'encoding/json'
6
+ end
7
+ end
@@ -1,3 +1,14 @@
1
1
  require "transport/base_protocol"
2
2
  require "transport/base"
3
- require "transport/http"
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 BaseProtocol
4
-
5
- # index api
6
-
3
+ module IndexProtocol
7
4
  def index(index, type, id, document, options={})
8
- raise NotImplementedError
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
- raise NotImplementedError
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
- raise NotImplementedError
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
- raise NotImplementedError
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
- raise NotImplementedError
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
- # admin index api
73
+ module IndexAdminProtocol
28
74
  def index_status(index_list, options={})
29
- raise NotImplementedError
75
+ standard_request(:get, {:index => index_list, :op => "_status"})
30
76
  end
31
77
 
32
78
  def create_index(index, create_options={}, options={})
33
- raise NotImplementedError
79
+ standard_request(:put, {:index => index}, {}, encoder.encode(create_options))
34
80
  end
35
81
 
36
82
  def delete_index(index, options={})
37
- raise NotImplementedError
83
+ standard_request(:delete, {:index => index})
38
84
  end
39
85
 
40
86
  def alias_index(operations, options={})
41
- raise NotImplementedError
87
+ standard_request(:post, {:op => "_aliases"}, {}, encoder.encode(operations))
42
88
  end
43
89
 
44
90
  def update_mapping(index, type, mapping, options)
45
- raise NotImplementedError
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
- raise NotImplementedError
95
+ standard_request(:post, {:index => index_list, :op => "_flush"}, options, "")
50
96
  end
51
-
97
+
52
98
  def refresh(index_list, options={})
53
- raise NotImplementedError
99
+ standard_request(:post, {:index => index_list, :op => "_refresh"}, {}, "")
54
100
  end
55
-
101
+
56
102
  def snapshot(index_list, options={})
57
- raise NotImplementedError
103
+ standard_request(:post, {:index => index_list, :type => "_gateway", :op => "snapshot"}, {}, "")
58
104
  end
59
-
105
+
60
106
  def optimize(index_list, options={})
61
- raise NotImplementedError
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
- raise NotImplementedError
113
+ standard_request(:get, {:index => "_cluster", :type => "health", :id => index_list}, options)
68
114
  end
69
115
 
70
116
  def cluster_state(options={})
71
- raise NotImplementedError
117
+ standard_request(:get, {:index => "_cluster", :op => "state"})
72
118
  end
73
119
 
74
120
  def nodes_info(node_list, options={})
75
- raise NotImplementedError
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
- raise NotImplementedError
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
- raise NotImplementedError
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
- raise NotImplementedError
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
- # misc
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
- raise NotImplementedError
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 standard_request(*args)
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
- #puts "request: #{@session.base_url}#{path} body:#{body}"
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