Skip to content

feat(datastore): add support for request tags#13732

Open
rahulmane-goog wants to merge 3 commits into
googleapis:mainfrom
rahulmane-goog:feat-datastore-request-tags
Open

feat(datastore): add support for request tags#13732
rahulmane-goog wants to merge 3 commits into
googleapis:mainfrom
rahulmane-goog:feat-datastore-request-tags

Conversation

@rahulmane-goog

Copy link
Copy Markdown
  • Add RequestOptions support that enables ability of passing request tags to Datastore queries.
  • Introduced instance level request tags in the DatastoreOptions.
  • Introduced the logic of populating RequestOptions for all datastore queries. These are query level tags.
  • Introduced overloaded run(...) and runAggregation(...) client methods, that pass RequestOptions proto as the last parameter.
  • The instance and query level tags are combined using the new RequestOptionsHelper utility and sent inside the query requests.

@rahulmane-goog rahulmane-goog requested review from a team as code owners July 13, 2026 13:26

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for passing and merging RequestOptions (such as request tags) in Datastore queries and aggregation queries, allowing both instance-level and request-level tags to be propagated. The feedback highlights a critical issue where instance-level tags are ignored when query-level RequestOptions are not explicitly provided in QueryResultsImpl and AggregationQueryRequestProtoPreparer. Additionally, the reviewer suggests simplifying the varargs withRequestTags overload in Datastore by delegating to the List version, and adding a test case to verify that instance-level tags are correctly populated and sent with queries.

@rahulmane-goog rahulmane-goog force-pushed the feat-datastore-request-tags branch from f124116 to aab1fd3 Compare July 13, 2026 13:43

@rahulmane-goog rahulmane-goog left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please review

@lqiu96 lqiu96 added the kokoro:force-run Add this label to force Kokoro to re-run the tests. label Jul 13, 2026
@yoshi-kokoro yoshi-kokoro removed the kokoro:force-run Add this label to force Kokoro to re-run the tests. label Jul 13, 2026
@jinseopkim0

Copy link
Copy Markdown
Contributor

ci / lint is failing. Could you please take a look?

return this;
}

public Builder setTags(ImmutableList<String> requestTags) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be better to use setReplaceTags?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think setXx inherently means that. Setting the existing with a new value which is effectively a replace.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I don't follow. Could you please explain a bit more?

I meant that because we are setting requestTags and since we have getRequestTags(), I'm thinking it'll be better to stay consistent. Having the builder methods match the property/getter name is standard convention in the SDK and helps users discover the setters via IDE auto-complete.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in your original comment "Would it be better to use setReplaceTags?" you intended to say setRequestTags instead of replace, and hence the confusion. I have renamed the method to setRequestTags()

return this;
}

public Builder setTags(String... requestTags) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be better to use setReplaceTags?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think setXx inherently means that. Setting the existing with a new value which is effectively a replace.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I don't follow. Could you please explain a bit more?

I meant that because we are setting requestTags and since we have getRequestTags(), I'm thinking it'll be better to stay consistent. Having the builder methods match the property/getter name is standard convention in the SDK and helps users discover the setters via IDE auto-complete.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replied in the previous comment.

Comment on lines +501 to +519
<T> QueryResults<T> run(Query<T> query, RequestOptions requestOptions);

/**
* Submits a {@link Query} with specified {@link com.google.datastore.v1.RequestOptions} and
* returns its result. {@link ReadOption}s can be specified if desired.
*/
<T> QueryResults<T> run(Query<T> query, RequestOptions requestOptions, ReadOption... options);

/**
* Submits a {@link Query} with specified {@link com.google.cloud.datastore.models.ExplainOptions}
* and {@link com.google.datastore.v1.RequestOptions} and returns its result. {@link ReadOption}s
* can be specified if desired.
*/
@BetaApi
<T> QueryResults<T> run(
Query<T> query,
ExplainOptions explainOptions,
RequestOptions requestOptions,
ReadOption... options);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like we have to overload run and runAggregation every single time we add a variation of ...Option in here.

Would it be possible to just create a wrapping Option class (e.g. DatastoreExecutionOption or w/e name that wraps these three options)?

That way any new request option doesn't require an overload of new public methods for the Datastore interface

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We actually dont foresee any additional options coming in near future. Shall we park this refactoring for later?

