gcloud 0.8.2 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +8 -8
  2. data/CHANGELOG.md +26 -0
  3. data/OVERVIEW.md +10 -8
  4. data/lib/gcloud.rb +12 -13
  5. data/lib/gcloud/bigquery/dataset/list.rb +2 -4
  6. data/lib/gcloud/bigquery/job/list.rb +3 -5
  7. data/lib/gcloud/bigquery/table/list.rb +3 -5
  8. data/lib/gcloud/datastore.rb +326 -97
  9. data/lib/gcloud/datastore/commit.rb +73 -56
  10. data/lib/gcloud/datastore/credentials.rb +1 -12
  11. data/lib/gcloud/datastore/cursor.rb +76 -0
  12. data/lib/gcloud/datastore/dataset.rb +337 -134
  13. data/lib/gcloud/datastore/dataset/lookup_results.rb +12 -12
  14. data/lib/gcloud/datastore/dataset/query_results.rb +117 -27
  15. data/lib/gcloud/datastore/entity.rb +159 -93
  16. data/lib/gcloud/datastore/errors.rb +0 -21
  17. data/lib/gcloud/datastore/gql_query.rb +211 -0
  18. data/lib/gcloud/datastore/grpc_utils.rb +131 -0
  19. data/lib/gcloud/datastore/key.rb +74 -65
  20. data/lib/gcloud/datastore/properties.rb +14 -1
  21. data/lib/gcloud/datastore/query.rb +188 -52
  22. data/lib/gcloud/datastore/service.rb +161 -0
  23. data/lib/gcloud/datastore/transaction.rb +175 -60
  24. data/lib/gcloud/dns/change/list.rb +2 -4
  25. data/lib/gcloud/dns/record/list.rb +2 -4
  26. data/lib/gcloud/dns/zone/list.rb +2 -4
  27. data/lib/gcloud/grpc_utils.rb +11 -0
  28. data/lib/gcloud/logging/entry.rb +6 -17
  29. data/lib/gcloud/logging/entry/list.rb +8 -9
  30. data/lib/gcloud/logging/metric/list.rb +4 -5
  31. data/lib/gcloud/logging/resource.rb +1 -12
  32. data/lib/gcloud/logging/resource_descriptor.rb +9 -12
  33. data/lib/gcloud/logging/resource_descriptor/list.rb +4 -5
  34. data/lib/gcloud/logging/sink/list.rb +4 -5
  35. data/lib/gcloud/pubsub/message.rb +1 -3
  36. data/lib/gcloud/pubsub/subscription.rb +5 -7
  37. data/lib/gcloud/pubsub/topic.rb +1 -3
  38. data/lib/gcloud/resource_manager/project/list.rb +2 -4
  39. data/lib/gcloud/translate/api.rb +5 -3
  40. data/lib/gcloud/translate/connection.rb +4 -4
  41. data/lib/gcloud/version.rb +1 -1
  42. metadata +9 -20
  43. data/lib/gcloud/datastore/connection.rb +0 -203
  44. data/lib/gcloud/datastore/proto.rb +0 -266
  45. data/lib/gcloud/proto/datastore_v1.pb.rb +0 -377
  46. data/lib/google/protobuf/any.rb +0 -17
  47. data/lib/google/protobuf/api.rb +0 -31
  48. data/lib/google/protobuf/duration.rb +0 -17
  49. data/lib/google/protobuf/empty.rb +0 -15
  50. data/lib/google/protobuf/field_mask.rb +0 -16
  51. data/lib/google/protobuf/source_context.rb +0 -16
  52. data/lib/google/protobuf/struct.rb +0 -35
  53. data/lib/google/protobuf/timestamp.rb +0 -17
  54. data/lib/google/protobuf/type.rb +0 -79
  55. data/lib/google/protobuf/wrappers.rb +0 -48
@@ -22,21 +22,23 @@ module Gcloud
22
22
  # in a single commit.
23
23
  #
24
24
  # @example
25
- # dataset.commit do |c|
25
+ # gcloud = Gcloud.new
26
+ # datastore = gcloud.datastore
27
+ # datastore.commit do |c|
26
28
  # c.save task1, task2
27
29
  # c.delete entity1, entity2
28
30
  # end
29
31
  #
30
- # See {Gcloud::Datastore::Dataset#commit} and
31
- # {Gcloud::Datastore::Transaction#commit}.
32
- #
32
+ # @see {Gcloud::Datastore::Dataset#commit}
33
+ # @see {Gcloud::Datastore::Transaction#commit}
33
34
  class Commit
34
35
  ##
35
36
  # @private Create a new Commit object.
36
37
  def initialize
37
- @shared_entities = []
38
- @shared_auto_ids = []
39
- @shared_deletes = []
38
+ @shared_upserts = []
39
+ @shared_inserts = []
40
+ @shared_updates = []
41
+ @shared_deletes = []
40
42
  end
41
43
 
42
44
  ##
@@ -46,19 +48,56 @@ module Gcloud
46
48
  #
47
49
  # @example
48
50
  # gcloud = Gcloud.new
49
- # dataset = gcloud.datastore
50
- # dataset.commit do |c|
51
+ # datastore = gcloud.datastore
52
+ # datastore.commit do |c|
51
53
  # c.save task1, task2
52
54
  # end
53
55
  #
