csdn-tire 0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +15 -0
- data/.travis.yml +13 -0
- data/Gemfile +4 -0
- data/MIT-LICENSE +20 -0
- data/README.markdown +291 -0
- data/Rakefile +78 -0
- data/examples/tire.rb +81 -0
- data/lib/tire.rb +26 -0
- data/lib/tire/configuration.rb +31 -0
- data/lib/tire/count.rb +24 -0
- data/lib/tire/dsl.rb +26 -0
- data/lib/tire/http/client.rb +63 -0
- data/lib/tire/http/clients/curb.rb +62 -0
- data/lib/tire/http/response.rb +28 -0
- data/lib/tire/index.rb +98 -0
- data/lib/tire/logger.rb +61 -0
- data/lib/tire/results/pagination.rb +55 -0
- data/lib/tire/rubyext/ruby_1_8.rb +54 -0
- data/lib/tire/rubyext/to_json.rb +22 -0
- data/lib/tire/search.rb +80 -0
- data/lib/tire/state.rb +27 -0
- data/lib/tire/utils.rb +40 -0
- data/lib/tire/version.rb +4 -0
- data/test/integration/count_test.rb +20 -0
- data/test/integration/index_test.rb +58 -0
- data/test/integration/search_test.rb +23 -0
- data/test/integration/state_test.rb +22 -0
- data/test/test_helper.rb +104 -0
- data/test/unit/configuration_test.rb +74 -0
- data/test/unit/http_client_test.rb +77 -0
- data/test/unit/http_response_test.rb +50 -0
- data/test/unit/logger_test.rb +126 -0
- data/tire.gemspec +49 -0
- metadata +275 -0
@@ -0,0 +1,54 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
# Stolen from ruby core's uri/common.rb, with modifications to support 1.8.x
|
3
|
+
#
|
4
|
+
# https://github.com/ruby/ruby/blob/trunk/lib/uri/common.rb
|
5
|
+
#
|
6
|
+
|
7
|
+
module URI
|
8
|
+
TBLENCWWWCOMP_ = {} # :nodoc:
|
9
|
+
256.times do |i|
|
10
|
+
TBLENCWWWCOMP_[i.chr] = '%%%02X' % i
|
11
|
+
end
|
12
|
+
TBLENCWWWCOMP_[' '] = '+'
|
13
|
+
TBLENCWWWCOMP_.freeze
|
14
|
+
TBLDECWWWCOMP_ = {} # :nodoc:
|
15
|
+
256.times do |i|
|
16
|
+
h, l = i>>4, i&15
|
17
|
+
TBLDECWWWCOMP_['%%%X%X' % [h, l]] = i.chr
|
18
|
+
TBLDECWWWCOMP_['%%%x%X' % [h, l]] = i.chr
|
19
|
+
TBLDECWWWCOMP_['%%%X%x' % [h, l]] = i.chr
|
20
|
+
TBLDECWWWCOMP_['%%%x%x' % [h, l]] = i.chr
|
21
|
+
end
|
22
|
+
TBLDECWWWCOMP_['+'] = ' '
|
23
|
+
TBLDECWWWCOMP_.freeze
|
24
|
+
|
25
|
+
# Encode given +s+ to URL-encoded form data.
|
26
|
+
#
|
27
|
+
# This method doesn't convert *, -, ., 0-9, A-Z, _, a-z, but does convert SP
|
28
|
+
# (ASCII space) to + and converts others to %XX.
|
29
|
+
#
|
30
|
+
# This is an implementation of
|
31
|
+
# http://www.w3.org/TR/html5/forms.html#url-encoded-form-data
|
32
|
+
#
|
33
|
+
# See URI.decode_www_form_component, URI.encode_www_form
|
34
|
+
def self.encode_www_form_component(s)
|
35
|
+
str = s.to_s
|
36
|
+
if RUBY_VERSION < "1.9" && $KCODE =~ /u/i
|
37
|
+
str.gsub(/([^ a-zA-Z0-9_.-]+)/) do
|
38
|
+
'%' + $1.unpack('H2' * Rack::Utils.bytesize($1)).join('%').upcase
|
39
|
+
end.tr(' ', '+')
|
40
|
+
else
|
41
|
+
str.gsub(/[^*\-.0-9A-Z_a-z]/) {|m| TBLENCWWWCOMP_[m]}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Decode given +str+ of URL-encoded form data.
|
46
|
+
#
|
47
|
+
# This decods + to SP.
|
48
|
+
#
|
49
|
+
# See URI.encode_www_form_component, URI.decode_www_form
|
50
|
+
def self.decode_www_form_component(str, enc=nil)
|
51
|
+
raise ArgumentError, "invalid %-encoding (#{str})" unless /\A(?:%[0-9a-fA-F]{2}|[^%])*\z/ =~ str
|
52
|
+
str.gsub(/\+|%[0-9a-fA-F]{2}/) {|m| TBLDECWWWCOMP_[m]}
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'time'
|
3
|
+
|
4
|
+
class Array
|
5
|
+
def to_json(options=nil)
|
6
|
+
MultiJson.encode(self)
|
7
|
+
end unless method_defined? :to_json
|
8
|
+
end
|
9
|
+
|
10
|
+
class Hash
|
11
|
+
def to_json(options=nil)
|
12
|
+
MultiJson.encode(self)
|
13
|
+
end unless method_defined? :to_json
|
14
|
+
|
15
|
+
alias_method :to_indexed_json, :to_json
|
16
|
+
end
|
17
|
+
|
18
|
+
class Time
|
19
|
+
def to_json(options=nil)
|
20
|
+
%Q/"#{self.iso8601}"/
|
21
|
+
end
|
22
|
+
end
|
data/lib/tire/search.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module Tire
|
3
|
+
class SearchRequestFailed < StandardError; end
|
4
|
+
|
5
|
+
class Search
|
6
|
+
|
7
|
+
attr_reader :payload, :payload_hash, :indices
|
8
|
+
|
9
|
+
def initialize(indices, types, payload)
|
10
|
+
@indices = Array(indices)
|
11
|
+
@types = Array(types).map { |type| Utils.escape(type) }
|
12
|
+
if payload.is_a?(String)
|
13
|
+
@payload = payload
|
14
|
+
else
|
15
|
+
@payload_hash = payload
|
16
|
+
end
|
17
|
+
|
18
|
+
@path = ['/', @indices.join(','), @types.join(','), '_search'].compact.join('/').squeeze('/')
|
19
|
+
end
|
20
|
+
|
21
|
+
def results
|
22
|
+
@json || (perform; @json)
|
23
|
+
end
|
24
|
+
|
25
|
+
def response
|
26
|
+
@response || (perform; @response)
|
27
|
+
end
|
28
|
+
|
29
|
+
def url
|
30
|
+
Configuration.url + @path
|
31
|
+
end
|
32
|
+
|
33
|
+
def perform
|
34
|
+
@response = Configuration.client.get(self.url, self.payload)
|
35
|
+
if @response.failure?
|
36
|
+
STDERR.puts "[REQUEST FAILED] #{self.to_curl}\n"
|
37
|
+
raise SearchRequestFailed, @response.to_s
|
38
|
+
end
|
39
|
+
@json = MultiJson.decode(@response.body)
|
40
|
+
return @json
|
41
|
+
ensure
|
42
|
+
logged
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_curl
|
46
|
+
%Q|curl -X GET #{url} -d '#{payload}'|
|
47
|
+
end
|
48
|
+
|
49
|
+
def payload
|
50
|
+
@payload || MultiJson.encode(@payload_hash)
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_hash
|
54
|
+
@hash_payload ||= MultiJson.decode(payload)
|
55
|
+
end
|
56
|
+
|
57
|
+
def logged(error=nil)
|
58
|
+
if Configuration.logger
|
59
|
+
|
60
|
+
Configuration.logger.log_request '_search', indices, to_curl
|
61
|
+
|
62
|
+
code = @response.code rescue nil
|
63
|
+
|
64
|
+
if Configuration.logger.level.to_s == 'debug'
|
65
|
+
# FIXME: Depends on RestClient implementation
|
66
|
+
body = if @json
|
67
|
+
defined?(Yajl) ? Yajl::Encoder.encode(@json, :pretty => true) : MultiJson.encode(@json)
|
68
|
+
else
|
69
|
+
@response.body rescue nil
|
70
|
+
end
|
71
|
+
else
|
72
|
+
body = ''
|
73
|
+
end
|
74
|
+
|
75
|
+
Configuration.logger.log_response code || 'N/A', "N/A", body || 'N/A'
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
data/lib/tire/state.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module Tire
|
3
|
+
class State
|
4
|
+
include Utils
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
|
8
|
+
end
|
9
|
+
|
10
|
+
def url
|
11
|
+
"#{Configuration.url}/cluster/_state"
|
12
|
+
end
|
13
|
+
|
14
|
+
def info(more = false)
|
15
|
+
desc_url = more ? "#{url}?more=true" : "#{url}?more=false"
|
16
|
+
@response = Configuration.client.get(desc_url)
|
17
|
+
if @response.success?
|
18
|
+
MultiJson.decode(@response.body)
|
19
|
+
else
|
20
|
+
[]
|
21
|
+
end
|
22
|
+
ensure
|
23
|
+
curl = %Q|curl -X GET #{desc_url}|
|
24
|
+
logged('CLUSTER_STATE', curl)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/tire/utils.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
module Tire
|
5
|
+
module Utils
|
6
|
+
|
7
|
+
def escape(s)
|
8
|
+
URI.encode_www_form_component(s.to_s)
|
9
|
+
end
|
10
|
+
|
11
|
+
def unescape(s)
|
12
|
+
s = s.to_s.respond_to?(:force_encoding) ? s.to_s.force_encoding(Encoding::UTF_8) : s.to_s
|
13
|
+
URI.decode_www_form_component(s)
|
14
|
+
end
|
15
|
+
|
16
|
+
def logged(endpoint='/', curl='')
|
17
|
+
if Configuration.logger
|
18
|
+
error = $!
|
19
|
+
|
20
|
+
Configuration.logger.log_request endpoint, @name, curl
|
21
|
+
|
22
|
+
code = @response ? @response.code : error.class rescue 200
|
23
|
+
|
24
|
+
if Configuration.logger.level.to_s == 'debug'
|
25
|
+
body = if @response
|
26
|
+
defined?(Yajl) ? Yajl::Encoder.encode(@response.body, :pretty => true) : MultiJson.encode(@response.body)
|
27
|
+
else
|
28
|
+
error.message rescue ''
|
29
|
+
end
|
30
|
+
else
|
31
|
+
body = ''
|
32
|
+
end
|
33
|
+
|
34
|
+
Configuration.logger.log_response code, nil, body
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
module_function :escape, :unescape
|
39
|
+
end
|
40
|
+
end
|
data/lib/tire/version.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'test_helper'
|
3
|
+
|
4
|
+
module Tire
|
5
|
+
|
6
|
+
class CountIntegrationTest < Test::Unit::TestCase
|
7
|
+
include Test::Integration
|
8
|
+
|
9
|
+
context "count" do
|
10
|
+
should "count results" do
|
11
|
+
setup_bulk
|
12
|
+
@index.refresh
|
13
|
+
count = Tire.count(INDEX, TYPE, '{"query":{"text":{"title":"java"}},"size":4,"from":0}')
|
14
|
+
num = count.results
|
15
|
+
assert_kind_of Integer, num
|
16
|
+
assert_equal 6, num
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'test_helper'
|
3
|
+
|
4
|
+
module Tire
|
5
|
+
|
6
|
+
class IndexIntegrationTest < Test::Unit::TestCase
|
7
|
+
include Test::Integration
|
8
|
+
|
9
|
+
context "index" do
|
10
|
+
|
11
|
+
should "regist_shard" do
|
12
|
+
assert @index.regist_shard(6)
|
13
|
+
end
|
14
|
+
|
15
|
+
should "not regist exist index" do
|
16
|
+
@index.regist_shard(6)
|
17
|
+
assert !@index.regist_shard(6)
|
18
|
+
end
|
19
|
+
|
20
|
+
should "delete index" do
|
21
|
+
assert @index.delete
|
22
|
+
end
|
23
|
+
|
24
|
+
should "get shard info" do
|
25
|
+
@index.regist_shard(6)
|
26
|
+
assert_kind_of Array, @index.shard_info
|
27
|
+
assert !@index.shard_info.empty?
|
28
|
+
end
|
29
|
+
|
30
|
+
should "create mapping" do
|
31
|
+
assert setup_mapping
|
32
|
+
end
|
33
|
+
|
34
|
+
should "get mapping info" do
|
35
|
+
setup_mapping
|
36
|
+
assert_kind_of Hash, @index.mapping(TYPE)
|
37
|
+
assert_not_nil @index.mapping(TYPE)[TYPE]
|
38
|
+
end
|
39
|
+
|
40
|
+
should "bulk insert" do
|
41
|
+
assert setup_bulk
|
42
|
+
end
|
43
|
+
|
44
|
+
should "flush index" do
|
45
|
+
setup_bulk
|
46
|
+
assert @index.flush
|
47
|
+
end
|
48
|
+
|
49
|
+
should "refresh index" do
|
50
|
+
setup_bulk
|
51
|
+
assert @index.refresh
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'test_helper'
|
3
|
+
|
4
|
+
module Tire
|
5
|
+
|
6
|
+
class SearchIntegrationTest < Test::Unit::TestCase
|
7
|
+
include Test::Integration
|
8
|
+
|
9
|
+
context "search" do
|
10
|
+
should "search results" do
|
11
|
+
setup_bulk
|
12
|
+
@index.refresh
|
13
|
+
search = Tire.search(INDEX, TYPE, '{"query":{"text":{"title":"java"}},"size":4,"from":0}')
|
14
|
+
results = search.results
|
15
|
+
assert_kind_of Hash, results
|
16
|
+
assert results["total"] > 0
|
17
|
+
assert_kind_of Array, results["hits"]
|
18
|
+
assert_equal 6, results["total"]
|
19
|
+
assert_equal 4, results["hits"].size
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'test_helper'
|
3
|
+
|
4
|
+
module Tire
|
5
|
+
|
6
|
+
class StateIntegrationTest < Test::Unit::TestCase
|
7
|
+
include Test::Integration
|
8
|
+
|
9
|
+
context "state" do
|
10
|
+
should "get short state" do
|
11
|
+
assert_kind_of Array, Tire.state.info
|
12
|
+
assert_kind_of Hash, Tire.state.info.first
|
13
|
+
end
|
14
|
+
|
15
|
+
should "get more state" do
|
16
|
+
assert_kind_of Array, Tire.state.info(true)
|
17
|
+
assert_kind_of Hash, Tire.state.info(true).first
|
18
|
+
assert_not_nil Tire.state.info(true).first["blog"]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'rubygems'
|
3
|
+
require 'bundler/setup'
|
4
|
+
|
5
|
+
require 'test/unit'
|
6
|
+
|
7
|
+
require 'yajl'
|
8
|
+
#require 'yajl/json_gem'
|
9
|
+
|
10
|
+
require 'shoulda'
|
11
|
+
require 'turn/autorun' unless defined?(RUBY_VERSION) && RUBY_VERSION < '1.9'
|
12
|
+
require 'mocha'
|
13
|
+
|
14
|
+
require 'tire'
|
15
|
+
|
16
|
+
class Test::Unit::TestCase
|
17
|
+
|
18
|
+
def mock_response(body, code=200, headers={})
|
19
|
+
Tire::HTTP::Response.new(body, code, headers)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
module Test::Integration
|
25
|
+
URL = "http://192.168.6.35:9400"
|
26
|
+
INDEX = "articles_test"
|
27
|
+
TYPE = "type_test"
|
28
|
+
|
29
|
+
def setup
|
30
|
+
ENV['ELASTICSEARCH_URL'] = URL
|
31
|
+
@index = Tire.index(INDEX)
|
32
|
+
end
|
33
|
+
|
34
|
+
def setup_shard
|
35
|
+
@index.regist_shard(6)
|
36
|
+
end
|
37
|
+
|
38
|
+
def setup_mapping
|
39
|
+
mapping = {TYPE => {"_source"=>{"enabled"=>false},
|
40
|
+
"properties"=>
|
41
|
+
{"title"=>
|
42
|
+
{"type"=>"string",
|
43
|
+
"term_vector"=>"with_positions_offsets",
|
44
|
+
"boost"=>2.0},
|
45
|
+
"body"=>{"type"=>"string", "term_vector"=>"with_positions_offsets"},
|
46
|
+
"username"=>{"type"=>"string", "index"=>"not_analyzed", "store"=>"no"},
|
47
|
+
"id"=>
|
48
|
+
{"type"=>"integer", "index"=>"not_analyzed", "include_in_all"=>false},
|
49
|
+
"created_at"=>
|
50
|
+
{"type"=>"integer", "index"=>"not_analyzed", "include_in_all"=>false}
|
51
|
+
}
|
52
|
+
}
|
53
|
+
}
|
54
|
+
setup_shard
|
55
|
+
@index.create_mapping(TYPE, mapping)
|
56
|
+
end
|
57
|
+
|
58
|
+
def setup_bulk
|
59
|
+
doc = [
|
60
|
+
{"title"=>"java 是好东西",
|
61
|
+
"body"=>"hey java",
|
62
|
+
"id"=>"1",
|
63
|
+
"username"=>"jack",
|
64
|
+
"created_at"=>2007072323},
|
65
|
+
{"title"=>"this java cool",
|
66
|
+
"body"=>"hey java",
|
67
|
+
"id"=>"2",
|
68
|
+
"created_at"=>2009072323,
|
69
|
+
"username"=>"robbin"},
|
70
|
+
{"title"=>"this is java cool",
|
71
|
+
"body"=>"hey java",
|
72
|
+
"id"=>"3",
|
73
|
+
"created_at"=>2010072323,
|
74
|
+
"username"=>"www"},
|
75
|
+
{"title"=>"java is really cool",
|
76
|
+
"body"=>"hey java",
|
77
|
+
"id"=>"4",
|
78
|
+
"created_at"=>2007062323,
|
79
|
+
"username"=>"google"},
|
80
|
+
{"title"=>"this is wakak cool",
|
81
|
+
"body"=>"hey java",
|
82
|
+
"id"=>"5",
|
83
|
+
"created_at"=>2007062323,
|
84
|
+
"username"=>"jackde"},
|
85
|
+
{"title"=>"this is java cool",
|
86
|
+
"body"=>"hey java",
|
87
|
+
"id"=>"6",
|
88
|
+
"created_at"=>2007012323,
|
89
|
+
"username"=>"jackk wa"},
|
90
|
+
{"title"=>"this java really cool",
|
91
|
+
"body"=>"hey java",
|
92
|
+
"id"=>"7",
|
93
|
+
"created_at"=>2002072323,
|
94
|
+
"username"=>"william"}
|
95
|
+
]
|
96
|
+
setup_mapping
|
97
|
+
@index.bulk(TYPE, doc)
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
def teardown
|
102
|
+
::RestClient.delete "#{URL}/#{INDEX}" rescue nil
|
103
|
+
end
|
104
|
+
end
|