appengine-apis 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +3 -0
- data/Manifest.txt +21 -0
- data/PostInstall.txt +12 -0
- data/README.rdoc +27 -0
- data/Rakefile +28 -0
- data/lib/appengine-apis.rb +23 -0
- data/lib/appengine-apis/apiproxy.rb +47 -0
- data/lib/appengine-apis/datastore.rb +515 -0
- data/lib/appengine-apis/datastore_types.rb +340 -0
- data/lib/appengine-apis/logger.rb +69 -0
- data/lib/appengine-apis/merb-logger.rb +55 -0
- data/lib/appengine-apis/testing.rb +123 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/spec/datastore_spec.rb +150 -0
- data/spec/datastore_types_spec.rb +198 -0
- data/spec/logger_spec.rb +68 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +11 -0
- data/tasks/rspec.rake +21 -0
- metadata +97 -0
data/History.txt
ADDED
data/Manifest.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
History.txt
|
2
|
+
Manifest.txt
|
3
|
+
PostInstall.txt
|
4
|
+
README.rdoc
|
5
|
+
Rakefile
|
6
|
+
lib/appengine-apis.rb
|
7
|
+
lib/appengine-apis/apiproxy.rb
|
8
|
+
lib/appengine-apis/datastore.rb
|
9
|
+
lib/appengine-apis/datastore_types.rb
|
10
|
+
lib/appengine-apis/logger.rb
|
11
|
+
lib/appengine-apis/merb-logger.rb
|
12
|
+
lib/appengine-apis/testing.rb
|
13
|
+
script/console
|
14
|
+
script/destroy
|
15
|
+
script/generate
|
16
|
+
spec/datastore_spec.rb
|
17
|
+
spec/datastore_types_spec.rb
|
18
|
+
spec/logger_spec.rb
|
19
|
+
spec/spec.opts
|
20
|
+
spec/spec_helper.rb
|
21
|
+
tasks/rspec.rake
|
data/PostInstall.txt
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
|
2
|
+
To use the local datastore you need to have the Google App Engine SDK for Java
|
3
|
+
installed
|
4
|
+
(http://code.google.com/appengine/docs/java/gettingstarted/installing.html).
|
5
|
+
Then you need to add several jars to your classpath.
|
6
|
+
|
7
|
+
For example:
|
8
|
+
$ export SDK=`pwd`/appengine-java-sdk/lib
|
9
|
+
$ export CLASSPATH=$SDK/shared/appengine-local-runtime-shared.jar:$SDK/impl/appengine-api-stubs.jar:$SDK/impl/appengine-api.jar:$SDK/impl/appengine-local-runtime.jar
|
10
|
+
|
11
|
+
For more information on appengine-apis, see
|
12
|
+
http://code.google.com/p/appengine-jruby
|
data/README.rdoc
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
= appengine-apis
|
2
|
+
|
3
|
+
* http://code.google.com/p/appengine-jruby
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
APIs and utilities for using JRuby on Google App Engine.
|
8
|
+
|
9
|
+
== REQUIREMENTS:
|
10
|
+
|
11
|
+
* Google App Engine SDK for Java (http://code.google.com/appengine)
|
12
|
+
|
13
|
+
== LICENSE:
|
14
|
+
|
15
|
+
Copyright 2009 Google Inc
|
16
|
+
|
17
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
18
|
+
you may not use this file except in compliance with the License.
|
19
|
+
You may obtain a copy of the License at
|
20
|
+
|
21
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
22
|
+
|
23
|
+
Unless required by applicable law or agreed to in writing, software
|
24
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
25
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
26
|
+
See the License for the specific language governing permissions and
|
27
|
+
limitations under the License.
|
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
%w[rubygems rake rake/clean fileutils newgem rubigen].each { |f| require f }
|
2
|
+
require File.dirname(__FILE__) + '/lib/appengine-apis'
|
3
|
+
|
4
|
+
# Generate all the Rake tasks
|
5
|
+
# Run 'rake -T' to see list of generated tasks (from gem root directory)
|
6
|
+
$hoe = Hoe.new('appengine-apis', AppEngine::VERSION) do |p|
|
7
|
+
p.developer('Ryan Brown', 'ribrdb@gmail.com')
|
8
|
+
p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
|
9
|
+
p.post_install_message = 'PostInstall.txt'
|
10
|
+
p.rubyforge_name = 'appengine-jruby'
|
11
|
+
# p.extra_deps = [
|
12
|
+
# ['activesupport','>= 2.0.2'],
|
13
|
+
# ]
|
14
|
+
p.extra_dev_deps = [
|
15
|
+
['newgem', ">= #{::Newgem::VERSION}"]
|
16
|
+
]
|
17
|
+
|
18
|
+
p.clean_globs |= %w[**/.DS_Store tmp *.log]
|
19
|
+
path = (p.rubyforge_name == p.name) ? p.rubyforge_name : "\#{p.rubyforge_name}/\#{p.name}"
|
20
|
+
p.remote_rdoc_dir = File.join(path.gsub(/^#{p.rubyforge_name}\/?/,''), 'rdoc')
|
21
|
+
p.rsync_args = '-av --delete --ignore-errors'
|
22
|
+
end
|
23
|
+
|
24
|
+
require 'newgem/tasks' # load /tasks/*.rake
|
25
|
+
Dir['tasks/**/*.rake'].each { |t| load t }
|
26
|
+
|
27
|
+
# TODO - want other tests/tasks run by default? Add them to the list
|
28
|
+
# task :default => [:spec, :features]
|
@@ -0,0 +1,23 @@
|
|
1
|
+
#!/usr/bin/ruby1.8 -w
|
2
|
+
#
|
3
|
+
# Copyright:: Copyright 2009 Google Inc.
|
4
|
+
# Original Author:: Ryan Brown (mailto:ribrdb@google.com)
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
|
18
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
19
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
20
|
+
|
21
|
+
module AppEngine
|
22
|
+
VERSION = '0.0.1'
|
23
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
#!/usr/bin/ruby1.8 -w
|
2
|
+
#
|
3
|
+
# Copyright:: Copyright 2009 Google Inc.
|
4
|
+
# Original Author:: Ryan Brown (mailto:ribrdb@google.com)
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
#
|
19
|
+
# Ruby interface to the Java ApiProxy.
|
20
|
+
|
21
|
+
require 'java'
|
22
|
+
|
23
|
+
module AppEngine
|
24
|
+
|
25
|
+
import Java.com.google.apphosting.api.ApiProxy
|
26
|
+
|
27
|
+
class << ApiProxy
|
28
|
+
def get_app_id
|
29
|
+
get_current_environment.getAppId
|
30
|
+
end
|
31
|
+
|
32
|
+
def get_auth_domain
|
33
|
+
get_current_environment.getAuthDomain
|
34
|
+
end
|
35
|
+
|
36
|
+
alias :add_log_record :log
|
37
|
+
|
38
|
+
def log(level, message)
|
39
|
+
message = (message || "").to_s.chomp
|
40
|
+
return if message.nil? || message.empty?
|
41
|
+
record = AppEngine::ApiProxy::LogRecord.new(
|
42
|
+
level, java.lang.System.currentTimeMillis() * 1000, message.to_s)
|
43
|
+
add_log_record(record)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
@@ -0,0 +1,515 @@
|
|
1
|
+
#!/usr/bin/ruby1.8 -w
|
2
|
+
#
|
3
|
+
# Copyright:: Copyright 2009 Google Inc.
|
4
|
+
# Original Author:: Ryan Brown (mailto:ribrdb@google.com)
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
#
|
19
|
+
# The Ruby datastore API used by app developers.
|
20
|
+
#
|
21
|
+
# Defines the Query class, as well as methods for all of the
|
22
|
+
# datastore's calls. Also defines conversions between the Ruby classes and
|
23
|
+
# their Java counterparts.
|
24
|
+
#
|
25
|
+
# The datastore errors are defined in datastore_types.rb.
|
26
|
+
|
27
|
+
require 'appengine-apis/apiproxy'
|
28
|
+
require 'appengine-apis/datastore_types'
|
29
|
+
|
30
|
+
module AppEngine
|
31
|
+
|
32
|
+
# The +Datastore+ provides access to a schema-less data
|
33
|
+
# storage system. The fundamental unit of data in this system is the
|
34
|
+
# +Entity+, which has an immutable identity (represented by a
|
35
|
+
# +Key+) and zero of more mutable properties. +Entity+
|
36
|
+
# objects can be created, updated, deleted, retrieved by identifier,
|
37
|
+
# and queried via a combination of properties.
|
38
|
+
#
|
39
|
+
# The +Datastore+ can be used transactionally and supports the
|
40
|
+
# notion of a "current" transaction. A current transaction is established by
|
41
|
+
# calling #begin_transaction. The transaction returned by this method
|
42
|
+
# ceases to be current when an attempt is made to commit or rollback or when
|
43
|
+
# another call is made to #begin_transaction. A transaction can only
|
44
|
+
# be current within the +Thread+ that created it.
|
45
|
+
#
|
46
|
+
# The various overloads of put, get, and delete all support transactions.
|
47
|
+
# Users of this class have the choice of explicitly passing a (potentially
|
48
|
+
# null) +Transaction+ to these methods or relying on the current transaction.
|
49
|
+
#
|
50
|
+
module Datastore
|
51
|
+
module_function
|
52
|
+
|
53
|
+
# call-seq:
|
54
|
+
# Datastore.get(transaction=current_transaction, key) -> Entity
|
55
|
+
# Datastore.get(transaction=current_transaction, [keys]) -> Entities
|
56
|
+
#
|
57
|
+
# Retrieves one or more entities from the datastore.
|
58
|
+
#
|
59
|
+
# Retrieves the entity or entities with the given key(s) from the datastore
|
60
|
+
# and returns them as fully populated Entity objects, as defined below. If
|
61
|
+
# there is an error, raises a subclass of Datastore::Error.
|
62
|
+
#
|
63
|
+
# With a single key, an Entity will be returned, or
|
64
|
+
# EntityNotFound will be raised if no existing entity matches the key.
|
65
|
+
#
|
66
|
+
# With an array of keys, an array of entities will be returned that
|
67
|
+
# corresponds to the sequence of keys. It will include entities for keys
|
68
|
+
# that were found and None placeholders for keys that were not found.
|
69
|
+
#
|
70
|
+
# If transaction is specified, it will be used instead of the
|
71
|
+
# current transaction.
|
72
|
+
#
|
73
|
+
def get(*args)
|
74
|
+
convert_exceptions do
|
75
|
+
args = extract_tx(args)
|
76
|
+
entities = @@db.get(*args)
|
77
|
+
if entities.kind_of? java.util.Map
|
78
|
+
keys = args[-1]
|
79
|
+
entities = keys.collect do |key|
|
80
|
+
entities.get(key)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
entities
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# call-seq:
|
88
|
+
# Datastore.put(transaction=current_transaction, entity) -> Key
|
89
|
+
# Datastore.put(transaction=current_transaction, entities) -> Keys
|
90
|
+
#
|
91
|
+
# Store one or more entities in the datastore.
|
92
|
+
#
|
93
|
+
# The entities may be new or previously existing. For new entities, #put will
|
94
|
+
# fill in the app id and key assigned by the datastore.
|
95
|
+
#
|
96
|
+
# If the argument is a single Entity, a single Key will be returned. If the
|
97
|
+
# argument is an array of Entity, an Enumerable of Keys will be returned.
|
98
|
+
#
|
99
|
+
# If transaction is specified this operation will execute within that
|
100
|
+
# transaction instead of the current transaction.
|
101
|
+
#
|
102
|
+
def put(*args)
|
103
|
+
convert_exceptions do
|
104
|
+
args = extract_tx(args)
|
105
|
+
@@db.put(*args)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# call-seq:
|
110
|
+
# Datastore.delete(transaction=current_transaction, key)
|
111
|
+
# Datastore.delete(transaction=current_transaction, [keys])
|
112
|
+
#
|
113
|
+
# Deletes one or more entities from the datastore.
|
114
|
+
#
|
115
|
+
# If transaction is specified this operation will execute within that
|
116
|
+
# transaction instead of the current transaction.
|
117
|
+
#
|
118
|
+
def delete(*args)
|
119
|
+
convert_exceptions do
|
120
|
+
args = extract_tx(args)
|
121
|
+
@@db.delete(*args)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# Begins a transaction agains the datastore. Callers are
|
126
|
+
# responsible for explicitly calling #Transaction.commit or
|
127
|
+
# #Transaction.rollback when they no longer need the Transaction.
|
128
|
+
#
|
129
|
+
# The Transaction returned by this call will be considered the
|
130
|
+
# current transaction and will be returned by subsequent, same-thread
|
131
|
+
# calls to #current_transaction until one of the following happens:
|
132
|
+
#
|
133
|
+
# 1. #begin_transaction is invoked from the same thread. In this case
|
134
|
+
# #current_transaction will return the result of the more recent
|
135
|
+
# call to #begin_transaction.
|
136
|
+
# 2. #Transaction.commit is invoked on the Transaction returned by
|
137
|
+
# this method. Whether or not the commit succeeds, the
|
138
|
+
# Transaction will no longer be current.
|
139
|
+
# 3. #Transaction.rollback is invoked on the Transaction returned by
|
140
|
+
# this method. Whether or not the rollback succeeds, the
|
141
|
+
# Transaction will no longer be current.
|
142
|
+
#
|
143
|
+
def begin_transaction
|
144
|
+
convert_exceptions do
|
145
|
+
@@db.begin_transaction
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# call-seq:
|
150
|
+
# Datastore.current_transaction -> transaction || IndexError
|
151
|
+
# Datastore.current_transaction(default) -> transaction
|
152
|
+
#
|
153
|
+
# Returns the current transaction for this thread. The current transaction
|
154
|
+
# is defined as the result of the most recent, same-thread invocation of
|
155
|
+
# #begin_transaction that has not been committed or rolled back.
|
156
|
+
#
|
157
|
+
# Raises IndexError if there is no current transaction and no default
|
158
|
+
# is specified.
|
159
|
+
#
|
160
|
+
def current_transaction(*args)
|
161
|
+
convert_exceptions do
|
162
|
+
@@db.current_transaction(*args)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# Returns all Transactions started by this thread upon which no
|
167
|
+
# attempt to commit or rollback has been made.
|
168
|
+
#
|
169
|
+
def active_transactions
|
170
|
+
convert_exceptions do
|
171
|
+
@@db.active_transactions
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# Runs the block inside a transaction. Every #get, #put, and #delete
|
176
|
+
# call in the block is made within the transaction, unless another
|
177
|
+
# transaction is explicitly specified.
|
178
|
+
#
|
179
|
+
# The block may raise any exception to roll back the transaction instead of
|
180
|
+
# committing it. If this happens, the transaction will be rolled back and the
|
181
|
+
# exception will be re-raised up to #transaction's caller.
|
182
|
+
#
|
183
|
+
# If you want to roll back intentionally, but don't have an appropriate
|
184
|
+
# exception to raise, you can raise an instance of Datastore::Rollback.
|
185
|
+
# It will cause a rollback, but will *not* be re-raised up to the caller.
|
186
|
+
#
|
187
|
+
# If retries is greater than 0 and the transaction fails to commit,
|
188
|
+
# the block may be run more than once, so it should be idempotent. It
|
189
|
+
# should avoid side effects, and it shouldn't have *any* side effects that
|
190
|
+
# aren't safe to occur multiple times. However, this doesn't
|
191
|
+
# include Put, Get, and Delete calls, of course.
|
192
|
+
#
|
193
|
+
def transaction(retries=0)
|
194
|
+
while retries >= 0
|
195
|
+
retries -= 1
|
196
|
+
tx = begin_transaction
|
197
|
+
begin
|
198
|
+
result = yield
|
199
|
+
tx.commit
|
200
|
+
return result
|
201
|
+
rescue Rollback
|
202
|
+
tx.rollback
|
203
|
+
return nil
|
204
|
+
rescue TransactionFailed
|
205
|
+
raise ex unless retries >= 0
|
206
|
+
ensure
|
207
|
+
begin
|
208
|
+
tx.rollback
|
209
|
+
rescue java.lang.IllegalStateException
|
210
|
+
# already commited/rolled back. ignore
|
211
|
+
rescue java.util.NoSuchElementException
|
212
|
+
# already commited/rolled back. ignore
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
raise TransactionFailed
|
217
|
+
end
|
218
|
+
|
219
|
+
def extract_tx(args) # :nodoc:
|
220
|
+
tx = :none
|
221
|
+
keys = args[0]
|
222
|
+
if keys.java_kind_of?(JavaDatastore::Transaction) || keys.nil?
|
223
|
+
tx = args.shift
|
224
|
+
keys = args[0]
|
225
|
+
end
|
226
|
+
if args.size > 1
|
227
|
+
keys = args
|
228
|
+
end
|
229
|
+
if keys.kind_of? Array
|
230
|
+
keys = Iterable.new(keys)
|
231
|
+
end
|
232
|
+
if tx == :none
|
233
|
+
[keys]
|
234
|
+
else
|
235
|
+
[tx, keys]
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def service # :nodoc:
|
240
|
+
@@db
|
241
|
+
end
|
242
|
+
|
243
|
+
# Query encapsulates a request for zero or more
|
244
|
+
# Entity objects out of the datastore. It supports querying on
|
245
|
+
# zero or more properties, querying by ancestor, and sorting.
|
246
|
+
# Entity objects which match the query can be retrieved in a single
|
247
|
+
# list, or with an unbounded iterator.
|
248
|
+
#
|
249
|
+
# A Query does not cache results. Each use of the Query results in a new
|
250
|
+
# trip to the Datastore.
|
251
|
+
#
|
252
|
+
class Query
|
253
|
+
JQuery = JavaDatastore::Query
|
254
|
+
FetchOptions = JavaDatastore::FetchOptions
|
255
|
+
|
256
|
+
module Constants
|
257
|
+
[JQuery::FilterOperator, JQuery::SortDirection].each do |enum|
|
258
|
+
enum.constants.each do |name|
|
259
|
+
const_set(name, enum.const_get(name))
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
include Constants
|
264
|
+
|
265
|
+
# call-seq:
|
266
|
+
# Query.new(kind)
|
267
|
+
# Query.new(ancestor)
|
268
|
+
# Query.new(kind, ancestor)
|
269
|
+
#
|
270
|
+
# Creates a new Query with the specified kind and/or ancestor.
|
271
|
+
#
|
272
|
+
# Args:
|
273
|
+
# - kind: String. Only return entities with this kind.
|
274
|
+
# - ancestor: Key. Only return entities with the given ancestor.
|
275
|
+
#
|
276
|
+
def initialize(*args)
|
277
|
+
@query = JQuery.new(*args)
|
278
|
+
end
|
279
|
+
|
280
|
+
def kind
|
281
|
+
@query.kind
|
282
|
+
end
|
283
|
+
|
284
|
+
|
285
|
+
def ancestor
|
286
|
+
@query.ancestor
|
287
|
+
end
|
288
|
+
|
289
|
+
# Sets an ancestor for this query.
|
290
|
+
#
|
291
|
+
# This restricts the query to only return result entities that are
|
292
|
+
# descended from a given entity. In other words, all of the results
|
293
|
+
# will have the ancestor as their parent, or parent's parent, or
|
294
|
+
# etc.
|
295
|
+
#
|
296
|
+
# If nil is specified, unsets any previously-set ancestor.
|
297
|
+
#
|
298
|
+
# Throws ArgumentError if the ancestor key is incomplete, or if
|
299
|
+
# you try to unset an ancestor and have not set a kind.
|
300
|
+
#
|
301
|
+
def ancestor=(key)
|
302
|
+
Datastore.convert_exceptions do
|
303
|
+
@query.set_ancestor(key)
|
304
|
+
end
|
305
|
+
clear_cache
|
306
|
+
end
|
307
|
+
|
308
|
+
# call-seq:
|
309
|
+
# query.set_ancestor(key) -> query
|
310
|
+
#
|
311
|
+
# Sets an ancestor for this query.
|
312
|
+
#
|
313
|
+
# This restricts the query to only return result entities that are
|
314
|
+
# descended from a given entity. In other words, all of the results
|
315
|
+
# will have the ancestor as their parent, or parent's parent, or
|
316
|
+
# etc.
|
317
|
+
#
|
318
|
+
# If nil is specified, unsets any previously-set ancestor.
|
319
|
+
#
|
320
|
+
# Throws ArgumentError if the ancestor key is incomplete, or if
|
321
|
+
# you try to unset an ancestor and have not set a kind.
|
322
|
+
#
|
323
|
+
def set_ancestor(key)
|
324
|
+
self.ancestor = key
|
325
|
+
self
|
326
|
+
end
|
327
|
+
|
328
|
+
# Add a filter on the specified property.
|
329
|
+
#
|
330
|
+
# Note that entities with multi-value properties identified by name
|
331
|
+
# will match this filter if the multi-value property has at least one
|
332
|
+
# value that matches the condition expressed by +operator+ and
|
333
|
+
# +value+. For more information on multi-value property filtering
|
334
|
+
# please see the {datastore
|
335
|
+
# documentation}[http://code.google.com/appengine/docs/java/datastore]
|
336
|
+
#
|
337
|
+
def filter(name, operator, value)
|
338
|
+
name = name.to_s if name.kind_of? Symbol
|
339
|
+
value = Datastore.ruby_to_java(value)
|
340
|
+
@query.add_filter(name, operator, value)
|
341
|
+
clear_cache
|
342
|
+
end
|
343
|
+
|
344
|
+
# Specify how the query results should be sorted.
|
345
|
+
#
|
346
|
+
# The first call to #sort will register the property that will
|
347
|
+
# serve as the primary sort key. A second call to #sort will set
|
348
|
+
# a secondary sort key, etc.
|
349
|
+
#
|
350
|
+
# This method will sort in ascending order by defaul. To control the
|
351
|
+
# order of the sort, pass ASCENDING or DESCENDING as the direction.
|
352
|
+
#
|
353
|
+
# Note that entities with multi-value properties identified by
|
354
|
+
# name will be sorted by the smallest value in the list.
|
355
|
+
# For more information on sorting properties with multiple values please see
|
356
|
+
# the {datastore
|
357
|
+
# documentation}[http://code.google.com/appengine/docs/java/datastore].
|
358
|
+
#
|
359
|
+
# Returns self (for chaining)
|
360
|
+
#
|
361
|
+
def sort(name, direction=ASCENDING)
|
362
|
+
name = name.to_s if name.kind_of? Symbol
|
363
|
+
@query.add_sort(name, direction)
|
364
|
+
clear_cache
|
365
|
+
end
|
366
|
+
|
367
|
+
# Returns an unmodifiable list of the current filter predicates.
|
368
|
+
def filter_predicates
|
369
|
+
@query.getFilterPredicates
|
370
|
+
end
|
371
|
+
|
372
|
+
# Returns an unmodifiable list of the current sort predicates.
|
373
|
+
def sort_predicates
|
374
|
+
@query.getSortPredicates
|
375
|
+
end
|
376
|
+
|
377
|
+
# Returns the number of entities that currently match this query.
|
378
|
+
def count
|
379
|
+
pquery.count
|
380
|
+
end
|
381
|
+
|
382
|
+
# Retrieves the one and only result for the {@code Query}.
|
383
|
+
#
|
384
|
+
# Throws TooManyResults if more than one result is returned
|
385
|
+
# from the Query.
|
386
|
+
#
|
387
|
+
# Returns the single, matching result, or nil if no entities match
|
388
|
+
#
|
389
|
+
def entity
|
390
|
+
Datastore.convert_exceptions do
|
391
|
+
pquery.as_single_entity
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
|
396
|
+
# Streams the matching entities from the datastore and yields each
|
397
|
+
# matching entity.
|
398
|
+
#
|
399
|
+
# See #convert_options for supported options
|
400
|
+
def each(options={}, &proc) # :yields: entity
|
401
|
+
options = convert_options(options)
|
402
|
+
Datastore.convert_exceptions do
|
403
|
+
pquery.as_iterator(options).each(&proc)
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
# Returns an Enumerable over the matching entities.
|
408
|
+
#
|
409
|
+
# See #convert_options for supported options
|
410
|
+
#
|
411
|
+
def iterator(options={})
|
412
|
+
options = convert_options(options)
|
413
|
+
Datastore.convert_exceptions do
|
414
|
+
pquery.as_iterator(options)
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
# Fetch all matching entities. For large result sets you should
|
419
|
+
# prefer #each or #iterator, which stream the results from the
|
420
|
+
# datastore.
|
421
|
+
#
|
422
|
+
def fetch(options={})
|
423
|
+
options = convert_options(options)
|
424
|
+
Datastore.convert_exceptions do
|
425
|
+
pquery.as_list(options)
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
429
|
+
# Returns a Java.ComGoogleAppengineApiDatastore.PreparedQuery
|
430
|
+
# for this query.
|
431
|
+
#
|
432
|
+
def pquery
|
433
|
+
@pquery ||= Datastore.service.prepare(@query)
|
434
|
+
end
|
435
|
+
|
436
|
+
# Returns a Java.ComGoogleAppengineApiDatastore.Query for this query.
|
437
|
+
def java_query
|
438
|
+
@query
|
439
|
+
end
|
440
|
+
|
441
|
+
# Converts an options hash into FetchOptions.
|
442
|
+
#
|
443
|
+
# Supported options:
|
444
|
+
# [:limit] Maximum number of results the query will return
|
445
|
+
# [:offset]
|
446
|
+
# Number of result to skip before returning any
|
447
|
+
# results. Results that are skipped due to offset do not count
|
448
|
+
# against +limit+.
|
449
|
+
# [:chunk]
|
450
|
+
# Determines the internal chunking strategy of the iterator
|
451
|
+
# returned by #iterator. This affects only the performance of
|
452
|
+
# the query, not the actual results returned.
|
453
|
+
#
|
454
|
+
def convert_options(options)
|
455
|
+
return options if options.java_kind_of? FetchOptions
|
456
|
+
limit = options.delete(:limit)
|
457
|
+
offset = options.delete(:offset)
|
458
|
+
chunk_size = options.delete(:chunk) || FetchOptions::DEFAULT_CHUNK_SIZE
|
459
|
+
unless options.empty?
|
460
|
+
raise ArgumentError, "Unsupported options #{options.inspect}"
|
461
|
+
end
|
462
|
+
options = FetchOptions::Builder.with_chunk_size(chunk_size)
|
463
|
+
options.offset(offset) if offset
|
464
|
+
options.limit(limit) if limit
|
465
|
+
options
|
466
|
+
end
|
467
|
+
|
468
|
+
private
|
469
|
+
def clear_cache
|
470
|
+
@pquery = nil
|
471
|
+
self
|
472
|
+
end
|
473
|
+
end
|
474
|
+
|
475
|
+
class Iterable # :nodoc:
|
476
|
+
include java.lang.Iterable
|
477
|
+
def initialize(array)
|
478
|
+
@array = array
|
479
|
+
end
|
480
|
+
|
481
|
+
def iterator
|
482
|
+
Iterator.new(@array)
|
483
|
+
end
|
484
|
+
end
|
485
|
+
|
486
|
+
class Iterator # :nodoc:
|
487
|
+
include java.util.Iterator
|
488
|
+
def initialize(array)
|
489
|
+
@array = array
|
490
|
+
@index = 0
|
491
|
+
@removed = false
|
492
|
+
end
|
493
|
+
|
494
|
+
def hasNext
|
495
|
+
@index < @array.size
|
496
|
+
end
|
497
|
+
|
498
|
+
def next
|
499
|
+
raise java.util.NoSuchElementException unless hasNext
|
500
|
+
@removed = false
|
501
|
+
@index += 1
|
502
|
+
@array[@index - 1]
|
503
|
+
end
|
504
|
+
|
505
|
+
def remove
|
506
|
+
raise java.lang.IllegalStateException if @removed
|
507
|
+
raise java.lang.IllegalStateException unless @index > 0
|
508
|
+
@removed = true
|
509
|
+
@array.delete_at(@index - 1)
|
510
|
+
end
|
511
|
+
end
|
512
|
+
|
513
|
+
@@db ||= JavaDatastore::DatastoreServiceFactory.getDatastoreService
|
514
|
+
end
|
515
|
+
end
|