54
56
  def save *entities
55
- entities.each do |entity|
56
- shared_auto_ids << entity if entity.key.incomplete?
57
- shared_entities << entity
58
- end
57
+ entities = Array(entities).flatten
58
+ @shared_upserts += entities unless entities.empty?
59
59
  # Do not save yet
60
60
  entities
61
61
  end
62
+ alias_method :upsert, :save
63
+
64
+ ##
65
+ # Inserts entities to the Datastore.
66
+ #
67
+ # @param [Entity] entities One or more Entity objects to insert.
68
+ #
69
+ # @example
70
+ # gcloud = Gcloud.new
71
+ # datastore = gcloud.datastore
72
+ # datastore.commit do |c|
73
+ # c.insert task1, task2
74
+ # end
75
+ #
76
+ def insert *entities
77
+ entities = Array(entities).flatten
78
+ @shared_inserts += entities unless entities.empty?
79
+ # Do not insert yet
80
+ entities
81
+ end
82
+
83
+ ##
84
+ # Updates entities to the Datastore.
85
+ #
86
+ # @param [Entity] entities One or more Entity objects to update.
87
+ #
88
+ # @example
89
+ # gcloud = Gcloud.new
90
+ # datastore = gcloud.datastore
91
+ # datastore.commit do |c|
92
+ # c.update task1, task2
93
+ # end
94
+ #
95
+ def update *entities
96
+ entities = Array(entities).flatten
97
+ @shared_updates += entities unless entities.empty?
98
+ # Do not update yet
99
+ entities
100
+ end
62
101
 
63
102
  ##
64
103
  # Remove entities from the Datastore.
@@ -68,63 +107,41 @@ module Gcloud
68
107
  #
69
108
  # @example
70
109
  # gcloud = Gcloud.new
71
- # dataset = gcloud.datastore
72
- # dataset.commit do |c|
73
- # c.delete entity1, entity2
110
+ # datastore = gcloud.datastore
111
+ # datastore.commit do |c|
112
+ # c.delete task1, task2
74
113
  # end
75
114
  #
76
115
  def delete *entities_or_keys
77
- keys = entities_or_keys.map do |e_or_k|
116
+ keys = Array(entities_or_keys).flatten.map do |e_or_k|
78
117
  e_or_k.respond_to?(:key) ? e_or_k.key : e_or_k
79
118
  end
80
- keys.each { |k| shared_deletes << k }
119
+ @shared_deletes += keys unless keys.empty?
81
120
  # Do not delete yet
82
121
  true
83
122
  end
84
123
 
85
- # @private Mutation object to be committed.
86
- def mutation
87
- Proto.new_mutation.tap do |m|
88
- m.insert_auto_id = shared_auto_ids.map(&:to_proto)
89
- m.upsert = shared_upserts.map(&:to_proto)
90
- m.delete = shared_deletes.map(&:to_proto)
124
+ # @private Mutations object to be committed.
125
+ def mutations
126
+ mutations = []
127
+ mutations += @shared_upserts.map do |entity|
128
+ Google::Datastore::V1beta3::Mutation.new upsert: entity.to_grpc
91
129
  end
92
- end
93
-
94
- # @private Entities that need key ids assigned.
95
- def auto_id_entities
96
- shared_auto_ids
130
+ mutations += @shared_inserts.map do |entity|
131
+ Google::Datastore::V1beta3::Mutation.new insert: entity.to_grpc
132
+ end
133
+ mutations += @shared_updates.map do |entity|
134
+ Google::Datastore::V1beta3::Mutation.new update: entity.to_grpc
135
+ end
136
+ mutations += @shared_deletes.map do |key|
137
+ Google::Datastore::V1beta3::Mutation.new delete: key.to_grpc
138
+ end
139
+ mutations
97
140
  end
98
141
 
99
142
  # @private All entities saved in the commit.
100
143
  def entities
101
- shared_entities
102
- end
103
-
104
- protected
105
-
106
- ##
107
- # @private List of Entity objects to be saved.
108
- def shared_entities
109
- @shared_entities
110
- end
111
-
112
- ##
113
- # @private List of Entity objects that need auto_ids
114
- def shared_auto_ids
115
- @shared_auto_ids
116
- end
117
-
118
- ##
119
- # @private List of Entity objects to be saved.
120
- def shared_upserts
121
- shared_entities - shared_auto_ids
122
- end
123
-
124
- ##
125
- # @private List of Key objects to be deleted.
126
- def shared_deletes
127
- @shared_deletes
144
+ @shared_upserts + @shared_inserts + @shared_updates
128
145
  end
129
146
  end
130
147
  end
@@ -26,21 +26,10 @@ module Gcloud
26
26
  #
27
27
  # @see https://developers.google.com/accounts/docs/application-default-credentials
28
28
  class Credentials < Gcloud::Credentials
29
- SCOPE = ["https://www.googleapis.com/auth/datastore",
30
- "https://www.googleapis.com/auth/userinfo.email"]
29
+ SCOPE = ["https://www.googleapis.com/auth/datastore"]
31
30
  PATH_ENV_VARS = %w(DATASTORE_KEYFILE GCLOUD_KEYFILE GOOGLE_CLOUD_KEYFILE)
32
31
  JSON_ENV_VARS = %w(DATASTORE_KEYFILE_JSON GCLOUD_KEYFILE_JSON
33
32
  GOOGLE_CLOUD_KEYFILE_JSON)