@lqiu96 lqiu96 Jul 15, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know some of the changes above from older implementations aren't related to your feature. However, even if we don't ever add in any new option variant, this PR will still add in 3 new public methods that can't be removed without breaking changes.

If we are to add anything new to the public surface, I would suggest that go for the variant that only adds in one (unless there is a valid reason to not do this):

e.g. run(Query, DatastoreExecutionOption) that manages to wrap all current options.

I think we can just delegate all the existing variants to create this new root options wrapper

Same below with runAggregation

Comment on lines +645 to +647
default Datastore withRequestTags(String... requestTags) {
return withRequestTags(java.util.Arrays.asList(requestTags));
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need a varargs variant? Why can't the list overload below work for all use cases?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, removed it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the method still exists upsteam. Might have forgot to push to remote

@rahulmane-goog

Copy link
Copy Markdown
Author

ci / lint is failing. Could you please take a look?

Fixed

return this;
}

public Builder setTags(ImmutableList<String> requestTags) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I don't follow. Could you please explain a bit more?

I meant that because we are setting requestTags and since we have getRequestTags(), I'm thinking it'll be better to stay consistent. Having the builder methods match the property/getter name is standard convention in the SDK and helps users discover the setters via IDE auto-complete.

return this;
}

public Builder setTags(String... requestTags) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I don't follow. Could you please explain a bit more?

I meant that because we are setting requestTags and since we have getRequestTags(), I'm thinking it'll be better to stay consistent. Having the builder methods match the property/getter name is standard convention in the SDK and helps users discover the setters via IDE auto-complete.

* @param requestTags the request tags to append to existing ones
*/
default Datastore withRequestTags(List<String> requestTags) {
ImmutableList.Builder<String> builder = ImmutableList.builder();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the requestTags be null here? And if so, would it make sense to just return the current client rather than build a new one?

e.g.

if (requestTags == null || requestTags.isEmpty()) {
    return this; 
  }

Comment on lines +797 to +802
CommitRequest.Builder requestPb =
CommitRequest.newBuilder()
.setMode(CommitRequest.Mode.NON_TRANSACTIONAL)
.setProjectId(getOptions().getProjectId())
.setDatabaseId(getOptions().getDatabaseId())
.addAllMutations(mutationsPb);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to support instance-level tags here? e.g.

if (!getOptions().getRequestTags().isEmpty()) {
  requestPb.setRequestOptions(RequestOptionsHelper.createRequestOptions(getOptions(), null));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to support instance-level tags here? e.g.

if (!getOptions().getRequestTags().isEmpty()) {
  requestPb.setRequestOptions(RequestOptionsHelper.createRequestOptions(getOptions(), null));
}

@@ -425,23 +508,21 @@ public List<Key> allocateId(IncompleteKey... keys) {
if (keys.length == 0) {
return Collections.emptyList();
}
com.google.datastore.v1.AllocateIdsRequest.Builder requestPb =
com.google.datastore.v1.AllocateIdsRequest.newBuilder();
AllocateIdsRequest.Builder requestPb = AllocateIdsRequest.newBuilder();
for (IncompleteKey key : keys) {
requestPb.addKeys(trimNameOrId(key).toPb());
}
requestPb.setProjectId(getOptions().getProjectId());
requestPb.setDatabaseId(getOptions().getDatabaseId());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to support instance-level tags here? e.g.

if (!getOptions().getRequestTags().isEmpty()) {
  requestPb.setRequestOptions(RequestOptionsHelper.createRequestOptions(getOptions(), null));
}

Comment on lines +649 to +659
/**
* Returns a new Datastore client with the specified request tags added.
*
* @param requestTags the request tags to append to existing ones
*/
default Datastore withRequestTags(List<String> requestTags) {
ImmutableList.Builder<String> builder = ImmutableList.builder();
builder.addAll(getOptions().getRequestTags());
builder.addAll(requestTags);
return getOptions().toBuilder().setTags(builder.build()).build().getService();
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

qq, what is the user need to re-build a Datastore client with new request tags via withRequestTags(...)? I'm not entirely sure I follow why we need a new default method on the Datastore interface.

Why can't the user just set the tag in the DatastoreOption builder and then manually create the client again

e.g. something like
DatastoreOptions.toBuilder().setTags(...).build.getService()

@rahulmane-goog rahulmane-goog force-pushed the feat-datastore-request-tags branch from 6c23afc to e5396c0 Compare July 15, 2026 18:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants