gcloud 0.8.0 → 0.8.2

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.
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