gcloud 0.8.2 → 0.9.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.
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