appengine-apis 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/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
|