34
-
35
- ##
36
- # Sign OAuth 2.0 API calls.
37
- def sign_http_request request
38
- if @client
39
- @client.fetch_access_token! if @client.expires_within? 30
40
- @client.generate_authenticated_request request: request
41
- end
42
- request
43
- end
44
33
  end
45
34
  end
46
35
  end
@@ -0,0 +1,76 @@
1
+ # Copyright 2016 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
+ module Gcloud
17
+ module Datastore
18
+ ##
19
+ # # Cursor
20
+ #
21
+ # Cursor is a point in query results. Cursors are returned in QueryResults.
22
+ #
23
+ # @example
24
+ # require "gcloud"
25
+ #
26
+ # gcloud = Gcloud.new
27
+ # datastore = gcloud.datastore
28
+ #
29
+ # query = datastore.query("Task").
30
+ # where("done", "=", false)
31
+ #
32
+ # tasks = datastore.run query
33
+ # tasks.cursor #=> Cursor
34
+ #
35
+ class Cursor
36
+ # Base64 encoded array of bytes
37
+ def initialize cursor
38
+ @cursor = cursor
39
+ end
40
+
41
+ # Base64 encoded array of bytes
42
+ def to_s
43
+ @cursor
44
+ end
45
+
46
+ # @private
47
+ def inspect
48
+ "#{self.class}(#{@cursor})"
49
+ end
50
+
51
+ # @private
52
+ def == other
53
+ return false unless other.is_a? Cursor
54
+ @cursor == other.to_s
55
+ end
56
+
57
+ # @private
58
+ def <=> other
59
+ return -1 unless other.is_a? Cursor
60
+ @cursor <=> other.to_s
61
+ end
62
+
63
+ # @private byte array as a string
64
+ def to_grpc
65
+ GRPCUtils.decode_bytes(@cursor)
66
+ end
67
+
68
+ # @private byte array as a string
69
+ def self.from_grpc grpc
70
+ grpc = String grpc
71
+ return nil if grpc.empty?
72
+ new GRPCUtils.encode_bytes(grpc)
73
+ end
74
+ end
75
+ end
76
+ end
@@ -14,12 +14,15 @@
14
14
 
15
15
 
16
16
  require "gcloud/gce"
17
- require "gcloud/datastore/connection"
17
+ require "gcloud/datastore/grpc_utils"
18
18
  require "gcloud/datastore/credentials"
19
+ require "gcloud/datastore/service"
19
20
  require "gcloud/datastore/commit"
20
21
  require "gcloud/datastore/entity"
21
22
  require "gcloud/datastore/key"
22
23
  require "gcloud/datastore/query"
24
+ require "gcloud/datastore/gql_query"
25
+ require "gcloud/datastore/cursor"
23
26
  require "gcloud/datastore/dataset/lookup_results"
24
27
  require "gcloud/datastore/dataset/query_results"
25
28
 
@@ -41,16 +44,17 @@ module Gcloud
41
44
  # require "gcloud"
42
45
  #
43
46
  # gcloud = Gcloud.new
44
- # dataset = gcloud.datastore
47
+ # datastore = gcloud.datastore
45
48
  #
46
- # query = dataset.query("Task").
47
- # where("completed", "=", true)
49
+ # query = datastore.query("Task").
50
+ # where("done", "=", false)
48
51
  #
49
- # tasks = dataset.run query
52
+ # tasks = datastore.run query
50
53
  #
51
54
  class Dataset
52
- # @private
53
- attr_accessor :connection
55
+ ##
56
+ # @private The gRPC Service object.
57
+ attr_accessor :service
54
58
 
55
59
  ##
56
60
  # @private Creates a new Dataset instance.
@@ -59,7 +63,7 @@ module Gcloud
59
63
  def initialize project, credentials
60
64
  project = project.to_s # Always cast to a string
61
65
  fail ArgumentError, "project is missing" if project.empty?
62
- @connection = Connection.new project, credentials
66
+ @service = Service.new project, credentials
63
67
  end
64
68
 
65
69
  ##
@@ -71,11 +75,11 @@ module Gcloud
71
75
  # gcloud = Gcloud.new "my-todo-project",
72
76
  # "/path/to/keyfile.json"
73
77
  #
74
- # dataset = gcloud.datastore
75
- # dataset.project #=> "my-todo-project"
78
+ # datastore = gcloud.datastore
79
+ # datastore.project #=> "my-todo-project"
76
80
  #
77
81
  def project
78
- connection.dataset_id
82
+ service.project
79
83
  end
80
84
 
81
85
  ##
@@ -97,19 +101,20 @@ module Gcloud
97
101
  # @return [Array<Gcloud::Datastore::Key>]
98
102
  #
99
103
  # @example
100
- # empty_key = dataset.key "Task"
101
- # task_keys = dataset.allocate_ids empty_key, 5
104
+ # task_key = datastore.key "Task"
105
+ # task_keys = datastore.allocate_ids task_key, 5
102
106
  #
103
107
  def allocate_ids incomplete_key, count = 1
104
108
  if incomplete_key.complete?
105
109
  fail Gcloud::Datastore::Error, "An incomplete key must be provided."
106
110
  end
107
111
 
