lowang-rubberband 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +202 -0
- data/README.rdoc +36 -0
- data/Rakefile +59 -0
- data/TODO +29 -0
- data/VERSION +1 -0
- data/lib/elasticsearch.rb +14 -0
- data/lib/elasticsearch/client.rb +20 -0
- data/lib/elasticsearch/client/abstract_client.rb +34 -0
- data/lib/elasticsearch/client/admin_cluster.rb +56 -0
- data/lib/elasticsearch/client/admin_index.rb +109 -0
- data/lib/elasticsearch/client/auto_discovering_client.rb +21 -0
- data/lib/elasticsearch/client/default_scope.rb +29 -0
- data/lib/elasticsearch/client/hits.rb +83 -0
- data/lib/elasticsearch/client/index.rb +102 -0
- data/lib/elasticsearch/client/retrying_client.rb +81 -0
- data/lib/elasticsearch/encoding.rb +7 -0
- data/lib/elasticsearch/encoding/base.rb +17 -0
- data/lib/elasticsearch/encoding/json.rb +19 -0
- data/lib/elasticsearch/transport.rb +14 -0
- data/lib/elasticsearch/transport/base.rb +37 -0
- data/lib/elasticsearch/transport/base_protocol.rb +235 -0
- data/lib/elasticsearch/transport/http.rb +50 -0
- data/lib/elasticsearch/transport/thrift.rb +96 -0
- data/lib/elasticsearch/transport/thrift/elasticsearch.thrift +81 -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/lowang-rubberband.gemspec +81 -0
- data/rubberband.gemspec +80 -0
- data/test/elasticsearch_test.rb +7 -0
- data/test/test_helper.rb +10 -0
- metadata +141 -0
@@ -0,0 +1,109 @@
|
|
1
|
+
module ElasticSearch
|
2
|
+
module Api
|
3
|
+
module Admin
|
4
|
+
module Index
|
5
|
+
PSEUDO_INDICES = [:all]
|
6
|
+
|
7
|
+
def index_status(*args)
|
8
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
9
|
+
indices = args.empty? ? [(default_index || :all)] : args.flatten
|
10
|
+
indices.collect! { |i| PSEUDO_INDICES.include?(i) ? "_#{i}" : i }
|
11
|
+
execute(:index_status, indices, options)
|
12
|
+
end
|
13
|
+
|
14
|
+
# options: number_of_shards, number_of_replicas
|
15
|
+
def create_index(index, create_options={}, options={})
|
16
|
+
unless create_options[:index]
|
17
|
+
create_options = { :index => create_options }
|
18
|
+
end
|
19
|
+
execute(:create_index, index, create_options, options)
|
20
|
+
end
|
21
|
+
|
22
|
+
def delete_index(index, options={})
|
23
|
+
execute(:delete_index, index, options)
|
24
|
+
end
|
25
|
+
|
26
|
+
# :add => { "index" => "alias" }
|
27
|
+
# :add => [{"index" => "alias"}, {"index2" => "alias2"}]
|
28
|
+
# :add => { "index" => "alias", "index2" => "alias2" }
|
29
|
+
# :remove => { "index" => "alias" }
|
30
|
+
# :remove => [{"index" => "alias", {"index2" => "alias2"}]
|
31
|
+
# :remove => { "index" => "alias", "index2" => "alias2" }
|
32
|
+
# :actions => [{:add => {:index => "index", :alias => "alias"}}]
|
33
|
+
def alias_index(operations, options={})
|
34
|
+
if operations[:actions]
|
35
|
+
alias_ops = operations
|
36
|
+
else
|
37
|
+
alias_ops = { :actions => [] }
|
38
|
+
[:add, :remove].each do |op|
|
39
|
+
next unless operations.has_key?(op)
|
40
|
+
op_actions = operations[op].is_a?(Array) ? operations[op] : [operations[op]]
|
41
|
+
op_actions.each do |action_hash|
|
42
|
+
action_hash.each do |index, index_alias|
|
43
|
+
alias_ops[:actions] << { op => { :index => index, :alias => index_alias }}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
execute(:alias_index, alias_ops, options)
|
49
|
+
end
|
50
|
+
|
51
|
+
# options: ignore_conflicts
|
52
|
+
def update_mapping(mapping, options={})
|
53
|
+
set_default_scope!(options)
|
54
|
+
raise "index and type or defaults required" unless options[:index] && options[:type]
|
55
|
+
|
56
|
+
options = options.dup
|
57
|
+
indices = Array(options.delete(:index))
|
58
|
+
type = options.delete(:type)
|
59
|
+
unless mapping[type]
|
60
|
+
mapping = { type => mapping }
|
61
|
+
end
|
62
|
+
|
63
|
+
indices.collect! { |i| PSEUDO_INDICES.include?(i) ? "_#{i}" : i }
|
64
|
+
execute(:update_mapping, indices, type, mapping, options)
|
65
|
+
end
|
66
|
+
|
67
|
+
# list of indices, or :all
|
68
|
+
# options: refresh
|
69
|
+
# default: default_index if defined, otherwise :all
|
70
|
+
def flush(*args)
|
71
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
72
|
+
indices = args.empty? ? [(default_index || :all)] : args.flatten
|
73
|
+
indices.collect! { |i| PSEUDO_INDICES.include?(i) ? "_#{i}" : i }
|
74
|
+
execute(:flush, indices, options)
|
75
|
+
end
|
76
|
+
|
77
|
+
# list of indices, or :all
|
78
|
+
# no options
|
79
|
+
# default: default_index if defined, otherwise all
|
80
|
+
def refresh(*args)
|
81
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
82
|
+
indices = args.empty? ? [(default_index || :all)] : args.flatten
|
83
|
+
indices.collect! { |i| PSEUDO_INDICES.include?(i) ? "_#{i}" : i }
|
84
|
+
execute(:refresh, indices, options)
|
85
|
+
end
|
86
|
+
|
87
|
+
# list of indices, or :all
|
88
|
+
# no options
|
89
|
+
# default: default_index if defined, otherwise all
|
90
|
+
def snapshot(*args)
|
91
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
92
|
+
indices = args.empty? ? [(default_index || :all)] : args.flatten
|
93
|
+
indices.collect! { |i| PSEUDO_INDICES.include?(i) ? "_#{i}" : i }
|
94
|
+
execute(:snapshot, indices, options)
|
95
|
+
end
|
96
|
+
|
97
|
+
# list of indices, or :all
|
98
|
+
# options: max_num_segments, only_expunge_deletes, refresh, flush
|
99
|
+
# default: default_index if defined, otherwise all
|
100
|
+
def optimize(*args)
|
101
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
102
|
+
indices = args.empty? ? [(default_index || :all)] : args.flatten
|
103
|
+
indices.collect! { |i| PSEUDO_INDICES.include?(i) ? "_#{i}" : i }
|
104
|
+
execute(:optimize, indices, options)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module ElasticSearch
|
2
|
+
module AutoDiscoveringClient
|
3
|
+
|
4
|
+
AUTO_DISCOVERING_DEFAULTS = {
|
5
|
+
:auto_discovery => true
|
6
|
+
}.freeze
|
7
|
+
|
8
|
+
def initialize(servers, options={})
|
9
|
+
super
|
10
|
+
@options = AUTO_DISCOVERING_DEFAULTS.merge(@options)
|
11
|
+
if @options[:auto_discovery]
|
12
|
+
auto_discover_nodes!
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
#TODO how to autodiscover on reconnect? don't want to overwrite methods of RetryingClient
|
17
|
+
def auto_discover_nodes!
|
18
|
+
@server_list = execute(:all_nodes)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module ElasticSearch
|
2
|
+
module Api
|
3
|
+
module DefaultScope
|
4
|
+
def default_index
|
5
|
+
@default_index ||= @options[:index]
|
6
|
+
end
|
7
|
+
|
8
|
+
def default_index=(index)
|
9
|
+
@default_index = index
|
10
|
+
end
|
11
|
+
|
12
|
+
def default_type
|
13
|
+
@default_type ||= @options[:type]
|
14
|
+
end
|
15
|
+
|
16
|
+
def default_type=(type)
|
17
|
+
@default_type = type
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def set_default_scope!(options)
|
23
|
+
options[:index] ||= default_index
|
24
|
+
options[:type] ||= default_type
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module ElasticSearch
|
4
|
+
module Api
|
5
|
+
class Hit < OpenStruct
|
6
|
+
undef_method :id if method_defined?(:id)
|
7
|
+
|
8
|
+
def initialize(hit)
|
9
|
+
hit = hit.dup
|
10
|
+
hit.merge!(hit["_source"]) if hit["_source"]
|
11
|
+
hit["id"] ||= hit["_id"]
|
12
|
+
super(hit)
|
13
|
+
@table.freeze
|
14
|
+
end
|
15
|
+
|
16
|
+
def attributes
|
17
|
+
@table
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module Pagination
|
22
|
+
def current_page
|
23
|
+
(@options[:page].respond_to?(:empty?) ? @options[:page].empty? : !@options[:page]) ? 1 : @options[:page].to_i
|
24
|
+
end
|
25
|
+
|
26
|
+
def next_page
|
27
|
+
current_page >= total_pages ? nil : current_page + 1
|
28
|
+
end
|
29
|
+
|
30
|
+
def previous_page
|
31
|
+
current_page == 1 ? nil : current_page - 1
|
32
|
+
end
|
33
|
+
|
34
|
+
def per_page
|
35
|
+
@options[:per_page] || 10
|
36
|
+
end
|
37
|
+
|
38
|
+
def total_pages
|
39
|
+
(total_entries / per_page.to_f).ceil
|
40
|
+
end
|
41
|
+
alias_method :page_count, :total_pages
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
class Hits
|
46
|
+
include Pagination
|
47
|
+
attr_reader :hits, :total_entries, :_shards, :response, :facets, :scroll_id
|
48
|
+
|
49
|
+
def initialize(response, options={})
|
50
|
+
@response = response
|
51
|
+
@options = options
|
52
|
+
@total_entries = response["hits"]["total"]
|
53
|
+
@_shards = response["_shards"]
|
54
|
+
@facets = response["facets"]
|
55
|
+
@scroll_id = response["_scrollId"]
|
56
|
+
populate(@options[:ids_only])
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_a
|
60
|
+
@hits
|
61
|
+
end
|
62
|
+
|
63
|
+
def freeze
|
64
|
+
@hits.freeze
|
65
|
+
super
|
66
|
+
end
|
67
|
+
|
68
|
+
def method_missing(method, *args, &block)
|
69
|
+
@hits.send(method, *args, &block)
|
70
|
+
end
|
71
|
+
|
72
|
+
def respond_to?(method, include_private = false)
|
73
|
+
super || @hits.respond_to?(method, include_private)
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def populate(ids_only=false)
|
79
|
+
@hits = @response["hits"]["hits"].collect { |h| ids_only ? h["_id"] : Hit.new(h).freeze }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'client/hits'
|
2
|
+
|
3
|
+
module ElasticSearch
|
4
|
+
module Api
|
5
|
+
module Index
|
6
|
+
def index(document, options={})
|
7
|
+
set_default_scope!(options)
|
8
|
+
raise "index and type or defaults required" unless options[:index] && options[:type]
|
9
|
+
# type
|
10
|
+
# index
|
11
|
+
# id (optional)
|
12
|
+
# op_type (optional)
|
13
|
+
# timeout (optional)
|
14
|
+
# document (optional)
|
15
|
+
|
16
|
+
result = execute(:index, options[:index], options[:type], options[:id], document, options)
|
17
|
+
if result["ok"]
|
18
|
+
result["_id"]
|
19
|
+
else
|
20
|
+
false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def get(id, options={})
|
25
|
+
set_default_scope!(options)
|
26
|
+
raise "index and type or defaults required" unless options[:index] && options[:type]
|
27
|
+
# index
|
28
|
+
# type
|
29
|
+
# id
|
30
|
+
# fields
|
31
|
+
|
32
|
+
hit = execute(:get, options[:index], options[:type], id, options)
|
33
|
+
if hit
|
34
|
+
Hit.new(hit).freeze
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def delete(id, options={})
|
39
|
+
set_default_scope!(options)
|
40
|
+
raise "index and type or defaults required" unless options[:index] && options[:type]
|
41
|
+
result = execute(:delete, options[:index], options[:type], id, options)
|
42
|
+
result["ok"]
|
43
|
+
end
|
44
|
+
|
45
|
+
#df The default field to use when no field prefix is defined within the query.
|
46
|
+
#analyzer The analyzer name to be used when analyzing the query string.
|
47
|
+
#default_operator The default operator to be used, can be AND or OR. Defaults to OR.
|
48
|
+
#explain For each hit, contain an explanation of how to scoring of the hits was computed.
|
49
|
+
#fields The selective fields of the document to return for each hit (fields must be stored), comma delimited. Defaults to the internal _source field.
|
50
|
+
#field Same as fields above, but each parameter contains a single field name to load. There can be several field parameters.
|
51
|
+
#sort Sorting to perform. Can either be in the form of fieldName, or fieldName:reverse (for reverse sorting). The fieldName can either be an actual field within the document, or the special score name to indicate sorting based on scores. There can be several sort parameters (order is important).
|
52
|
+
#from The starting from index of the hits to return. Defaults to 0.
|
53
|
+
#size The number of hits to return. Defaults to 10.
|
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
|
57
|
+
def search(query, options={})
|
58
|
+
set_default_scope!(options)
|
59
|
+
|
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
|
61
|
+
#query = {:query => query} if query.is_a?(Hash) && !query[:query] # if there is no query element, wrap query in one
|
62
|
+
|
63
|
+
search_options = slice_hash(options, :df, :analyzer, :default_operator, :explain, :fields, :field, :sort, :from, :size, :search_type, :limit, :per_page, :page, :offset, :scroll)
|
64
|
+
|
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
|
67
|
+
search_options[:from] ||= search_options[:offset] if search_options[:offset]
|
68
|
+
|
69
|
+
search_options[:fields] = "_id" if options[:ids_only]
|
70
|
+
|
71
|
+
response = execute(:search, options[:index], options[:type], query, search_options)
|
72
|
+
Hits.new(response, slice_hash(options, :per_page, :page, :ids_only)).freeze #ids_only returns array of ids instead of hits
|
73
|
+
end
|
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
|
+
|
81
|
+
#df The default field to use when no field prefix is defined within the query.
|
82
|
+
#analyzer The analyzer name to be used when analyzing the query string.
|
83
|
+
#default_operator The default operator to be used, can be AND or OR. Defaults to OR.
|
84
|
+
def count(query, options={})
|
85
|
+
set_default_scope!(options)
|
86
|
+
|
87
|
+
count_options = slice_hash(options, :df, :analyzer, :default_operator)
|
88
|
+
response = execute(:count, options[:index], options[:type], query, count_options)
|
89
|
+
response["count"].to_i #TODO check if count is nil
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def slice_hash(hash, *keys)
|
95
|
+
h = {}
|
96
|
+
keys.each { |k| h[k] = hash[k] if hash.has_key?(k) }
|
97
|
+
h
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# mostly ripped from thrift_client
|
2
|
+
|
3
|
+
module ElasticSearch
|
4
|
+
class NoServersAvailable < StandardError; end
|
5
|
+
|
6
|
+
module RetryingClient
|
7
|
+
|
8
|
+
RETRYING_DEFAULTS = {
|
9
|
+
:randomize_server_list => true,
|
10
|
+
:retries => nil,
|
11
|
+
:server_retry_period => 1,
|
12
|
+
:server_max_requests => nil,
|
13
|
+
:retry_overrides => {}
|
14
|
+
}.freeze
|
15
|
+
|
16
|
+
# use cluster status to get server list
|
17
|
+
def initialize(servers, options={})
|
18
|
+
super
|
19
|
+
@options = RETRYING_DEFAULTS.merge(@options)
|
20
|
+
@retries = options[:retries] || @server_list.size
|
21
|
+
@request_count = 0
|
22
|
+
@max_requests = @options[:server_max_requests]
|
23
|
+
@retry_period = @options[:server_retry_period]
|
24
|
+
rebuild_live_server_list!
|
25
|
+
end
|
26
|
+
|
27
|
+
def connect!
|
28
|
+
@current_server = next_server
|
29
|
+
super
|
30
|
+
rescue ElasticSearch::RetryableError
|
31
|
+
retry
|
32
|
+
end
|
33
|
+
|
34
|
+
def disconnect!
|
35
|
+
# Keep live servers in the list if we have a retry period. Otherwise,
|
36
|
+
# always eject, because we will always re-add them.
|
37
|
+
if @retry_period && @current_server
|
38
|
+
@live_server_list.unshift(@current_server)
|
39
|
+
end
|
40
|
+
|
41
|
+
super
|
42
|
+
@request_count = 0
|
43
|
+
end
|
44
|
+
|
45
|
+
#TODO this can spin indefinitely if timeout > retry_period
|
46
|
+
def next_server
|
47
|
+
if @retry_period
|
48
|
+
rebuild_live_server_list! if Time.now > @last_rebuild + @retry_period
|
49
|
+
raise NoServersAvailable, "No live servers in #{@server_list.inspect} since #{@last_rebuild.inspect}." if @live_server_list.empty?
|
50
|
+
elsif @live_server_list.empty?
|
51
|
+
rebuild_live_server_list!
|
52
|
+
end
|
53
|
+
@live_server_list.pop
|
54
|
+
end
|
55
|
+
|
56
|
+
def rebuild_live_server_list!
|
57
|
+
@last_rebuild = Time.now
|
58
|
+
if @options[:randomize_server_list]
|
59
|
+
@live_server_list = @server_list.sort_by { rand }
|
60
|
+
else
|
61
|
+
@live_server_list = @server_list.dup
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def execute(method_name, *args)
|
66
|
+
disconnect_on_max! if @max_requests and @request_count >= @max_requests
|
67
|
+
@request_count += 1
|
68
|
+
begin
|
69
|
+
super
|
70
|
+
rescue ElasticSearch::RetryableError
|
71
|
+
disconnect!
|
72
|
+
retry
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def disconnect_on_max!
|
77
|
+
@live_server_list.push(@current_server)
|
78
|
+
disconnect!
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|