ccp 0.3.5 → 0.4.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.
@@ -20,18 +20,19 @@ Gem::Specification.new do |s|
20
20
  s.require_paths = ["lib"]
21
21
 
22
22
  if RUBY_VERSION >= "1.9"
23
- s.add_dependency "activesupport"
23
+ s.add_runtime_dependency "activesupport"
24
24
  else
25
- s.add_dependency "activesupport", "~> 3.2.0"
25
+ s.add_runtime_dependency "activesupport", "~> 3.2.0"
26
26
  end
27
27
 
28
- s.add_dependency "typed", ">= 0.2.2"
29
- s.add_dependency "must", ">= 0.3.0"
30
- s.add_dependency "dsl_accessor", ">= 0.4.1"
31
- s.add_dependency "json"
32
- s.add_dependency "yajl-ruby"
33
- s.add_dependency "msgpack", "> 0.4"
34
- s.add_dependency "tokyocabinet", "~> 1.29.1"
28
+ s.add_runtime_dependency "typed", ">= 0.2.2"
29
+ s.add_runtime_dependency "must", ">= 0.3.0"
30
+ s.add_runtime_dependency "dsl_accessor", ">= 0.4.1"
31
+ s.add_runtime_dependency "json"
32
+ s.add_runtime_dependency "yajl-ruby"
33
+ s.add_runtime_dependency "msgpack", "> 0.4"
35
34
 
36
35
  s.add_development_dependency "rspec"
36
+ s.add_development_dependency "tokyocabinet", "~> 1.29.1"
37
+ s.add_development_dependency "kyotocabinet-ruby", "~> 1.27.1"
37
38
  end
@@ -10,6 +10,14 @@ module Ccp
10
10
 
11
11
  DICTIONARY = {} # cache for (extname -> Kvs)
12
12
 
13
+ def self.load(path)
14
+ array = path.to_s.split(".")
15
+ kvs = Ccp::Kvs[array.pop].new(path)
16
+ codec = Ccp::Serializers[array.pop]
17
+ kvs.codec!(codec)
18
+ return kvs
19
+ end
20
+
13
21
  include Enumerable
14
22
  delegate :delete, :to=>"DICTIONARY"
15
23
 
@@ -39,8 +47,6 @@ module Ccp
39
47
  end
40
48
 
41
49
  require 'ccp/kvs/hash'
42
- require 'ccp/kvs/tokyo'
43
50
  require 'ccp/kvs/tch'
51
+ require 'ccp/kvs/kch'
44
52
 
45
- Ccp::Kvs << Ccp::Kvs::Hash
46
- Ccp::Kvs << Ccp::Kvs::Tch
@@ -35,12 +35,6 @@ module Ccp
35
35
  # bulk operation
36
36
  def read ; keys.inject({}){|h,k| h[k] = get(k); h } ; end
37
37
  def write(h) ; h.each_pair{|k,v| set(k,v)} ; end
38
-
39
- # backward compat (until 0.3.6)
40
- def read!
41
- STDERR.puts "DEPRECATION WARNING: #{self.class}#read! will be removed in 0.3.6, use read instead"
42
- read
43
- end
44
38
  end
45
39
  end
46
40
  end
@@ -16,3 +16,5 @@ module Ccp
16
16
  end
17
17
  end
18
18
  end
