gcloud 0.8.0 → 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- ZmFhODViYTg5Nzc4Yjg0ZDJmMGFlZjQ3MmNiYTZjYWNlOTdlMjI5Zg==
4
+ ZjM3ZTdhZGU0OWE0NTcxY2M0ZDAxNTY4NDAwNmI4MzNiNjlhZjBjZg==
5
5
  data.tar.gz: !binary |-
6
- ZDI5YjQ0YjRlMDE1YTNmYTNmMjYyN2I0ZTIzZTQyMjllNDljMTAwZQ==
6
+ NTU2MWEwNzk1MjhlOWU1Mzc4MzUxNDdhZjEzNmYzZmI5NDViZjBkZA==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- ZTNkNDUxZTIzZWFkMjFkZmI2ODBjZDc1NWE2YjBlYmYwODUzMmU1Y2M4OTZl
10
- MDBhYTI1YmI5NjI4N2QwODJmYzM4MTFkNDAwY2Y1MWFhYmJjYTlhMGQxNWE0
11
- ZDQ1YzIyMjE4MTIxN2NjM2NkMTYzYzc5ZGQ5ZTQwZGQwOWVhNTM=
9
+ Y2JlMDZiZGFiZmZkNTQ3N2QxOTRlZjlkOTdjNDk2ZWEwNTQ5NjA1MjUxMTVk
10
+ NjBhNDcxZjMyOWM1ZjEzMTFjYzQzMzU1YTA2ZmQ1ZGYwMTQ3OTg3OGRhMzhm
11
+ YTMyNjQzODhlYTVhMThmMjkwY2Q0MjhmMmZhZGIwOTcyODU1Mjc=
12
12
  data.tar.gz: !binary |-
13
- MjBlNTAxNTBkYzVmMjA0MTBjZTNjZWE2NmM4OGZmMzE0NTM3Nzg5YTdiNjE5
14
- ZmE0ODVhMDQxNGUwNjg0NjViYmFhNGU3OWYxNWY3ZWY0MWExNjhhMjExOTQx
15
- YjhkNDUxOTQwODliYjAxMDM5ZTZlZDhjYmFiNzE4NWVhMTc0NTc=
13
+ NjNmMDkzZDhjNWZjYjFlMGVkOWM4MTliOGU4ZjRlN2E4MjAxYjgzNzBiNTgx
14
+ ZjdiOWY2NmY1YTY0OGE4OWQ5ZTg1MDEzMDE2ZGYwNjg0M2M2ZjNhMmI3YmY2
15
+ MGE4MDBlNWY4YTU2NGM3Mzc1MThlZDAyMjA0YTkzZGE2NDAxMGI=
@@ -1,5 +1,24 @@
1
1
  # Release History
2
2
 
3
+ ### 0.8.2 / 2016-05-04
4
+
5
+ #### Changes
6
+
7
+ * Datastore
8
+ * Fix issue with blob values being stored in base64 (bmclean)
9
+
10
+ ### 0.8.1 / 2016-05-03
11
+
12
+ #### Changes
13
+
14
+ * Datastore
15
+ * Add support for blob values (bmclean)
16
+ * Add support for Date and DateTime values
17
+ * Add support for setting read consistency
18
+ * Add support for batch operations outside of a transaction (timanovsky)
19
+ * Fix handling of rollback errors (timanovsky)
20
+ * Remove setting of project/dataset_id in query partition (toots)
21
+
3
22
  ### 0.8.0 / 2016-04-28
4
23
 
5
24
  #### Major changes
