kyototycoon 0.1.0

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/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