google-cloud-datastore 0.20.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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