@@ -0,0 +1,131 @@
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
+ # # Commit
20
+ #
21
+ # Object yielded from `commit` methods to allow multiple changes to be made
22
+ # in a single commit.
23
+ #
24
+ # @example
25
+ # dataset.commit do |c|
26
+ # c.save task1, task2
27
+ # c.delete entity1, entity2
28
+ # end
29
+ #
30
+ # See {Gcloud::Datastore::Dataset#commit} and
31
+ # {Gcloud::Datastore::Transaction#commit}.
32
+ #
33
+ class Commit
34
+ ##
35
+ # @private Create a new Commit object.
36
+ def initialize
37
+ @shared_entities = []
38
+ @shared_auto_ids = []
39
+ @shared_deletes = []
40
+ end
41
+
42
+ ##
43
+ # Saves entities to the Datastore.
44
+ #
45
+ # @param [Entity] entities One or more Entity objects to save.
46
+ #
47
+ # @example
48
+ # gcloud = Gcloud.new
49
+ # dataset = gcloud.datastore
50
+ # dataset.commit do |c|
51
+ # c.save task1, task2
52
+ # end
53
+ #
54
+ def save *entities
55
+ entities.each do |entity|
56
+ shared_auto_ids << entity if entity.key.incomplete?
57
+ shared_entities << entity
58
+ end
59
+ # Do not save yet
60
+ entities
61
+ end
62
+
63
+ ##
64
+ # Remove entities from the Datastore.
65
+ #
66
+ # @param [Entity, Key] entities_or_keys One or more Entity or Key
67
+ # objects to remove.
68
+ #
69
+ # @example
70
+ # gcloud = Gcloud.new
71
+ # dataset = gcloud.datastore
72
+ # dataset.commit do |c|
73
+ # c.delete entity1, entity2
74
+ # end
75
+ #
76
+ def delete *entities_or_keys
77
+ keys = entities_or_keys.map do |e_or_k|
78
+ e_or_k.respond_to?(:key) ? e_or_k.key : e_or_k
79
+ end
80
+ keys.each { |k| shared_deletes << k }
81
+ # Do not delete yet
82
+ true
83
+ end
84
+
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)
91
+ end
92
+ end
93
+
94
+ # @private Entities that need key ids assigned.
95
+ def auto_id_entities
96
+ shared_auto_ids
97
+ end
98
+
99
+ # @private All entities saved in the commit.
100
+ 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
128
+ end
129
+ end
130
+ end
131
+ end
@@ -63,19 +63,33 @@ module Gcloud
63
63
 
64
64
  ##
65
65
  # Look up entities by keys.
66
- def lookup *keys
67
- lookup = Proto::LookupRequest.new
68
- lookup.key = keys
66
+ def lookup *keys, consistency: nil, transaction: nil
67
+ lookup = Proto::LookupRequest.new key: keys
68
+ if consistency == :eventual
69
+ lookup.read_options = Proto::ReadOptions.new(read_consistency: 2)
70
+ elsif consistency == :strong
71
+ lookup.read_options = Proto::ReadOptions.new(read_consistency: 1)
72
+ elsif transaction
73
+ lookup.read_options = Proto::ReadOptions.new(
74
+ transaction: transaction)
75
+ end
69
76
 
70
77
  Proto::LookupResponse.decode rpc("lookup", lookup)
71
78
  end
72
79
 
73
80
  # Query for entities.
74
- def run_query query, partition = nil
81
+ def run_query query, partition = nil, consistency: nil, transaction: nil
75
82
  run_query = Proto::RunQueryRequest.new.tap do |rq|
76
83
  rq.query = query
77
84
  rq.partition_id = partition if partition
78
- rq
85
+ end
86
+ if consistency == :eventual
87
+ run_query.read_options = Proto::ReadOptions.new(read_consistency: 2)
88
+ elsif consistency == :strong
89
+ run_query.read_options = Proto::ReadOptions.new(read_consistency: 1)
90
+ elsif transaction
91
+ run_query.read_options = Proto::ReadOptions.new(
92
+ transaction: transaction)
79
93
  end
80
94
 
81
95
  Proto::RunQueryResponse.decode rpc("runQuery", run_query)
@@ -16,6 +16,7 @@
16
16
  require "gcloud/gce"
17
17
  require "gcloud/datastore/connection"
18
18
  require "gcloud/datastore/credentials"
19
+ require "gcloud/datastore/commit"
19
20
  require "gcloud/datastore/entity"
20
21
  require "gcloud/datastore/key"
21
22
  require "gcloud/datastore/query"
@@ -114,8 +115,7 @@ module Gcloud
114
115
  ##
115
116
  # Persist one or more entities to the Datastore.
116
117
  #
117
- # @param [Entity] entities One or more entity objects to be saved without
118
- # `id` or `name` set.
118
+ # @param [Entity] entities One or more entity objects to be saved.
119
119
  #