108
- incomplete_keys = count.times.map { incomplete_key.to_proto }
109
- response = connection.allocate_ids(*incomplete_keys)
110
- Array(response.key).map do |key|
111
- Key.from_proto key
112
- end
112
+ ensure_service!
113
+ incomplete_keys = count.times.map { incomplete_key.to_grpc }
114
+ allocate_res = service.allocate_ids(*incomplete_keys)
115
+ allocate_res.keys.map { |key| Key.from_grpc key }
116
+ rescue GRPC::BadStatus => e
117
+ raise Gcloud::Error.from_error(e)
113
118
  end
114
119
 
115
120
  ##
@@ -119,12 +124,106 @@ module Gcloud
119
124
  #
120
125
  # @return [Array<Gcloud::Datastore::Entity>]
121
126
  #
122
- # @example
123
- # dataset.save task1, task2
127
+ # @example Insert a new entity:
128
+ # task = datastore.entity "Task" do |t|
129
+ # t["type"] = "Personal"
130
+ # t["done"] = false
131
+ # t["priority"] = 4
132
+ # t["description"] = "Learn Cloud Datastore"
133
+ # end
134
+ # task.key.id #=> nil
135
+ # datastore.save task
136
+ # task.key.id #=> 123456
137
+ #
138
+ # @example Insert multiple new entities in a batch:
139
+ # task1 = datastore.entity "Task" do |t|
140
+ # t["type"] = "Personal"
141
+ # t["done"] = false
142
+ # t["priority"] = 4
143
+ # t["description"] = "Learn Cloud Datastore"
144
+ # end
145
+ #
146
+ # task2 = datastore.entity "Task" do |t|
147
+ # t["type"] = "Personal"
148
+ # t["done"] = false
149
+ # t["priority"] = 5
150
+ # t["description"] = "Integrate Cloud Datastore"
151
+ # end
152
+ #
153
+ # task_key1, task_key2 = datastore.save(task1, task2).map(&:key)
154
+ #
155
+ # @example Update an existing entity:
156
+ # task = datastore.find "Task", "sampleTask"
157
+ # task["priority"] = 5
158
+ # datastore.save task
124
159
  #
125
160
  def save *entities
126
161
  commit { |c| c.save(*entities) }
127
162
  end
163
+ alias_method :upsert, :save
164
+
165
+ ##
166
+ # Insert one or more entities to the Datastore. An InvalidArgumentError
167
+ # will raised if the entities cannot be inserted.
168
+ #
169
+ # @param [Entity] entities One or more entity objects to be inserted.
170
+ #
171
+ # @return [Array<Gcloud::Datastore::Entity>]
172
+ #
173
+ # @example Insert a new entity:
174
+ # task = datastore.entity "Task" do |t|
175
+ # t["type"] = "Personal"
176
+ # t["done"] = false
177
+ # t["priority"] = 4
178
+ # t["description"] = "Learn Cloud Datastore"
179
+ # end
180
+ # task.key.id #=> nil
181
+ # datastore.insert task
182
+ # task.key.id #=> 123456
183
+ #
184
+ # @example Insert multiple new entities in a batch:
185
+ # task1 = datastore.entity "Task" do |t|
186
+ # t["type"] = "Personal"
187
+ # t["done"] = false
188
+ # t["priority"] = 4
189
+ # t["description"] = "Learn Cloud Datastore"
190
+ # end
191
+ #
192
+ # task2 = datastore.entity "Task" do |t|
193
+ # t["type"] = "Personal"
194
+ # t["done"] = false
195
+ # t["priority"] = 5
196
+ # t["description"] = "Integrate Cloud Datastore"
197
+ # end
198
+ #
199
+ # task_key1, task_key2 = datastore.insert(task1, task2).map(&:key)
200
+ #
201
+ def insert *entities
202
+ commit { |c| c.insert(*entities) }
203
+ end
204
+
205
+ ##
206
+ # Update one or more entities to the Datastore. An InvalidArgumentError
207
+ # will raised if the entities cannot be updated.
208
+ #
209
+ # @param [Entity] entities One or more entity objects to be updated.
210
+ #
211
+ # @return [Array<Gcloud::Datastore::Entity>]
212
+ #
213
+ # @example Update an existing entity:
214
+ # task = datastore.find "Task", "sampleTask"
215
+ # task["done"] = true
216
+ # datastore.save task
217
+ #
218
+ # @example update multiple new entities in a batch:
219
+ # query = datastore.query("Task").where("done", "=", false)
220
+ # tasks = datastore.run query
221
+ # tasks.each { |t| t["done"] = true }
222
+ # datastore.update tasks
223
+ #
224
+ def update *entities
225
+ commit { |c| c.update(*entities) }
226
+ end
128
227
 
129
228
  ##
130
229
  # Remove entities from the Datastore.
@@ -136,8 +235,8 @@ module Gcloud
136
235
  #
137
236
  # @example
138
237
  # gcloud = Gcloud.new
139
- # dataset = gcloud.datastore
140
- # dataset.delete entity1, entity2
238
+ # datastore = gcloud.datastore
239
+ # datastore.delete task1, task2
141
240
  #
142
241
  def delete *entities_or_keys
143
242
  commit { |c| c.delete(*entities_or_keys) }
@@ -154,26 +253,34 @@ module Gcloud
154
253
  # persisted.
155
254
  #
