kyototycoon 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ gem "rspec", "~> 2.1.0"
10
+ gem "bundler", "~> 1.0.0"
11
+ gem "jeweler", "~> 1.5.1"
12
+ gem "rcov", ">= 0"
13
+ gem "msgpack", ">= 0"
14
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,30 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.1.2)
5
+ git (1.2.5)
6
+ jeweler (1.5.1)
7
+ bundler (~> 1.0.0)
8
+ git (>= 1.2.5)
9
+ rake
10
+ msgpack (0.4.4)
11
+ rake (0.8.7)
12
+ rcov (0.9.9)
13
+ rspec (2.1.0)
14
+ rspec-core (~> 2.1.0)
15
+ rspec-expectations (~> 2.1.0)
16
+ rspec-mocks (~> 2.1.0)
17
+ rspec-core (2.1.0)
18
+ rspec-expectations (2.1.0)
19
+ diff-lcs (~> 1.1.2)
20
+ rspec-mocks (2.1.0)
21
+
22
+ PLATFORMS
23
+ ruby
24
+
25
+ DEPENDENCIES
26
+ bundler (~> 1.0.0)
27
+ jeweler (~> 1.5.1)
28
+ msgpack
29
+ rcov
30
+ rspec (~> 2.1.0)
data/MIT-LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2010 uu59<a@tt25.org>
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,95 @@
1
+ # Install
2
+
3
+ $ gem install kyototycoon
4
+
5
+ # Example
6
+
7
+ ## Simple case
8
+
9
+ @kt = KyotoTycoon.new
10
+
11
+ # traditional style
12
+ @kt.set('foo', 123)
13
+ p @kt.get('foo') # => "123". carefully, it is String, not Integer, by default
14
+
15
+ # Ruby's hash style
16
+ @kt['bar'] = 42
17
+ p @kt['bar'] # => "42".
18
+ @kt[:baz] = :aaa # => key/value are automatically #to_s
19
+
20
+ @kt.delete(:foo)
21
+
22
+
23
+ ## Complex case
24
+
25
+ @kt = KyotoTycoon.new('remotehost', 12345) # connect any host, any port
26
+ @kt.db = '*' # on memory, or DB file as KT known
27
+
28
+ # set/bulk_set/get/bulk_get uses msgpack. default as :default
29
+ @kt.serializer = :msgpack
30
+
31
+ # KT library logging
32
+ logger = Logger.new(STDERR)
33
+ logger.level = Logger::WARN
34
+ @kt.logger = logger
35
+ # or you can use these:
36
+ # @kt.logger = 'ktlib.log'
37
+ # @kt.logger = STDOUT
38
+ # @kt.logger = Logger.new(STDOUT)
39
+
40
+ # HTTP agent
41
+ @kt.agent = :skinny # low-level socket communicate. a bit of faster than :nethttp(default). try benchmark/agent.rb
42
+
43
+ # standby server
44
+ @kt.connect_timeout = 0.5 # => wait 0.5 sec for connection open
45
+ @kt.servers << ['server2', 1978] # standby server that will use when primary server (a.k.a. KT#new(host, port)) is dead.
46
+ @kt.servers << ['server3', 1978] # same as above
47
+
48
+ # get/set
49
+ @kt.set('foo', 42, 100) # => expire at 100 seconds after
50
+ @kt['foo'] # => 42. it is interger by msgpack serializer works
51
+
52
+ # delete all record
53
+ @kt.clear
54
+
55
+ # bulk set/get
56
+ @kt.set_bulk({
57
+ 'foo' => 'foo',
58
+ 'bar' => 'bar',
59
+ })
60
+ @kt.get_bulk([:foo, 'bar']) # => {'_foo' => 'foo', '_bar' => 'bar', 'num' => '2'}
61
+ @kt.remove_bulk([:foo, :bar])
62
+
63
+ # it can store when msgpack using.
64
+ @kt['baz'] = {'a' => 'a', 'b' => 'b}
65
+ @kt['baz'] # => {'a' => 'a', 'b' => 'b}
66
+
67
+ # increment
68
+ @kt.increment('bar') # => 1
69
+ @kt.increment('bar') # => 2
70
+ @kt.increment('bar', 10) # => 12
71
+ @kt.increment('bar', -5) # => 7
72
+
73
+ # delete keys
74
+ @kt.delete(:foo, :bar, :baz)
75
+
76
+ # prefix keys
77
+ @kt.match_prefix('fo') # => all start with 'fo' keys
78
+ @kt.match_regex('^fo') # => save as above
79
+ @kt.match_regex(/^fo/) # => save as above
80
+
81
+ # reporting / statistics
82
+ p @kt.report
83
+ p @kt.status
84
+ all_record_count = @kt.status['count']
85
+
86
+ # Requirements
87
+
88
+ - msgpack(optional)
89
+
90
+ # Trap
91
+
92
+ KyotoTycoon is based on HTTP so all variable types are become String.
93
+ It means `(@kt["counter"] ||= 1) < 10` does not work by default.
94
+
95
+ Using :msgpack serializer for avoid this trap.
data/Rakefile ADDED
@@ -0,0 +1,50 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
+ gem.name = "kyototycoon"
16
+ gem.homepage = "http://github.com/uu59/kyototycoon-ruby"
17
+ gem.license = "MIT"
18
+ gem.summary = %Q{KyotoTycoon client for Ruby}
19
+ gem.description = %Q{KyotoTycoon client for Ruby}
20
+ gem.email = "a@tt25.org"
21
+ gem.authors = ["uu59"]
22
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
23
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
24
+ # gem.add_runtime_dependency 'jabber4r', '> 0.1'
25
+ # gem.add_development_dependency 'rspec', '> 1.2.3'
26
+ end
27
+ Jeweler::RubygemsDotOrgTasks.new
28
+
29
+ require 'rspec/core'
30
+ require 'rspec/core/rake_task'
31
+ RSpec::Core::RakeTask.new(:spec) do |spec|
32
+ spec.pattern = FileList['spec/*.rb']
33
+ end
34
+
35
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
36
+ spec.pattern = 'spec/**/*_spec.rb'
37
+ spec.rcov = true
38
+ end
39
+
40
+ task :default => :spec
41
+
42
+ require 'rake/rdoctask'
43
+ Rake::RDocTask.new do |rdoc|
44
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
45
+
46
+ rdoc.rdoc_dir = 'rdoc'
47
+ rdoc.title = "test #{version}"
48
+ rdoc.rdoc_files.include('README*')
49
+ rdoc.rdoc_files.include('lib/**/*.rb')
50
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,27 @@
1
+ # -- coding: utf-8
2
+
3
+ require "rubygems"
4
+ require "benchmark"
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ require 'kyototycoon.rb'
7
+
8
+ kt = KyotoTycoon.new
9
+ job = lambda {|kt|
10
+ 10000.times{|n|
11
+ kt.set(n.to_s, n)
12
+ kt.get(n)
13
+ }
14
+ kt.clear
15
+ }
16
+ Benchmark.bm do |x|
17
+ x.report('skinny') {
18
+ kt.agent = :skinny
19
+ kt.serializer=:default
20
+ job.call(kt)
21
+ }
22
+ x.report('nethttp') {
23
+ kt.agent = :nethttp
24
+ kt.serializer=:default
25
+ job.call(kt)
26
+ }
27
+ end
data/benchmark/bulk.rb ADDED
@@ -0,0 +1,37 @@
1
+ # -- coding: utf-8
2
+
3
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
4
+ require "rubygems"
5
+ require "benchmark"
6
+ require 'kyototycoon.rb'
7
+
8
+ kt = KyotoTycoon.new
9
+ bulk={}
10
+ 50000.times.map{|n|
11
+ bulk[n.to_s] = "#{n}-#{rand}"
12
+ }
13
+ job = lambda {|kt|
14
+ kt.set_bulk(bulk)
15
+ kt.get_bulk(bulk.keys)
16
+ kt.clear
17
+ }
18
+ Benchmark.bm do |x|
19
+ x.report('default') {
20
+ kt.serializer=:default
21
+ job.call(kt)
22
+ }
23
+ x.report('msgpack') {
24
+ kt.serializer=:msgpack
25
+ job.call(kt)
26
+ }
27
+ x.report('default(skinny)') {
28
+ kt.agent = :skinny
29
+ kt.serializer=:default
30
+ job.call(kt)
31
+ }
32
+ x.report('msgpack(skinny)') {
33
+ kt.agent = :skinny
34
+ kt.serializer=:msgpack
35
+ job.call(kt)
36
+ }
37
+ end
@@ -0,0 +1,28 @@
1
+ # -- coding: utf-8
2
+
3
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
4
+ require "rubygems"
5
+ require "benchmark"
6
+ require 'kyototycoon.rb'
7
+
8
+ kt = KyotoTycoon.new
9
+ bulk={}
10
+ str = "string ああ" * 10000
11
+ 100.times.map{|n|
12
+ bulk[n.to_s] = str
13
+ }
14
+ job = lambda {|kt|
15
+ kt.set_bulk(bulk)
16
+ kt.get_bulk(bulk.keys)
17
+ kt.clear
18
+ }
19
+ Benchmark.bm do |x|
20
+ x.report('default') {
21
+ kt.serializer=:default
22
+ job.call(kt)
23
+ }
24
+ x.report('msgpack') {
25
+ kt.serializer=:msgpack
26
+ job.call(kt)
27
+ }
28
+ end
@@ -0,0 +1,37 @@
1
+ # -- coding: utf-8
2
+
3
+ require "rubygems"
4
+ require "benchmark"
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ require 'kyoto_tycoon.rb'
7
+
8
+ kt = KyotoTycoon.new
9
+ job = lambda {|kt|
10
+ 1000.times{|n|
11
+ kt.set(n.to_s, n)
12
+ kt.get(n)
13
+ }
14
+ kt.clear
15
+ }
16
+ Benchmark.bm do |x|
17
+ x.report('default') {
18
+ kt.agent = :nethttp
19
+ kt.serializer=:default
20
+ job.call(kt)
21
+ }
22
+ x.report('msgpack') {
23
+ kt.agent = :nethttp
24
+ kt.serializer=:msgpack
25
+ job.call(kt)
26
+ }
27
+ x.report('default(skinny)') {
28
+ kt.agent = :skinny
29
+ kt.serializer=:default
30
+ job.call(kt)
31
+ }
32
+ x.report('msgpack(skinny)') {
33
+ kt.agent = :skinny
34
+ kt.serializer=:msgpack
35
+ job.call(kt)
36
+ }
37
+ end
@@ -0,0 +1,33 @@
1
+ # -- coding: utf-8
2
+
3
+ require "rubygems"
4
+ require "benchmark"
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ require "msgpack"
7
+ require 'kyototycoon.rb'
8
+
9
+ job = lambda {|kt|
10
+ cnt = 0
11
+ begin
12
+ timeout(1){
13
+ loop do
14
+ kt[:foo] = :bar
15
+ kt[:foo]
16
+ cnt += 1
17
+ end
18
+ }
19
+ rescue Timeout::Error
20
+ end
21
+ kt.clear
22
+ cnt
23
+ }
24
+
25
+ kt = KyotoTycoon.new
26
+
27
+ %w"nethttp skinny".each{|agent|
28
+ %w!default msgpack!.each{|serializer|
29
+ kt.agent = agent.to_sym
30
+ kt.serializer = serializer.to_sym
31
+ puts "#{agent}/#{serializer}: #{job.call(kt)}"
32
+ }
33
+ }
@@ -0,0 +1,74 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{kyototycoon}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["uu59"]
12
+ s.date = %q{2010-12-09}
13
+ s.description = %q{KyotoTycoon client for Ruby}
14
+ s.email = %q{a@tt25.org}
15
+ s.extra_rdoc_files = [
16
+ "README.markdown"
17
+ ]
18
+ s.files = [
19
+ "Gemfile",
20
+ "Gemfile.lock",
21
+ "MIT-LICENSE",
22
+ "README.markdown",
23
+ "Rakefile",
24
+ "VERSION",
25
+ "benchmark/agent.rb",
26
+ "benchmark/bulk.rb",
27
+ "benchmark/bulk_bigdata.rb",
28
+ "benchmark/getset.rb",
29
+ "benchmark/getset_while_1sec.rb",
30
+ "kyototycoon.gemspec",
31
+ "lib/kyototycoon.rb",
32
+ "lib/kyototycoon/serializer.rb",
33
+ "lib/kyototycoon/serializer/default.rb",
34
+ "lib/kyototycoon/serializer/msgpack.rb",
35
+ "lib/kyototycoon/tsvrpc.rb",
36
+ "lib/kyototycoon/tsvrpc/nethttp.rb",
37
+ "lib/kyototycoon/tsvrpc/skinny.rb",
38
+ "spec/helper.rb"
39
+ ]
40
+ s.homepage = %q{http://github.com/uu59/kyototycoon-ruby}
41
+ s.licenses = ["MIT"]
42
+ s.require_paths = ["lib"]
43
+ s.rubygems_version = %q{1.3.7}
44
+ s.summary = %q{KyotoTycoon client for Ruby}
45
+ s.test_files = [
46
+ "spec/helper.rb"
47
+ ]
48
+
49
+ if s.respond_to? :specification_version then
50
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
51
+ s.specification_version = 3
52
+
53
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
54
+ s.add_development_dependency(%q<rspec>, ["~> 2.1.0"])
55
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
56
+ s.add_development_dependency(%q<jeweler>, ["~> 1.5.1"])
57
+ s.add_development_dependency(%q<rcov>, [">= 0"])
58
+ s.add_development_dependency(%q<msgpack>, [">= 0"])
59
+ else
60
+ s.add_dependency(%q<rspec>, ["~> 2.1.0"])
61
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
62
+ s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
63
+ s.add_dependency(%q<rcov>, [">= 0"])
64
+ s.add_dependency(%q<msgpack>, [">= 0"])
65
+ end
66
+ else
67
+ s.add_dependency(%q<rspec>, ["~> 2.1.0"])
68
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
69
+ s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
70
+ s.add_dependency(%q<rcov>, [">= 0"])
71
+ s.add_dependency(%q<msgpack>, [">= 0"])
72
+ end
73
+ end
74
+
@@ -0,0 +1,15 @@
1
+ # -- coding: utf-8
2
+
3
+ class KyotoTycoon
4
+ module Serializer
5
+ class Default
6
+ def self.encode(obj)
7
+ obj
8
+ end
9
+
10
+ def self.decode(str)
11
+ str
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,18 @@
1
+ # -- coding: utf-8
2
+
3
+ require "rubygems"
4
+ require "msgpack"
5
+
6
+ class KyotoTycoon
7
+ module Serializer
8
+ class Msgpack
9
+ def self.encode(obj)
10
+ obj.to_msgpack
11
+ end
12
+
13
+ def self.decode(str)
14
+ MessagePack.unpack(str) if str
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ # -- coding: utf-8
2
+
3
+ class KyotoTycoon
4
+ module Serializer
5
+ def self.get(adaptor)
6
+ dir = "#{File.dirname(__FILE__)}/serializer"
7
+ if File.exists?(File.join(dir, "#{adaptor}.rb"))
8
+ require "#{dir}/#{adaptor}.rb"
9
+ end
10
+ const_get(adaptor.to_s.capitalize)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,28 @@
1
+ # -- coding: utf-8
2
+
3
+ require "rubygems"
4
+
5
+ class KyotoTycoon
6
+ class Tsvrpc
7
+ class Nethttp
8
+ def initialize(host, port)
9
+ @host = host
10
+ @port = port
11
+ @http ||= ::Net::HTTP.new(@host, @port)
12
+ end
13
+
14
+ def request(path, params, colenc)
15
+ query = KyotoTycoon::Tsvrpc.build_query(params, colenc)
16
+ req = Net::HTTP::Post.new(path)
17
+ if query.length > 0
18
+ req.body = query
19
+ req['Content-Length'] = query.size
20
+ end
21
+ req['Content-Type'] = "text/tab-separated-values; colenc=#{colenc}"
22
+ req['Connection'] = "close"
23
+ res = @http.request(req)
24
+ [res.code.to_i, res.body]
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,40 @@
1
+ # -- coding: utf-8
2
+
3
+ class KyotoTycoon
4
+ class Tsvrpc
5
+ class Skinny
6
+ def initialize(host, port)
7
+ @host = host
8
+ @port = port
9
+ @tpl = ""
10
+ @tpl << "POST %s HTTP/1.0\r\n"
11
+ @tpl << "Content-Length: %d\r\n"
12
+ @tpl << "Content-Type: text/tab-separated-values; colenc=%s\r\n"
13
+ @tpl << "\r\n%s"
14
+ end
15
+
16
+ def request(path, params, colenc)
17
+ sock ||= ::TCPSocket.new(@host, @port)
18
+ query = KyotoTycoon::Tsvrpc.build_query(params, colenc)
19
+ request = @tpl % [path, query.bytesize, colenc, query]
20
+ sock.write(request)
21
+ status = sock.gets[9, 3]
22
+ bodylen = 0
23
+ body = ""
24
+ loop do
25
+ line = sock.gets
26
+ if line['Content-Length']
27
+ bodylen = line.match(/[0-9]+/)[0].to_i
28
+ next
29
+ end
30
+ if line == "\r\n"
31
+ break
32
+ end
33
+ end
34
+ body = sock.read(bodylen)
35
+ sock.close
36
+ [status.to_i, body]
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,55 @@
1
+ # -- coding: utf-8
2
+
3
+
4
+ class KyotoTycoon
5
+ class Tsvrpc
6
+ def initialize(host, port)
7
+ @host = host
8
+ @port = port
9
+ end
10
+
11
+ def http(agent)
12
+ case agent
13
+ when :skinny
14
+ Skinny.new(@host, @port)
15
+ else
16
+ Nethttp.new(@host, @port)
17
+ end
18
+ end
19
+
20
+ def request(path, params, agent, colenc)
21
+ status,body = *http(agent).request(path, params, colenc)
22
+ if ![200, 450].include?(status)
23
+ raise body
24
+ end
25
+ {:status => status, :body => body}
26
+ end
27
+
28
+ def self.parse(body)
29
+ body.split("\n").inject({}){|r, line|
30
+ k,v = *line.split("\t", 2).map{|v| CGI.unescape(v)}
31
+ r[k] = v
32
+ r
33
+ }
34
+ end
35
+
36
+ def self.build_query(params, colenc='U')
37
+ query = ""
38
+ if params
39
+ case colenc.to_s.upcase.to_sym
40
+ when :U
41
+ query = params.inject([]){|r, tmp|
42
+ r << tmp.map{|v| CGI.escape(v.to_s)}.join("\t")
43
+ }.join("\r\n")
44
+ when :B
45
+ query = params.inject([]){|r, tmp|
46
+ r << tmp.map{|v| Base64.encode64(v.to_s).rstrip}.join("\t")
47
+ }.join("\r\n")
48
+ else
49
+ raise "Unknown colenc '#{colenc}'"
50
+ end
51
+ end
52
+ query
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,232 @@
1
+ # -- coding: utf-8
2
+
3
+ require "logger"
4
+ require "cgi"
5
+ require "socket"
6
+ require "base64"
7
+ require "net/http"
8
+ require "kyototycoon/serializer.rb"
9
+ require "kyototycoon/serializer/default.rb"
10
+ require "kyototycoon/tsvrpc.rb"
11
+ require "kyototycoon/tsvrpc/skinny.rb"
12
+ require "kyototycoon/tsvrpc/nethttp.rb"
13
+
14
+ class KyotoTycoon
15
+ attr_accessor :colenc, :connect_timeout, :servers
16
+
17
+ def initialize(host='0.0.0.0', port=1978)
18
+ @servers = [[host, port]]
19
+ @serializer = KyotoTycoon::Serializer::Default
20
+ @logger = Logger.new(nil)
21
+ @agent = :nethttp
22
+ @colenc = :U
23
+ @connect_timeout = 0.5
24
+ end
25
+
26
+ def serializer= (adaptor=:default)
27
+ klass = KyotoTycoon::Serializer.get(adaptor)
28
+ @serializer = klass
29
+ end
30
+
31
+ def db= (db)
32
+ @db = db
33
+ end
34
+
35
+ def logger= (logger)
36
+ if logger.class != Logger
37
+ logger = Logger.new(logger)
38
+ end
39
+ @logger = logger
40
+ end
41
+
42
+ def agent=(agent)
43
+ @agent = agent
44
+ end
45
+
46
+ def get(key)
47
+ res = request('/rpc/get', {:key => key})
48
+ @serializer.decode(Tsvrpc.parse(res[:body])['value'])
49
+ end
50
+ alias_method :[], :get
51
+
52
+ def remove(*keys)
53
+ remove_bulk(keys.flatten)
54
+ end
55
+ alias_method :delete, :remove
56
+
57
+ def set(key, value, xt=nil)
58
+ res = request('/rpc/set', {:key => key, :value => @serializer.encode(value), :xt => xt})
59
+ Tsvrpc.parse(res[:body])
60
+ end
61
+ alias_method :[]=, :set
62
+
63
+ def add(key, value, xt=nil)
64
+ res = request('/rpc/add', {:key => key, :value => @serializer.encode(value), :xt => xt})
65
+ Tsvrpc.parse(res[:body])
66
+ end
67
+
68
+ def replace(key, value, xt=nil)
69
+ res = request('/rpc/replace', {:key => key, :value => @serializer.encode(value), :xt => xt})
70
+ Tsvrpc.parse(res[:body])
71
+ end
72
+
73
+ def append(key, value, xt=nil)
74
+ request('/rpc/append', {:key => key, :value => @serializer.encode(value), :xt => xt})
75
+ end
76
+
77
+ def cas(key, oldval, newval, xt=nil)
78
+ res = request('/rpc/cas', {:key => key, :oval=> @serializer.encode(oldval), :nval => @serializer.encode(newval), :xt => xt})
79
+ case res[:status].to_i
80
+ when 200
81
+ true
82
+ when 450
83
+ false
84
+ end
85
+ end
86
+
87
+ def increment(key, num=1, xt=nil)
88
+ res = request('/rpc/increment', {:key => key, :num => num, :xt => xt})
89
+ Tsvrpc.parse(res[:body])['num'].to_i
90
+ end
91
+
92
+ def increment_double(key, num, xt=nil)
93
+ res = request('/rpc/increment_double', {:key => key, :num => num, :xt => xt})
94
+ Tsvrpc.parse(res[:body])['num'].to_f
95
+ end
96
+
97
+ def set_bulk(records)
98
+ # records={'a' => 'aa', 'b' => 'bb'}
99
+ values = {}
100
+ records.each{|k,v|
101
+ values["_#{k}"] = @serializer.encode(v)
102
+ }
103
+ res = request('/rpc/set_bulk', values)
104
+ Tsvrpc.parse(res[:body])
105
+ end
106
+
107
+ def get_bulk(keys)
108
+ params = keys.inject({}){|params, k|
109
+ params[k.to_s.match(/^_/) ? k.to_s : "_#{k}"] = ''
110
+ params
111
+ }
112
+ res = request('/rpc/get_bulk', params)
113
+ ret = {}
114
+ Tsvrpc.parse(res[:body]).each{|k,v|
115
+ ret[k] = k.match(/^_/) ? @serializer.decode(v) : v
116
+ }
117
+ ret
118
+ end
119
+
120
+ def remove_bulk(keys)
121
+ params = keys.inject({}){|params, k|
122
+ params[k.to_s.match(/^_/) ? k.to_s : "_#{k}"] = ''
123
+ params
124
+ }
125
+ res = request('/rpc/remove_bulk', params)
126
+ Tsvrpc.parse(res[:body])
127
+ end
128
+
129
+ def clear
130
+ request('/rpc/clear')
131
+ end
132
+
133
+ def vacuum
134
+ request('/rpc/vacuum')
135
+ end
136
+
137
+ def sync(params={})
138
+ request('/rpc/synchronize', params)
139
+ end
140
+ alias_method :syncronize, :sync
141
+
142
+ def echo(value)
143
+ res = request('/rpc/echo', value)
144
+ Tsvrpc.parse(res[:body])
145
+ end
146
+
147
+ def report
148
+ res = request('/rpc/report')
149
+ Tsvrpc.parse(res[:body])
150
+ end
151
+
152
+ def status
153
+ res = request('/rpc/status')
154
+ Tsvrpc.parse(res[:body])
155
+ end
156
+
157
+ def match_prefix(prefix)
158
+ res = request('/rpc/match_prefix', {:prefix => prefix})
159
+ keys = []
160
+ Tsvrpc.parse(res[:body]).each{|k,v|
161
+ if k != 'num'
162
+ keys << k[1, k.length]
163
+ end
164
+ }
165
+ keys
166
+ end
167
+
168
+ def match_regex(re)
169
+ if re.class == Regexp
170
+ re = re.source
171
+ end
172
+ res = request('/rpc/match_regex', {:regex => re})
173
+ keys = []
174
+ Tsvrpc.parse(res[:body]).each{|k,v|
175
+ if k != 'num'
176
+ keys << k[1, k.length]
177
+ end
178
+ }
179
+ keys
180
+ end
181
+
182
+ def keys
183
+ match_prefix("")
184
+ end
185
+
186
+ def request(path, params=nil)
187
+ if @db
188
+ params ||= {}
189
+ params[:DB] = @db
190
+ end
191
+ if @servers.length > 1
192
+ @servers.each{|s|
193
+ host,port = *s
194
+ if ping(host, port)
195
+ @servers = [[host, port]]
196
+ break
197
+ end
198
+ }
199
+ end
200
+ if @servers.length == 0
201
+ msg = "alived server not exists"
202
+ @logger.crit(msg)
203
+ raise msg
204
+ end
205
+ @tsvrpc ||= begin
206
+ host, port = *@servers.first
207
+ Tsvrpc.new(host, port)
208
+ end
209
+ res = @tsvrpc.request(path, params, @agent, @colenc)
210
+ @logger.info("#{path}: #{res[:code]} with query parameters #{params.inspect}")
211
+ res
212
+ end
213
+
214
+ def ping(host, port)
215
+ begin
216
+ timeout(@connect_timeout){
217
+ @logger.debug("connect check #{host}:#{port}")
218
+ rpc = Tsvrpc.new(host, port)
219
+ res = rpc.request('/rpc/echo', {'0' => '0'}, :skinny, :U)
220
+ @logger.debug(res)
221
+ }
222
+ true
223
+ rescue Timeout::Error => ex
224
+ # Ruby 1.8.7 compatible
225
+ @logger.warn("connect failed at #{host}:#{port}")
226
+ false
227
+ rescue => ex
228
+ @logger.warn("connect failed at #{host}:#{port}")
229
+ false
230
+ end
231
+ end
232
+ end
data/spec/helper.rb ADDED
@@ -0,0 +1,122 @@
1
+ # -- coding: utf-8
2
+
3
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + "/../lib")
4
+ require "rubygems"
5
+ require "kyototycoon.rb"
6
+
7
+ describe do
8
+ before(:all) do
9
+ @kt = KyotoTycoon.new
10
+ @kt.serializer=:default # or :msgpack
11
+ @kt.db='*' # in memory
12
+ @kt.logger=nil
13
+ end
14
+
15
+ before(:each) do
16
+ @kt.clear
17
+ end
18
+
19
+ it 'should handle multi servers' do
20
+ kt = KyotoTycoon.new('www.example.com', 11111)
21
+ kt.connect_timeout = 0.1
22
+ kt.servers << ['example.net', 1978]
23
+ kt.servers << ['0.0.0.0', 1978]
24
+ kt['foo'] = 'bar'
25
+ kt[:foo].should == 'bar'
26
+ end
27
+
28
+ it 'should provide simple kvs feature' do
29
+ @kt.set('a', 'b')
30
+ @kt.get('a').should == 'b'
31
+ @kt['foo'] = 'bar'
32
+ @kt['foo'].should == 'bar'
33
+ @kt.delete('foo')
34
+ @kt['foo'].should be_nil
35
+ @kt.clear
36
+ @kt.report['db_total_count'].to_i.should == 0
37
+ @kt.status['count'].to_i.should == 0
38
+
39
+ @kt['foo'] = 'oldbaz'
40
+ @kt.cas('foo', 'oldbaz', 'newbaz').should be_true
41
+ @kt.cas('foo', 'oldbaz', 'newbaz').should be_false
42
+ @kt.get('foo').should == 'newbaz'
43
+ @kt.clear
44
+
45
+ @kt['foo'] ||= 'aaa'
46
+ @kt['foo'] ||= 'bbb'
47
+ @kt['foo'].should == 'aaa'
48
+ @kt.clear
49
+
50
+ @kt[:a] = 1
51
+ @kt[:b] = 1
52
+ @kt.keys.sort.should == %w!a b!.sort
53
+ end
54
+
55
+ it 'should provide bulk' do
56
+ data = {}
57
+ receive = {}
58
+ 10.times{|n|
59
+ data[n.to_s] = n.to_s
60
+ receive["_#{n}"] = n.to_s
61
+ }
62
+ receive['num'] = "10"
63
+ @kt.set_bulk(data)
64
+ @kt.get_bulk(data.keys).sort.should == receive.sort
65
+ @kt.remove_bulk(data.keys)
66
+ @kt.get_bulk(data.keys).should == {'num' => '0'}
67
+ end
68
+
69
+ it 'should can handle strange key/value' do
70
+ # '+' is known ambiguity key on URL encode/decode processing
71
+ %w!a\tb a\nb a\r\nb a*-b a^@b!.each{|outlaw|
72
+ @kt[outlaw] = outlaw
73
+ @kt[outlaw].should == outlaw
74
+ }
75
+ end
76
+
77
+ it 'should provide delete variation' do
78
+ build = lambda {|kt|
79
+ kt.clear
80
+ kt.set_bulk({
81
+ :a => 1,
82
+ :b => 1,
83
+ :c => 1,
84
+ })
85
+ }
86
+ build.call(@kt)
87
+ @kt.delete('a','b')
88
+ @kt.keys.should == ['c']
89
+
90
+ build.call(@kt)
91
+ @kt.delete(['a', 'b'])
92
+ @kt.keys.should == ['c']
93
+ end
94
+
95
+ it 'should increment' do
96
+ @kt.increment('foo').should == 1
97
+ @kt.increment('foo').should == 2
98
+ @kt.increment('foo').should == 3
99
+ @kt.increment('foo', 10).should == 13
100
+ @kt.increment('foo', -10).should == 3
101
+ end
102
+
103
+ it 'should provide status/report' do
104
+ @kt[:a] = 1
105
+ @kt[:b] = 2
106
+ @kt.report['db_total_count'].to_i.should == 2
107
+ @kt.status['count'].to_i.should == 2
108
+ end
109
+
110
+ it 'should match prefixes' do
111
+ @kt['123'] = 1
112
+ @kt['124'] = 1
113
+ @kt['125'] = 1
114
+ @kt['999'] = 1
115
+ @kt['9999'] = 1
116
+ @kt.match_prefix("12").sort.should == %w!123 124 125!.sort
117
+ @kt.match_prefix("9").sort.should == %w!999 9999!.sort
118
+ @kt.match_prefix("9999").sort.should == %w!9999!
119
+ @kt.match_regex(/^12/).sort.should == %w!123 124 125!.sort
120
+ @kt.match_regex(/^9+$/).sort.should == %w!999 9999!.sort
121
+ end
122
+ end
metadata ADDED
@@ -0,0 +1,154 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kyototycoon
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - uu59
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-12-09 00:00:00 +09:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rspec
22
+ requirement: &id001 !ruby/object:Gem::Requirement
23
+ none: false
24
+ requirements:
25
+ - - ~>
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 2
29
+ - 1
30
+ - 0
31
+ version: 2.1.0
32
+ type: :development
33
+ prerelease: false
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: bundler
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ~>
41
+ - !ruby/object:Gem::Version
42
+ segments:
43
+ - 1
44
+ - 0
45
+ - 0
46
+ version: 1.0.0
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: *id002
50
+ - !ruby/object:Gem::Dependency
51
+ name: jeweler
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ~>
56
+ - !ruby/object:Gem::Version
57
+ segments:
58
+ - 1
59
+ - 5
60
+ - 1
61
+ version: 1.5.1
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: *id003
65
+ - !ruby/object:Gem::Dependency
66
+ name: rcov
67
+ requirement: &id004 !ruby/object:Gem::Requirement
68
+ none: false
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ segments:
73
+ - 0
74
+ version: "0"
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: *id004
78
+ - !ruby/object:Gem::Dependency
79
+ name: msgpack
80
+ requirement: &id005 !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ segments:
86
+ - 0
87
+ version: "0"
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: *id005
91
+ description: KyotoTycoon client for Ruby
92
+ email: a@tt25.org
93
+ executables: []
94
+
95
+ extensions: []
96
+
97
+ extra_rdoc_files:
98
+ - README.markdown
99
+ files:
100
+ - Gemfile
101
+ - Gemfile.lock
102
+ - MIT-LICENSE
103
+ - README.markdown
104
+ - Rakefile
105
+ - VERSION
106
+ - benchmark/agent.rb
107
+ - benchmark/bulk.rb
108
+ - benchmark/bulk_bigdata.rb
109
+ - benchmark/getset.rb
110
+ - benchmark/getset_while_1sec.rb
111
+ - kyototycoon.gemspec
112
+ - lib/kyototycoon.rb
113
+ - lib/kyototycoon/serializer.rb
114
+ - lib/kyototycoon/serializer/default.rb
115
+ - lib/kyototycoon/serializer/msgpack.rb
116
+ - lib/kyototycoon/tsvrpc.rb
117
+ - lib/kyototycoon/tsvrpc/nethttp.rb
118
+ - lib/kyototycoon/tsvrpc/skinny.rb
119
+ - spec/helper.rb
120
+ has_rdoc: true
121
+ homepage: http://github.com/uu59/kyototycoon-ruby
122
+ licenses:
123
+ - MIT
124
+ post_install_message:
125
+ rdoc_options: []
126
+
127
+ require_paths:
128
+ - lib
129
+ required_ruby_version: !ruby/object:Gem::Requirement
130
+ none: false
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ hash: 999236175897460152
135
+ segments:
136
+ - 0
137
+ version: "0"
138
+ required_rubygems_version: !ruby/object:Gem::Requirement
139
+ none: false
140
+ requirements:
141
+ - - ">="
142
+ - !ruby/object:Gem::Version
143
+ segments:
144
+ - 0
145
+ version: "0"
146
+ requirements: []
147
+
148
+ rubyforge_project:
149
+ rubygems_version: 1.3.7
150
+ signing_key:
151
+ specification_version: 3
152
+ summary: KyotoTycoon client for Ruby
153
+ test_files:
154
+ - spec/helper.rb