120
120
  # @return [Array<Gcloud::Datastore::Entity>]
121
121
  #
@@ -123,11 +123,51 @@ module Gcloud
123
123
  # dataset.save task1, task2
124
124
  #
125
125
  def save *entities
126
- mutation = Proto.new_mutation
127
- save_entities_to_mutation entities, mutation
128
- response = connection.commit mutation
129
- auto_id_assign_ids response.mutation_result.insert_auto_id_key
126
+ commit { |c| c.save(*entities) }
127
+ end
128
+
129
+ ##
130
+ # Remove entities from the Datastore.
131
+ #
132
+ # @param [Entity, Key] entities_or_keys One or more Entity or Key objects
133
+ # to remove.
134
+ #
135
+ # @return [Boolean] Returns `true` if successful
136
+ #
137
+ # @example
138
+ # gcloud = Gcloud.new
139
+ # dataset = gcloud.datastore
140
+ # dataset.delete entity1, entity2
141
+ #
142
+ def delete *entities_or_keys
143
+ commit { |c| c.delete(*entities_or_keys) }
144
+ true
145
+ end
146
+
147
+ ##
148
+ # Make multiple changes in a single commit.
149
+ #
150
+ # @yield [commit] a block for making changes
151
+ # @yieldparam [Commit] commit The object that changes are made on
152
+ #
153
+ # @return [Array<Gcloud::Datastore::Entity>] The entities that were
154
+ # persisted.
155
+ #
156
+ # @example
157
+ # dataset.commit do |c|
158
+ # c.save task1, task2
159
+ # c.delete entity1, entity2
160
+ # end
161
+ #
162
+ def commit
163
+ return unless block_given?
164
+ c = Commit.new
165
+ 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
130
169
  # Make sure all entity keys are frozen so all show as persisted
170
+ entities = c.entities
131
171
  entities.each { |e| e.key.freeze unless e.persisted? }
132
172
  entities
133
173
  end
@@ -138,6 +178,14 @@ module Gcloud
138
178
  # @param [Key, String] key_or_kind A Key object or `kind` string value.
139
179
  # @param [Integer, String, nil] id_or_name The Key's `id` or `name` value
140
180
  # if a `kind` was provided in the first parameter.
181
+ # @param [Symbol] consistency The non-transactional read consistency to
182
+ # use. Cannot be set to `:strong` for global queries. Accepted values
183
+ # are `:eventual` and `:strong`.
184
+ #
185
+ # The default consistency depends on the type of lookup used. See
186
+ # [Eventual Consistency in Google Cloud
187
+ # Datastore](https://cloud.google.com/datastore/docs/articles/balancing-strong-and-eventual-consistency-with-google-cloud-datastore/#h.tf76fya5nqk8)
188
+ # for more information.
141
189
  #
142
190
  # @return [Gcloud::Datastore::Entity, nil]
143
191
  #
@@ -148,12 +196,12 @@ module Gcloud
148
196
  # @example Finding an entity with a `kind` and `id`/`name`:
149
197
  # task = dataset.find "Task", 123456
150
198
  #
151
- def find key_or_kind, id_or_name = nil
199
+ def find key_or_kind, id_or_name = nil, consistency: nil
152
200
  key = key_or_kind
153
201
  unless key.is_a? Gcloud::Datastore::Key
154
202
  key = Key.new key_or_kind, id_or_name
155
203
  end
156
- find_all(key).first
204
+ find_all(key, consistency: consistency).first
157
205
  end
158
206
  alias_method :get, :find
159
207
 
@@ -161,6 +209,14 @@ module Gcloud
161
209
  # Retrieve the entities for the provided keys.
162
210
  #
163
211
  # @param [Key] keys One or more Key objects to find records for.
212
+ # @param [Symbol] consistency The non-transactional read consistency to
213
+ # use. Cannot be set to `:strong` for global queries. Accepted values
214
+ # are `:eventual` and `:strong`.
215
+ #
216
+ # The default consistency depends on the type of lookup used. See
217
+ # [Eventual Consistency in Google Cloud
218
+ # Datastore](https://cloud.google.com/datastore/docs/articles/balancing-strong-and-eventual-consistency-with-google-cloud-datastore/#h.tf76fya5nqk8)
219
+ # for more information.
164
220
  #