156
255
  # @example
157
- # dataset.commit do |c|
158
- # c.save task1, task2
159
- # c.delete entity1, entity2
256
+ # gcloud = Gcloud.new
257
+ # datastore = gcloud.datastore
258
+ # datastore.commit do |c|
259
+ # c.save task3, task4
260
+ # c.delete task1, task2
160
261
  # end
161
262
  #
162
263
  def commit
163
264
  return unless block_given?
164
265
  c = Commit.new
165
266
  yield c
166
- response = connection.commit c.mutation
167
- auto_id_assign_ids c.auto_id_entities,
168
- response.mutation_result.insert_auto_id_key
169
- # Make sure all entity keys are frozen so all show as persisted
267
+
268
+ ensure_service!
269
+ commit_res = service.commit c.mutations
170
270
  entities = c.entities
271
+ returned_keys = commit_res.mutation_results.map(&:key)
272
+ returned_keys.each_with_index do |key, index|
273
+ next if entities[index].nil?
274
+ entities[index].key = Key.from_grpc(key) unless key.nil?
275
+ end
171
276
  entities.each { |e| e.key.freeze unless e.persisted? }
172
277
  entities
278
+ rescue GRPC::BadStatus => e
279
+ raise Gcloud::Error.from_error(e)
173
280
  end
174
281
 
175
282
  ##
176
- # Retrieve an entity by providing key information.
283
+ # Retrieve an entity by key.
177
284
  #
178
285
  # @param [Key, String] key_or_kind A Key object or `kind` string value.
179
286
  # @param [Integer, String, nil] id_or_name The Key's `id` or `name` value
@@ -190,11 +297,11 @@ module Gcloud
190
297
  # @return [Gcloud::Datastore::Entity, nil]
191
298
  #
192
299
  # @example Finding an entity with a key:
193
- # key = dataset.key "Task", 123456
194
- # task = dataset.find key
300
+ # task_key = datastore.key "Task", "sampleTask"
301
+ # task = datastore.find task_key
195
302
  #
196
303
  # @example Finding an entity with a `kind` and `id`/`name`:
197
- # task = dataset.find "Task", 123456
304
+ # task = datastore.find "Task", "sampleTask"
198
305
  #
199
306
  def find key_or_kind, id_or_name = nil, consistency: nil
200
307
  key = key_or_kind
@@ -206,7 +313,8 @@ module Gcloud
206
313
  alias_method :get, :find
207
314
 
208
315
  ##
209
- # Retrieve the entities for the provided keys.
316
+ # Retrieve the entities for the provided keys. The order of results is
317
+ # undefined and has no relation to the order of `keys` arguments.
210
318
  #
211
319
  # @param [Key] keys One or more Key objects to find records for.
212
320
  # @param [Symbol] consistency The non-transactional read consistency to
@@ -222,26 +330,30 @@ module Gcloud
222
330
  #
223
331
  # @example
224
332
  # gcloud = Gcloud.new
225
- # dataset = gcloud.datastore
226
- # key1 = dataset.key "Task", 123456
227
- # key2 = dataset.key "Task", 987654
228
- # tasks = dataset.find_all key1, key2
333
+ # datastore = gcloud.datastore
334
+ #
335
+ # task_key1 = datastore.key "Task", "sampleTask1"
336
+ # task_key2 = datastore.key "Task", "sampleTask2"
337
+ # tasks = datastore.find_all task_key1, task_key2
229
338
  #
230
339
  def find_all *keys, consistency: nil
340
+ ensure_service!
231
341
  check_consistency! consistency
232
- response = connection.lookup(*keys.map(&:to_proto),
233
- consistency: consistency)
234
- entities = to_gcloud_entities response.found
235
- deferred = to_gcloud_keys response.deferred
236
- missing = to_gcloud_entities response.missing
342
+ lookup_res = service.lookup(*keys.map(&:to_grpc),
343
+ consistency: consistency)
344
+ entities = to_gcloud_entities lookup_res.found
345
+ deferred = to_gcloud_keys lookup_res.deferred
346
+ missing = to_gcloud_entities lookup_res.missing
237
347
  LookupResults.new entities, deferred, missing
348
+ rescue GRPC::BadStatus => e
349
+ raise Gcloud::Error.from_error(e)
238
350
  end
239
351
  alias_method :lookup, :find_all
240
352
 
241
353
  ##
242
354
  # Retrieve entities specified by a Query.
243
355
  #
244
- # @param [Query] query The Query object with the search criteria.
356
+ # @param [Query, GqlQuery] query The object with the search criteria.
245
357
  # @param [String] namespace The namespace the query is to run within.
246
358
  # @param [Symbol] consistency The non-transactional read consistency to
247
359
  # use. Cannot be set to `:strong` for global queries. Accepted values
@@ -255,24 +367,43 @@ module Gcloud
255
367
  # @return [Gcloud::Datastore::Dataset::QueryResults]
256
368
  #
257
369
  # @example
258
- # query = dataset.query("Task").
259
- # where("completed", "=", true)
260
- # tasks = dataset.run query
370
+ # query = datastore.query("Task").
371
+ # where("done", "=", false)
372
+ # tasks = datastore.run query
373
+ #
374
+ # @example Run an ancestor query with eventual consistency:
375
+ # task_list_key = datastore.key "TaskList", "default"
376
+ # query.kind("Task").
377
+ # ancestor(task_list_key)
378
+ #
379
+ # tasks = datastore.run query, consistency: :eventual
261
380
  #
