google-cloud-datastore 0.20.0

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.
@@ -0,0 +1,222 @@
1
+ # Copyright 2014 Google Inc. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ require "delegate"
17
+
18
+ module Google
19
+ module Cloud
20
+ module Datastore
21
+ class Dataset
22
+ ##
23
+ # LookupResults is a special case Array with additional values.
24
+ # A LookupResults object is returned from Dataset#find_all and
25
+ # contains the entities as well as the Keys that were deferred from
26
+ # the results and the Entities that were missing in the dataset.
27
+ #
28
+ # Please be cautious when treating the QueryResults as an Array.
29
+ # Many common Array methods will return a new Array instance.
30
+ #
31
+ # @example
32
+ # tasks = datastore.find_all task_key1, task_key2, task_key3
33
+ # tasks.size #=> 3
34
+ # tasks.deferred #=> []
35
+ # tasks.missing #=> []
36
+ #
37
+ # @example Caution, many Array methods will return a new Array instance:
38
+ # tasks = datastore.find_all task_key1, task_key2, task_key3
39
+ # tasks.size #=> 3
40
+ # tasks.deferred #=> []
41
+ # tasks.missing #=> []
42
+ # descriptions = tasks.map { |task| task["description"] }
43
+ # descriptions.size #=> 3
44
+ # descriptions.deferred #=> NoMethodError
45
+ # descriptions.missing #=> NoMethodError
46
+ #
47
+ class LookupResults < DelegateClass(::Array)
48
+ ##
49
+ # Keys that were not looked up due to resource constraints.
50
+ attr_accessor :deferred
51
+
52
+ ##
53
+ # Entities not found, with only the key populated.
54
+ attr_accessor :missing
55
+
56
+ ##
57
+ # @private Create a new LookupResults with an array of values.
58
+ def initialize arr = []
59
+ super arr
60
+ end
61
+
62
+ ##
63
+ # Whether there are more results available.
64
+ #
65
+ # @return [Boolean]
66
+ #
67
+ # @example
68
+ # gcloud = Google::Cloud.new
69
+ # datastore = gcloud.datastore
70
+ #
71
+ # task_key1 = datastore.key "Task", "sampleTask1"
72
+ # task_key2 = datastore.key "Task", "sampleTask2"
73
+ # tasks = datastore.find_all task_key1, task_key2
74
+ # if tasks.next?
75
+ # next_tasks = tasks.next
76
+ # end
77
+ #
78
+ def next?
79
+ Array(@deferred).any?
80
+ end
81
+
82
+ ##
83
+ # Retrieve the next page of results.
84
+ #
85
+ # @return [LookupResults]
86
+ #
87
+ # @example
88
+ # gcloud = Google::Cloud.new
89
+ # datastore = gcloud.datastore
90
+ #
91
+ # task_key1 = datastore.key "Task", "sampleTask1"
92
+ # task_key2 = datastore.key "Task", "sampleTask2"
93
+ # tasks = datastore.find_all task_key1, task_key2
94
+ # if tasks.next?
95
+ # next_tasks = tasks.next
96
+ # end
97
+ #
98
+ def next
99
+ return nil unless next?
100
+ ensure_service!
101
+ lookup_res = @service.lookup(
102
+ *Array(@deferred).flatten.map(&:to_grpc),
103
+ consistency: @consistency, transaction: @transaction)
104
+ self.class.from_grpc lookup_res, @service, @consistency
105
+ end
106
+
107
+ ##
108
+ # Retrieves all lookup results by repeatedly loading {#next} until
109
+ # {#next?} returns `false`. Calls the given block once for each
110
+ # result, which is passed as the parameter.
111
+ #
112
+ # An Enumerator is returned if no block is given.
113
+ #
114
+ # This method may make several API calls until all lookup results are
115
+ # retrieved. Be sure to use as narrow a search criteria as possible.
116
+ # Please use with caution.
117
+ #
118
+ # @param [Integer] request_limit The upper limit of API requests to
119
+ # make to load all lookup results. Default is no limit.
120
+ # @yield [result] The block for accessing each lookup result.
121
+ # @yieldparam [Entity] result The lookup result object.
122
+ #
123
+ # @return [Enumerator]
124
+ #
125
+ # @example Iterating each result by passing a block:
126
+ # gcloud = Google::Cloud.new
127
+ # datastore = gcloud.datastore
128
+ #
129
+ # task_key1 = datastore.key "Task", "sampleTask1"
130
+ # task_key2 = datastore.key "Task", "sampleTask2"
131
+ # tasks = datastore.find_all task_key1, task_key2
132
+ # tasks.all do |task|
133
+ # puts "Task #{task.key.id} (#cursor)"
134
+ # end
135
+ #
136
+ # @example Using the enumerator by not passing a block:
137
+ # gcloud = Google::Cloud.new
138
+ # datastore = gcloud.datastore
139
+ #
140
+ # task_key1 = datastore.key "Task", "sampleTask1"
141
+ # task_key2 = datastore.key "Task", "sampleTask2"
142
+ # tasks = datastore.find_all task_key1, task_key2
143
+ # all_keys = tasks.all.map(&:key).each do |task|
144
+ # task.key
145
+ # end
146
+ #
147
+ # @example Limit the number of API calls made:
148
+ # gcloud = Google::Cloud.new
149
+ # datastore = gcloud.datastore
150
+ #
151
+ # task_key1 = datastore.key "Task", "sampleTask1"
152
+ # task_key2 = datastore.key "Task", "sampleTask2"
153
+ # tasks = datastore.find_all task_key1, task_key2
154
+ # tasks.all(request_limit: 10) do |task|
155
+ # puts "Task #{task.key.id} (#cursor)"
156
+ # end
157
+ #
158
+ def all request_limit: nil
159
+ request_limit = request_limit.to_i if request_limit
160
+ unless block_given?
161
+ return enum_for(:all, request_limit: request_limit)
162
+ end
163
+ results = self
164
+ loop do
165
+ results.each { |r| yield r }
166
+ if request_limit
167
+ request_limit -= 1
168
+ break if request_limit < 0
169
+ end
170
+ break unless results.next?
171
+ results = results.next
172
+ end
173
+ end
174
+
175
+ ##
176
+ # @private New Dataset::LookupResults from a
177
+ # Google::Dataset::V1::LookupResponse object.
178
+ def self.from_grpc lookup_res, service, consistency = nil, tx = nil
179
+ entities = to_gcloud_entities lookup_res.found
180
+ deferred = to_gcloud_keys lookup_res.deferred
181
+ missing = to_gcloud_entities lookup_res.missing
182
+ new(entities).tap do |lr|
183
+ lr.instance_variable_set :@service, service
184
+ lr.instance_variable_set :@consistency, consistency
185
+ lr.instance_variable_set :@transaction, tx
186
+ lr.instance_variable_set :@deferred, deferred
187
+ lr.instance_variable_set :@missing, missing
188
+ end
189
+ end
190
+
191
+ protected
192
+
193
+ ##
194
+ # @private Raise an error unless an active connection to the service
195
+ # is available.
196
+ def ensure_service!
197
+ msg = "Must have active connection to datastore service to get next"
198
+ fail msg if @service.nil?
199
+ end
200
+
201
+ ##
202
+ # Convenience method to convert GRPC entities to google-cloud
203
+ # entities.
204
+ def self.to_gcloud_entities grpc_entity_results
205
+ # Entities are nested in an object.
206
+ Array(grpc_entity_results).map do |result|
207
+ # TODO: Make this return an EntityResult with cursor...
208
+ Entity.from_grpc result.entity
209
+ end
210
+ end
211
+
212
+ ##
213
+ # Convenience method to convert GRPC keys to google-cloud keys.
214
+ def self.to_gcloud_keys grpc_keys
215
+ # Keys are not nested in an object like entities are.
216
+ Array(grpc_keys).map { |key| Key.from_grpc key }
217
+ end
218
+ end
219
+ end
220
+ end
221
+ end
222
+ end
@@ -0,0 +1,389 @@
1
+ # Copyright 2014 Google Inc. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ require "delegate"
17
+
18
+ module Google
19
+ module Cloud
20
+ module Datastore
21
+ class Dataset
22
+ ##
23
+ # QueryResults is a special case Array with additional values.
24
+ # A QueryResults object is returned from Dataset#run and contains
25
+ # the Entities from the query as well as the query's cursor and
26
+ # more_results value.
27
+ #
28
+ # Please be cautious when treating the QueryResults as an Array.
29
+ # Many common Array methods will return a new Array instance.
30
+ #
31
+ # @example
32
+ # require "google/cloud"
33
+ #
34
+ # gcloud = Google::Cloud.new
35
+ # datastore = gcloud.datastore
36
+ #
37
+ # query = datastore.query("Task")
38
+ # tasks = datastore.run query
39
+ #
40
+ # tasks.size #=> 3
41
+ # tasks.cursor #=> Cursor(c3VwZXJhd2Vzb21lIQ)
42
+ #
43
+ # @example Caution, many Array methods will return a new Array instance:
44
+ # require "google/cloud"
45
+ #
46
+ # gcloud = Google::Cloud.new
47
+ # datastore = gcloud.datastore
48
+ #
49
+ # query = datastore.query("Task")
50
+ # tasks = datastore.run query
51
+ #
52
+ # tasks.size #=> 3
53
+ # tasks.end_cursor #=> Cursor(c3VwZXJhd2Vzb21lIQ)
54
+ # descriptions = tasks.map { |task| task["description"] }
55
+ # descriptions.size #=> 3
56
+ # descriptions.cursor #=> NoMethodError
57
+ #
58
+ class QueryResults < DelegateClass(::Array)
59
+ ##
60
+ # The end_cursor of the QueryResults.
61
+ #
62
+ # @return [Google::Cloud::Datastore::Cursor]
63
+ attr_reader :end_cursor
64
+ alias_method :cursor, :end_cursor
65
+
66
+ ##
67
+ # The state of the query after the current batch.
68
+ #
69
+ # Expected values are:
70
+ #
71
+ # * `:NOT_FINISHED`
72
+ # * `:MORE_RESULTS_AFTER_LIMIT`
73
+ # * `:MORE_RESULTS_AFTER_CURSOR`
74
+ # * `:NO_MORE_RESULTS`
75
+ attr_reader :more_results
76
+
77
+ ##
78
+ # @private
79
+ attr_accessor :service, :namespace, :cursors, :query
80
+
81
+ ##
82
+ # @private
83
+ attr_writer :end_cursor, :more_results
84
+
85
+ ##
86
+ # Convenience method for determining if the `more_results` value
87
+ # is `:NOT_FINISHED`
88
+ def not_finished?
89
+ more_results == :NOT_FINISHED
90
+ end
91
+
92
+ ##
93
+ # Convenience method for determining if the `more_results` value
94
+ # is `:MORE_RESULTS_AFTER_LIMIT`
95
+ def more_after_limit?
96
+ more_results == :MORE_RESULTS_AFTER_LIMIT
97
+ end
98
+
99
+ ##
100
+ # Convenience method for determining if the `more_results` value
101
+ # is `:MORE_RESULTS_AFTER_CURSOR`
102
+ def more_after_cursor?
103
+ more_results == :MORE_RESULTS_AFTER_CURSOR
104
+ end
105
+
106
+ ##
107
+ # Convenience method for determining if the `more_results` value
108
+ # is `:NO_MORE_RESULTS`
109
+ def no_more?
110
+ more_results == :NO_MORE_RESULTS
111
+ end
112
+
113
+ ##
114
+ # @private Create a new QueryResults with an array of values.
115
+ def initialize arr = []
116
+ super arr
117
+ end
118
+
119
+ ##
120
+ # Whether there are more results available.
121
+ #
122
+ # @return [Boolean]
123
+ #
124
+ # @example
125
+ # require "google/cloud"
126
+ #
127
+ # gcloud = Google::Cloud.new
128
+ # datastore = gcloud.datastore
129
+ # query = datastore.query "Task"
130
+ # tasks = datastore.run query
131
+ #
132
+ # if tasks.next?
133
+ # next_tasks = tasks.next
134
+ # end
135
+ #
136
+ def next?
137
+ not_finished?
138
+ end
139
+
140
+ ##
141
+ # Retrieve the next page of results.
142
+ #
143
+ # @return [QueryResults]
144
+ #
145
+ # @example
146
+ # require "google/cloud"
147
+ #
148
+ # gcloud = Google::Cloud.new
149
+ # datastore = gcloud.datastore
150
+ # query = datastore.query "Task"
151
+ # tasks = datastore.run query
152
+ #
153
+ # if tasks.next?
154
+ # next_tasks = tasks.next
155
+ # end
156
+ #
157
+ def next
158
+ return nil unless next?
159
+ return nil if end_cursor.nil?
160
+ ensure_service!
161
+ query.start_cursor = cursor.to_grpc # should always be a Cursor...
162
+ query_res = service.run_query query, namespace
163
+ self.class.from_grpc query_res, service, namespace, query
164
+ end
165
+
166
+ ##
167
+ # Retrieve the {Cursor} for the provided result.
168
+ #
169
+ # @param [Entity] result The entity object to get a cursor for.
170
+ #
171
+ # @return [Cursor]
172
+ #
173
+ # @example
174
+ # require "google/cloud"
175
+ #
176
+ # gcloud = Google::Cloud.new
177
+ # datastore = gcloud.datastore
178
+ # query = datastore.query "Task"
179
+ # tasks = datastore.run query
180
+ #
181
+ # first_task = tasks.first
182
+ # first_cursor = tasks.cursor_for first_task
183
+ #
184
+ def cursor_for result
185
+ cursor_index = index result
186
+ return nil if cursor_index.nil?
187
+ cursors[cursor_index]
188
+ end
189
+
190
+ ##
191
+ # Calls the given block once for each result and cursor combination,
192
+ # which are passed as parameters.
193
+ #
194
+ # An Enumerator is returned if no block is given.
195
+ #
196
+ # @yield [result, cursor] The block for accessing each query result
197
+ # and cursor.
198
+ # @yieldparam [Entity] result The query result object.
199
+ # @yieldparam [Cursor] cursor The cursor object.
200
+ #
201
+ # @return [Enumerator]
202
+ #
203
+ # @example
204
+ # require "google/cloud"
205
+ #
206
+ # gcloud = Google::Cloud.new
207
+ # datastore = gcloud.datastore
208
+ # query = datastore.query "Task"
209
+ # tasks = datastore.run query
210
+ # tasks.each_with_cursor do |task, cursor|
211
+ # puts "Task #{task.key.id} (#cursor)"
212
+ # end
213
+ #
214
+ def each_with_cursor
215
+ return enum_for(:each_with_cursor) unless block_given?
216
+ zip(cursors).each { |r, c| yield [r, c] }
217
+ end
218
+
219
+ ##
220
+ # Retrieves all query results by repeatedly loading {#next} until
221
+ # {#next?} returns `false`. Calls the given block once for each query
222
+ # result, which is passed as the parameter.
223
+ #
224
+ # An Enumerator is returned if no block is given.
225
+ #
226
+ # This method may make several API calls until all query results are
227
+ # retrieved. Be sure to use as narrow a search criteria as possible.
228
+ # Please use with caution.
229
+ #
230
+ # @param [Integer] request_limit The upper limit of API requests to
231
+ # make to load all query results. Default is no limit.
232
+ # @yield [result] The block for accessing each query result.
233
+ # @yieldparam [Entity] result The query result object.
234
+ #
235
+ # @return [Enumerator]
236
+ #
237
+ # @example Iterating each query result by passing a block:
238
+ # require "google/cloud"
239
+ #
240
+ # gcloud = Google::Cloud.new
241
+ # datastore = gcloud.datastore
242
+ # query = datastore.query "Task"
243
+ # tasks = datastore.run query
244
+ # tasks.all do |task|
245
+ # puts "Task #{task.key.id} (#cursor)"
246
+ # end
247
+ #
248
+ # @example Using the enumerator by not passing a block:
249
+ # require "google/cloud"
250
+ #
251
+ # gcloud = Google::Cloud.new
252
+ # datastore = gcloud.datastore
253
+ # query = datastore.query "Task"
254
+ # tasks = datastore.run query
255
+ # tasks.all.map(&:key).each do |key|
256
+ # puts "Key #{key.id}"
257
+ # end
258
+ #
259
+ # @example Limit the number of API calls made:
260
+ # require "google/cloud"
261
+ #
262
+ # gcloud = Google::Cloud.new
263
+ # datastore = gcloud.datastore
264
+ # query = datastore.query "Task"
265
+ # tasks = datastore.run query
266
+ # tasks.all(request_limit: 10) do |task|
267
+ # puts "Task #{task.key.id} (#cursor)"
268
+ # end
269
+ #
270
+ def all request_limit: nil
271
+ request_limit = request_limit.to_i if request_limit
272
+ unless block_given?
273
+ return enum_for(:all, request_limit: request_limit)
274
+ end
275
+ results = self
276
+ loop do
277
+ results.each { |r| yield r }
278
+ if request_limit
279
+ request_limit -= 1
280
+ break if request_limit < 0
281
+ end
282
+ break unless results.next?
283
+ results = results.next
284
+ end
285
+ end
286
+
287
+ ##
288
+ # Retrieves all query results and cursors by repeatedly loading
289
+ # {#next} until {#next?} returns `false`. Calls the given block once
290
+ # for each result and cursor combination, which are passed as
291
+ # parameters.
292
+ #
293
+ # An Enumerator is returned if no block is given.
294
+ #
295
+ # This method may make several API calls until all query results are
296
+ # retrieved. Be sure to use as narrow a search criteria as possible.
297
+ # Please use with caution.
298
+ #
299
+ # @param [Integer] request_limit The upper limit of API requests to
300
+ # make to load all tables. Default is no limit.
301
+ # @yield [result, cursor] The block for accessing each query result
302
+ # and cursor.
303
+ # @yieldparam [Entity] result The query result object.
304
+ # @yieldparam [Cursor] cursor The cursor object.
305
+ #
306
+ # @return [Enumerator]
307
+ #
308
+ # @example Iterating all results and cursors by passing a block:
309
+ # require "google/cloud"
310
+ #
311
+ # gcloud = Google::Cloud.new
312
+ # datastore = gcloud.datastore
313
+ # query = datastore.query "Task"
314
+ # tasks = datastore.run query
315
+ # tasks.all_with_cursor do |task, cursor|
316
+ # puts "Task #{task.key.id} (#cursor)"
317
+ # end
318
+ #
319
+ # @example Using the enumerator by not passing a block:
320
+ # require "google/cloud"
321
+ #
322
+ # gcloud = Google::Cloud.new
323
+ # datastore = gcloud.datastore
324
+ # query = datastore.query "Task"
325
+ # tasks = datastore.run query
326
+ # tasks.all_with_cursor.count #=> number of result/cursor pairs
327
+ #
328
+ # @example Limit the number of API calls made:
329
+ # require "google/cloud"
330
+ #
331
+ # gcloud = Google::Cloud.new
332
+ # datastore = gcloud.datastore
333
+ # query = datastore.query "Task"
334
+ # tasks = datastore.run query
335
+ # tasks.all_with_cursor(request_limit: 10) do |task, cursor|
336
+ # puts "Task #{task.key.id} (#cursor)"
337
+ # end
338
+ #
339
+ def all_with_cursor request_limit: nil
340
+ request_limit = request_limit.to_i if request_limit
341
+ unless block_given?
342
+ return enum_for(:all_with_cursor, request_limit: request_limit)
343
+ end
344
+ results = self
345
+
346
+ loop do
347
+ results.zip(results.cursors).each { |r, c| yield r, c }
348
+ if request_limit
349
+ request_limit -= 1
350
+ break if request_limit < 0
351
+ end
352
+ break unless results.next?
353
+ results = results.next
354
+ end
355
+ end
356
+
357
+ ##
358
+ # @private New Dataset::QueryResults from a
359
+ # Google::Dataset::V1::RunQueryResponse object.
360
+ def self.from_grpc query_res, service, namespace, query
361
+ r, c = Array(query_res.batch.entity_results).map do |result|
362
+ [Entity.from_grpc(result.entity), Cursor.from_grpc(result.cursor)]
363
+ end.transpose
364
+ r ||= []
365
+ c ||= []
366
+ new(r).tap do |qr|
367
+ qr.cursors = c
368
+ qr.end_cursor = Cursor.from_grpc query_res.batch.end_cursor
369
+ qr.more_results = query_res.batch.more_results
370
+ qr.service = service
371
+ qr.namespace = namespace
372
+ qr.query = query_res.query || query
373
+ end
374
+ end
375
+
376
+ protected
377
+
378
+ ##
379
+ # @private Raise an error unless an active connection to the service
380
+ # is available.
381
+ def ensure_service!
382
+ msg = "Must have active connection to datastore service to get next"
383
+ fail msg if @service.nil? || @query.nil?
384
+ end
385
+ end
386
+ end
387
+ end
388
+ end
389
+ end