165
221
  # @return [Gcloud::Datastore::Dataset::LookupResults]
166
222
  #
@@ -171,8 +227,10 @@ module Gcloud
171
227
  # key2 = dataset.key "Task", 987654
172
228
  # tasks = dataset.find_all key1, key2
173
229
  #
174
- def find_all *keys
175
- response = connection.lookup(*keys.map(&:to_proto))
230
+ def find_all *keys, consistency: nil
231
+ check_consistency! consistency
232
+ response = connection.lookup(*keys.map(&:to_proto),
233
+ consistency: consistency)
176
234
  entities = to_gcloud_entities response.found
177
235
  deferred = to_gcloud_keys response.deferred
178
236
  missing = to_gcloud_entities response.missing
@@ -180,35 +238,19 @@ module Gcloud
180
238
  end
181
239
  alias_method :lookup, :find_all
182
240
 
183
- ##
184
- # Remove entities from the Datastore.
185
- #
186
- # @param [Entity, Key] entities_or_keys One or more Entity or Key objects
187
- # to remove.
188
- #
189
- # @return [Boolean] Returns `true` if successful
190
- #
191
- # @example
192
- # gcloud = Gcloud.new
193
- # dataset = gcloud.datastore
194
- # dataset.delete entity1, entity2
195
- #
196
- def delete *entities_or_keys
197
- keys = entities_or_keys.map do |e_or_k|
198
- e_or_k.respond_to?(:key) ? e_or_k.key.to_proto : e_or_k.to_proto
199
- end
200
- mutation = Proto.new_mutation.tap do |m|
201
- m.delete = keys
202
- end
203
- connection.commit mutation
204
- true
205
- end
206
-
207
241
  ##
208
242
  # Retrieve entities specified by a Query.
209
243
  #
210
244
  # @param [Query] query The Query object with the search criteria.
211
245
  # @param [String] namespace The namespace the query is to run within.
246
+ # @param [Symbol] consistency The non-transactional read consistency to
247
+ # use. Cannot be set to `:strong` for global queries. Accepted values
248
+ # are `:eventual` and `:strong`.
249
+ #
250
+ # The default consistency depends on the type of query used. See
251
+ # [Eventual Consistency in Google Cloud
252
+ # Datastore](https://cloud.google.com/datastore/docs/articles/balancing-strong-and-eventual-consistency-with-google-cloud-datastore/#h.tf76fya5nqk8)
253
+ # for more information.
212
254
  #
213
255
  # @return [Gcloud::Datastore::Dataset::QueryResults]
214
256
  #
@@ -222,9 +264,11 @@ module Gcloud
222
264
  # where("completed", "=", true)
223
265
  # tasks = dataset.run query, namespace: "ns~todo-project"
224
266
  #
225
- def run query, namespace: nil
267
+ def run query, namespace: nil, consistency: nil
226
268
  partition = optional_partition_id namespace
227
- response = connection.run_query query.to_proto, partition
269
+ check_consistency! consistency
270
+ response = connection.run_query query.to_proto, partition,
271
+ consistency: consistency
228
272
  entities = to_gcloud_entities response.batch.entity_result
229
273
  cursor = Proto.encode_cursor response.batch.end_cursor
230
274
  more_results = Proto.to_more_results_string response.batch.more_results
@@ -284,8 +328,14 @@ module Gcloud
284
328
  yield tx
285
329
  tx.commit
286
330
  rescue => e
287
- tx.rollback
288
- raise TransactionError.new("Transaction failed to commit.", e)
331
+ begin
332
+ tx.rollback
333
+ rescue => re
334
+ msg = "Transaction failed to commit and rollback."
335
+ raise TransactionError.new(msg, commit_error: e, rollback_error: re)
336
+ end
337
+ raise TransactionError.new("Transaction failed to commit.",
338
+ commit_error: e)
289
339
  end
290
340
  end
291
341
 
@@ -411,45 +461,28 @@ module Gcloud
411
461
  end
412
462
  end
413
463
 
414
- ##
415
- # @private Save a key to be given an ID when comitted.
416
- def auto_id_register entity
417
- @_auto_id_entities ||= []
418
- @_auto_id_entities << entity
419
- end
420
-
421
464
  ##