19
+
20
+ Ccp::Kvs << Ccp::Kvs::Hash
@@ -0,0 +1,33 @@
1
+ begin
2
+ require 'kyotocabinet'
3
+ rescue LoadError
4
+ load_error = true
5
+ end
6
+
7
+ unless load_error
8
+ require 'ccp/kvs/kyoto'
9
+
10
+ module Ccp
11
+ module Kvs
12
+ class Kch < Kyoto::Cabinet
13
+ # core
14
+ def get(k) ; R{ super }; end
15
+ def set(k,v) ; W{ super }; end
16
+ def del(k) ; W{ super }; end
17
+ def count ; R{ super }; end
18
+ def read ; R{ super }; end
19
+ def write(h) ; W{ super }; end
20
+
21
+ # enum
22
+ def each(&b) ; R{ super }; end
23
+ def each_pair(&b) ; R{ super }; end
24
+ def each_key(&b) ; R{ super }; end
25
+ def keys ; R{ super }; end
26
+ def first_key ; R{ super }; end
27
+ def first ; R{ super }; end
28
+ end
29
+ end
30
+ end
31
+
32
+ Ccp::Kvs << Ccp::Kvs::Kch
33
+ end
@@ -0,0 +1,17 @@
1
+ require 'kyotocabinet'
2
+
3
+ module Ccp
4
+ module Kvs
5
+ module Kyoto
6
+ Error = Class.new(Ccp::Kvs::Error)
7
+ Locked = Class.new(Error)
8
+
9
+ CONNECTIONS = {}
10
+ end
11
+ end
12
+ end
13
+
14
+ require 'ccp/kvs/kyoto/base'
15
+ require 'ccp/kvs/kyoto/state_machine'
16
+ require 'ccp/kvs/kyoto/cabinet'
17
+
@@ -0,0 +1,60 @@
1
+ module Ccp
2
+ module Kvs
3
+ module Kyoto
4
+ class Base
5
+ include Ccp::Kvs::Core
6
+ include KyotoCabinet
7
+
8
+ ######################################################################
9
+ ### info
10
+
11
+ def info
12
+ raise "not implemented yet"
13
+ end
14
+
15
+ ######################################################################
16
+ ### kvs
17
+
18
+ def path
19
+ file = @source.to_s.sub(/#.*$/, '') # parse "foo.tch#mode=r"
20
+ Pathname(file)
21
+ end
22
+
23
+ private
24
+ # Check ecode and then raise. too boring... The library should implement this as atomic operation!
25
+ def atomic(&block)
26
+ raise NotImplementedError, "tc keep ecode until new erros occured"
27
+
28
+ if kyoto_error?
29
+ raise "tc already error before atomic: #{@db.ecode}"
30
+ end
31
+ v = block.call
32
+ kyoto_error! if kyoto_error?
33
+ return v
34
+ end
35
+
36
+ def kyoto_error!(label = nil)
37
+ raise Ccp::Kvs::Kyoto::Error, "%s%s (%s)" % [label, error_message, @source]
38
+ end
39
+
40
+ def kyoto_error?
41
+ @db.error.is_a?(KyotoCabinet::Error::XSUCCESS)
42
+ end
43
+
44
+ def threading_error?
45
+ false
46
+ end
47
+
48
+ def error_message
49
+ if @db
50
+ @db.error
51
+ else
52
+ '[Not Initialized]'
53
+ end
54
+ rescue Exception => e
55
+ "[BUG] #{e}"
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,137 @@
1
+ module Ccp
2
+ module Kvs
3
+ module Kyoto
4
+ class Cabinet < Base
5
+ include StateMachine
6
+
7
+ def initialize(source)
8
+ @source = source
9
+ @db = DB.new
10
+ end
11
+
12
+ ######################################################################
13
+ ### kvs
14
+
15
+ def get(k)
16
+ tryR("get")
17
+ v = @db[k.to_s]
18
+ if v
19
+ return decode(v)
20
+ else
21
+ if @db.error.is_a?(KyotoCabinet::Error::XNOREC)
22
+ return nil
23
+ else
24
+ kyoto_error!("get(%s): " % k)
25
+ end
26
+ end
27
+ end
28
+
29
+ def set(k,v)
30
+ tryW("set")
31
+ val = encode(v)
32
+ @db[k.to_s] = val or
33
+ kyoto_error!("set(%s): " % k)
34
+ end
35
+
36
+ def del(k)
37
+ tryW("del")
38
+ v = @db[k.to_s]
39
+ if v
40
+ if @db.delete(k.to_s)
41
+ return decode(v)
42
+ else
43
+ kyoto_error!("del(%s): " % k)
44
+ end
45
+ else
46
+ return nil
47
+ end
48
+ end
49
+
50
+ def exist?(k)
51
+ tryR("exist?")
52
+ return !! @db[k.to_s] # TODO: fast access
53
+ end
54
+
55
+ def count
56
+ tryR("count")
57
+ return @db.count
58
+ end
59
+
60
+ ######################################################################
61
+ ### bulk operations (not DRY but fast)
62
+
63
+ def read
64
+ tryR("read")
65
+ hash = {}
66
+ @db.each do |k, v|
67
+ v or kyoto_error!("each(%s): " % k)
68
+ hash[k] = decode(v)
69
+ end
70
+ return hash
71
+ end
72
+
73
+ def write(h)
74
+ tryW("write")
75
+ h.each_pair do |k,v|
76
+ val = encode(v)
77
+ @db[k.to_s] = val or kyoto_error!("write(%s): " % k)
78
+ end
79
+ return h
80
+ end
81
+
82
+ ######################################################################
83
+ ### iterator
84
+
85
+ def each(&block)
86
+ each_pair(&block)
87
+ end
88
+
89
+ def each_pair(&block)
90
+ tryR("each_pair")
91
+
92
+ # TODO: Waste memory! But kc ignores exceptions in his each block.
93
+ array = []
94
+ @db.each{|k, v| array << [k, decode(v)]} or kyoto_error!("each_pair: ")
95
+ array.each do |a|
96
+ block.call(a[0], a[1])
97
+ end
98
+ end
99
+
100
+ def each_key(&block)
101
+ tryR("each_key")
102
+
103
+ # TODO: Waste memory! But kc ignores exceptions in his each block.
104
+ array = []
105
+ @db.each_key{|k| array << k.first} or kyoto_error!("each_key: ")
106
+ array.each do |k|
107
+ block.call(k)
108
+ end
109
+ end
110
+
111
+ def keys
112
+ tryR("keys")
113
+
114
+ array = []
115
+ each_key do |key|
116
+ array << key
117
+ end
118
+ return array
119
+ end
120
+
121
+ def first_key
122
+ first.first
123
+ end
124
+
125
+ def first
126
+ tryR("first")
127
+ @db.cursor_process {|cur|
128
+ cur.jump
129
+ k, v = cur.get(true)
130
+ return [k, decode(v)]
131
+ }
132
+ end
133
+
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,142 @@
1
+ module Ccp
2
+ module Kvs
3
+ module Kyoto
4
+ module StateMachine
5
+ include KyotoCabinet
6
+
7
+ ######################################################################
8
+ ### state machine
9
+
10
+ CLOSED = 1
11
+ READABLE = 2
12
+ WRITABLE = 3
13
+
14
+ LOCKED_BY = proc{|c| Array(c).select{|i| i !~ %r{/ruby/[^/]+/gems/}}[0,5].join("\n") || c rescue c}
15
+
16
+ def state
17
+ @state || CLOSED
18
+ end
19
+
20
+ def locker_info
21
+ if CONNECTIONS[@source]
22
+ return LOCKED_BY[CONNECTIONS[@source]]
23
+ end
24
+
25
+ target = File.basename(@source)
26
+ CONNECTIONS.each_pair do |file, reason|
27
+ return LOCKED_BY[reason] if File.basename(file) == target
28
+ end
29
+
30
+ if CONNECTIONS.any?
31
+ return CONNECTIONS.inspect
32
+ else
33
+ return 'no brockers. maybe locked by other systems?'
34
+ end
35
+ end
36
+
37
+ def open(mode, locker = nil)
38
+ Pathname(@source.to_s).parent.mkpath
39
+
40
+ # open and mark filename for threading error
41
+ if @db.open(@source.to_s, mode)
42
+ locker ||= (caller rescue "???")
43
+ STDERR.puts "LOCK: #{@source} by [#{LOCKED_BY[locker]}]" if @debug
44
+ CONNECTIONS[@db.path.to_s] = locker
45
+ elsif threading_error?
46
+ raise Kyoto::Locked, "%s is locked by [%s]" % [@source, locker_info]
47
+ else
48
+ kyoto_error!("%s#open(%s,%s): " % [self.class, @source, mode])
49
+ end
50
+ end
51
+
52
+ def __close__(locker = nil)
53
+ @db.close
54
+ CONNECTIONS[@db.path] = nil
55
+ STDERR.puts "UNLOCK: #{@source} by [#{LOCKED_BY[locker || caller]}]" if @debug
56
+ end
57
+
58
+ def close(locker = nil)
59
+ C!(locker)
60
+ end
61
+
62
+ def C!(locker = nil)
63
+ case state
64
+ when CLOSED ; # NOP
65
+ when READABLE,
66
+ WRITABLE ; __close__(locker); @state = CLOSED
67
+ else ; raise "unknown state: #{state}"
68
+ end
69
+ end
70
+
71
+ def R!(locker = nil)
72
+ case state
73
+ when CLOSED ; open(DB::OREADER, locker); @state = READABLE
74
+ when READABLE ; # NOP
75
+ when WRITABLE ; # NOP
76
+ else ; raise "unknown state: #{state}"
77
+ end
78
+ end
79
+
80
+ def W!(locker = nil)
81
+ case state
82
+ when CLOSED ; open(DB::OCREATE | DB::OWRITER, locker); @state = WRITABLE
83
+ when READABLE ; C!(locker); W!(locker)
84
+ when WRITABLE ; # NOP
85
+ else ; raise "unknown state: #{state}"
86
+ end
87
+ end
88
+
89
+ def R(locker = nil, &block)
90
+ case state
91
+ when CLOSED ; begin; R!(locker); yield; ensure; close(locker); end
92
+ when READABLE ; yield
93
+ when WRITABLE ; yield
94
+ else ; raise "unknown state: #{state}"
95
+ end
96
+ end
97
+
98
+ def W(locker = nil, &block)
99
+ case state
100
+ when CLOSED ; begin; W!(locker); yield; ensure; close(locker); end
101
+ when READABLE ; raise "reopen from read to write is not permitted"
102
+ # TODO: close -> W -> close -> R ???
103
+ when WRITABLE ; yield
104
+ else ; raise "unknown state: #{state}"
105
+ end
106
+ end
107
+
108
+ def touch
109
+ W() {}
110
+ end
111
+
112
+ private
113
+ def isReadable
114
+ case state
115
+ when CLOSED ; false
116
+ when READABLE ; true
117
+ when WRITABLE ; true
118
+ else ; raise "unknown state: #{state}"
119
+ end
120
+ end
121
+
122
+ def isWritable
123
+ case state
124
+ when CLOSED ; false
125
+ when READABLE ; false
126
+ when WRITABLE ; true
127
+ else ; raise "unknown state: #{state}"
128
+ end
129
+ end
130
+
131
+ def tryR(op)
132
+ isReadable or raise NotAllowed, "use R! or R{tch.%s} (%s)" % [op, source]
133
+ end
134
+
135
+ def tryW(op)
136
+ isWritable or raise NotAllowed, "use W! or W{tch.%s} (%s)" % [op, source]
137
+ end
138
+
139
+ end
140
+ end
141
+ end
142
+ end