tiny_ds 0.0.1
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/LICENSE +20 -0
- data/README.rdoc +25 -0
- data/Rakefile +45 -0
- data/lib/tiny_ds/base.rb +290 -0
- data/lib/tiny_ds/base.rb~ +115 -0
- data/lib/tiny_ds/base_tx.rb +161 -0
- data/lib/tiny_ds/base_tx.rb~ +180 -0
- data/lib/tiny_ds/low_ds.rb +77 -0
- data/lib/tiny_ds/low_ds.rb~ +80 -0
- data/lib/tiny_ds/property_definition.rb +68 -0
- data/lib/tiny_ds/property_definition.rb~ +54 -0
- data/lib/tiny_ds/query.rb +76 -0
- data/lib/tiny_ds/query.rb~ +68 -0
- data/lib/tiny_ds/validations.rb +26 -0
- data/lib/tiny_ds/validations.rb~ +9 -0
- data/lib/tiny_ds/version.rb +3 -0
- data/lib/tiny_ds.rb +16 -0
- data/lib/tiny_ds.rb~ +2 -0
- data/lib/tinyds.rb~ +2 -0
- data/spec/basic_spec.rb +648 -0
- data/spec/spec_helper.rb +54 -0
- data/spec/spec_helper.rb~ +0 -0
- metadata +86 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009-2010 Takeru Sasaki, sasaki.takeru@gmail.com
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
* TinyDS -- tiny datastore library for GAE/JRuby
|
2
|
+
github: http://github.com/takeru/tiny_ds
|
3
|
+
|
4
|
+
- CRUD like a ActiveRecord or DataMapepr
|
5
|
+
- set parent, key, id, name, and find by these, YES entity-group-transaction!!
|
6
|
+
- BASE transaction : http://blog.notdot.net/2009/9/Distributed-Transactions-on-App-Engine
|
7
|
+
- query : very thin wrapper of low level API.
|
8
|
+
- Shout demo app. (sinatra)
|
9
|
+
- some specs..
|
10
|
+
|
11
|
+
** run shout demo
|
12
|
+
% dev_appserver.rb .
|
13
|
+
|
14
|
+
** how to run specs
|
15
|
+
install jruby 1.4.0
|
16
|
+
% source set_classpath_for_jruby.sh (set classpath, please read spec/spec_helper.rb)
|
17
|
+
% jruby -S spec -c -b spec/basic_spec.rb
|
18
|
+
or
|
19
|
+
% jruby -e "ENV['RSPEC']='true'; ENV['AUTOTEST']='true'; system('jruby -S autotest', *ARGV)"
|
20
|
+
|
21
|
+
** demo
|
22
|
+
Rubyist Social Graph : http://rubyist-sg.appspot.com/ is running with GAE/JRuby+Rails2.3.5+TinyDS.
|
23
|
+
|
24
|
+
** author
|
25
|
+
takeru : sasaki.takeru@gmail.com, twitter:@urekat (almost japanese!), blog:http://d.hatena.ne.jp/urekat
|
data/Rakefile
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/rdoctask'
|
3
|
+
require 'rake/gempackagetask'
|
4
|
+
require 'rubygems/specification'
|
5
|
+
require 'date'
|
6
|
+
|
7
|
+
require File.dirname(__FILE__) + '/lib/tiny_ds/version'
|
8
|
+
|
9
|
+
# set up pretty rdoc if possible
|
10
|
+
begin
|
11
|
+
gem 'rdoc'
|
12
|
+
require 'sdoc'
|
13
|
+
ENV['RDOCOPT'] = '-T lightblue'
|
14
|
+
rescue Exception
|
15
|
+
end
|
16
|
+
|
17
|
+
spec = Gem::Specification.new do |s|
|
18
|
+
s.name = "tiny_ds"
|
19
|
+
s.version = TinyDS::VERSION
|
20
|
+
s.platform = Gem::Platform::RUBY
|
21
|
+
s.has_rdoc = true
|
22
|
+
s.extra_rdoc_files = ["README.rdoc", "LICENSE"]
|
23
|
+
s.description = "Tiny datastore library for Google App Engine with JRuby"
|
24
|
+
s.summary = "Supports CRUD like a ActiveRecord or DataMapepr " +
|
25
|
+
"but with parent/child and entity-group-transaction"
|
26
|
+
s.author = "Takeru Sasaki"
|
27
|
+
s.email = "sasaki.takeru@gmail.com"
|
28
|
+
s.homepage = "http://github.com/takeru/tiny_ds"
|
29
|
+
s.require_path = 'lib'
|
30
|
+
s.files = %w(LICENSE README.rdoc Rakefile) +
|
31
|
+
Dir.glob("spec/**/*") + Dir.glob("lib/**/*")
|
32
|
+
s.add_dependency('appengine-apis')
|
33
|
+
end
|
34
|
+
|
35
|
+
task :default => :gem
|
36
|
+
|
37
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
38
|
+
pkg.gem_spec = spec
|
39
|
+
end
|
40
|
+
|
41
|
+
Rake::RDocTask.new do |rd|
|
42
|
+
rd.main = "README.rdoc"
|
43
|
+
rd.rdoc_files.include("README.rdoc", "lib/**/*.rb")
|
44
|
+
end
|
45
|
+
|
data/lib/tiny_ds/base.rb
ADDED
@@ -0,0 +1,290 @@
|
|
1
|
+
# TODO
|
2
|
+
# callback before/after
|
3
|
+
# validations
|
4
|
+
# associations
|
5
|
+
#x default value
|
6
|
+
#x property list
|
7
|
+
# property Key
|
8
|
+
# property others
|
9
|
+
# nil=>false
|
10
|
+
# index=>false setUnindexedProperty
|
11
|
+
# version
|
12
|
+
# schema_version
|
13
|
+
#x new_record?
|
14
|
+
# find_by_xxx
|
15
|
+
#x get_by_id(id) get_by_name(name) : root entity only!!
|
16
|
+
#x get_by_id(parent, id) get_by_name(parent, name) : ancestor+Kind => key get
|
17
|
+
#x Foo.query.filter(:key=>123)
|
18
|
+
# Foo.filter(:key=>123)
|
19
|
+
# logger
|
20
|
+
# logging low level API calls.
|
21
|
+
|
22
|
+
require "time"
|
23
|
+
|
24
|
+
module TinyDS
|
25
|
+
class Base
|
26
|
+
class << self; attr_accessor :_property_definitions; end
|
27
|
+
RESERVED_PROPERTY_NAME = [:id, :name, :key, :entity, :parent_key, :parent]
|
28
|
+
def self.property(pname, ptype, opts={})
|
29
|
+
pname = pname.to_sym
|
30
|
+
if RESERVED_PROPERTY_NAME.include?(pname)
|
31
|
+
raise "property name '#{pname}' is reserved."
|
32
|
+
end
|
33
|
+
property_definitions[pname] = PropertyDefinition.new(pname, ptype, opts)
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.property_definitions
|
37
|
+
self._property_definitions ||= {}
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.property_definition(name)
|
41
|
+
property_definitions[name.to_sym] or raise "unknown property='#{name}'"
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.default_attrs
|
45
|
+
attrs = {}
|
46
|
+
property_definitions.each do |pname,pdef|
|
47
|
+
if pdef.has_default?
|
48
|
+
attrs[pname] = pdef.default_value
|
49
|
+
end
|
50
|
+
end
|
51
|
+
attrs
|
52
|
+
end
|
53
|
+
|
54
|
+
# kind-string of entity
|
55
|
+
def self.kind
|
56
|
+
name
|
57
|
+
end
|
58
|
+
|
59
|
+
#include ActiveModel::Naming
|
60
|
+
# def self.model_name # for form-builder
|
61
|
+
# @model_name ||= ::ActiveModel::Name.new(self, kind)
|
62
|
+
# end
|
63
|
+
|
64
|
+
# foo.key
|
65
|
+
def key
|
66
|
+
@entity.key
|
67
|
+
end
|
68
|
+
def id
|
69
|
+
key.id
|
70
|
+
end
|
71
|
+
def name
|
72
|
+
key.name
|
73
|
+
end
|
74
|
+
|
75
|
+
# foo.parent_key
|
76
|
+
def parent_key
|
77
|
+
@entity.parent
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.to_key(m)
|
81
|
+
case m
|
82
|
+
when AppEngine::Datastore::Key
|
83
|
+
m
|
84
|
+
when AppEngine::Datastore::Entity
|
85
|
+
m.key
|
86
|
+
when String
|
87
|
+
LowDS::KeyFactory.stringToKey(m)
|
88
|
+
else
|
89
|
+
if m.kind_of?(Base)
|
90
|
+
m.key
|
91
|
+
else
|
92
|
+
raise "unknown key type #{m.class}/#{m.inspect}"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Foo.create({:title=>"hello",...}, :parent=>aaa, :id=>bbb, :name=>ccc, :key=>...)
|
98
|
+
def self.create(attrs={}, opts={})
|
99
|
+
m = new(attrs, opts)
|
100
|
+
m.save
|
101
|
+
m
|
102
|
+
end
|
103
|
+
|
104
|
+
# Foo.new
|
105
|
+
def initialize(attrs={}, opts={})
|
106
|
+
@entity = opts.delete(:entity)
|
107
|
+
unless @entity
|
108
|
+
if opts[:parent] && opts[:parent].kind_of?(Base)
|
109
|
+
opts = opts.dup
|
110
|
+
opts[:parent] = opts[:parent].entity
|
111
|
+
end
|
112
|
+
@entity = LowDS.build(self.class.kind, {}, opts)
|
113
|
+
self.attributes = self.class.default_attrs.merge(attrs || {})
|
114
|
+
@new_record = true
|
115
|
+
else
|
116
|
+
@new_record = false
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.new_from_entity(_entity)
|
121
|
+
new(nil, :entity=>_entity)
|
122
|
+
end
|
123
|
+
attr_reader :entity
|
124
|
+
|
125
|
+
def new_record?
|
126
|
+
@new_record
|
127
|
+
end
|
128
|
+
|
129
|
+
# foo.save
|
130
|
+
def save
|
131
|
+
do_save
|
132
|
+
true
|
133
|
+
end
|
134
|
+
|
135
|
+
def do_save
|
136
|
+
__before_save_set_timestamps
|
137
|
+
# if @new_record && @entity.key && parent
|
138
|
+
# TinyDS.tx{
|
139
|
+
# if LowDS.get(@entity.key)
|
140
|
+
# raise KeyIsAlreadyTaken
|
141
|
+
# end
|
142
|
+
# LowDS.save(@entity)
|
143
|
+
# }
|
144
|
+
# else
|
145
|
+
LowDS.save(@entity)
|
146
|
+
# end
|
147
|
+
@new_record = false
|
148
|
+
nil
|
149
|
+
end
|
150
|
+
# class KeyIsAlreadyTaken < StandardError
|
151
|
+
# end
|
152
|
+
|
153
|
+
def __before_save_set_timestamps
|
154
|
+
if self.class.property_definitions[:created_at] && new_record?
|
155
|
+
self.created_at = Time.now
|
156
|
+
end
|
157
|
+
if self.class.property_definitions[:updated_at]
|
158
|
+
self.updated_at = Time.now
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# Foo.get(key)
|
163
|
+
def self.get!(key)
|
164
|
+
self.new_from_entity(LowDS.get(key, :kind=>self.kind))
|
165
|
+
end
|
166
|
+
def self.get(key)
|
167
|
+
get!(key)
|
168
|
+
rescue AppEngine::Datastore::EntityNotFound => e
|
169
|
+
nil
|
170
|
+
end
|
171
|
+
|
172
|
+
def self._get_by_id_or_name!(id_or_name, parent)
|
173
|
+
key = if parent
|
174
|
+
parent = to_key(parent)
|
175
|
+
kfb = LowDS::KeyFactory::Builder.new(parent)
|
176
|
+
kfb.addChild(kind, id_or_name)
|
177
|
+
kfb.key
|
178
|
+
else
|
179
|
+
LowDS::KeyFactory::Builder.new(kind, id_or_name).key
|
180
|
+
end
|
181
|
+
get!(key)
|
182
|
+
end
|
183
|
+
|
184
|
+
def self.get_by_id!(id, parent=nil)
|
185
|
+
if id.kind_of?(String) && id==id.to_i.to_s
|
186
|
+
id = id.to_i
|
187
|
+
end
|
188
|
+
raise "id is not Integer" unless id.kind_of?(Integer)
|
189
|
+
_get_by_id_or_name!(id, parent)
|
190
|
+
end
|
191
|
+
def self.get_by_name!(name, parent=nil)
|
192
|
+
raise "id is not String" unless name.kind_of?(String)
|
193
|
+
_get_by_id_or_name!(name, parent)
|
194
|
+
end
|
195
|
+
def self.get_by_id(id, parent=nil)
|
196
|
+
if id.kind_of?(String) && id==id.to_i.to_s
|
197
|
+
id = id.to_i
|
198
|
+
end
|
199
|
+
_get_by_id_or_name!(id, parent)
|
200
|
+
rescue AppEngine::Datastore::EntityNotFound => e
|
201
|
+
nil
|
202
|
+
end
|
203
|
+
def self.get_by_name(name, parent=nil)
|
204
|
+
_get_by_id_or_name!(name, parent)
|
205
|
+
rescue AppEngine::Datastore::EntityNotFound => e
|
206
|
+
nil
|
207
|
+
end
|
208
|
+
|
209
|
+
# # Foo.find
|
210
|
+
# def self.find(*args)
|
211
|
+
# raise "todo"
|
212
|
+
# direction = dire==:desc ? AppEngine::Datastore::Query::DESCENDING : AppEngine::Datastore::Query::ASCENDING
|
213
|
+
# AppEngine::Datastore::Query.new("TxSrc").
|
214
|
+
# filter(:tx_kind, AppEngine::Datastore::Query::EQUAL, tx_kind).
|
215
|
+
# filter(:status, AppEngine::Datastore::Query::EQUAL, status).
|
216
|
+
# sort(:created_at, direction)
|
217
|
+
# end
|
218
|
+
|
219
|
+
def self.query
|
220
|
+
Query.new(self)
|
221
|
+
end
|
222
|
+
|
223
|
+
def self.count
|
224
|
+
query.count
|
225
|
+
end
|
226
|
+
|
227
|
+
# foo.destroy
|
228
|
+
def destroy
|
229
|
+
self.class.destroy(self)
|
230
|
+
end
|
231
|
+
|
232
|
+
# Foo.destroy([model, entity, key, ...])
|
233
|
+
def self.destroy(array)
|
234
|
+
array = [array] unless array.kind_of?(Array)
|
235
|
+
keys = array.collect do |m|
|
236
|
+
to_key(m)
|
237
|
+
end
|
238
|
+
AppEngine::Datastore.delete(keys)
|
239
|
+
end
|
240
|
+
def self.destroy_all
|
241
|
+
destroy(query.keys)
|
242
|
+
end
|
243
|
+
|
244
|
+
# set attributes
|
245
|
+
def attributes=(attrs)
|
246
|
+
attrs.each do |k,v|
|
247
|
+
set_property(k, v)
|
248
|
+
end
|
249
|
+
nil
|
250
|
+
end
|
251
|
+
|
252
|
+
# set property-value into @entity
|
253
|
+
def set_property(k,v)
|
254
|
+
prop_def = self.class.property_definition(k)
|
255
|
+
ds_v = prop_def.to_ds_value(v)
|
256
|
+
if ds_v.nil?
|
257
|
+
@entity.removeProperty(k)
|
258
|
+
else
|
259
|
+
@entity[k] = ds_v
|
260
|
+
end
|
261
|
+
# todo cache value read/write
|
262
|
+
end
|
263
|
+
|
264
|
+
# get property-value from @entity
|
265
|
+
def get_property(k)
|
266
|
+
prop_def = self.class.property_definition(k)
|
267
|
+
prop_def.to_ruby_value(@entity[k])
|
268
|
+
end
|
269
|
+
|
270
|
+
def method_missing(m, *args)
|
271
|
+
k, is_set = if m.to_s =~ /(.+)=$/
|
272
|
+
[$1.to_sym, true]
|
273
|
+
else
|
274
|
+
[m.to_sym, false]
|
275
|
+
end
|
276
|
+
if prop_def = self.class.property_definitions[k]
|
277
|
+
# TODO define method.
|
278
|
+
if is_set
|
279
|
+
raise if args.size!=1
|
280
|
+
set_property(k, args.first)
|
281
|
+
else
|
282
|
+
raise if args.size!=0
|
283
|
+
get_property(k)
|
284
|
+
end
|
285
|
+
else
|
286
|
+
super(m, *args)
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
=begin
|
2
|
+
special pname
|
3
|
+
- key
|
4
|
+
- parent
|
5
|
+
- id
|
6
|
+
- name
|
7
|
+
=end
|
8
|
+
|
9
|
+
|
10
|
+
class ModelBase
|
11
|
+
@@properties = {}
|
12
|
+
def self.property(pname, ptype, opts={})
|
13
|
+
pname = pname.to_sym
|
14
|
+
if @@properties[pname]
|
15
|
+
raise "duplicated pname=#{pname}"
|
16
|
+
end
|
17
|
+
@@properties[pname] = Property.new(pname, ptype, opts)
|
18
|
+
end
|
19
|
+
|
20
|
+
# kind-string of entity
|
21
|
+
def self.kind
|
22
|
+
name
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.tx(retries=0, &block)
|
26
|
+
transaction{
|
27
|
+
yield(block)
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
# Foo.create(attrs
|
32
|
+
def self.create(attrs={})
|
33
|
+
m = new(attrs)
|
34
|
+
m.save
|
35
|
+
m
|
36
|
+
end
|
37
|
+
|
38
|
+
# Foo.new
|
39
|
+
def initialize(attrs={})
|
40
|
+
attributes = attrs
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.new_from_entity(entity)
|
44
|
+
m = new()
|
45
|
+
end
|
46
|
+
|
47
|
+
# foo.save
|
48
|
+
def save
|
49
|
+
_save
|
50
|
+
end
|
51
|
+
|
52
|
+
def _save
|
53
|
+
end
|
54
|
+
|
55
|
+
# Foo.get(key)
|
56
|
+
def self.get(key)
|
57
|
+
if key.kind_of?(String)
|
58
|
+
key = LowDS::KeyFactory.stringToKey(key)
|
59
|
+
end
|
60
|
+
raise "invalid key=#{key}" unless key.kind_of?(AppEngine::Datastore::Key)
|
61
|
+
self.new_from_entity(LowDS.get(key, :kind=>self.kind))
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.find(*args)
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
# foo.destroy
|
69
|
+
def destroy
|
70
|
+
end
|
71
|
+
|
72
|
+
def attributes=(attrs)
|
73
|
+
# TODO key, parent, id, name
|
74
|
+
|
75
|
+
attrs.each do |k,v|
|
76
|
+
prop = @@properties[k.to_sym]
|
77
|
+
prop.value = v
|
78
|
+
end
|
79
|
+
nil
|
80
|
+
end
|
81
|
+
|
82
|
+
def method_missing(m, *args)
|
83
|
+
prop, is_set = if m =~ /(.+)=$/
|
84
|
+
[@@properties[$1.to_sym], true]
|
85
|
+
else
|
86
|
+
[@@properties[m.to_sym], false]
|
87
|
+
end
|
88
|
+
if prop
|
89
|
+
# TODO define method.
|
90
|
+
if is_set
|
91
|
+
raise if args.size!=1
|
92
|
+
prop.value = args.first
|
93
|
+
else
|
94
|
+
raise if args.size!=0
|
95
|
+
prop.value
|
96
|
+
end
|
97
|
+
else
|
98
|
+
super(m, *args)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
class Property
|
103
|
+
def initialize(pname, ptype, opts)
|
104
|
+
@pname = pname
|
105
|
+
@ptype = ptype
|
106
|
+
@opts = opts
|
107
|
+
end
|
108
|
+
def value=(v)
|
109
|
+
# check_type or cast
|
110
|
+
@value = v
|
111
|
+
end
|
112
|
+
attr_reader :value
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
@@ -0,0 +1,161 @@
|
|
1
|
+
# memo デプロイ時に未完了のトランザクションがあってトランザクション定義がかわったら?version???
|
2
|
+
module TinyDS
|
3
|
+
class BaseTx
|
4
|
+
def src_phase(src, args)
|
5
|
+
raise "no impl."
|
6
|
+
end
|
7
|
+
def dest_phase(dest, args)
|
8
|
+
raise "no impl."
|
9
|
+
end
|
10
|
+
def self.tx_kind
|
11
|
+
name
|
12
|
+
end
|
13
|
+
def roll_forward_retries_limit
|
14
|
+
100
|
15
|
+
end
|
16
|
+
def tx_retries
|
17
|
+
3
|
18
|
+
end
|
19
|
+
attr_reader :tx_key
|
20
|
+
|
21
|
+
# トランザクション実行
|
22
|
+
# : src_phase dest_phase
|
23
|
+
# raised : failed ----
|
24
|
+
# false : OK failed
|
25
|
+
# true : OK OK
|
26
|
+
def self.exec(src, dest, args={})
|
27
|
+
tx = new
|
28
|
+
tx.create_tx(src, dest, args)
|
29
|
+
begin
|
30
|
+
tx.roll_forward
|
31
|
+
rescue AppEngine::Datastore::TransactionFailed => e
|
32
|
+
return false
|
33
|
+
end
|
34
|
+
return true
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.roll_forward_all(limit=50)
|
38
|
+
pending_tx_query.each(:limit=>limit) do |tx_src|
|
39
|
+
tx = new
|
40
|
+
tx.restore_tx(tx_src)
|
41
|
+
begin
|
42
|
+
tx.roll_forward
|
43
|
+
rescue => e
|
44
|
+
#p "roll_forward failed. tx=[#{tx.tx_key}] e=[#{e.inspect}]"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.pending_tx_query(status="pending", dire=:asc) # pending/done/failed
|
51
|
+
TxSrc.query.
|
52
|
+
filter(:tx_kind, "==", tx_kind).
|
53
|
+
filter(:status, "==", status).
|
54
|
+
sort(:created_at, dire)
|
55
|
+
end
|
56
|
+
|
57
|
+
# doneなTxSrcと対応するTxDoneを削除する
|
58
|
+
# def self.delete_done_tx
|
59
|
+
# end
|
60
|
+
|
61
|
+
class TxSrc < Base
|
62
|
+
property :tx_kind, :string
|
63
|
+
property :dest_key, :string
|
64
|
+
property :status, :string, :default=>"pending"
|
65
|
+
property :roll_forward_failed_count, :integer, :default=>0
|
66
|
+
property :args, :text
|
67
|
+
property :created_at, :time
|
68
|
+
property :done_at, :time
|
69
|
+
end
|
70
|
+
|
71
|
+
# トランザクション前半
|
72
|
+
# TODO TxIDを指定できるようにする。TxSrc#key.nameにTxIDを指定して重複実行防止
|
73
|
+
def create_tx(src, dest, args)
|
74
|
+
tx_src = nil
|
75
|
+
TinyDS.tx(tx_retries){
|
76
|
+
src = src.class.get(src.key)
|
77
|
+
src_phase(src, args)
|
78
|
+
src.save!
|
79
|
+
|
80
|
+
attrs = {
|
81
|
+
:tx_kind => self.class.tx_kind,
|
82
|
+
:dest_key => dest.key.to_s,
|
83
|
+
:args => args.to_yaml,
|
84
|
+
}
|
85
|
+
tx_src = TxSrc.create!(attrs, :parent=>src) # srcがparent, tx_srcがchild
|
86
|
+
# COMMIT:「srcの処理(=src_phase)、TxSrc作成」
|
87
|
+
}
|
88
|
+
@tx_key = tx_src.key
|
89
|
+
nil
|
90
|
+
end
|
91
|
+
|
92
|
+
def restore_tx(tx_src)
|
93
|
+
@tx_key = tx_src.key
|
94
|
+
nil
|
95
|
+
end
|
96
|
+
|
97
|
+
class TxDone < Base
|
98
|
+
property :done_at, :time
|
99
|
+
end
|
100
|
+
|
101
|
+
# トランザクション後半
|
102
|
+
def roll_forward
|
103
|
+
tx_src = TxSrc.get(@tx_key)
|
104
|
+
|
105
|
+
TinyDS.tx(tx_retries){
|
106
|
+
dest_key = LowDS::KeyFactory.stringToKey(tx_src.dest_key)
|
107
|
+
dest = dest_key.kind.constantize.get(dest_key)
|
108
|
+
done_name = "TxDone_#{@tx_key.to_s}"
|
109
|
+
done_key = LowDS::KeyFactory.createKey(dest.key, TxDone.kind, done_name)
|
110
|
+
begin
|
111
|
+
TxDone.get!(done_key)
|
112
|
+
# なにもしない : TxDoneが存在しているということはdest_phaseは処理済み
|
113
|
+
rescue AppEngine::Datastore::EntityNotFound => e
|
114
|
+
# TxDoneが無い→dest_phaseが未実行
|
115
|
+
attrs = {:done_at=>Time.now}
|
116
|
+
tx_done = TxDone.create!(attrs, :parent=>dest, :name=>done_name)
|
117
|
+
dest_phase(dest, YAML.load(tx_src.args)) # destの処理を実行
|
118
|
+
dest.save!
|
119
|
+
end
|
120
|
+
# memo: done_keyが同じTxDoneをcommitしようとするとTransactionFailedになるはず→dest_phaseもキャンセル
|
121
|
+
# COMMIT:「destの処理(=dest_phase)、TxDone作成」
|
122
|
+
}
|
123
|
+
|
124
|
+
# TxSrc#statusをdoneに
|
125
|
+
TinyDS.tx(tx_retries){
|
126
|
+
tx_src = TxSrc.get!(@tx_key)
|
127
|
+
if tx_src.status=="pending"
|
128
|
+
tx_src.status = "done"
|
129
|
+
tx_src.done_at = Time.now
|
130
|
+
tx_src.save!
|
131
|
+
end
|
132
|
+
}
|
133
|
+
return true
|
134
|
+
rescue => e
|
135
|
+
puts e.inspect
|
136
|
+
TinyDS.tx(tx_retries){
|
137
|
+
tx_src = TxSrc.get!(@tx_key)
|
138
|
+
tx_src.roll_forward_failed_count += 1
|
139
|
+
if roll_forward_retries_limit < tx_src.roll_forward_failed_count
|
140
|
+
tx_src.status = "failed"
|
141
|
+
end
|
142
|
+
tx_src.save!
|
143
|
+
}
|
144
|
+
return false
|
145
|
+
end
|
146
|
+
|
147
|
+
if false
|
148
|
+
require "benchmark"
|
149
|
+
def create_tx(src, dest, args)
|
150
|
+
RAILS_DEFAULT_LOGGER.info ["create_tx", Benchmark.measure{
|
151
|
+
_create_tx(src, dest, args)
|
152
|
+
}].inspect
|
153
|
+
end
|
154
|
+
def roll_forward
|
155
|
+
RAILS_DEFAULT_LOGGER.info ["roll_forward", Benchmark.measure{
|
156
|
+
_roll_forward
|
157
|
+
}].inspect
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|