422
465
  # @private Update saved keys with new IDs post-commit.
423
- def auto_id_assign_ids auto_ids
424
- @_auto_id_entities ||= []
466
+ def auto_id_assign_ids entities, auto_ids
425
467
  Array(auto_ids).each_with_index do |key, index|
426
- entity = @_auto_id_entities[index]
468
+ entity = entities[index]
427
469
  entity.key = Key.from_proto key
428
470
  end
429
- @_auto_id_entities = []
430
- end
431
-
432
- ##
433
- # @private Add entities to a Mutation, and register they key to be
434
- # updated with an auto ID if needed.
435
- def save_entities_to_mutation entities, mutation
436
- entities.each do |entity|
437
- if entity.key.id.nil? && entity.key.name.nil?
438
- mutation.insert_auto_id << entity.to_proto
439
- auto_id_register entity
440
- else
441
- mutation.upsert << entity.to_proto
442
- end
443
- end
444
471
  end
445
472
 
446
473
  def optional_partition_id namespace = nil
447
474
  return nil if namespace.nil?
448
475
  Proto::PartitionId.new.tap do |p|
449
476
  p.namespace = namespace
450
- p.dataset_id = project
451
477
  end
452
478
  end
479
+
480
+ def check_consistency! consistency
481
+ fail(ArgumentError,
482
+ format("Consistency must be :eventual or :strong, not %s.",
483
+ consistency.inspect)
484
+ ) unless [:eventual, :strong, nil].include? consistency
485
+ end
453
486
  end
454
487
  end
455
488
  end
@@ -46,6 +46,9 @@ module Gcloud
46
46
  ##
47
47
  # Retrieve a property value by providing the name.
48
48
  #
49
+ # Property values are converted from the Datastore value type
50
+ # automatically. Blob properties are returned as StringIO objects.
51
+ #
49
52
  # @param [String, Symbol] prop_name The name of the property.
50
53
  #
51
54
  # @return [Object, nil] Returns `nil` if the property doesn't exist
@@ -66,6 +69,14 @@ module Gcloud
66
69
  # user = dataset.find "User", "heidi@example.com"
67
70
  # user[:name] #=> "Heidi Henderson"
68
71
  #
72
+ # @example Getting a blob value returns a StringIO object:
73
+ # require "gcloud"
74
+ #
75
+ # gcloud = Gcloud.new
76
+ # dataset = gcloud.datastore
77
+ # user = dataset.find "User", "heidi@example.com"
78
+ # user["avatar"] #=> StringIO("\x89PNG\r\n\x1A...")
79
+ #
69
80
  def [] prop_name
70
81
  @properties[prop_name]
71
82
  end
@@ -73,6 +84,12 @@ module Gcloud
73
84
  ##
74
85
  # Set a property value by name.
75
86
  #
87
+ # Property values are converted to use the proper Datastore value type
88
+ # automatically. Use an IO-compatible object (File, StringIO, Tempfile) to
89
+ # indicate the property value should be stored as a Datastore `blob`.
90
+ # IO-compatible objects are converted to StringIO objects when they are
91
+ # set.
92
+ #
76
93
  # @param [String, Symbol] prop_name The name of the property.
77
94
  # @param [Object] prop_value The value of the property.
78
95
  #
@@ -92,6 +109,15 @@ module Gcloud
92
109
  # user = dataset.find "User", "heidi@example.com"
93
110
  # user[:name] = "Heidi H. Henderson"
94
111
  #
112
+ # @example Setting a blob value using an IO:
113
+ # require "gcloud"
114
+ #
115
+ # gcloud = Gcloud.new
116
+ # dataset = gcloud.datastore
117
+ # user = dataset.find "User", "heidi@example.com"
118
+ # user["avatar"] = File.open "/avatars/heidi.png"
119
+ # user["avatar"] #=> StringIO("\x89PNG\r\n\x1A...")
120
+ #
95
121
  def []= prop_name, prop_value
96
122
  @properties[prop_name] = prop_value
97
123
  end
@@ -66,12 +66,18 @@ module Gcloud
66
66
  class TransactionError < Gcloud::Datastore::Error