262
381
  # @example Run the query within a namespace with the `namespace` option:
263
- # query = Gcloud::Datastore::Query.new.kind("Task").
264
- # where("completed", "=", true)
265
- # tasks = dataset.run query, namespace: "ns~todo-project"
382
+ # query = datastore.query("Task").
383
+ # where("done", "=", false)
384
+ # tasks = datastore.run query, namespace: "ns~todo-project"
385
+ #
386
+ # @example Run the query with a GQL string.
387
+ # gql_query = datastore.gql "SELECT * FROM Task WHERE done = @done",
388
+ # done: false
389
+ # tasks = datastore.run gql_query
390
+ #
391
+ # @example Run the GQL query within a namespace with `namespace` option:
392
+ # gql_query = datastore.gql "SELECT * FROM Task WHERE done = @done",
393
+ # done: false
394
+ # tasks = datastore.run gql_query, namespace: "ns~todo-project"
266
395
  #
267
396
  def run query, namespace: nil, consistency: nil
268
- partition = optional_partition_id namespace
397
+ ensure_service!
398
+ unless query.is_a?(Query) || query.is_a?(GqlQuery)
399
+ fail ArgumentError, "Cannot run a #{query.class} object."
400
+ end
269
401
  check_consistency! consistency
270
- response = connection.run_query query.to_proto, partition,
271
- consistency: consistency
272
- entities = to_gcloud_entities response.batch.entity_result
273
- cursor = Proto.encode_cursor response.batch.end_cursor
274
- more_results = Proto.to_more_results_string response.batch.more_results
275
- QueryResults.new entities, cursor, more_results
402
+ query_res = service.run_query query.to_grpc, namespace,
403
+ consistency: consistency
404
+ QueryResults.from_grpc query_res, service, namespace, query.to_grpc.dup
405
+ rescue GRPC::BadStatus => e
406
+ raise Gcloud::Error.from_error(e)
276
407
  end
277
408
  alias_method :run_query, :run
278
409
 
@@ -286,16 +417,18 @@ module Gcloud
286
417
  # require "gcloud"
287
418
  #
288
419
  # gcloud = Gcloud.new
289
- # dataset = gcloud.datastore
420
+ # datastore = gcloud.datastore
290
421
  #
291
- # user = dataset.entity "User", "heidi" do |u|
292
- # u["name"] = "Heidi Henderson"
293
- # u["email"] = "heidi@example.net"
422
+ # task = datastore.entity "Task", "sampleTask" do |t|
423
+ # t["type"] = "Personal"
424
+ # t["done"] = false
425
+ # t["priority"] = 4
426
+ # t["description"] = "Learn Cloud Datastore"
294
427
  # end
295
428
  #
296
- # dataset.transaction do |tx|
297
- # if tx.find(user.key).nil?
298
- # tx.save user
429
+ # datastore.transaction do |tx|
430
+ # if tx.find(task.key).nil?
431
+ # tx.save task
299
432
  # end
300
433
  # end
301
434
  #
@@ -303,17 +436,19 @@ module Gcloud
303
436
  # require "gcloud"
304
437
  #
305
438
  # gcloud = Gcloud.new
306
- # dataset = gcloud.datastore
439
+ # datastore = gcloud.datastore
307
440
  #
308
- # user = dataset.entity "User", "heidi" do |u|
309
- # u["name"] = "Heidi Henderson"
310
- # u["email"] = "heidi@example.net"
441
+ # task = datastore.entity "Task", "sampleTask" do |t|
442
+ # t["type"] = "Personal"
443
+ # t["done"] = false
444
+ # t["priority"] = 4
445
+ # t["description"] = "Learn Cloud Datastore"
311
446
  # end
312
447
  #
313
- # tx = dataset.transaction
448
+ # tx = datastore.transaction
314
449
  # begin
315
- # if tx.find(user.key).nil?
316
- # tx.save user
450
+ # if tx.find(task.key).nil?
451
+ # tx.save task
317
452
  # end
318
453
  # tx.commit
319
454
  # rescue
@@ -321,7 +456,7 @@ module Gcloud
321
456
  # end
322
457
  #
323
458
  def transaction
324
- tx = Transaction.new connection
459
+ tx = Transaction.new service
325
460
  return tx unless block_given?
326
461
 
327
462
  begin
@@ -348,15 +483,15 @@ module Gcloud
348
483
  # @return [Gcloud::Datastore::Query]
349
484
  #
350
485
  # @example
351
- # query = dataset.query("Task").
352
- # where("completed", "=", true)
353
- # tasks = dataset.run query
486
+ # query = datastore.query("Task").
487
+ # where("done", "=", false)
488
+ # tasks = datastore.run query
354
489
  #
355
490
  # @example The previous example is equivalent to:
356
491
  # query = Gcloud::Datastore::Query.new.
357
492
  # kind("Task").
358
- # where("completed", "=", true)
359
- # tasks = dataset.run query
493
+ # where("done", "=", false)
494
+ # tasks = datastore.run query
360
495
  #
361
496
  def query *kinds
362
497
  query = Query.new
@@ -364,77 +499,155 @@ module Gcloud
364
499
  query
