tiny_ds 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,180 @@
1
+ =begin
2
+ 使い方
3
+ class TxMoveUserPoint < BaseTx
4
+ def src_phase(src, args)
5
+ src[:point] -= args[:amount]
6
+ end
7
+ def dest_phase(dest, args)
8
+ dest[:point] += args[:amount]
9
+ end
10
+ end
11
+
12
+ u1 = User.create(:point=>1000)
13
+ => #<User @sid=21 @name=nil @message=nil @point=1000 @created_at=Thu, 17 Dec 2009 21:14:29 +0900 @updated_at=Thu, 17 Dec 2009 21:14:29 +0900>
14
+
15
+ u2 = User.create(:point=>1000)
16
+ => #<User @sid=22 @name=nil @message=nil @point=1000 @created_at=Thu, 17 Dec 2009 21:14:33 +0900 @updated_at=Thu, 17 Dec 2009 21:14:33 +0900>
17
+
18
+ TxMoveUserPoint.exec(u1.__entity__, u2.__entity__, :amount=>200)
19
+ => true
20
+
21
+ u1.reload
22
+ => #<User @sid=21 @name=nil @message=nil @point=800 @created_at=Thu, 17 Dec 2009 21:14:29 +0900 @updated_at=Thu, 17 Dec 2009 21:14:29 +0900>
23
+
24
+ u2.reload
25
+ => #<User @sid=22 @name=nil @message=nil @point=1200 @created_at=Thu, 17 Dec 2009 21:14:33 +0900 @updated_at=Thu, 17 Dec 2009 21:14:33 +0900>
26
+
27
+ TxMoveUserPoint.roll_forward_all
28
+ =end
29
+ # memo デプロイ時に未完了のトランザクションがあってトランザクション定義がかわったら?version???
30
+
31
+ class BaseTx
32
+ LowDS = TinyDS::LowDS
33
+
34
+ def src_phase(src, args)
35
+ raise "no impl."
36
+ end
37
+ def dest_phase(dest, args)
38
+ raise "no impl."
39
+ end
40
+ def self.tx_kind
41
+ name
42
+ end
43
+ def roll_forward_retries_limit
44
+ 100
45
+ end
46
+ attr_reader :tx_key
47
+
48
+ # トランザクション実行
49
+ # : src_phase dest_phase
50
+ # raised : failed ----
51
+ # false : OK failed
52
+ # true : OK OK
53
+ def self.exec(src, dest, args={})
54
+ tx = new
55
+ tx.create_tx(src, dest, args)
56
+ begin
57
+ tx.roll_forward
58
+ rescue AppEngine::Datastore::TransactionFailed => e
59
+ return false
60
+ end
61
+ return true
62
+ end
63
+
64
+ def self.roll_forward_all(limit=50)
65
+ pending_tx_query.each(:limit=>limit) do |tx_ent|
66
+ tx = new
67
+ tx.restore_tx(tx_ent)
68
+ begin
69
+ tx.roll_forward
70
+ rescue => e
71
+ # logger.warn("roll_forward failed. tx=[#{tx.tx_key}] e=[#{e.inspect}]")
72
+ end
73
+ end
74
+ nil
75
+ end
76
+
77
+ def self.pending_tx_query(status="pending", dire=:asc) # pending/done/failed
78
+ direction = dire==:desc ? AppEngine::Datastore::Query::DESCENDING : AppEngine::Datastore::Query::ASCENDING
79
+ AppEngine::Datastore::Query.new("TxSrc").
80
+ filter(:tx_kind, AppEngine::Datastore::Query::EQUAL, tx_kind).
81
+ filter(:status, AppEngine::Datastore::Query::EQUAL, status).
82
+ sort(:created_at, direction)
83
+ end
84
+
85
+ # doneなTxSrcと対応するTxDoneを削除する
86
+ # def self.delete_done_tx
87
+ # end
88
+
89
+ require "benchmark"
90
+ def create_tx(src, dest, args)
91
+ #RAILS_DEFAULT_LOGGER.info ["create_tx", Benchmark.measure{
92
+ _create_tx(src, dest, args)
93
+ #}].inspect
94
+ end
95
+ def roll_forward
96
+ #RAILS_DEFAULT_LOGGER.info ["roll_forward", Benchmark.measure{
97
+ _roll_forward
98
+ #}].inspect
99
+ end
100
+
101
+ # トランザクション前半
102
+ # TODO TxIDを指定できるようにする。TxSrc#key.nameにTxIDを指定して重複実行防止
103
+ def _create_tx(src, dest, args)
104
+ tx_ent = nil
105
+ ds_transaction{
106
+ src = src.class.get(src.key)
107
+ src_phase(src, args)
108
+ src.save
109
+
110
+ attrs = {
111
+ :tx_kind => self.class.tx_kind,
112
+ :dest_key => dest.key.to_s,
113
+ :status => "pending",
114
+ :roll_forward_failed_count => 0,
115
+ :args => args.to_yaml,
116
+ :created_at => Time.now
117
+ }
118
+ tx_ent = LowDS.create("TxSrc", attrs, :parent=>src.entity) # srcがparent, tx_entがchild
119
+ # COMMIT:「srcの処理(=src_phase)、TxSrc作成」
120
+ }
121
+ @tx_key = tx_ent.key.to_s
122
+ nil
123
+ end
124
+
125
+ def restore_tx(tx_ent)
126
+ @tx_key = tx_ent.key.to_s
127
+ nil
128
+ end
129
+
130
+ # トランザクション後半
131
+ def _roll_forward
132
+ tx_ent = LowDS.get(@tx_key, :kind=>"TxSrc")
133
+
134
+ ds_transaction{
135
+ dest_key = LowDS::KeyFactory.stringToKey(tx_ent[:dest_key])
136
+ dest = dest_key.kind.constantize.get(dest_key)
137
+ done_name = "TxDone_#{@tx_key}"
138
+ done_key = LowDS::KeyFactory.createKey(dest.key, "TxDone", done_name)
139
+ begin
140
+ LowDS.get(done_key, "TxDone")
141
+ # なにもしない : TxDoneが存在しているということはdest_phaseは処理済み
142
+ rescue AppEngine::Datastore::EntityNotFound => e
143
+ # TxDoneが無い→dest_phaseが未実行
144
+ attrs = {:done_at=>Time.now}
145
+ done_ent = LowDS.create("TxDone", attrs, :parent=>dest.entity, :name=>done_name)
146
+ dest_phase(dest, YAML.load(tx_ent[:args])) # destの処理を実行
147
+ dest.save
148
+ end
149
+ # memo: done_keyが同じTxDoneをcommitしようとするとTransactionFailedになるはず→dest_phaseもキャンセル
150
+ # COMMIT:「destの処理(=dest_phase)、TxDone作成」
151
+ }
152
+
153
+ # TxSrc#statusをdoneに
154
+ ds_transaction{
155
+ tx_ent = LowDS.get(@tx_key, :kind=>"TxSrc")
156
+ if tx_ent[:status]=="pending"
157
+ tx_ent[:status] = "done"
158
+ tx_ent[:done_at] = Time.now
159
+ LowDS.save(tx_ent)
160
+ end
161
+ }
162
+ return true
163
+ rescue
164
+ ds_transaction{
165
+ tx_ent = LowDS.get(@tx_key, :kind=>"TxSrc")
166
+ tx_ent[:roll_forward_failed_count] += 1
167
+ if roll_forward_retries_limit < tx_ent[:roll_forward_failed_count]
168
+ tx_ent[:status] = "failed"
169
+ end
170
+ LowDS.save(tx_ent)
171
+ }
172
+ return false
173
+ end
174
+
175
+ # EntityGroup単位のtransaction
176
+ def ds_transaction(&block)
177
+ retries = 3
178
+ AppEngine::Datastore.transaction(retries, &block)
179
+ end
180
+ end
@@ -0,0 +1,77 @@
1
+ require 'appengine-apis/datastore'
2
+
3
+ module TinyDS
4
+ module LowDS
5
+ KeyFactory = com.google.appengine.api.datastore.KeyFactory
6
+
7
+ # build
8
+ def self.build(kind, attrs, opts={})
9
+ raise "invalid kind=#{kind}" unless kind.kind_of?(String)
10
+ raise "invalid opts=#{opts.inspect}" if [:id, :name, :key].collect{|k| opts[k] }.compact.size >= 2
11
+ raise ":id must be Integer" if opts[:id] && !opts[:id].kind_of?(Integer)
12
+ raise ":name must be String" if opts[:name] && !opts[:name].kind_of?(String)
13
+ raise ":key must be Key or String" if opts[:key] && !(opts[:key].kind_of?(String) || opts[:key].kind_of?(AppEngine::Datastore::Key))
14
+ name_or_id = opts[:name] || opts[:id] # name(String) or id(Integer)
15
+ key = opts[:key].kind_of?(String) ? KeyFactory.stringToKey(opts[:key]) : opts[:key]
16
+ parent = opts[:parent]
17
+ ent = if key
18
+ AppEngine::Datastore::Entity.new(key)
19
+ elsif parent
20
+ parent_key = case parent
21
+ when AppEngine::Datastore::Entity; parent.key
22
+ when AppEngine::Datastore::Key; parent
23
+ when String; KeyFactory.stringToKey(parent)
24
+ else raise "invalid parent type parent=[#{parent.inspect}]"
25
+ end
26
+ if name_or_id
27
+ new_key = KeyFactory.createKey(parent_key, kind, name_or_id)
28
+ AppEngine::Datastore::Entity.new(new_key)
29
+ else
30
+ AppEngine::Datastore::Entity.new(kind, parent_key)
31
+ end
32
+ else
33
+ if name_or_id
34
+ new_key = KeyFactory.createKey(kind, name_or_id)
35
+ AppEngine::Datastore::Entity.new(new_key)
36
+ else
37
+ AppEngine::Datastore::Entity.new(kind)
38
+ end
39
+ end
40
+ attrs.each do |k,v|
41
+ ent[k] = v
42
+ end
43
+ ent
44
+ end
45
+
46
+ # create
47
+ def self.create(kind, attrs, opts={})
48
+ ent = build(kind, attrs, opts)
49
+ AppEngine::Datastore.put(ent)
50
+ ent
51
+ end
52
+
53
+ # get by key
54
+ def self.get(key, opts={})
55
+ key = case key
56
+ when AppEngine::Datastore::Key; key
57
+ when String; KeyFactory.stringToKey(key)
58
+ else raise "invalid key type key.class=[#{key.class}] key.inspect=[#{key.inspect}]"
59
+ end
60
+ ent = AppEngine::Datastore.get(key)
61
+ if opts[:kind]
62
+ raise "kind missmatch. #{ent.kind}!=#{opts[:kind]}" if ent.kind!=opts[:kind]
63
+ end
64
+ ent
65
+ end
66
+
67
+ # update
68
+ def self.save(ent)
69
+ AppEngine::Datastore.put(ent)
70
+ end
71
+
72
+ # delete
73
+ def self.delete(ent)
74
+ AppEngine::Datastore.delete([ent.key])
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,80 @@
1
+ module LowDS
2
+ KeyFactory = com.google.appengine.api.datastore.KeyFactory
3
+
4
+ # create
5
+ def self.build(kind, attrs, opts={})
6
+ raise "invalid opts=#{opts.inspect}" if [:id, :name, :key].collect{|k| opts[k] }.compact.size >= 2
7
+ raise ":id must be Integer" if opts[:id] && !opts[:id].kind_of?(Integer)
8
+ raise ":name must be String" if opts[:name] && !opts[:name].kind_of?(String)
9
+ raise ":key must be Key or String" if opts[:key] && !(opts[:key].kind_of?(String) || opts[:key].kind_of?(AppEngine::Datastore::Key))
10
+ name_or_id = opts[:name] || opts[:id] # name(String) or id(Integer)
11
+ key = opts[:key].kind_of?(String) ? KeyFactory.stringToKey(parent) : opts[:key]
12
+ parent = opts[:parent]
13
+ ent = if key
14
+ AppEngine::Datastore::Entity.new(key)
15
+ elsif parent
16
+ parent_key = case parent
17
+ when AppEngine::Datastore::Entity; parent.key
18
+ when AppEngine::Datastore::Key; parent
19
+ when String; KeyFactory.stringToKey(parent)
20
+ else raise "invalid parent type parent=[#{parent.inspect}]"
21
+ end
22
+ if name_or_id
23
+ new_key = KeyFactory.createKey(parent_key, kind, name_or_id)
24
+ AppEngine::Datastore::Entity.new(new_key)
25
+ else
26
+ AppEngine::Datastore::Entity.new(kind, parent_key)
27
+ end
28
+ else
29
+ if name_or_id
30
+ new_key = KeyFactory.createKey(kind, name_or_id)
31
+ AppEngine::Datastore::Entity.new(new_key)
32
+ else
33
+ AppEngine::Datastore::Entity.new(kind)
34
+ end
35
+ end
36
+ attrs.each do |k,v|
37
+ ent[k] = v
38
+ end
39
+ ent
40
+ end
41
+
42
+ def self.create(kind, attrs, opts={})
43
+ ent = build(kind, attrs, opts)
44
+ AppEngine::Datastore.put(ent)
45
+ ent
46
+ end
47
+
48
+ # get by key
49
+ def self.get(key, opts={})
50
+ key = case key
51
+ when AppEngine::Datastore::Key; key
52
+ when String; KeyFactory.stringToKey(key)
53
+ else raise "invalid key type key.class=[#{key.class}] key.inspect=[#{key.inspect}]"
54
+ end
55
+ ent = AppEngine::Datastore.get(key)
56
+ if opts[:kind]
57
+ raise "kind missmatch. #{ent.kind}!=#{opts[:kind]}" if ent.kind!=opts[:kind]
58
+ end
59
+ ent
60
+ end
61
+
62
+ # search
63
+ def self.search
64
+ # TODO
65
+ end
66
+
67
+ # update
68
+ def self.save(ent)
69
+ AppEngine::Datastore.put(ent)
70
+ end
71
+
72
+ # delete
73
+ def self.delete(ent)
74
+ AppEngine::Datastore.delete(ent.key)
75
+ end
76
+
77
+ def self.tx(retries=0, &block)
78
+ AppEngine::Datastore.transaction(retries, &block)
79
+ end
80
+ end
@@ -0,0 +1,68 @@
1
+ module TinyDS
2
+ class PropertyDefinition
3
+ def initialize(pname, ptype, opts)
4
+ @pname = pname
5
+ @ptype = ptype
6
+ @opts = opts
7
+ end
8
+ def default_value
9
+ raise "no default." unless has_default?
10
+ default = @opts[:default]
11
+ case default
12
+ when Proc
13
+ default.call
14
+ else
15
+ default
16
+ end
17
+ end
18
+ def has_default?
19
+ @opts.has_key?(:default)
20
+ end
21
+ def to_ds_value(v)
22
+ case @ptype
23
+ when :string
24
+ v.nil? ? nil : v.to_s
25
+ when :integer
26
+ v.nil? ? nil : v.to_i
27
+ when :text
28
+ v.nil? ? nil : com.google.appengine.api.datastore::Text.new(v.to_s)
29
+ when :time
30
+ case v
31
+ when Time
32
+ v
33
+ when NilClass
34
+ nil
35
+ else
36
+ raise "not Time value."
37
+ end
38
+ when :list
39
+ case v
40
+ when Array
41
+ v.empty? ? nil : v
42
+ when NilClass
43
+ nil
44
+ else
45
+ raise "not Array value."
46
+ end
47
+ else
48
+ raise "unknown property type '#{@ptype}'"
49
+ end
50
+ end
51
+ def to_ruby_value(ds_v)
52
+ case @ptype
53
+ when :string
54
+ ds_v.nil? ? nil : ds_v.to_s
55
+ when :integer
56
+ ds_v.nil? ? nil : ds_v.to_i
57
+ when :text
58
+ ds_v.nil? ? nil : ds_v.to_s
59
+ when :time
60
+ ds_v.nil? ? nil : ds_v
61
+ when :list
62
+ ds_v.nil? ? [] : ds_v.to_a
63
+ else
64
+ raise "unknown property type '#{@ptype}'"
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,54 @@
1
+ module TinyDS
2
+ class PropertyDefinition
3
+ def initialize(pname, ptype, opts)
4
+ @pname = pname
5
+ @ptype = ptype
6
+ @opts = opts
7
+ end
8
+ def default_value
9
+ if @opts.has_key?(:default)
10
+ default = @opts[:default]
11
+ case default
12
+ when Proc
13
+ default.call
14
+ else
15
+ default
16
+ end
17
+ end
18
+ end
19
+ def to_ds_value(v)
20
+ return nil if v.nil?
21
+ case @ptype
22
+ when :string
23
+ v.to_s
24
+ when :integer
25
+ v.to_i
26
+ when :text
27
+ com.google.appengine.api.datastore::Text.new(java.lang.String.new(v))
28
+ when :time
29
+ Time.parse(v.to_s)
30
+ #when :list
31
+ # raise "todo"
32
+ else
33
+ raise "unknown type @ptype=#{@ptype}"
34
+ end
35
+ end
36
+ def to_ruby_value(ds_v)
37
+ return nil if ds_v.nil?
38
+ case @ptype
39
+ when :string
40
+ ds_v.to_s
41
+ when :integer
42
+ ds_v.to_i
43
+ when :text
44
+ ds_v.to_s
45
+ when :time
46
+ Time.parse(ds_v.to_s)
47
+ #when :list
48
+ # raise "todo"
49
+ else
50
+ raise "unknown type @ptype=#{@ptype}"
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,76 @@
1
+ module TinyDS
2
+ class Query
3
+ def initialize(model_class)
4
+ @model_class = model_class
5
+ @q = AppEngine::Datastore::Query.new(@model_class.kind)
6
+ end
7
+ def ancestor(anc)
8
+ anc = case anc
9
+ when Base; anc.key
10
+ when String; LowDS::KeyFactory.stringToKey(anc)
11
+ when AppEngine::Datastore::Key; anc
12
+ else raise "unknown type. anc=#{anc.inspect}"
13
+ end
14
+ @q.set_ancestor(anc)
15
+ self
16
+ end
17
+ def filter(*args)
18
+ if args.size==1 && args.first.kind_of?(Hash)
19
+ args.first.each do |k,v|
20
+ filter(k,"==",v)
21
+ end
22
+ else
23
+ name, operator, value = *args
24
+ @model_class.property_definition(name) # check exist?
25
+ @q.filter(name, operator, value)
26
+ end
27
+ self
28
+ end
29
+ def sort(name, dir=:asc)
30
+ @model_class.property_definition(name) # check exist?
31
+ direction = {
32
+ :asc => AppEngine::Datastore::Query::ASCENDING,
33
+ :desc => AppEngine::Datastore::Query::DESCENDING
34
+ }[dir]
35
+ @q.sort(name, direction)
36
+ self
37
+ end
38
+ def keys_only
39
+ @q.java_query.setKeysOnly
40
+ self
41
+ end
42
+ def count #todo(tx=nil)
43
+ @q.count
44
+ end
45
+ def one #todo(tx=nil)
46
+ @model_class.new_from_entity(@q.entity)
47
+ end
48
+ def all(opts={}) #todo(tx=nil)
49
+ models = []
50
+ @q.each(opts) do |entity|
51
+ models << @model_class.new_from_entity(entity)
52
+ end
53
+ models
54
+ end
55
+ def each(opts={}) #todo(tx=nil)
56
+ @q.each(opts) do |entity|
57
+ yield(@model_class.new_from_entity(entity))
58
+ end
59
+ end
60
+ def collect(opts={}) #todo(tx=nil)
61
+ collected = []
62
+ @q.each(opts) do |entity|
63
+ collected << yield(@model_class.new_from_entity(entity))
64
+ end
65
+ collected
66
+ end
67
+ def keys(opts={}) #todo(tx=nil)
68
+ keys = []
69
+ self.keys_only
70
+ @q.each(opts) do |entity|
71
+ keys << entity.key
72
+ end
73
+ keys
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,68 @@
1
+ module TinyDS
2
+ class Query
3
+ def initialize(model_class)
4
+ @model_class = model_class
5
+ @q = AppEngine::Datastore::Query.new(@model_class.kind)
6
+ end
7
+ def ancestor(anc)
8
+ anc = case anc
9
+ when Base; anc.key
10
+ when String; LowDS::KeyFactory.stringToKey(anc)
11
+ when AppEngine::Datastore::Key; anc
12
+ else raise "unknown type. anc=#{anc.inspect}"
13
+ end
14
+ @q.set_ancestor(anc)
15
+ self
16
+ end
17
+ def filter(name, operator, value)
18
+ @q.filter(name, operator, value)
19
+ self
20
+ end
21
+ def sort(name, dir=:asc)
22
+ direction = {
23
+ :asc => AppEngine::Datastore::Query::ASCENDING,
24
+ :desc => AppEngine::Datastore::Query::DESCENDING
25
+ }[dir]
26
+ @q.sort(name, direction)
27
+ self
28
+ end
29
+ def keys_only
30
+ @q.java_query.setKeysOnly
31
+ self
32
+ end
33
+
34
+ def count #todo(tx=nil)
35
+ @q.count
36
+ end
37
+ def one #todo(tx=nil)
38
+ @model_class.new_from_entity(@q.entity)
39
+ end
40
+ def all(opts={}) #todo(tx=nil)
41
+ models = []
42
+ @q.each(opts) do |entity|
43
+ models << @model_class.new_from_entity(entity)
44
+ end
45
+ models
46
+ end
47
+ def each(opts={}) #todo(tx=nil)
48
+ @q.each(opts) do |entity|
49
+ yield(@model_class.new_from_entity(entity))
50
+ end
51
+ end
52
+ def collect(opts={}) #todo(tx=nil)
53
+ collected = []
54
+ @q.each(opts) do |entity|
55
+ collected << yield(@model_class.new_from_entity(entity))
56
+ end
57
+ collected
58
+ end
59
+ def keys(opts={}) #todo(tx=nil)
60
+ keys = []
61
+ self.keys_only
62
+ @q.each(opts) do |entity|
63
+ keys << entity.key
64
+ end
65
+ keys
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,26 @@
1
+ module TinyDS
2
+ class Base
3
+ def valid?
4
+ true # todo
5
+ end
6
+ def self.create!(attrs={}, opts={})
7
+ m = new(attrs, opts)
8
+ m.save!
9
+ m
10
+ end
11
+ def save
12
+ save!
13
+ true
14
+ rescue RecordInvalid => e
15
+ false
16
+ end
17
+ def save!
18
+ unless valid?
19
+ raise RecordInvalid
20
+ end
21
+ do_save
22
+ end
23
+ end
24
+ class RecordInvalid < StandardError
25
+ end
26
+ end
@@ -0,0 +1,9 @@
1
+
2
+ module TinyDS
3
+ module Validations
4
+ def save
5
+ end
6
+ def save!
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ module TinyDS
2
+ VERSION = '0.0.1'.freeze
3
+ end
data/lib/tiny_ds.rb ADDED
@@ -0,0 +1,16 @@
1
+ require 'appengine-apis'
2
+ require 'appengine-apis/datastore'
3
+ require File.dirname(__FILE__)+"/tiny_ds/low_ds.rb"
4
+ require File.dirname(__FILE__)+"/tiny_ds/property_definition.rb"
5
+ require File.dirname(__FILE__)+"/tiny_ds/base.rb"
6
+ require File.dirname(__FILE__)+"/tiny_ds/query.rb"
7
+ require File.dirname(__FILE__)+"/tiny_ds/validations.rb"
8
+ require File.dirname(__FILE__)+"/tiny_ds/base_tx.rb"
9
+
10
+ module TinyDS
11
+ def self.tx(retries=0, &block)
12
+ AppEngine::Datastore.transaction(retries){
13
+ yield(block)
14
+ }
15
+ end
16
+ end
data/lib/tiny_ds.rb~ ADDED
@@ -0,0 +1,2 @@
1
+ module TinyDS
2
+ end
data/lib/tinyds.rb~ ADDED
@@ -0,0 +1,2 @@
1
+ module TinyDs
2
+ end