67
67
  ##
68
68
  # An error that occurred within the transaction. (optional)
69
- attr_reader :inner
69
+ attr_reader :commit_error
70
+ alias_method :inner, :commit_error # backwards compatibility
71
+
72
+ ##
73
+ # An error that occurred within the transaction. (optional)
74
+ attr_reader :rollback_error
70
75
 
71
76
  # @private
72
- def initialize message, inner = nil
77
+ def initialize message, commit_error: nil, rollback_error: nil
73
78
  super(message)
74
- @inner = inner
79
+ @commit_error = commit_error
80
+ @rollback_error = rollback_error
75
81
  end
76
82
  end
77
83
  end
@@ -13,6 +13,8 @@
13
13
  # limitations under the License.
14
14
 
15
15
 
16
+ require "stringio"
17
+
16
18
  module Gcloud
17
19
  module Datastore
18
20
  ##
@@ -86,8 +88,7 @@ module Gcloud
86
88
  # Ensures the value is a type that can be persisted,
87
89
  # otherwise a PropertyError is raised.
88
90
  def ensure_value_type value
89
- if Time === value ||
90
- Gcloud::Datastore::Key === value ||
91
+ if Gcloud::Datastore::Key === value ||
91
92
  Gcloud::Datastore::Entity === value ||
92
93
  NilClass === value ||
93
94
  TrueClass === value ||
@@ -97,6 +98,12 @@ module Gcloud
97
98
  String === value ||
98
99
  Array === value
99
100
  return value
101
+ elsif value.respond_to?(:to_time)
102
+ return value
103
+ elsif value.respond_to?(:read) && value.respond_to?(:rewind)
104
+ # Always convert an IO object to a StringIO when storing.
105
+ value.rewind
106
+ return StringIO.new(value.read.force_encoding("ASCII-8BIT"))
100
107
  elsif defined?(BigDecimal) && BigDecimal === value
101
108
  return value
102
109
  end
@@ -15,6 +15,7 @@
15
15
 
16
16
  require "gcloud/proto/datastore_v1.pb"
17
17
  require "gcloud/datastore/errors"
18
+ require "stringio"
18
19
 
19
20
  module Gcloud
20
21
  module Datastore
@@ -53,6 +54,8 @@ module Gcloud
53
54
  return Array(proto_value.list_value).map do |item|
54
55
  from_proto_value item
55
56
  end
57
+ elsif !proto_value.blob_value.nil?
58
+ return StringIO.new(proto_value.blob_value.force_encoding("ASCII-8BIT"))
56
59
  else
57
60
  nil
58
61
  end
@@ -60,9 +63,7 @@ module Gcloud
60
63
 
61
64
  def self.to_proto_value value
62
65
  v = Gcloud::Datastore::Proto::Value.new
63
- if Time === value
64
- v.timestamp_microseconds_value = self.microseconds_from_time value
65
- elsif Gcloud::Datastore::Key === value
66
+ if Gcloud::Datastore::Key === value
66
67
  v.key_value = value.to_proto
67
68
  elsif Gcloud::Datastore::Entity === value
68
69
  v.entity_value = value.to_proto
@@ -82,6 +83,11 @@ module Gcloud
82
83
  v.string_value = value
83
84
  elsif Array === value
84
85
  v.list_value = value.map { |item| to_proto_value item }
86
+ elsif value.respond_to?(:to_time)
87
+ v.timestamp_microseconds_value = self.microseconds_from_time value.to_time
88
+ elsif value.respond_to?(:read) && value.respond_to?(:rewind)
89
+ value.rewind
90
+ v.blob_value = value.read.force_encoding("ASCII-8BIT")
85
91
  else
86
92
  fail PropertyError, "A property of type #{value.class} is not supported."
87
93
  end
@@ -44,8 +44,7 @@ module Gcloud
44
44
  # end
45
45
  #
46
46
  def save *entities
47
- save_entities_to_mutation entities, shared_mutation
48
- @_saved_entities += entities
47
+ @commit.save(*entities)
49
48
  # Do not save or assign auto_ids yet
50
49
  entities
51
50
  end
@@ -61,16 +60,94 @@ module Gcloud
61
60
  # end
62
61
  #
