machu-appengine-pstore 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +4 -11
- data/VERSION +1 -1
- data/appengine-pstore.gemspec +2 -2
- data/lib/appengine-pstore.rb +81 -20
- data/spec/appengine-pstore_spec.rb +196 -3
- data/spec/spec_helper.rb +5 -3
- metadata +2 -2
data/README.rdoc
CHANGED
@@ -1,17 +1,10 @@
|
|
1
1
|
= appengine-pstore
|
2
2
|
|
3
|
-
|
3
|
+
* http://github.com/machu/appengine-pstore
|
4
4
|
|
5
|
-
==
|
6
|
-
|
7
|
-
|
8
|
-
* Make your feature addition or bug fix.
|
9
|
-
* Add tests for it. This is important so I don't break it in a
|
10
|
-
future version unintentionally.
|
11
|
-
* Commit, do not mess with rakefile, version, or history.
|
12
|
-
(if you want to have your own version, that is fine but
|
13
|
-
bump version in a commit by itself I can ignore when I pull)
|
14
|
-
* Send me a pull request. Bonus points for topic branches.
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
The PStore interfaces for the Google App Engine Datastore.
|
15
8
|
|
16
9
|
== Copyright
|
17
10
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.1
|
data/appengine-pstore.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{appengine-pstore}
|
8
|
-
s.version = "0.1.
|
8
|
+
s.version = "0.1.1"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["MATSUOKA Kohei"]
|
12
|
-
s.date = %q{2009-09-
|
12
|
+
s.date = %q{2009-09-27}
|
13
13
|
s.description = %q{PStore compatible interface for Google Apps Engine.}
|
14
14
|
s.email = %q{kohei@machu.jp}
|
15
15
|
s.extra_rdoc_files = [
|
data/lib/appengine-pstore.rb
CHANGED
@@ -1,6 +1,28 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Copyright:: Copyright 2009 MATSUOKA Kohei
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
1
16
|
require 'rubygems'
|
2
17
|
require 'appengine-apis/datastore'
|
3
18
|
|
19
|
+
# from pstore.rb
|
20
|
+
class PStore
|
21
|
+
# The error type thrown by all PStore methods.
|
22
|
+
class Error < StandardError
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
4
26
|
module AppEngine
|
5
27
|
module Datastore
|
6
28
|
class Query
|
@@ -23,51 +45,88 @@ module AppEngine
|
|
23
45
|
end
|
24
46
|
|
25
47
|
def in_transaction
|
26
|
-
|
27
|
-
|
48
|
+
if @transaction == nil || @transaction.active? == false
|
49
|
+
#unless transaction && transaction.active
|
50
|
+
#unless AppEngine::Datastore.current_transaction(nil)
|
51
|
+
raise ::PStore::Error, "not in transaction"
|
28
52
|
end
|
29
53
|
end
|
30
54
|
|
31
55
|
def in_transaction_wr
|
32
56
|
in_transaction
|
33
|
-
raise PStore::Error, "in read-only transaction" if @rdonly
|
57
|
+
raise ::PStore::Error, "in read-only transaction" if @rdonly
|
34
58
|
end
|
35
59
|
private :in_transaction, :in_transaction_wr
|
36
60
|
|
37
61
|
def transaction(readonly = false)
|
62
|
+
raise ::PStore::Error, "nested transaction" if @transaction
|
38
63
|
@rdonly = readonly
|
39
64
|
@transaction = AppEngine::Datastore.begin_transaction
|
40
|
-
|
41
|
-
|
65
|
+
# uncommited entities
|
66
|
+
@uncommited = {
|
67
|
+
:added => {},
|
68
|
+
:deleted => {}
|
69
|
+
}
|
70
|
+
begin
|
71
|
+
catch(:pstore_abort_transaction) do
|
72
|
+
yield self
|
73
|
+
end
|
74
|
+
rescue Exception
|
75
|
+
@transaction.rollback if @transaction.active?
|
76
|
+
raise
|
77
|
+
ensure
|
78
|
+
@transaction.commit if @transaction.active?
|
79
|
+
@transaction = nil
|
80
|
+
@uncommited = nil
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def commit
|
85
|
+
in_transaction
|
42
86
|
@transaction.commit
|
43
|
-
|
87
|
+
throw :pstore_abort_transaction
|
88
|
+
end
|
89
|
+
|
90
|
+
def abort
|
91
|
+
in_transaction
|
92
|
+
@transaction.rollback
|
93
|
+
throw :pstore_abort_transaction
|
44
94
|
end
|
45
95
|
|
46
96
|
def [](name)
|
47
97
|
in_transaction
|
48
|
-
|
49
|
-
if @
|
50
|
-
|
51
|
-
|
52
|
-
|
98
|
+
# return uncommited data if exist
|
99
|
+
return @uncommited[:added][name] if @uncommited[:added].key?(name)
|
100
|
+
return nil if @uncommited[:deleted].key?(name)
|
101
|
+
|
102
|
+
key = AppEngine::Datastore::Key.from_path(@parent_key, @kind, dump(name))
|
103
|
+
begin
|
53
104
|
entity = AppEngine::Datastore.get(@transaction, key)
|
54
105
|
load(entity[:value])
|
106
|
+
rescue AppEngine::Datastore::EntityNotFound
|
107
|
+
nil
|
55
108
|
end
|
56
109
|
end
|
57
110
|
|
58
111
|
def []=(name, value)
|
59
112
|
in_transaction_wr
|
60
|
-
entity = AppEngine::Datastore::Entity.new(@kind, name, @parent_key)
|
113
|
+
entity = AppEngine::Datastore::Entity.new(@kind, dump(name), @parent_key)
|
61
114
|
entity[:value] = dump(value)
|
62
115
|
AppEngine::Datastore.put(@transaction, entity)
|
63
|
-
@
|
116
|
+
@uncommited[:added][name] = value
|
117
|
+
@uncommited[:deleted].delete(name)
|
64
118
|
value
|
65
119
|
end
|
66
120
|
|
67
121
|
def delete(name)
|
68
|
-
|
69
|
-
AppEngine::Datastore.
|
70
|
-
|
122
|
+
in_transaction_wr
|
123
|
+
key = AppEngine::Datastore::Key.from_path(@parent_key, @kind, dump(name))
|
124
|
+
value = self[name]
|
125
|
+
# Datastore.delete requires array keys
|
126
|
+
AppEngine::Datastore.delete(@transaction, [key])
|
127
|
+
@uncommited[:added].delete(name)
|
128
|
+
@uncommited[:deleted][name] = value
|
129
|
+
value
|
71
130
|
end
|
72
131
|
|
73
132
|
def load(content)
|
@@ -81,17 +140,19 @@ module AppEngine
|
|
81
140
|
def roots
|
82
141
|
in_transaction
|
83
142
|
query = AppEngine::Datastore::Query.new(@kind, @parent_key)
|
84
|
-
query.keysonly.fetch.map {|
|
85
|
-
|
86
|
-
}
|
143
|
+
db_keys = query.keysonly.fetch.map {|entity|
|
144
|
+
load(entity.key.name)
|
145
|
+
}
|
146
|
+
(db_keys + @uncommited[:added].keys - @uncommited[:deleted].keys).uniq
|
87
147
|
end
|
88
148
|
|
89
149
|
def root?(key)
|
90
150
|
in_transaction
|
151
|
+
self.roots.include?(key)
|
91
152
|
end
|
92
153
|
|
93
154
|
def path
|
94
|
-
@parent_key
|
155
|
+
@parent_key.name
|
95
156
|
end
|
96
157
|
end
|
97
158
|
end
|
@@ -1,7 +1,200 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
require 'appengine-pstore'
|
3
|
+
require 'pp'
|
2
4
|
|
3
|
-
describe
|
4
|
-
|
5
|
-
|
5
|
+
describe AppEngine::PStore do
|
6
|
+
# initializing datastore in memory each time
|
7
|
+
before :each do
|
8
|
+
AppEngine::Testing.install_test_datastore
|
9
|
+
@db = AppEngine::PStore.new('test.pstore')
|
10
|
+
end
|
11
|
+
|
12
|
+
after :each do
|
13
|
+
#it should close transactions
|
14
|
+
AppEngine::Datastore.active_transactions.to_a.should == []
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should support get/put' do
|
18
|
+
@db.transaction do |db|
|
19
|
+
db['var1'] = "hello"
|
20
|
+
db['var2'] = "world"
|
21
|
+
db['var1'].should == "hello"
|
22
|
+
db['var2'].should == "world"
|
23
|
+
end
|
24
|
+
@db.transaction do |db|
|
25
|
+
db['var1'].should == "hello"
|
26
|
+
db['var2'].should == "world"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should support symbol key' do
|
31
|
+
@db.transaction do |db|
|
32
|
+
db[:key] = "hello"
|
33
|
+
db['key'] = "world"
|
34
|
+
end
|
35
|
+
@db.transaction do |db|
|
36
|
+
db[:key].should == "hello"
|
37
|
+
db['key'].should == "world"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should return nil' do
|
42
|
+
@db.transaction do |db|
|
43
|
+
db['undefined'].should == nil
|
44
|
+
db.delete('undefined').should == nil
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'should support multiple database' do
|
49
|
+
db1 = AppEngine::PStore.new('test1.pstore')
|
50
|
+
db2 = AppEngine::PStore.new('test2.pstore')
|
51
|
+
db1.transaction do |db1|
|
52
|
+
db2.transaction do |db2|
|
53
|
+
db1[:key] = "hello"
|
54
|
+
db2[:key] = "world"
|
55
|
+
db1[:key].should == "hello"
|
56
|
+
db2[:key].should == "world"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
db1.transaction do |db1|
|
61
|
+
db2.transaction do |db2|
|
62
|
+
db1[:key].should == "hello"
|
63
|
+
db2[:key].should == "world"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'should support delete' do
|
69
|
+
@db.transaction do |db|
|
70
|
+
db[:key] = 'hello'
|
71
|
+
end
|
72
|
+
@db.transaction do |db|
|
73
|
+
value = db.delete(:key)
|
74
|
+
value.should == 'hello'
|
75
|
+
db[:key].should == nil
|
76
|
+
end
|
77
|
+
@db.transaction do |db|
|
78
|
+
db[:key].should == nil
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'should suppert roots' do
|
83
|
+
@db.transaction do |db|
|
84
|
+
db[:key1] = "hello"
|
85
|
+
db[:key2] = "world"
|
86
|
+
db.roots.should == [:key1, :key2]
|
87
|
+
end
|
88
|
+
@db.transaction do |db|
|
89
|
+
db.roots.should == [:key1, :key2]
|
90
|
+
db.delete(:key1)
|
91
|
+
db[:key3] = "!"
|
92
|
+
db.roots.should == [:key2, :key3]
|
93
|
+
end
|
94
|
+
@db.transaction do |db|
|
95
|
+
db.roots.should == [:key2, :key3]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'should suppert root?' do
|
100
|
+
@db.transaction do |db|
|
101
|
+
db.root?(:key).should == false
|
102
|
+
db[:key] = "hello"
|
103
|
+
db.root?(:key).should == true
|
104
|
+
end
|
105
|
+
@db.transaction do |db|
|
106
|
+
db.root?(:key).should == true
|
107
|
+
db.delete(:key)
|
108
|
+
db.root?(:key).should == false
|
109
|
+
end
|
110
|
+
@db.transaction do |db|
|
111
|
+
db.root?(:key).should == false
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'should support path' do
|
116
|
+
db = AppEngine::PStore.new('somewhere')
|
117
|
+
db.path.should == 'somewhere'
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'should support readonly database' do
|
121
|
+
p = lambda {
|
122
|
+
@db.transaction(true) do |db|
|
123
|
+
db[:key] = "hello"
|
124
|
+
end
|
125
|
+
}
|
126
|
+
p.should raise_error PStore::Error
|
127
|
+
p = lambda {
|
128
|
+
@db.transaction(true) do |db|
|
129
|
+
db.delete(:key)
|
130
|
+
end
|
131
|
+
}
|
132
|
+
p.should raise_error PStore::Error
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'should return block value' do
|
136
|
+
@db.transaction{ 'hello' }.should == 'hello'
|
137
|
+
end
|
138
|
+
|
139
|
+
describe 'transaction' do
|
140
|
+
it 'should support commit' do
|
141
|
+
@db.transaction do |db|
|
142
|
+
db[:key] = "hello"
|
143
|
+
db.commit
|
144
|
+
# never reached
|
145
|
+
raise StandardError
|
146
|
+
end
|
147
|
+
@db.transaction do |db|
|
148
|
+
db[:key].should == "hello"
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'should support abort' do
|
153
|
+
@db.transaction do |db|
|
154
|
+
db[:key] = "hello"
|
155
|
+
db.abort
|
156
|
+
# never reached
|
157
|
+
raise StandardError
|
158
|
+
end
|
159
|
+
@db.transaction do |db|
|
160
|
+
db[:key].should == nil
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
it 'should abort transaction if raised any Exception' do
|
165
|
+
p = lambda {
|
166
|
+
@db.transaction do |db|
|
167
|
+
db[:key] = "hello"
|
168
|
+
# abort transaction
|
169
|
+
raise StandardError
|
170
|
+
end
|
171
|
+
}
|
172
|
+
p.should raise_error StandardError
|
173
|
+
@db.transaction do |db|
|
174
|
+
db[:key].should == nil
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'should not support nested transaction' do
|
179
|
+
p = lambda {
|
180
|
+
@db.transaction do |db1|
|
181
|
+
@db.transaction do |db2|
|
182
|
+
end
|
183
|
+
end
|
184
|
+
}
|
185
|
+
p.should raise_error PStore::Error
|
186
|
+
end
|
187
|
+
|
188
|
+
it 'should raise PStore::Error outside transaction' do
|
189
|
+
p = lambda { @db[:key1] = "hello" }
|
190
|
+
p.should raise_error PStore::Error
|
191
|
+
p = lambda { @db[:key1] }
|
192
|
+
p.should raise_error PStore::Error
|
193
|
+
p = lambda { @db.delete(:key1) }
|
194
|
+
p.should raise_error PStore::Error
|
195
|
+
p = lambda { @db.roots }
|
196
|
+
p.should raise_error PStore::Error
|
197
|
+
end
|
6
198
|
end
|
7
199
|
end
|
200
|
+
|
data/spec/spec_helper.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
2
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
|
-
require 'appengine-pstore'
|
4
3
|
require 'spec'
|
5
|
-
require '
|
4
|
+
require 'appengine-sdk'
|
5
|
+
AppEngine::SDK.load_apiproxy
|
6
|
+
require 'appengine-apis/testing'
|
7
|
+
AppEngine::Testing.install_test_env
|
6
8
|
|
7
9
|
Spec::Runner.configure do |config|
|
8
|
-
|
10
|
+
# config.include(ProtoMethods)
|
9
11
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: machu-appengine-pstore
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- MATSUOKA Kohei
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-09-
|
12
|
+
date: 2009-09-27 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|