365
500
  end
366
501
 
502
+ ##
503
+ # Create a new GqlQuery instance. This is a convenience method to make the
504
+ # creation of GqlQuery objects easier.
505
+ #
506
+ # @param [String] query The GQL query string.
507
+ # @param [Hash] bindings Named bindings for the GQL query string, each
508
+ # key must match regex `[A-Za-z_$][A-Za-z_$0-9]*`, must not match regex
509
+ # `__.*__`, and must not be `""`. The value must be an `Object` that can
510
+ # be stored as an Entity property value, or a `Cursor`.
511
+ #
512
+ # @return [Gcloud::Datastore::GqlQuery]
513
+ #
514
+ # @example
515
+ # gql_query = datastore.gql "SELECT * FROM Task WHERE done = @done",
516
+ # done: false
517
+ # tasks = datastore.run gql_query
518
+ #
519
+ # @example The previous example is equivalent to:
520
+ # gql_query = Gcloud::Datastore::GqlQuery.new
521
+ # gql_query.query_string = "SELECT * FROM Task WHERE done = @done"
522
+ # gql_query.named_bindings = {done: false}
523
+ # tasks = datastore.run gql_query
524
+ #
525
+ def gql query, bindings = {}
526
+ gql = GqlQuery.new
527
+ gql.query_string = query
528
+ gql.named_bindings = bindings unless bindings.empty?
529
+ gql
530
+ end
531
+
367
532
  ##
368
533
  # Create a new Key instance. This is a convenience method to make the
369
534
  # creation of Key objects easier.
370
535
  #
371
- # @param [String] kind The kind of the Key. This is optional.
372
- # @param [Integer, String] id_or_name The id or name of the Key. This is
373
- # optional.
536
+ # @param [Array<Array(String,(String|Integer|nil))>] path An optional list
537
+ # of pairs for the key's path. Each pair may include the key's kind
538
+ # (String) and an id (Integer) or name (String). This is optional.
539
+ # @param [String] project The project of the Key. This is optional.
540
+ # @param [String] namespace namespace kind of the Key. This is optional.
374
541
  #
375
542
  # @return [Gcloud::Datastore::Key]
376
543
  #
377
544
  # @example
378
- # key = dataset.key "User", "heidi@example.com"
545
+ # task_key = datastore.key "Task", "sampleTask"
379
546
  #
380
547
  # @example The previous example is equivalent to:
381
- # key = Gcloud::Datastore::Key.new "User", "heidi@example.com"
382
- #
383
- def key kind = nil, id_or_name = nil
384
- Key.new kind, id_or_name
548
+ # task_key = Gcloud::Datastore::Key.new "Task", "sampleTask"
549
+ #
550
+ # @example Create an empty key:
551
+ # key = datastore.key
552
+ #
553
+ # @example Create an incomplete key:
554
+ # key = datastore.key "User"
555
+ #
556
+ # @example Create a key with a parent:
557
+ # key = datastore.key [["TaskList", "default"], ["Task", "sampleTask"]]
558
+ # key.path #=> [["TaskList", "default"], ["Task", "sampleTask"]]
559
+ #
560
+ # @example Create a key with multi-level ancestry:
561
+ # key = datastore.key([
562
+ # ["User", "alice"],
563
+ # ["TaskList", "default"],
564
+ # ["Task", "sampleTask"]
565
+ # ])
566
+ # key.path #=> [["User", "alice"], ["TaskList", "default"], [ ... ]]
567
+ #
568
+ # @example Create an incomplete key with a parent:
569
+ # key = datastore.key "TaskList", "default", "Task"
570
+ # key.path #=> [["TaskList", "default"], ["Task", nil]]
571
+ #
572
+ # @example Create a key with a project and namespace:
573
+ # key = datastore.key ["TaskList", "default"], ["Task", "sampleTask"],
574
+ # project: "my-todo-project",
575
+ # namespace: "ns~todo-project"
576
+ # key.path #=> [["TaskList", "default"], ["Task", "sampleTask"]]
577
+ # key.project #=> "my-todo-project",
578
+ # key.namespace #=> "ns~todo-project"
579
+ #
580
+ def key *path, project: nil, namespace: nil
581
+ path = path.flatten.each_slice(2).to_a # group in pairs
582
+ kind, id_or_name = path.pop
583
+ Key.new(kind, id_or_name).tap do |k|
584
+ k.project = project
585
+ k.namespace = namespace
586
+ unless path.empty?
587
+ k.parent = key path, project: project, namespace: namespace
588
+ end
589
+ end
385
590
  end
386
591
 
387
592
  ##
388
593
  # Create a new empty Entity instance. This is a convenience method to make
389
594
  # the creation of Entity objects easier.
390
595
  #
391
- # @param [Key, String, nil] key_or_kind A Key object or `kind` string
392
- # value. This is optional.
393
- # @param [Integer, String, nil] id_or_name The Key's `id` or `name` value
394
- # if a `kind` was provided in the first parameter.
596
+ # @param [Key, Array<Array(String,(String|Integer|nil))>] key_or_path An
597
+ # optional list of pairs for the key's path. Each pair may include the #
598
+ # key's kind (String) and an id (Integer) or name (String). This is #
599
+ # optional.
600
+ # @param [String] project The project of the Key. This is optional.
601
+ # @param [String] namespace namespace kind of the Key. This is optional.
395
602
  # @yield [entity] a block yielding a new entity