63
62
  def delete *entities_or_keys
64
- keys = entities_or_keys.map do |e_or_k|
65
- e_or_k.respond_to?(:key) ? e_or_k.key.to_proto : e_or_k.to_proto
66
- end
67
- shared_mutation.tap do |m|
68
- m.delete = keys
69
- end
63
+ @commit.delete(*entities_or_keys)
70
64
  # Do not delete yet
71
65
  true
72
66
  end
73
67
 
68
+ ##
69
+ # Retrieve an entity by providing key information. The lookup is run
70
+ # within the transaction.
71
+ #
72
+ # @param [Key, String] key_or_kind A Key object or `kind` string value.
73
+ #
74
+ # @return [Gcloud::Datastore::Entity, nil]
75
+ #
76
+ # @example Finding an entity with a key:
77
+ # key = dataset.key "Task", 123456
78
+ # task = dataset.find key
79
+ #
80
+ # @example Finding an entity with a `kind` and `id`/`name`:
81
+ # task = dataset.find "Task", 123456
82
+ #
83
+ def find key_or_kind, id_or_name = nil
84
+ key = key_or_kind
85
+ unless key.is_a? Gcloud::Datastore::Key
86
+ key = Key.new key_or_kind, id_or_name
87
+ end
88
+ find_all(key).first
89
+ end
90
+ alias_method :get, :find
91
+
92
+ ##
93
+ # Retrieve the entities for the provided keys. The lookup is run within
94
+ # the transaction.
95
+ #
96
+ # @param [Key] keys One or more Key objects to find records for.
97
+ #
98
+ # @return [Gcloud::Datastore::Dataset::LookupResults]
99
+ #
100
+ # @example
101
+ # gcloud = Gcloud.new
102
+ # dataset = gcloud.datastore
103
+ # key1 = dataset.key "Task", 123456
104
+ # key2 = dataset.key "Task", 987654
105
+ # tasks = dataset.find_all key1, key2
106
+ #
107
+ def find_all *keys
108
+ response = connection.lookup(*keys.map(&:to_proto),
109
+ transaction: @id)
110
+ entities = to_gcloud_entities response.found
111
+ deferred = to_gcloud_keys response.deferred
112
+ missing = to_gcloud_entities response.missing
113
+ LookupResults.new entities, deferred, missing
114
+ end
115
+ alias_method :lookup, :find_all
116
+
117
+ ##
118
+ # Retrieve entities specified by a Query. The query is run within the
119
+ # transaction.
120
+ #
121
+ # @param [Query] query The Query object with the search criteria.
122
+ # @param [String] namespace The namespace the query is to run within.
123
+ #
124
+ # @return [Gcloud::Datastore::Dataset::QueryResults]
125
+ #
126
+ # @example
127
+ # query = dataset.query("Task").
128
+ # where("completed", "=", true)
129
+ # dataset.transaction do |tx|
130
+ # tasks = tx.run query
131
+ # end
132
+ #
133
+ # @example Run the query within a namespace with the `namespace` option:
134
+ # query = Gcloud::Datastore::Query.new.kind("Task").
135
+ # where("completed", "=", true)
136
+ # dataset.transaction do |tx|
137
+ # tasks = tx.run query, namespace: "ns~todo-project"
138
+ # end
139
+ #
140
+ def run query, namespace: nil
141
+ partition = optional_partition_id namespace
142
+ response = connection.run_query query.to_proto, partition,
143
+ transaction: @id
144
+ entities = to_gcloud_entities response.batch.entity_result
145
+ cursor = Proto.encode_cursor response.batch.end_cursor
146
+ more_results = Proto.to_more_results_string response.batch.more_results
147
+ QueryResults.new entities, cursor, more_results
148
+ end
149
+ alias_method :run_query, :run
150
+
74
151
  ##
75
152
  # Begins a transaction.
76
153
  # This method is run when a new Transaction is created.
@@ -84,20 +161,84 @@ module Gcloud
84
161
 
85
162
  ##
86
163
  # Commits a transaction.
