Skip to content

Commit b1f6186

Browse files
feat(datastore): add support for request tags (#13732)
This PR introduced sending request tags for different datastore requests. The tags can be passed using 2 mechanisms: 1. Instance Level tags. 2. Request Level tags. - RequestOptions is the proto that holds all the instance and request level tags and is a part of all request protos. - DatastoreOptions allows the ability to set the instance level tags using setRequestTags. Any operation executed via this Datastore client automatically includes these global request tags in the outbound RPC request payload. - RequestOptionsHelper an internal utility that combines instance-level tags from DatastoreOptions with any request-level RequestOptions protobuf message before building the wire request. - DatastoreExecutionOptions is a holder object for all different options like ExplainOptions, RequestOptions and ReadOption. - Overloaded versions for taking in the DatastoreExecutionOptions for each of the following operations: - put - get - fetch - delete - newTransaction - commit - rollback - allocateId - reserveIds - runQuery - runAggregationQuery - Each of these overloaded versions copy the request tags into the request protos.
1 parent 9337a93 commit b1f6186

24 files changed

Lines changed: 2606 additions & 196 deletions

java-datastore/google-cloud-datastore/src/main/java/com/google/cloud/datastore/Datastore.java

Lines changed: 119 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,24 @@ public interface Datastore extends Service<DatastoreOptions>, DatastoreReaderWri
4444
*/
4545
Transaction newTransaction();
4646

47+
/**
48+
* Returns a new Datastore transaction with specified {@link DatastoreExecutionOptions}.
49+
*
50+
* @throws DatastoreException upon failure
51+
*/
52+
@BetaApi
53+
Transaction newTransaction(DatastoreExecutionOptions executionOptions);
54+
55+
/**
56+
* Returns a new Datastore transaction with specified {@link TransactionOptions} and {@link
57+
* DatastoreExecutionOptions}.
58+
*
59+
* @throws DatastoreException upon failure
60+
*/
61+
@BetaApi
62+
Transaction newTransaction(
63+
TransactionOptions options, DatastoreExecutionOptions executionOptions);
64+
4765
/**
4866
* A callback for running with a transactional {@link
4967
* com.google.cloud.datastore.DatastoreReaderWriter}. The associated transaction will be committed
@@ -179,6 +197,15 @@ interface TransactionCallable<T> {
179197
*/
180198
List<Key> allocateId(IncompleteKey... keys);
181199

200+
/**
201+
* Returns a list of keys using the allocated ids with specified {@link
202+
* DatastoreExecutionOptions}.
203+
*
204+
* @throws DatastoreException upon failure
205+
*/
206+
@BetaApi
207+
List<Key> allocateId(List<IncompleteKey> keys, DatastoreExecutionOptions executionOptions);
208+
182209
/**
183210
* Reserve one or more keys, preventing them from being automatically allocated by Datastore.
184211
*
@@ -196,6 +223,14 @@ interface TransactionCallable<T> {
196223
*/
197224
List<Key> reserveIds(Key... keys);
198225

226+
/**
227+
* Reserve one or more keys with specified {@link DatastoreExecutionOptions}.
228+
*
229+
* @throws DatastoreException upon failure
230+
*/
231+
@BetaApi
232+
List<Key> reserveIds(List<Key> keys, DatastoreExecutionOptions executionOptions);
233+
199234
/**
200235
* {@inheritDoc}
201236
*
@@ -226,6 +261,16 @@ interface TransactionCallable<T> {
226261
@Override
227262
Entity add(FullEntity<?> entity);
228263

264+
/**
265+
* Datastore add operation: inserts the provided entity with specified {@link
266+
* DatastoreExecutionOptions}. This method will automatically allocate an id if necessary.
267+
*
268+
* @throws DatastoreException upon failure
269+
* @throws IllegalArgumentException if the given entity is missing a key
270+
*/
271+
@BetaApi
272+
Entity add(FullEntity<?> entity, DatastoreExecutionOptions executionOptions);
273+
229274
/**
230275
* {@inheritDoc}
231276
*
@@ -264,6 +309,17 @@ interface TransactionCallable<T> {
264309
@Override
265310
List<Entity> add(FullEntity<?>... entities);
266311

312+
/**
313+
* Datastore add operation: inserts the provided entities with specified {@link
314+
* DatastoreExecutionOptions}. This method will automatically allocate id for any entity with an
315+
* incomplete key.
316+
*
317+
* @throws DatastoreException upon failure
318+
* @throws IllegalArgumentException if any of the given entities is missing a key
319+
*/
320+
@BetaApi
321+
List<Entity> add(List<FullEntity<?>> entities, DatastoreExecutionOptions executionOptions);
322+
267323
/**
268324
* {@inheritDoc}
269325
*
@@ -290,6 +346,15 @@ interface TransactionCallable<T> {
290346
@Override
291347
void update(Entity... entities);
292348

349+
/**
350+
* A Datastore update operation with specified {@link DatastoreExecutionOptions}. The operation
351+
* will fail if an entity with the same key does not already exist.
352+
*
353+
* @throws DatastoreException upon failure
354+
*/
355+
@BetaApi
356+
void update(List<Entity> entities, DatastoreExecutionOptions executionOptions);
357+
293358
/**
294359
* {@inheritDoc}
295360
*
@@ -309,31 +374,16 @@ interface TransactionCallable<T> {
309374
@Override
310375
Entity put(FullEntity<?> entity);
311376

377+
@Override
378+
List<Entity> put(FullEntity<?>... entities);
379+
312380
/**
313-
* {@inheritDoc}
314-
*
315-
* <p>Example of putting multiple entities.
316-
*
317-
* <pre>{@code
318-
* String keyName1 = "my_key_name1";
319-
* String keyName2 = "my_key_name2";
320-
* Key key1 = datastore.newKeyFactory().setKind("MyKind").newKey(keyName1);
321-
* Entity.Builder entityBuilder1 = Entity.newBuilder(key1);
322-
* entityBuilder1.set("propertyName", "value1");
323-
* Entity entity1 = entityBuilder1.build();
324-
*
325-
* Key key2 = datastore.newKeyFactory().setKind("MyKind").newKey(keyName2);
326-
* Entity.Builder entityBuilder2 = Entity.newBuilder(key2);
327-
* entityBuilder2.set("propertyName", "value2");
328-
* Entity entity2 = entityBuilder2.build();
329-
*
330-
* datastore.put(entity1, entity2);
331-
* }</pre>
381+
* {@inheritDoc} with specified {@link DatastoreExecutionOptions}.
332382
*
333383
* @throws DatastoreException upon failure
334384
*/
335-
@Override
336-
List<Entity> put(FullEntity<?>... entities);
385+
@BetaApi
386+
List<Entity> put(List<FullEntity<?>> entities, DatastoreExecutionOptions executionOptions);
337387

338388
/**
339389
* {@inheritDoc}
@@ -353,6 +403,14 @@ interface TransactionCallable<T> {
353403
@Override
354404
void delete(Key... keys);
355405

406+
/**
407+
* A datastore delete operation with specified {@link DatastoreExecutionOptions}.
408+
*
409+
* @throws DatastoreException upon failure
410+
*/
411+
@BetaApi
412+
void delete(List<Key> keys, DatastoreExecutionOptions executionOptions);
413+
356414
/**
357415
* Returns a new KeyFactory for this service
358416
*
@@ -381,6 +439,15 @@ interface TransactionCallable<T> {
381439
*/
382440
Entity get(Key key, ReadOption... options);
383441

442+
/**
443+
* Returns an {@link Entity} for the given {@link Key} with specified {@link
444+
* DatastoreExecutionOptions}.
445+
*
446+
* @throws DatastoreException upon failure
447+
*/
448+
@BetaApi
449+
Entity get(Key key, DatastoreExecutionOptions executionOptions);
450+
384451
/**
385452
* Returns an {@link Entity} for each given {@link Key} that exists in the Datastore. The order of
386453
* the result is unspecified. Results are loaded lazily, so it is possible to get a {@code
@@ -409,6 +476,15 @@ interface TransactionCallable<T> {
409476
*/
410477
Iterator<Entity> get(Iterable<Key> keys, ReadOption... options);
411478

479+
/**
480+
* Returns an {@link Entity} for each given {@link Key} with specified {@link
481+
* DatastoreExecutionOptions}.
482+
*
483+
* @throws DatastoreException upon failure
484+
*/
485+
@BetaApi
486+
Iterator<Entity> get(Iterable<Key> keys, DatastoreExecutionOptions executionOptions);
487+
412488
/**
413489
* Returns a list with a value for each given key (ordered by input). {@code null} values are
414490
* returned for nonexistent keys. When possible prefer using {@link #get(Key...)} to avoid eagerly
@@ -430,6 +506,13 @@ interface TransactionCallable<T> {
430506
*/
431507
List<Entity> fetch(Iterable<Key> keys, ReadOption... options);
432508

509+
/**
510+
* Returns a list with a value for each given key with specified {@link
511+
* DatastoreExecutionOptions}.
512+
*/
513+
@BetaApi
514+
List<Entity> fetch(Iterable<Key> keys, DatastoreExecutionOptions executionOptions);
515+
433516
/**
434517
* Submits a {@link Query} and returns its result. {@link ReadOption}s can be specified if
435518
* desired.
@@ -492,6 +575,13 @@ interface TransactionCallable<T> {
492575
@BetaApi
493576
<T> QueryResults<T> run(Query<T> query, ExplainOptions explainOptions, ReadOption... options);
494577

578+
/**
579+
* Submits a {@link Query} with specified {@link DatastoreExecutionOptions} and returns its
580+
* result.
581+
*/
582+
@BetaApi
583+
<T> QueryResults<T> run(Query<T> query, DatastoreExecutionOptions executionOptions);
584+
495585
/**
496586
* Submits a {@link AggregationQuery} and returns {@link AggregationResults}. {@link ReadOption}s
497587
* can be specified if desired.
@@ -564,6 +654,14 @@ interface TransactionCallable<T> {
564654
AggregationResults runAggregation(
565655
AggregationQuery query, ExplainOptions explainOptions, ReadOption... options);
566656

657+
/**
658+
* Submits an {@link AggregationQuery} with specified {@link DatastoreExecutionOptions} and
659+
* returns {@link AggregationResults}.
660+
*/
661+
@BetaApi
662+
AggregationResults runAggregation(
663+
AggregationQuery query, DatastoreExecutionOptions executionOptions);
664+
567665
/**
568666
* Closes the gRPC channels associated with this instance and frees up their resources. This
569667
* method blocks until all channels are closed. Once this method is called, this Datastore client
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/*
2+
* Copyright 2026 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.datastore;
18+
19+
import com.google.api.core.BetaApi;
20+
import com.google.cloud.datastore.models.ExplainOptions;
21+
import com.google.cloud.datastore.models.RequestOptions;
22+
import com.google.common.base.Objects;
23+
import com.google.common.base.Preconditions;
24+
import com.google.common.collect.ImmutableList;
25+
import java.util.Collections;
26+
import java.util.List;
27+
import org.jspecify.annotations.NullMarked;
28+
import org.jspecify.annotations.Nullable;
29+
30+
/**
31+
* Class representing options for query execution in Google Cloud Datastore. Combines {@link
32+
* ExplainOptions}, {@link RequestOptions}, and {@link ReadOption}s.
33+
*/
34+
@BetaApi
35+
@NullMarked
36+
public class DatastoreExecutionOptions {
37+
38+
private final @Nullable ExplainOptions explainOptions;
39+
private final RequestOptions requestOptions;
40+
private final List<ReadOption> readOptions;
41+
42+
private DatastoreExecutionOptions(Builder builder) {
43+
this.explainOptions = builder.explainOptions;
44+
this.requestOptions = builder.requestOptions;
45+
this.readOptions = ImmutableList.copyOf(builder.readOptions);
46+
}
47+
48+
public @Nullable ExplainOptions getExplainOptions() {
49+
return explainOptions;
50+
}
51+
52+
public RequestOptions getRequestOptions() {
53+
return requestOptions;
54+
}
55+
56+
public List<ReadOption> getReadOptions() {
57+
return readOptions;
58+
}
59+
60+
@Override
61+
public boolean equals(Object o) {
62+
if (this == o) return true;
63+
if (!(o instanceof DatastoreExecutionOptions)) return false;
64+
DatastoreExecutionOptions that = (DatastoreExecutionOptions) o;
65+
return Objects.equal(explainOptions, that.explainOptions)
66+
&& Objects.equal(requestOptions, that.requestOptions)
67+
&& Objects.equal(readOptions, that.readOptions);
68+
}
69+
70+
@Override
71+
public int hashCode() {
72+
return Objects.hashCode(explainOptions, requestOptions, readOptions);
73+
}
74+
75+
public Builder toBuilder() {
76+
return new Builder(this);
77+
}
78+
79+
public static Builder newBuilder() {
80+
return new Builder();
81+
}
82+
83+
/** Returns a default {@code DatastoreExecutionOptions} instance. */
84+
public static DatastoreExecutionOptions getDefaultInstance() {
85+
return newBuilder().build();
86+
}
87+
88+
/** Builder for {@link DatastoreExecutionOptions}. */
89+
public static class Builder {
90+
private @Nullable ExplainOptions explainOptions;
91+
private RequestOptions requestOptions = RequestOptions.getDefaultInstance();
92+
private List<ReadOption> readOptions = Collections.emptyList();
93+
94+
private Builder() {}
95+
96+
private Builder(DatastoreExecutionOptions options) {
97+
this.explainOptions = options.explainOptions;
98+
this.requestOptions = options.requestOptions;
99+
this.readOptions = options.readOptions;
100+
}
101+
102+
public Builder setExplainOptions(@Nullable ExplainOptions explainOptions) {
103+
this.explainOptions = explainOptions;
104+
return this;
105+
}
106+
107+
public Builder setRequestOptions(RequestOptions requestOptions) {
108+
Preconditions.checkNotNull(requestOptions, "requestOptions cannot be null");
109+
this.requestOptions = requestOptions;
110+
return this;
111+
}
112+
113+
public Builder setReadOptions(List<ReadOption> readOptions) {
114+
Preconditions.checkNotNull(readOptions, "readOptions cannot be null");
115+
this.readOptions = readOptions;
116+
return this;
117+
}
118+
119+
public DatastoreExecutionOptions build() {
120+
return new DatastoreExecutionOptions(this);
121+
}
122+
}
123+
}

java-datastore/google-cloud-datastore/src/main/java/com/google/cloud/datastore/DatastoreHelper.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ static List<Entity> fetch(Datastore reader, Key[] keys, ReadOption... options) {
7474
return compileEntities(keys, reader.get(Arrays.asList(keys), options));
7575
}
7676

77-
private static List<Entity> compileEntities(Key[] keys, Iterator<Entity> entities) {
77+
static List<Entity> compileEntities(Key[] keys, Iterator<Entity> entities) {
7878
Map<Key, Entity> map = Maps.newHashMapWithExpectedSize(keys.length);
7979
while (entities.hasNext()) {
8080
Entity entity = entities.next();

0 commit comments

Comments
 (0)