396
603
  # @yieldparam [Entity] entity the newly created entity object
397
604
  #
398
605
  # @return [Gcloud::Datastore::Entity]
399
606
  #
400
607
  # @example
401
- # entity = dataset.entity
608
+ # task = datastore.entity
402
609
  #
403
610
  # @example The previous example is equivalent to:
404
- # entity = Gcloud::Datastore::Entity.new
611
+ # task = Gcloud::Datastore::Entity.new
405
612
  #
406
613
  # @example The key can also be passed in as an object:
407
- # key = dataset.key "User", "heidi@example.com"
408
- # entity = dataset.entity key
614
+ # task_key = datastore.key "Task", "sampleTask"
615
+ # task = datastore.entity task_key
409
616
  #
410
617
  # @example Or the key values can be passed in as parameters:
411
- # entity = dataset.entity "User", "heidi@example.com"
618
+ # task = datastore.entity "Task", "sampleTask"
412
619
  #
413
620
  # @example The previous example is equivalent to:
414
- # key = Gcloud::Datastore::Key.new "User", "heidi@example.com"
415
- # entity = Gcloud::Datastore::Entity.new
416
- # entity.key = key
621
+ # task_key = Gcloud::Datastore::Key.new "Task", "sampleTask"
622
+ # task = Gcloud::Datastore::Entity.new
623
+ # task.key = task_key
417
624
  #
418
625
  # @example The newly created entity can also be configured using a block:
419
- # user = dataset.entity "User", "heidi@example.com" do |u|
420
- # u["name"] = "Heidi Henderson"
421
- # end
626
+ # task = datastore.entity "Task", "sampleTask" do |t|
627
+ # t["type"] = "Personal"
628
+ # t["done"] = false
629
+ # t["priority"] = 4
630
+ # t["description"] = "Learn Cloud Datastore"
631
+ # end
422
632
  #
423
633
  # @example The previous example is equivalent to:
424
- # key = Gcloud::Datastore::Key.new "User", "heidi@example.com"
425
- # entity = Gcloud::Datastore::Entity.new
426
- # entity.key = key
427
- # entity["name"] = "Heidi Henderson"
428
- #
429
- def entity key_or_kind = nil, id_or_name = nil
634
+ # task_key = Gcloud::Datastore::Key.new "Task", "sampleTask"
635
+ # task = Gcloud::Datastore::Entity.new
636
+ # task.key = task_key
637
+ # task["type"] = "Personal"
638
+ # task["done"] = false
639
+ # task["priority"] = 4
640
+ # task["description"] = "Learn Cloud Datastore"
641
+ #
642
+ def entity *key_or_path, project: nil, namespace: nil
430
643
  entity = Entity.new
431
644
 
432
645
  # Set the key
433
- key = key_or_kind
434
- unless key.is_a? Gcloud::Datastore::Key
435
- key = Key.new key_or_kind, id_or_name
646
+ if key_or_path.flatten.first.is_a? Gcloud::Datastore::Key
647
+ entity.key = key_or_path.flatten.first
648
+ else
649
+ entity.key = key key_or_path, project: project, namespace: namespace
436
650
  end
437
- entity.key = key
438
651
 
439
652
  yield entity if block_given?
440
653
 
@@ -444,37 +657,27 @@ module Gcloud
444
657
  protected
445
658
 
446
659
  ##
447
- # Convenince method to convert proto entities to Gcloud entities.
448
- def to_gcloud_entities proto_results
449
- # Entities are nested in an object.
450
- Array(proto_results).map do |result|
451
- Entity.from_proto result.entity
452
- end
660
+ # @private Raise an error unless an active connection to the service is
661
+ # available.
662
+ def ensure_service!
663
+ fail "Must have active connection to service" unless service
453
664
  end
454
665
 
455
666
  ##
456
- # Convenince method to convert proto keys to Gcloud keys.
457
- def to_gcloud_keys proto_results
458
- # Keys are not nested in an object like entities are.
459
- Array(proto_results).map do |key|
460
- Key.from_proto key
667
+ # Convenience method to convert GRPC entities to Gcloud entities.
668
+ def to_gcloud_entities grpc_entity_results
669
+ # Entities are nested in an object.
670
+ Array(grpc_entity_results).map do |result|
671
+ # TODO: Make this return an EntityResult with cursor...
672
+ Entity.from_grpc result.entity
461
673
  end
462
674
  end
463
675
 
464
676
  ##
465
- # @private Update saved keys with new IDs post-commit.
466
- def auto_id_assign_ids entities, auto_ids
467
- Array(auto_ids).each_with_index do |key, index|
468
- entity = entities[index]
469
- entity.key = Key.from_proto key
470
- end
471
- end
472
-
473
- def optional_partition_id namespace = nil
474
- return nil if namespace.nil?
475
- Proto::PartitionId.new.tap do |p|
476
- p.namespace = namespace
477
- end
677
+ # Convenience method to convert GRPC keys to Gcloud keys.
678
+ def to_gcloud_keys grpc_keys
679
+ # Keys are not nested in an object like entities are.
680
+ Array(grpc_keys).map { |key| Key.from_grpc key }
478
681
  end
479
682
 
480
683
  def check_consistency! consistency