164
+ #
165
+ # @yield [commit] an optional block for making changes
166
+ # @yieldparam [Commit] commit The object that changes are made on
167
+ #
168
+ # @example
169
+ # require "gcloud"
170
+ #
171
+ # gcloud = Gcloud.new
172
+ # dataset = gcloud.datastore
173
+ #
174
+ # user = dataset.entity "User", "heidi" do |u|
175
+ # u["name"] = "Heidi Henderson"
176
+ # u["email"] = "heidi@example.net"
177
+ # end
178
+ #
179
+ # tx = dataset.transaction
180
+ # begin
181
+ # if tx.find(user.key).nil?
182
+ # tx.save user
183
+ # end
184
+ # tx.commit
185
+ # rescue
186
+ # tx.rollback
187
+ # end
188
+ #
189
+ # @example Commit can be passed a block, same as {Dataset#commit}:
190
+ # require "gcloud"
191
+ #
192
+ # gcloud = Gcloud.new
193
+ # dataset = gcloud.datastore
194
+ #
195
+ # tx = dataset.transaction
196
+ # begin
197
+ # tx.commit do |c|
198
+ # c.save task1, task2
199
+ # c.delete entity1, entity2
200
+ # end
201
+ # rescue
202
+ # tx.rollback
203
+ # end
204
+ #
87
205
  def commit
88
206
  if @id.nil?
89
207
  fail TransactionError, "Cannot commit when not in a transaction."
90
208
  end
91
209
 
92
- response = connection.commit shared_mutation, @id
93
- auto_id_assign_ids response.mutation_result.insert_auto_id_key
210
+ yield @commit if block_given?
211
+ response = connection.commit @commit.mutation, @id
212
+ auto_id_assign_ids @commit.auto_id_entities,
213
+ response.mutation_result.insert_auto_id_key
94
214
  # Make sure all entity keys are frozen so all show as persisted
95
- @_saved_entities.each { |e| e.key.freeze unless e.persisted? }
215
+ @commit.entities.each { |e| e.key.freeze unless e.persisted? }
96
216
  true
97
217
  end
98
218
 
99
219
  ##
100
220
  # Rolls a transaction back.
221
+ #
222
+ # @example
223
+ # require "gcloud"
224
+ #
225
+ # gcloud = Gcloud.new
226
+ # dataset = gcloud.datastore
227
+ #
228
+ # user = dataset.entity "User", "heidi" do |u|
229
+ # u["name"] = "Heidi Henderson"
230
+ # u["email"] = "heidi@example.net"
231
+ # end
232
+ #
233
+ # tx = dataset.transaction
234
+ # begin
235
+ # if tx.find(user.key).nil?
236
+ # tx.save user
237
+ # end
238
+ # tx.commit
239
+ # rescue
240
+ # tx.rollback
241
+ # end
101
242
  def rollback
102
243
  if @id.nil?
103
244
  fail TransactionError, "Cannot rollback when not in a transaction."
@@ -111,19 +252,8 @@ module Gcloud
111
252
  # Reset the transaction.
112
253
  # {Transaction#start} must be called afterwards.
113
254
  def reset!
114
- @shared_mutation = nil
115
255
  @id = nil
116
- @_auto_id_entities = []
117
- @_saved_entities = []
118
- end
119
-
120
- protected
121
-
122
- ##
123
- # @private Mutation to be shared across save, delete, and commit calls.
124
- # This enables updates to happen when commit is called.
125
- def shared_mutation
126
- @shared_mutation ||= Proto.new_mutation
256
+ @commit = Commit.new
127
257
  end
128
258
  end
129
259
  end
@@ -14,5 +14,5 @@
14
14
 
15
15
 
16
16
  module Gcloud
17
- VERSION = "0.8.0"
17
+ VERSION = "0.8.2"
18
18
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gcloud
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.8.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Silvano Luciani
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2016-04-28 00:00:00.000000000 Z
13
+ date: 2016-05-04 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: grpc
@@ -290,6 +290,7 @@ files:
290
290
  - lib/gcloud/bigquery/view.rb
291
291
  - lib/gcloud/credentials.rb
292
292
  - lib/gcloud/datastore.rb
293
+ - lib/gcloud/datastore/commit.rb
293
294
  - lib/gcloud/datastore/connection.rb
294
295
  - lib/gcloud/datastore/credentials.rb
295
296
  - lib/gcloud/datastore/dataset.rb