From 138ebb84f315f6731ee4f5666316ceb02d37c5f5 Mon Sep 17 00:00:00 2001 From: Tom Andersen Date: Fri, 19 Jan 2024 13:20:20 -0500 Subject: [PATCH 01/20] Thread safe UpdateBuilder --- .../main/java/com/google/cloud/firestore/UpdateBuilder.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java index 87cf83307..dcb90f7d6 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java @@ -36,6 +36,7 @@ import java.util.Map.Entry; import java.util.SortedSet; import java.util.TreeSet; +import java.util.concurrent.CopyOnWriteArrayList; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -62,7 +63,7 @@ public String toString() { final FirestoreImpl firestore; - private final List writes = new ArrayList<>(); + private final List writes = new CopyOnWriteArrayList<>(); protected boolean committed; From facf7cfe189f0205a4b6b44cb5067c6b52dacfa2 Mon Sep 17 00:00:00 2001 From: Tom Andersen Date: Fri, 19 Jan 2024 15:19:50 -0500 Subject: [PATCH 02/20] Add comment --- .../main/java/com/google/cloud/firestore/UpdateBuilder.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java index dcb90f7d6..06134c4e0 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java @@ -30,6 +30,7 @@ import io.opencensus.trace.AttributeValue; import io.opencensus.trace.Tracing; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -63,6 +64,9 @@ public String toString() { final FirestoreImpl firestore; + // CopyOnWriteArrayList is a thread safe implementation. Client code, + // especially within asynchronous callbacks running on thread pool, can + // concurrently add writes to builder. private final List writes = new CopyOnWriteArrayList<>(); protected boolean committed; @@ -288,7 +292,6 @@ private T performSet( if (options.isMerge() || options.getFieldMask() != null) { write.setUpdateMask(documentMask.toPb()); } - writes.add(new WriteOperation(documentReference, write)); return wrapResult(writes.size() - 1); From bb0bf778934d572ed2e4ff59126ffc9d0dfe14ec Mon Sep 17 00:00:00 2001 From: Owl Bot Date: Fri, 19 Jan 2024 20:27:17 +0000 Subject: [PATCH 03/20] =?UTF-8?q?=F0=9F=A6=89=20Updates=20from=20OwlBot=20?= =?UTF-8?q?post-processor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --- .../src/main/java/com/google/cloud/firestore/UpdateBuilder.java | 1 - 1 file changed, 1 deletion(-) diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java index 06134c4e0..8fa89de40 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java @@ -30,7 +30,6 @@ import io.opencensus.trace.AttributeValue; import io.opencensus.trace.Tracing; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; From a306c0e7135ac82a8f698da730d54d94e9a25f7a Mon Sep 17 00:00:00 2001 From: Joe Wang <106995533+JoeWang1127@users.noreply.github.com> Date: Fri, 19 Jan 2024 21:30:55 +0000 Subject: [PATCH 04/20] chore: add an unmanaged dependency check (#1532) --- .../workflows/unmanaged-dependency-check.yaml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .github/workflows/unmanaged-dependency-check.yaml diff --git a/.github/workflows/unmanaged-dependency-check.yaml b/.github/workflows/unmanaged-dependency-check.yaml new file mode 100644 index 000000000..ef0f492d2 --- /dev/null +++ b/.github/workflows/unmanaged-dependency-check.yaml @@ -0,0 +1,19 @@ +on: + pull_request: +name: Unmanaged dependency check +jobs: + unmanaged_dependency_check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-java@v3 + with: + distribution: temurin + java-version: 11 + - name: Install modules + shell: bash + run: .kokoro/build.sh + - name: Unmanaged dependency check + uses: googleapis/sdk-platform-java/java-shared-dependencies/unmanaged-dependency-check@unmanaged-dependencies-check-latest + with: + bom-path: google-cloud-firestore-bom/pom.xml From 763cdf5e7ce711d91370c32d4439667e181aa211 Mon Sep 17 00:00:00 2001 From: Tom Andersen Date: Fri, 19 Jan 2024 16:32:41 -0500 Subject: [PATCH 05/20] refactor: Optimize FieldMask instantiation (#1536) * Optimize FieldMask instantiation * Pretty --- .../com/google/cloud/firestore/FieldMask.java | 16 ++++++++-------- .../google/cloud/firestore/UpdateBuilder.java | 17 ++++++++++++----- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/FieldMask.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/FieldMask.java index 0277029da..bec096abb 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/FieldMask.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/FieldMask.java @@ -16,10 +16,8 @@ package com.google.cloud.firestore; -import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; -import java.util.List; +import java.util.Collections; import java.util.Map; import java.util.SortedSet; import java.util.TreeSet; @@ -47,7 +45,7 @@ private FieldMask(SortedSet fieldPaths) { */ @Nonnull public static FieldMask of(String... fieldPaths) { - List paths = new ArrayList<>(); + TreeSet paths = new TreeSet<>(); for (String fieldPath : fieldPaths) { paths.add(FieldPath.fromDotSeparatedString(fieldPath)); } @@ -62,16 +60,18 @@ public static FieldMask of(String... fieldPaths) { */ @Nonnull public static FieldMask of(FieldPath... fieldPaths) { - return new FieldMask(Arrays.asList(fieldPaths)); + TreeSet paths = new TreeSet<>(); + Collections.addAll(paths, fieldPaths); + return new FieldMask(paths); } static FieldMask fromObject(Map values) { - List fieldPaths = extractFromMap(values, FieldPath.empty()); + TreeSet fieldPaths = extractFromMap(values, FieldPath.empty()); return new FieldMask(fieldPaths); } - private static List extractFromMap(Map values, FieldPath path) { - List fieldPaths = new ArrayList<>(); + private static TreeSet extractFromMap(Map values, FieldPath path) { + TreeSet fieldPaths = new TreeSet<>(); for (Map.Entry entry : values.entrySet()) { Object value = entry.getValue(); diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java index 8fa89de40..ea0a83bd2 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java @@ -16,6 +16,9 @@ package com.google.cloud.firestore; +import static com.google.common.base.Predicates.not; +import static java.util.stream.Collectors.toCollection; + import com.google.api.core.ApiFuture; import com.google.api.core.ApiFutures; import com.google.api.core.InternalExtensionOnly; @@ -276,9 +279,11 @@ private T performSet( DocumentTransform.fromFieldPathMap(documentReference, documentData); if (options.getFieldMask() != null) { - List fieldMask = new ArrayList<>(options.getFieldMask()); - fieldMask.removeAll(documentTransform.getFields()); - documentMask = new FieldMask(fieldMask); + TreeSet fieldPaths = + options.getFieldMask().stream() + .filter(not(documentTransform.getFields()::contains)) + .collect(toCollection(TreeSet::new)); + documentMask = new FieldMask(fieldPaths); } else if (options.isMerge()) { documentMask = FieldMask.fromObject(fields); } @@ -547,10 +552,12 @@ public boolean allowTransform() { return true; } }); - List fieldPaths = new ArrayList<>(fields.keySet()); DocumentTransform documentTransform = DocumentTransform.fromFieldPathMap(documentReference, fields); - fieldPaths.removeAll(documentTransform.getFields()); + TreeSet fieldPaths = + fields.keySet().stream() + .filter(not(documentTransform.getFields()::contains)) + .collect(toCollection(TreeSet::new)); FieldMask fieldMask = new FieldMask(fieldPaths); Write.Builder write = documentSnapshot.toPb(); From 96175e3020e2096fafda6f30f7fb09d3f8736450 Mon Sep 17 00:00:00 2001 From: Tom Andersen Date: Mon, 22 Jan 2024 11:36:01 -0500 Subject: [PATCH 06/20] Use synchronize --- .../cloud/firestore/BulkCommitBatch.java | 21 +++++---- .../google/cloud/firestore/UpdateBuilder.java | 45 ++++++++++++------- 2 files changed, 40 insertions(+), 26 deletions(-) diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/BulkCommitBatch.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/BulkCommitBatch.java index 5ad786867..815253301 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/BulkCommitBatch.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/BulkCommitBatch.java @@ -69,24 +69,20 @@ ApiFuture wrapResult(int writeIndex) { *

The writes in the batch are not applied atomically and can be applied out of order. */ ApiFuture bulkCommit() { + BatchWriteRequest request = buildBatchWriteRequest(); + Tracing.getTracer() .getCurrentSpan() .addAnnotation( TraceUtil.SPAN_NAME_BATCHWRITE, - ImmutableMap.of("numDocuments", AttributeValue.longAttributeValue(getWrites().size()))); - - final BatchWriteRequest.Builder request = BatchWriteRequest.newBuilder(); - request.setDatabase(firestore.getDatabaseName()); - - for (WriteOperation writeOperation : getWrites()) { - request.addWrites(writeOperation.write); - } + ImmutableMap.of( + "numDocuments", AttributeValue.longAttributeValue(request.getWritesCount()))); committed = true; ApiFuture response = processExceptions( - firestore.sendRequest(request.build(), firestore.getClient().batchWriteCallable())); + firestore.sendRequest(request, firestore.getClient().batchWriteCallable())); return ApiFutures.transformAsync( response, @@ -117,6 +113,13 @@ ApiFuture bulkCommit() { executor); } + private BatchWriteRequest buildBatchWriteRequest() { + BatchWriteRequest.Builder builder = BatchWriteRequest.newBuilder(); + builder.setDatabase(firestore.getDatabaseName()); + forEach(builder::addWrites); + return builder.build(); + } + /** Maps an RPC failure to each individual write's result. */ private ApiFuture processExceptions(ApiFuture response) { return ApiFutures.catching( diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java index ea0a83bd2..db841a920 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java @@ -33,13 +33,14 @@ import io.opencensus.trace.AttributeValue; import io.opencensus.trace.Tracing; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.SortedSet; import java.util.TreeSet; -import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Consumer; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -50,12 +51,12 @@ @InternalExtensionOnly public abstract class UpdateBuilder { static class WriteOperation { - Write.Builder write; + Write write; DocumentReference documentReference; WriteOperation(DocumentReference documentReference, Write.Builder write) { this.documentReference = documentReference; - this.write = write; + this.write = write.build(); } @Override @@ -69,7 +70,7 @@ public String toString() { // CopyOnWriteArrayList is a thread safe implementation. Client code, // especially within asynchronous callbacks running on thread pool, can // concurrently add writes to builder. - private final List writes = new CopyOnWriteArrayList<>(); + private final List writes = Collections.synchronizedList(new ArrayList<>()); protected boolean committed; @@ -161,9 +162,7 @@ private T performCreate( write.addAllUpdateTransforms(documentTransform.toPb()); } - writes.add(new WriteOperation(documentReference, write)); - - return wrapResult(writes.size() - 1); + return writesAdd(new WriteOperation(documentReference, write)); } private void verifyNotCommitted() { @@ -296,9 +295,17 @@ private T performSet( if (options.isMerge() || options.getFieldMask() != null) { write.setUpdateMask(documentMask.toPb()); } - writes.add(new WriteOperation(documentReference, write)); - return wrapResult(writes.size() - 1); + return writesAdd(new WriteOperation(documentReference, write)); + } + + private T writesAdd(WriteOperation operation) { + int writeIndex; + synchronized (writes) { + writes.add(operation); + writeIndex = writes.size() - 1; + } + return wrapResult(writeIndex); } /** Removes all values in 'fields' that are not specified in 'fieldMask'. */ @@ -567,9 +574,8 @@ public boolean allowTransform() { if (!documentTransform.isEmpty()) { write.addAllUpdateTransforms(documentTransform.toPb()); } - writes.add(new WriteOperation(documentReference, write)); - return wrapResult(writes.size() - 1); + return writesAdd(new WriteOperation(documentReference, write)); } /** @@ -605,9 +611,8 @@ private T performDelete( if (!precondition.isEmpty()) { write.setCurrentDocument(precondition.toPb()); } - writes.add(new WriteOperation(documentReference, write)); - return wrapResult(writes.size() - 1); + return writesAdd(new WriteOperation(documentReference, write)); } /** Commit the current batch. */ @@ -621,8 +626,10 @@ ApiFuture> commit(@Nullable ByteString transactionId) { final CommitRequest.Builder request = CommitRequest.newBuilder(); request.setDatabase(firestore.getDatabaseName()); - for (WriteOperation writeOperation : writes) { - request.addWrites(writeOperation.write); + synchronized (writes) { + for (WriteOperation writeOperation : writes) { + request.addWrites(writeOperation.write); + } } if (transactionId != null) { @@ -656,8 +663,12 @@ boolean isEmpty() { return writes.isEmpty(); } - List getWrites() { - return writes; + void forEach(Consumer consumer) { + synchronized (writes) { + for (WriteOperation writeOperation : writes) { + consumer.accept(writeOperation.write); + } + } } /** Get the number of writes. */ From 8967cb7b8be02233e692645b0d4a8a489558cea8 Mon Sep 17 00:00:00 2001 From: Tom Andersen Date: Mon, 22 Jan 2024 11:44:51 -0500 Subject: [PATCH 07/20] Update comment --- .../main/java/com/google/cloud/firestore/UpdateBuilder.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java index db841a920..4a435cd94 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java @@ -67,9 +67,7 @@ public String toString() { final FirestoreImpl firestore; - // CopyOnWriteArrayList is a thread safe implementation. Client code, - // especially within asynchronous callbacks running on thread pool, can - // concurrently add writes to builder. + // This class can have writes added from multiple threads. private final List writes = Collections.synchronizedList(new ArrayList<>()); protected boolean committed; From 7b3f9a856c72c6fd71451f51612a801557edb1bc Mon Sep 17 00:00:00 2001 From: Tom Andersen Date: Mon, 22 Jan 2024 12:09:58 -0500 Subject: [PATCH 08/20] Make sure commit prevents writes. --- .../cloud/firestore/BulkCommitBatch.java | 4 +-- .../google/cloud/firestore/UpdateBuilder.java | 34 +++++++++---------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/BulkCommitBatch.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/BulkCommitBatch.java index 815253301..00bbb1836 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/BulkCommitBatch.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/BulkCommitBatch.java @@ -69,6 +69,8 @@ ApiFuture wrapResult(int writeIndex) { *

The writes in the batch are not applied atomically and can be applied out of order. */ ApiFuture bulkCommit() { + committed = true; + BatchWriteRequest request = buildBatchWriteRequest(); Tracing.getTracer() @@ -78,8 +80,6 @@ ApiFuture bulkCommit() { ImmutableMap.of( "numDocuments", AttributeValue.longAttributeValue(request.getWritesCount()))); - committed = true; - ApiFuture response = processExceptions( firestore.sendRequest(request, firestore.getClient().batchWriteCallable())); diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java index 4a435cd94..10b7c0a4e 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java @@ -300,6 +300,7 @@ private T performSet( private T writesAdd(WriteOperation operation) { int writeIndex; synchronized (writes) { + verifyNotCommitted(); writes.add(operation); writeIndex = writes.size() - 1; } @@ -615,29 +616,18 @@ private T performDelete( /** Commit the current batch. */ ApiFuture> commit(@Nullable ByteString transactionId) { + committed = true; + + CommitRequest request = buildCommitRequest(transactionId); + Tracing.getTracer() .getCurrentSpan() .addAnnotation( TraceUtil.SPAN_NAME_COMMIT, - ImmutableMap.of("numDocuments", AttributeValue.longAttributeValue(writes.size()))); - - final CommitRequest.Builder request = CommitRequest.newBuilder(); - request.setDatabase(firestore.getDatabaseName()); - - synchronized (writes) { - for (WriteOperation writeOperation : writes) { - request.addWrites(writeOperation.write); - } - } - - if (transactionId != null) { - request.setTransaction(transactionId); - } - - committed = true; + ImmutableMap.of("numDocuments", AttributeValue.longAttributeValue(request.getWritesCount()))); ApiFuture response = - firestore.sendRequest(request.build(), firestore.getClient().commitCallable()); + firestore.sendRequest(request, firestore.getClient().commitCallable()); return ApiFutures.transform( response, @@ -656,6 +646,16 @@ ApiFuture> commit(@Nullable ByteString transactionId) { MoreExecutors.directExecutor()); } + private CommitRequest buildCommitRequest(ByteString transactionId) { + CommitRequest.Builder builder = CommitRequest.newBuilder(); + builder.setDatabase(firestore.getDatabaseName()); + forEach(builder::addWrites); + if (transactionId != null) { + builder.setTransaction(transactionId); + } + return builder.build(); + } + /** Checks whether any updates have been queued. */ boolean isEmpty() { return writes.isEmpty(); From 2e66019e79f1e391aba736a8bdbcff0f35f17533 Mon Sep 17 00:00:00 2001 From: Tom Andersen Date: Mon, 22 Jan 2024 12:10:45 -0500 Subject: [PATCH 09/20] Pretty --- .../main/java/com/google/cloud/firestore/UpdateBuilder.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java index 10b7c0a4e..1fda6e111 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java @@ -624,7 +624,8 @@ ApiFuture> commit(@Nullable ByteString transactionId) { .getCurrentSpan() .addAnnotation( TraceUtil.SPAN_NAME_COMMIT, - ImmutableMap.of("numDocuments", AttributeValue.longAttributeValue(request.getWritesCount()))); + ImmutableMap.of( + "numDocuments", AttributeValue.longAttributeValue(request.getWritesCount()))); ApiFuture response = firestore.sendRequest(request, firestore.getClient().commitCallable()); From 17a7ae6bf40e2748b5b94142fb9eb3cd2cfdd0cd Mon Sep 17 00:00:00 2001 From: Tom Andersen Date: Mon, 22 Jan 2024 16:26:51 -0500 Subject: [PATCH 10/20] Refactor --- .../google/cloud/firestore/UpdateBuilder.java | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java index 1fda6e111..5830f1417 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java @@ -30,6 +30,7 @@ import com.google.firestore.v1.CommitResponse; import com.google.firestore.v1.Write; import com.google.protobuf.ByteString; +import com.google.protobuf.Timestamp; import io.opencensus.trace.AttributeValue; import io.opencensus.trace.Tracing; import java.util.ArrayList; @@ -41,6 +42,7 @@ import java.util.SortedSet; import java.util.TreeSet; import java.util.function.Consumer; +import java.util.stream.Collectors; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -633,16 +635,10 @@ ApiFuture> commit(@Nullable ByteString transactionId) { return ApiFutures.transform( response, commitResponse -> { - List writeResults = - commitResponse.getWriteResultsList(); - - List result = new ArrayList<>(); - - for (com.google.firestore.v1.WriteResult writeResult : writeResults) { - result.add(WriteResult.fromProto(writeResult, commitResponse.getCommitTime())); - } - - return result; + Timestamp commitTime = commitResponse.getCommitTime(); + return commitResponse.getWriteResultsList().stream() + .map(writeResult -> WriteResult.fromProto(writeResult, commitTime)) + .collect(Collectors.toList()); }, MoreExecutors.directExecutor()); } From ae56dec3ad7cdaf1c1a7a443fa6a93a5d7f66cd0 Mon Sep 17 00:00:00 2001 From: Tom Andersen Date: Tue, 23 Jan 2024 11:29:34 -0500 Subject: [PATCH 11/20] Add comments and make committed volatile. --- .../cloud/firestore/BulkCommitBatch.java | 11 ++++++----- .../google/cloud/firestore/UpdateBuilder.java | 19 +++++++++++++++++-- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/BulkCommitBatch.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/BulkCommitBatch.java index 00bbb1836..f9f7d7637 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/BulkCommitBatch.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/BulkCommitBatch.java @@ -69,8 +69,9 @@ ApiFuture wrapResult(int writeIndex) { *

The writes in the batch are not applied atomically and can be applied out of order. */ ApiFuture bulkCommit() { - committed = true; + // Follows same thread safety logic as `UpdateBuilder::commit`. + committed = true; BatchWriteRequest request = buildBatchWriteRequest(); Tracing.getTracer() @@ -87,18 +88,18 @@ ApiFuture bulkCommit() { return ApiFutures.transformAsync( response, batchWriteResponse -> { - List> pendingUserCallbacks = new ArrayList<>(); - List writeResults = batchWriteResponse.getWriteResultsList(); List statuses = batchWriteResponse.getStatusList(); - for (int i = 0; i < writeResults.size(); ++i) { - com.google.firestore.v1.WriteResult writeResult = writeResults.get(i); + int size = writeResults.size(); + List> pendingUserCallbacks = new ArrayList<>(size); + for (int i = 0; i < size; ++i) { com.google.rpc.Status status = statuses.get(i); BulkWriterOperation operation = pendingOperations.get(i); Status code = Status.fromCodeValue(status.getCode()); if (code == Status.OK) { + com.google.firestore.v1.WriteResult writeResult = writeResults.get(i); pendingUserCallbacks.add( operation.onSuccess( new WriteResult(Timestamp.fromProto(writeResult.getUpdateTime())))); diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java index 5830f1417..29010989e 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java @@ -72,7 +72,7 @@ public String toString() { // This class can have writes added from multiple threads. private final List writes = Collections.synchronizedList(new ArrayList<>()); - protected boolean committed; + protected volatile boolean committed; boolean isCommitted() { return committed; @@ -618,8 +618,23 @@ private T performDelete( /** Commit the current batch. */ ApiFuture> commit(@Nullable ByteString transactionId) { - committed = true; + // Sequence is thread safe. + // + // 1. Set committed = true + // 2. Build commit request + // + // Step 1 sets uses volatile property to ensure committed is visible to all + // threads immediately. + // + // Step 2 uses `forEach(..)` that is synchronized, therefore will be blocked + // until any writes are complete. + // + // Writes will `verifyNotCommit()` within synchronized block of code before + // appending writes. Since committed is set to true before accessing writes, + // we are ensured that no more writes will be appended after commit accesses + // writes. + committed = true; CommitRequest request = buildCommitRequest(transactionId); Tracing.getTracer() From 81002e00ae48984d2f9c3795533c7d9466d0824d Mon Sep 17 00:00:00 2001 From: Tom Andersen Date: Tue, 23 Jan 2024 11:34:44 -0500 Subject: [PATCH 12/20] Make WriteOperation immutable. --- .../main/java/com/google/cloud/firestore/UpdateBuilder.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java index 29010989e..607bf800f 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java @@ -52,9 +52,9 @@ */ @InternalExtensionOnly public abstract class UpdateBuilder { - static class WriteOperation { - Write write; - DocumentReference documentReference; + static final class WriteOperation { + final Write write; + final DocumentReference documentReference; WriteOperation(DocumentReference documentReference, Write.Builder write) { this.documentReference = documentReference; From b2ff58566ea5207ce7d3f8c7cdb97001a68d7007 Mon Sep 17 00:00:00 2001 From: Tom Andersen Date: Tue, 23 Jan 2024 14:15:10 -0500 Subject: [PATCH 13/20] Refactor --- .../google/cloud/firestore/UpdateBuilder.java | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java index 607bf800f..6f1166866 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java @@ -146,7 +146,6 @@ public T create( private T performCreate( @Nonnull DocumentReference documentReference, @Nonnull Map fields) { - verifyNotCommitted(); Tracing.getTracer().getCurrentSpan().addAnnotation(TraceUtil.SPAN_NAME_CREATEDOCUMENT); DocumentSnapshot documentSnapshot = DocumentSnapshot.fromObject( @@ -165,14 +164,6 @@ private T performCreate( return writesAdd(new WriteOperation(documentReference, write)); } - private void verifyNotCommitted() { - Preconditions.checkState( - !isCommitted(), - String.format( - "Cannot modify a %s that has already been committed.", - this.getClass().getSimpleName())); - } - /** * Creates a new Document at the DocumentReference location. It fails the write if the document * exists. @@ -261,7 +252,6 @@ private T performSet( @Nonnull DocumentReference documentReference, @Nonnull Map fields, @Nonnull SetOptions options) { - verifyNotCommitted(); Map documentData; if (options.getFieldMask() != null) { @@ -302,7 +292,11 @@ private T performSet( private T writesAdd(WriteOperation operation) { int writeIndex; synchronized (writes) { - verifyNotCommitted(); + Preconditions.checkState( + !isCommitted(), + String.format( + "Cannot modify a %s that has already been committed.", + this.getClass().getSimpleName())); writes.add(operation); writeIndex = writes.size() - 1; } @@ -540,7 +534,6 @@ private T performUpdate( @Nonnull DocumentReference documentReference, @Nonnull final Map fields, @Nonnull Precondition precondition) { - verifyNotCommitted(); Preconditions.checkArgument(!fields.isEmpty(), "Data for update() cannot be empty."); Tracing.getTracer().getCurrentSpan().addAnnotation(TraceUtil.SPAN_NAME_UPDATEDOCUMENT); Map deconstructedMap = expandObject(fields); @@ -605,7 +598,6 @@ public T delete(@Nonnull DocumentReference documentReference) { private T performDelete( @Nonnull DocumentReference documentReference, @Nonnull Precondition precondition) { - verifyNotCommitted(); Tracing.getTracer().getCurrentSpan().addAnnotation(TraceUtil.SPAN_NAME_DELETEDOCUMENT); Write.Builder write = Write.newBuilder().setDelete(documentReference.getName()); From 5cb6c52c7a11a1b2f45c9368d4a1a6dd9a1feb92 Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 13:51:28 -0500 Subject: [PATCH 14/20] fix(deps): Update the Java code generator (gapic-generator-java) to 2.32.0 (#1534) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: Add FindNearest API to the preview branch docs: Improve the documentation on Document.fields PiperOrigin-RevId: 599602467 Source-Link: https://github.com/googleapis/googleapis/commit/d32bd9795d2620d327f1fd21477c53e828ab5a86 Source-Link: https://github.com/googleapis/googleapis-gen/commit/0545ffc488b82d3a4771118c923d64cd0b759953 Copy-Tag: eyJwIjoiLmdpdGh1Yi8uT3dsQm90LnlhbWwiLCJoIjoiMDU0NWZmYzQ4OGI4MmQzYTQ3NzExMThjOTIzZDY0Y2QwYjc1OTk1MyJ9 * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * fix(deps): Update the Java code generator (gapic-generator-java) to 2.32.0 PiperOrigin-RevId: 599914188 Source-Link: https://github.com/googleapis/googleapis/commit/17e6661f8bf43374633adf950454ed8e69bdcee1 Source-Link: https://github.com/googleapis/googleapis-gen/commit/d86ba5be537e489435105ca85566cc4103301aba Copy-Tag: eyJwIjoiLmdpdGh1Yi8uT3dsQm90LnlhbWwiLCJoIjoiZDg2YmE1YmU1MzdlNDg5NDM1MTA1Y2E4NTU2NmNjNDEwMzMwMWFiYSJ9 * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot --- .../firestore/v1/FirestoreAdminClient.java | 155 +++++----- .../v1/stub/FirestoreAdminStubSettings.java | 20 +- .../cloud/firestore/v1/FirestoreClient.java | 105 +++---- .../v1/stub/FirestoreStubSettings.java | 20 +- .../com/google/firestore/v1/Document.java | 264 +++++++++--------- .../firestore/v1/DocumentOrBuilder.java | 120 ++++---- .../google/firestore/v1/StructuredQuery.java | 16 ++ .../proto/google/firestore/v1/document.proto | 24 +- .../proto/google/firestore/v1/query.proto | 8 + 9 files changed, 395 insertions(+), 337 deletions(-) diff --git a/google-cloud-firestore-admin/src/main/java/com/google/cloud/firestore/v1/FirestoreAdminClient.java b/google-cloud-firestore-admin/src/main/java/com/google/cloud/firestore/v1/FirestoreAdminClient.java index a74645ac9..49ceb63c4 100644 --- a/google-cloud-firestore-admin/src/main/java/com/google/cloud/firestore/v1/FirestoreAdminClient.java +++ b/google-cloud-firestore-admin/src/main/java/com/google/cloud/firestore/v1/FirestoreAdminClient.java @@ -125,280 +125,281 @@ * Method * Description * Method Variants + * * - * CreateIndex + *

CreateIndex *

Creates a composite index. This returns a [google.longrunning.Operation][google.longrunning.Operation] which may be used to track the status of the creation. The metadata for the operation will be the type [IndexOperationMetadata][google.firestore.admin.v1.IndexOperationMetadata]. * *

Request object method variants only take one parameter, a request object, which must be constructed before the call.

*
    - *
  • createIndexAsync(CreateIndexRequest request) + *
  • createIndexAsync(CreateIndexRequest request) *

*

Methods that return long-running operations have "Async" method variants that return `OperationFuture`, which is used to track polling of the service.

*
    - *
  • createIndexAsync(CollectionGroupName parent, Index index) - *
  • createIndexAsync(String parent, Index index) + *
  • createIndexAsync(CollectionGroupName parent, Index index) + *

  • createIndexAsync(String parent, Index index) *

*

Callable method variants take no parameters and return an immutable API callable object, which can be used to initiate calls to the service.

*
    - *
  • createIndexOperationCallable() - *
  • createIndexCallable() + *
  • createIndexOperationCallable() + *

  • createIndexCallable() *

* * * - * ListIndexes + *

ListIndexes *

Lists composite indexes. * *

Request object method variants only take one parameter, a request object, which must be constructed before the call.

*
    - *
  • listIndexes(ListIndexesRequest request) + *
  • listIndexes(ListIndexesRequest request) *

*

"Flattened" method variants have converted the fields of the request object into function parameters to enable multiple ways to call the same method.

*
    - *
  • listIndexes(CollectionGroupName parent) - *
  • listIndexes(String parent) + *
  • listIndexes(CollectionGroupName parent) + *

  • listIndexes(String parent) *

*

Callable method variants take no parameters and return an immutable API callable object, which can be used to initiate calls to the service.

*
    - *
  • listIndexesPagedCallable() - *
  • listIndexesCallable() + *
  • listIndexesPagedCallable() + *

  • listIndexesCallable() *

* * * - * GetIndex + *

GetIndex *

Gets a composite index. * *

Request object method variants only take one parameter, a request object, which must be constructed before the call.

*
    - *
  • getIndex(GetIndexRequest request) + *
  • getIndex(GetIndexRequest request) *

*

"Flattened" method variants have converted the fields of the request object into function parameters to enable multiple ways to call the same method.

*
    - *
  • getIndex(IndexName name) - *
  • getIndex(String name) + *
  • getIndex(IndexName name) + *

  • getIndex(String name) *

*

Callable method variants take no parameters and return an immutable API callable object, which can be used to initiate calls to the service.

*
    - *
  • getIndexCallable() + *
  • getIndexCallable() *

* * * - * DeleteIndex + *

DeleteIndex *

Deletes a composite index. * *

Request object method variants only take one parameter, a request object, which must be constructed before the call.

*
    - *
  • deleteIndex(DeleteIndexRequest request) + *
  • deleteIndex(DeleteIndexRequest request) *

*

"Flattened" method variants have converted the fields of the request object into function parameters to enable multiple ways to call the same method.

*
    - *
  • deleteIndex(IndexName name) - *
  • deleteIndex(String name) + *
  • deleteIndex(IndexName name) + *

  • deleteIndex(String name) *

*

Callable method variants take no parameters and return an immutable API callable object, which can be used to initiate calls to the service.

*
    - *
  • deleteIndexCallable() + *
  • deleteIndexCallable() *

* * * - * GetField + *

GetField *

Gets the metadata and configuration for a Field. * *

Request object method variants only take one parameter, a request object, which must be constructed before the call.

*
    - *
  • getField(GetFieldRequest request) + *
  • getField(GetFieldRequest request) *

*

"Flattened" method variants have converted the fields of the request object into function parameters to enable multiple ways to call the same method.

*
    - *
  • getField(FieldName name) - *
  • getField(String name) + *
  • getField(FieldName name) + *

  • getField(String name) *

*

Callable method variants take no parameters and return an immutable API callable object, which can be used to initiate calls to the service.

*
    - *
  • getFieldCallable() + *
  • getFieldCallable() *

* * * - * UpdateField + *

UpdateField *

Updates a field configuration. Currently, field updates apply only to single field index configuration. However, calls to [FirestoreAdmin.UpdateField][google.firestore.admin.v1.FirestoreAdmin.UpdateField] should provide a field mask to avoid changing any configuration that the caller isn't aware of. The field mask should be specified as: `{ paths: "index_config" }`. *

This call returns a [google.longrunning.Operation][google.longrunning.Operation] which may be used to track the status of the field update. The metadata for the operation will be the type [FieldOperationMetadata][google.firestore.admin.v1.FieldOperationMetadata]. *

To configure the default field settings for the database, use the special `Field` with resource name: `projects/{project_id}/databases/{database_id}/collectionGroups/__default__/fields/*`. * *

Request object method variants only take one parameter, a request object, which must be constructed before the call.

*
    - *
  • updateFieldAsync(UpdateFieldRequest request) + *
  • updateFieldAsync(UpdateFieldRequest request) *

*

Methods that return long-running operations have "Async" method variants that return `OperationFuture`, which is used to track polling of the service.

*
    - *
  • updateFieldAsync(Field field) + *
  • updateFieldAsync(Field field) *

*

Callable method variants take no parameters and return an immutable API callable object, which can be used to initiate calls to the service.

*
    - *
  • updateFieldOperationCallable() - *
  • updateFieldCallable() + *
  • updateFieldOperationCallable() + *

  • updateFieldCallable() *

* * * - * ListFields + *

ListFields *

Lists the field configuration and metadata for this database. *

Currently, [FirestoreAdmin.ListFields][google.firestore.admin.v1.FirestoreAdmin.ListFields] only supports listing fields that have been explicitly overridden. To issue this query, call [FirestoreAdmin.ListFields][google.firestore.admin.v1.FirestoreAdmin.ListFields] with the filter set to `indexConfig.usesAncestorConfig:false` or `ttlConfig:*`. * *

Request object method variants only take one parameter, a request object, which must be constructed before the call.

*
    - *
  • listFields(ListFieldsRequest request) + *
  • listFields(ListFieldsRequest request) *

*

"Flattened" method variants have converted the fields of the request object into function parameters to enable multiple ways to call the same method.

*
    - *
  • listFields(CollectionGroupName parent) - *
  • listFields(String parent) + *
  • listFields(CollectionGroupName parent) + *

  • listFields(String parent) *

*

Callable method variants take no parameters and return an immutable API callable object, which can be used to initiate calls to the service.

*
    - *
  • listFieldsPagedCallable() - *
  • listFieldsCallable() + *
  • listFieldsPagedCallable() + *

  • listFieldsCallable() *

* * * - * ExportDocuments + *

ExportDocuments *

Exports a copy of all or a subset of documents from Google Cloud Firestore to another storage system, such as Google Cloud Storage. Recent updates to documents may not be reflected in the export. The export occurs in the background and its progress can be monitored and managed via the Operation resource that is created. The output of an export may only be used once the associated operation is done. If an export operation is cancelled before completion it may leave partial data behind in Google Cloud Storage. *

For more details on export behavior and output format, refer to: https://cloud.google.com/firestore/docs/manage-data/export-import * *

Request object method variants only take one parameter, a request object, which must be constructed before the call.

*
    - *
  • exportDocumentsAsync(ExportDocumentsRequest request) + *
  • exportDocumentsAsync(ExportDocumentsRequest request) *

*

Methods that return long-running operations have "Async" method variants that return `OperationFuture`, which is used to track polling of the service.

*
    - *
  • exportDocumentsAsync(DatabaseName name) - *
  • exportDocumentsAsync(String name) + *
  • exportDocumentsAsync(DatabaseName name) + *

  • exportDocumentsAsync(String name) *

*

Callable method variants take no parameters and return an immutable API callable object, which can be used to initiate calls to the service.

*
    - *
  • exportDocumentsOperationCallable() - *
  • exportDocumentsCallable() + *
  • exportDocumentsOperationCallable() + *

  • exportDocumentsCallable() *

* * * - * ImportDocuments + *

ImportDocuments *

Imports documents into Google Cloud Firestore. Existing documents with the same name are overwritten. The import occurs in the background and its progress can be monitored and managed via the Operation resource that is created. If an ImportDocuments operation is cancelled, it is possible that a subset of the data has already been imported to Cloud Firestore. * *

Request object method variants only take one parameter, a request object, which must be constructed before the call.

*
    - *
  • importDocumentsAsync(ImportDocumentsRequest request) + *
  • importDocumentsAsync(ImportDocumentsRequest request) *

*

Methods that return long-running operations have "Async" method variants that return `OperationFuture`, which is used to track polling of the service.

*
    - *
  • importDocumentsAsync(DatabaseName name) - *
  • importDocumentsAsync(String name) + *
  • importDocumentsAsync(DatabaseName name) + *

  • importDocumentsAsync(String name) *

*

Callable method variants take no parameters and return an immutable API callable object, which can be used to initiate calls to the service.

*
    - *
  • importDocumentsOperationCallable() - *
  • importDocumentsCallable() + *
  • importDocumentsOperationCallable() + *

  • importDocumentsCallable() *

* * * - * CreateDatabase + *

CreateDatabase *

Create a database. * *

Request object method variants only take one parameter, a request object, which must be constructed before the call.

*
    - *
  • createDatabaseAsync(CreateDatabaseRequest request) + *
  • createDatabaseAsync(CreateDatabaseRequest request) *

*

Methods that return long-running operations have "Async" method variants that return `OperationFuture`, which is used to track polling of the service.

*
    - *
  • createDatabaseAsync(ProjectName parent, Database database, String databaseId) - *
  • createDatabaseAsync(String parent, Database database, String databaseId) + *
  • createDatabaseAsync(ProjectName parent, Database database, String databaseId) + *

  • createDatabaseAsync(String parent, Database database, String databaseId) *

*

Callable method variants take no parameters and return an immutable API callable object, which can be used to initiate calls to the service.

*
    - *
  • createDatabaseOperationCallable() - *
  • createDatabaseCallable() + *
  • createDatabaseOperationCallable() + *

  • createDatabaseCallable() *

* * * - * GetDatabase + *

GetDatabase *

Gets information about a database. * *

Request object method variants only take one parameter, a request object, which must be constructed before the call.

*
    - *
  • getDatabase(GetDatabaseRequest request) + *
  • getDatabase(GetDatabaseRequest request) *

*

"Flattened" method variants have converted the fields of the request object into function parameters to enable multiple ways to call the same method.

*
    - *
  • getDatabase(DatabaseName name) - *
  • getDatabase(String name) + *
  • getDatabase(DatabaseName name) + *

  • getDatabase(String name) *

*

Callable method variants take no parameters and return an immutable API callable object, which can be used to initiate calls to the service.

*
    - *
  • getDatabaseCallable() + *
  • getDatabaseCallable() *

* * * - * ListDatabases + *

ListDatabases *

List all the databases in the project. * *

Request object method variants only take one parameter, a request object, which must be constructed before the call.

*
    - *
  • listDatabases(ListDatabasesRequest request) + *
  • listDatabases(ListDatabasesRequest request) *

*

"Flattened" method variants have converted the fields of the request object into function parameters to enable multiple ways to call the same method.

*
    - *
  • listDatabases(ProjectName parent) - *
  • listDatabases(String parent) + *
  • listDatabases(ProjectName parent) + *

  • listDatabases(String parent) *

*

Callable method variants take no parameters and return an immutable API callable object, which can be used to initiate calls to the service.

*
    - *
  • listDatabasesCallable() + *
  • listDatabasesCallable() *

* * * - * UpdateDatabase + *

UpdateDatabase *

Updates a database. * *

Request object method variants only take one parameter, a request object, which must be constructed before the call.

*
    - *
  • updateDatabaseAsync(UpdateDatabaseRequest request) + *
  • updateDatabaseAsync(UpdateDatabaseRequest request) *

*

Methods that return long-running operations have "Async" method variants that return `OperationFuture`, which is used to track polling of the service.

*
    - *
  • updateDatabaseAsync(Database database, FieldMask updateMask) + *
  • updateDatabaseAsync(Database database, FieldMask updateMask) *

*

Callable method variants take no parameters and return an immutable API callable object, which can be used to initiate calls to the service.

*
    - *
  • updateDatabaseOperationCallable() - *
  • updateDatabaseCallable() + *
  • updateDatabaseOperationCallable() + *

  • updateDatabaseCallable() *

* * * - * DeleteDatabase + *

DeleteDatabase *

Deletes a database. * *

Request object method variants only take one parameter, a request object, which must be constructed before the call.

*
    - *
  • deleteDatabaseAsync(DeleteDatabaseRequest request) + *
  • deleteDatabaseAsync(DeleteDatabaseRequest request) *

*

Methods that return long-running operations have "Async" method variants that return `OperationFuture`, which is used to track polling of the service.

*
    - *
  • deleteDatabaseAsync(DatabaseName name) - *
  • deleteDatabaseAsync(String name) + *
  • deleteDatabaseAsync(DatabaseName name) + *

  • deleteDatabaseAsync(String name) *

*

Callable method variants take no parameters and return an immutable API callable object, which can be used to initiate calls to the service.

*
    - *
  • deleteDatabaseOperationCallable() - *
  • deleteDatabaseCallable() + *
  • deleteDatabaseOperationCallable() + *

  • deleteDatabaseCallable() *

* * diff --git a/google-cloud-firestore-admin/src/main/java/com/google/cloud/firestore/v1/stub/FirestoreAdminStubSettings.java b/google-cloud-firestore-admin/src/main/java/com/google/cloud/firestore/v1/stub/FirestoreAdminStubSettings.java index 2be7176ac..e7416e878 100644 --- a/google-cloud-firestore-admin/src/main/java/com/google/cloud/firestore/v1/stub/FirestoreAdminStubSettings.java +++ b/google-cloud-firestore-admin/src/main/java/com/google/cloud/firestore/v1/stub/FirestoreAdminStubSettings.java @@ -403,6 +403,15 @@ public FirestoreAdminStub createStub() throws IOException { "Transport not supported: %s", getTransportChannelProvider().getTransportName())); } + /** Returns the endpoint set by the user or the the service's default endpoint. */ + @Override + public String getEndpoint() { + if (super.getEndpoint() != null) { + return super.getEndpoint(); + } + return getDefaultEndpoint(); + } + /** Returns the default service name. */ @Override public String getServiceName() { @@ -713,7 +722,6 @@ private static Builder createDefault() { builder.setTransportChannelProvider(defaultTransportChannelProvider()); builder.setCredentialsProvider(defaultCredentialsProviderBuilder().build()); builder.setInternalHeaderProvider(defaultApiClientHeaderProviderBuilder().build()); - builder.setEndpoint(getDefaultEndpoint()); builder.setMtlsEndpoint(getDefaultMtlsEndpoint()); builder.setSwitchToMtlsEndpointAllowed(true); @@ -726,7 +734,6 @@ private static Builder createHttpJsonDefault() { builder.setTransportChannelProvider(defaultHttpJsonTransportProviderBuilder().build()); builder.setCredentialsProvider(defaultCredentialsProviderBuilder().build()); builder.setInternalHeaderProvider(defaultHttpJsonApiClientHeaderProviderBuilder().build()); - builder.setEndpoint(getDefaultEndpoint()); builder.setMtlsEndpoint(getDefaultMtlsEndpoint()); builder.setSwitchToMtlsEndpointAllowed(true); @@ -1119,6 +1126,15 @@ public UnaryCallSettings.Builder deleteDatabas return deleteDatabaseOperationSettings; } + /** Returns the endpoint set by the user or the the service's default endpoint. */ + @Override + public String getEndpoint() { + if (super.getEndpoint() != null) { + return super.getEndpoint(); + } + return getDefaultEndpoint(); + } + @Override public FirestoreAdminStubSettings build() throws IOException { return new FirestoreAdminStubSettings(this); diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/v1/FirestoreClient.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/v1/FirestoreClient.java index 5e472ac97..23be95fd3 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/v1/FirestoreClient.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/v1/FirestoreClient.java @@ -105,148 +105,149 @@ * Method * Description * Method Variants + * * - * GetDocument + *

GetDocument *

Gets a single document. * *

Request object method variants only take one parameter, a request object, which must be constructed before the call.

*
    - *
  • getDocument(GetDocumentRequest request) + *
  • getDocument(GetDocumentRequest request) *

*

Callable method variants take no parameters and return an immutable API callable object, which can be used to initiate calls to the service.

*
    - *
  • getDocumentCallable() + *
  • getDocumentCallable() *

* * * - * ListDocuments + *

ListDocuments *

Lists documents. * *

Request object method variants only take one parameter, a request object, which must be constructed before the call.

*
    - *
  • listDocuments(ListDocumentsRequest request) + *
  • listDocuments(ListDocumentsRequest request) *

*

Callable method variants take no parameters and return an immutable API callable object, which can be used to initiate calls to the service.

*
    - *
  • listDocumentsPagedCallable() - *
  • listDocumentsCallable() + *
  • listDocumentsPagedCallable() + *

  • listDocumentsCallable() *

* * * - * UpdateDocument + *

UpdateDocument *

Updates or inserts a document. * *

Request object method variants only take one parameter, a request object, which must be constructed before the call.

*
    - *
  • updateDocument(UpdateDocumentRequest request) + *
  • updateDocument(UpdateDocumentRequest request) *

*

"Flattened" method variants have converted the fields of the request object into function parameters to enable multiple ways to call the same method.

*
    - *
  • updateDocument(Document document, DocumentMask updateMask) + *
  • updateDocument(Document document, DocumentMask updateMask) *

*

Callable method variants take no parameters and return an immutable API callable object, which can be used to initiate calls to the service.

*
    - *
  • updateDocumentCallable() + *
  • updateDocumentCallable() *

* * * - * DeleteDocument + *

DeleteDocument *

Deletes a document. * *

Request object method variants only take one parameter, a request object, which must be constructed before the call.

*
    - *
  • deleteDocument(DeleteDocumentRequest request) + *
  • deleteDocument(DeleteDocumentRequest request) *

*

"Flattened" method variants have converted the fields of the request object into function parameters to enable multiple ways to call the same method.

*
    - *
  • deleteDocument(String name) + *
  • deleteDocument(String name) *

*

Callable method variants take no parameters and return an immutable API callable object, which can be used to initiate calls to the service.

*
    - *
  • deleteDocumentCallable() + *
  • deleteDocumentCallable() *

* * * - * BatchGetDocuments + *

BatchGetDocuments *

Gets multiple documents. *

Documents returned by this method are not guaranteed to be returned in the same order that they were requested. * *

Callable method variants take no parameters and return an immutable API callable object, which can be used to initiate calls to the service.

*
    - *
  • batchGetDocumentsCallable() + *
  • batchGetDocumentsCallable() *

* * * - * BeginTransaction + *

BeginTransaction *

Starts a new transaction. * *

Request object method variants only take one parameter, a request object, which must be constructed before the call.

*
    - *
  • beginTransaction(BeginTransactionRequest request) + *
  • beginTransaction(BeginTransactionRequest request) *

*

"Flattened" method variants have converted the fields of the request object into function parameters to enable multiple ways to call the same method.

*
    - *
  • beginTransaction(String database) + *
  • beginTransaction(String database) *

*

Callable method variants take no parameters and return an immutable API callable object, which can be used to initiate calls to the service.

*
    - *
  • beginTransactionCallable() + *
  • beginTransactionCallable() *

* * * - * Commit + *

Commit *

Commits a transaction, while optionally updating documents. * *

Request object method variants only take one parameter, a request object, which must be constructed before the call.

*
    - *
  • commit(CommitRequest request) + *
  • commit(CommitRequest request) *

*

"Flattened" method variants have converted the fields of the request object into function parameters to enable multiple ways to call the same method.

*
    - *
  • commit(String database, List<Write> writes) + *
  • commit(String database, List<Write> writes) *

*

Callable method variants take no parameters and return an immutable API callable object, which can be used to initiate calls to the service.

*
    - *
  • commitCallable() + *
  • commitCallable() *

* * * - * Rollback + *

Rollback *

Rolls back a transaction. * *

Request object method variants only take one parameter, a request object, which must be constructed before the call.

*
    - *
  • rollback(RollbackRequest request) + *
  • rollback(RollbackRequest request) *

*

"Flattened" method variants have converted the fields of the request object into function parameters to enable multiple ways to call the same method.

*
    - *
  • rollback(String database, ByteString transaction) + *
  • rollback(String database, ByteString transaction) *

*

Callable method variants take no parameters and return an immutable API callable object, which can be used to initiate calls to the service.

*
    - *
  • rollbackCallable() + *
  • rollbackCallable() *

* * * - * RunQuery + *

RunQuery *

Runs a query. * *

Callable method variants take no parameters and return an immutable API callable object, which can be used to initiate calls to the service.

*
    - *
  • runQueryCallable() + *
  • runQueryCallable() *

* * * - * RunAggregationQuery + *

RunAggregationQuery *

Runs an aggregation query. *

Rather than producing [Document][google.firestore.v1.Document] results like [Firestore.RunQuery][google.firestore.v1.Firestore.RunQuery], this API allows running an aggregation to produce a series of [AggregationResult][google.firestore.v1.AggregationResult] server-side. *

High-Level Example: @@ -254,91 +255,91 @@ * *

Callable method variants take no parameters and return an immutable API callable object, which can be used to initiate calls to the service.

*
    - *
  • runAggregationQueryCallable() + *
  • runAggregationQueryCallable() *

* * * - * PartitionQuery + *

PartitionQuery *

Partitions a query by returning partition cursors that can be used to run the query in parallel. The returned partition cursors are split points that can be used by RunQuery as starting/end points for the query results. * *

Request object method variants only take one parameter, a request object, which must be constructed before the call.

*
    - *
  • partitionQuery(PartitionQueryRequest request) + *
  • partitionQuery(PartitionQueryRequest request) *

*

Callable method variants take no parameters and return an immutable API callable object, which can be used to initiate calls to the service.

*
    - *
  • partitionQueryPagedCallable() - *
  • partitionQueryCallable() + *
  • partitionQueryPagedCallable() + *

  • partitionQueryCallable() *

* * * - * Write + *

Write *

Streams batches of document updates and deletes, in order. This method is only available via gRPC or WebChannel (not REST). * *

Callable method variants take no parameters and return an immutable API callable object, which can be used to initiate calls to the service.

*
    - *
  • writeCallable() + *
  • writeCallable() *

* * * - * Listen + *

Listen *

Listens to changes. This method is only available via gRPC or WebChannel (not REST). * *

Callable method variants take no parameters and return an immutable API callable object, which can be used to initiate calls to the service.

*
    - *
  • listenCallable() + *
  • listenCallable() *

* * * - * ListCollectionIds + *

ListCollectionIds *

Lists all the collection IDs underneath a document. * *

Request object method variants only take one parameter, a request object, which must be constructed before the call.

*
    - *
  • listCollectionIds(ListCollectionIdsRequest request) + *
  • listCollectionIds(ListCollectionIdsRequest request) *

*

"Flattened" method variants have converted the fields of the request object into function parameters to enable multiple ways to call the same method.

*
    - *
  • listCollectionIds(String parent) + *
  • listCollectionIds(String parent) *

*

Callable method variants take no parameters and return an immutable API callable object, which can be used to initiate calls to the service.

*
    - *
  • listCollectionIdsPagedCallable() - *
  • listCollectionIdsCallable() + *
  • listCollectionIdsPagedCallable() + *

  • listCollectionIdsCallable() *

* * * - * BatchWrite + *

BatchWrite *

Applies a batch of write operations. *

The BatchWrite method does not apply the write operations atomically and can apply them out of order. Method does not allow more than one write per document. Each write succeeds or fails independently. See the [BatchWriteResponse][google.firestore.v1.BatchWriteResponse] for the success status of each write. *

If you require an atomically applied set of writes, use [Commit][google.firestore.v1.Firestore.Commit] instead. * *

Request object method variants only take one parameter, a request object, which must be constructed before the call.

*
    - *
  • batchWrite(BatchWriteRequest request) + *
  • batchWrite(BatchWriteRequest request) *

*

Callable method variants take no parameters and return an immutable API callable object, which can be used to initiate calls to the service.

*
    - *
  • batchWriteCallable() + *
  • batchWriteCallable() *

* * * - * CreateDocument + *

CreateDocument *

Creates a new document. * *

Request object method variants only take one parameter, a request object, which must be constructed before the call.

*
    - *
  • createDocument(CreateDocumentRequest request) + *
  • createDocument(CreateDocumentRequest request) *

*

Callable method variants take no parameters and return an immutable API callable object, which can be used to initiate calls to the service.

*
    - *
  • createDocumentCallable() + *
  • createDocumentCallable() *

* * diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/v1/stub/FirestoreStubSettings.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/v1/stub/FirestoreStubSettings.java index 65345559b..f34ce9361 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/v1/stub/FirestoreStubSettings.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/v1/stub/FirestoreStubSettings.java @@ -426,6 +426,15 @@ public FirestoreStub createStub() throws IOException { "Transport not supported: %s", getTransportChannelProvider().getTransportName())); } + /** Returns the endpoint set by the user or the the service's default endpoint. */ + @Override + public String getEndpoint() { + if (super.getEndpoint() != null) { + return super.getEndpoint(); + } + return getDefaultEndpoint(); + } + /** Returns the default service name. */ @Override public String getServiceName() { @@ -769,7 +778,6 @@ private static Builder createDefault() { builder.setTransportChannelProvider(defaultTransportChannelProvider()); builder.setCredentialsProvider(defaultCredentialsProviderBuilder().build()); builder.setInternalHeaderProvider(defaultApiClientHeaderProviderBuilder().build()); - builder.setEndpoint(getDefaultEndpoint()); builder.setMtlsEndpoint(getDefaultMtlsEndpoint()); builder.setSwitchToMtlsEndpointAllowed(true); @@ -782,7 +790,6 @@ private static Builder createHttpJsonDefault() { builder.setTransportChannelProvider(defaultHttpJsonTransportProviderBuilder().build()); builder.setCredentialsProvider(defaultCredentialsProviderBuilder().build()); builder.setInternalHeaderProvider(defaultHttpJsonApiClientHeaderProviderBuilder().build()); - builder.setEndpoint(getDefaultEndpoint()); builder.setMtlsEndpoint(getDefaultMtlsEndpoint()); builder.setSwitchToMtlsEndpointAllowed(true); @@ -969,6 +976,15 @@ public UnaryCallSettings.Builder createDocument return createDocumentSettings; } + /** Returns the endpoint set by the user or the the service's default endpoint. */ + @Override + public String getEndpoint() { + if (super.getEndpoint() != null) { + return super.getEndpoint(); + } + return getDefaultEndpoint(); + } + @Override public FirestoreStubSettings build() throws IOException { return new FirestoreStubSettings(this); diff --git a/proto-google-cloud-firestore-v1/src/main/java/com/google/firestore/v1/Document.java b/proto-google-cloud-firestore-v1/src/main/java/com/google/firestore/v1/Document.java index 8932dabcf..ffee1bcd4 100644 --- a/proto-google-cloud-firestore-v1/src/main/java/com/google/firestore/v1/Document.java +++ b/proto-google-cloud-firestore-v1/src/main/java/com/google/firestore/v1/Document.java @@ -164,23 +164,23 @@ public int getFieldsCount() { * * The map keys represent field names. * - * A simple field name contains only characters `a` to `z`, `A` to `Z`, - * `0` to `9`, or `_`, and must not start with `0` to `9`. For example, - * `foo_bar_17`. - * * Field names matching the regular expression `__.*__` are reserved. Reserved - * field names are forbidden except in certain documented contexts. The map - * keys, represented as UTF-8, must not exceed 1,500 bytes and cannot be + * field names are forbidden except in certain documented contexts. The field + * names, represented as UTF-8, must not exceed 1,500 bytes and cannot be * empty. * * Field paths may be used in other contexts to refer to structured fields - * defined here. For `map_value`, the field path is represented by the simple - * or quoted field names of the containing fields, delimited by `.`. For - * example, the structured field - * `"foo" : { map_value: { "x&y" : { string_value: "hello" }}}` would be - * represented by the field path `foo.x&y`. + * defined here. For `map_value`, the field path is represented by a + * dot-delimited (`.`) string of segments. Each segment is either a simple + * field name (defined below) or a quoted field name. For example, the + * structured field `"foo" : { map_value: { "x&y" : { string_value: "hello" + * }}}` would be represented by the field path `` foo.`x&y` ``. * - * Within a field path, a quoted field name starts and ends with `` ` `` and + * A simple field name contains only characters `a` to `z`, `A` to `Z`, + * `0` to `9`, or `_`, and must not start with `0` to `9`. For example, + * `foo_bar_17`. + * + * A quoted field name starts and ends with `` ` `` and * may contain any character. Some characters, including `` ` ``, must be * escaped using a `\`. For example, `` `x&y` `` represents `x&y` and * `` `bak\`tik` `` represents `` bak`tik ``. @@ -209,23 +209,23 @@ public java.util.Map getFields( * * The map keys represent field names. * - * A simple field name contains only characters `a` to `z`, `A` to `Z`, - * `0` to `9`, or `_`, and must not start with `0` to `9`. For example, - * `foo_bar_17`. - * * Field names matching the regular expression `__.*__` are reserved. Reserved - * field names are forbidden except in certain documented contexts. The map - * keys, represented as UTF-8, must not exceed 1,500 bytes and cannot be + * field names are forbidden except in certain documented contexts. The field + * names, represented as UTF-8, must not exceed 1,500 bytes and cannot be * empty. * * Field paths may be used in other contexts to refer to structured fields - * defined here. For `map_value`, the field path is represented by the simple - * or quoted field names of the containing fields, delimited by `.`. For - * example, the structured field - * `"foo" : { map_value: { "x&y" : { string_value: "hello" }}}` would be - * represented by the field path `foo.x&y`. + * defined here. For `map_value`, the field path is represented by a + * dot-delimited (`.`) string of segments. Each segment is either a simple + * field name (defined below) or a quoted field name. For example, the + * structured field `"foo" : { map_value: { "x&y" : { string_value: "hello" + * }}}` would be represented by the field path `` foo.`x&y` ``. + * + * A simple field name contains only characters `a` to `z`, `A` to `Z`, + * `0` to `9`, or `_`, and must not start with `0` to `9`. For example, + * `foo_bar_17`. * - * Within a field path, a quoted field name starts and ends with `` ` `` and + * A quoted field name starts and ends with `` ` `` and * may contain any character. Some characters, including `` ` ``, must be * escaped using a `\`. For example, `` `x&y` `` represents `x&y` and * `` `bak\`tik` `` represents `` bak`tik ``. @@ -245,23 +245,23 @@ public java.util.Map getFieldsM * * The map keys represent field names. * - * A simple field name contains only characters `a` to `z`, `A` to `Z`, - * `0` to `9`, or `_`, and must not start with `0` to `9`. For example, - * `foo_bar_17`. - * * Field names matching the regular expression `__.*__` are reserved. Reserved - * field names are forbidden except in certain documented contexts. The map - * keys, represented as UTF-8, must not exceed 1,500 bytes and cannot be + * field names are forbidden except in certain documented contexts. The field + * names, represented as UTF-8, must not exceed 1,500 bytes and cannot be * empty. * * Field paths may be used in other contexts to refer to structured fields - * defined here. For `map_value`, the field path is represented by the simple - * or quoted field names of the containing fields, delimited by `.`. For - * example, the structured field - * `"foo" : { map_value: { "x&y" : { string_value: "hello" }}}` would be - * represented by the field path `foo.x&y`. + * defined here. For `map_value`, the field path is represented by a + * dot-delimited (`.`) string of segments. Each segment is either a simple + * field name (defined below) or a quoted field name. For example, the + * structured field `"foo" : { map_value: { "x&y" : { string_value: "hello" + * }}}` would be represented by the field path `` foo.`x&y` ``. + * + * A simple field name contains only characters `a` to `z`, `A` to `Z`, + * `0` to `9`, or `_`, and must not start with `0` to `9`. For example, + * `foo_bar_17`. * - * Within a field path, a quoted field name starts and ends with `` ` `` and + * A quoted field name starts and ends with `` ` `` and * may contain any character. Some characters, including `` ` ``, must be * escaped using a `\`. For example, `` `x&y` `` represents `x&y` and * `` `bak\`tik` `` represents `` bak`tik ``. @@ -289,23 +289,23 @@ public java.util.Map getFieldsM * * The map keys represent field names. * - * A simple field name contains only characters `a` to `z`, `A` to `Z`, - * `0` to `9`, or `_`, and must not start with `0` to `9`. For example, - * `foo_bar_17`. - * * Field names matching the regular expression `__.*__` are reserved. Reserved - * field names are forbidden except in certain documented contexts. The map - * keys, represented as UTF-8, must not exceed 1,500 bytes and cannot be + * field names are forbidden except in certain documented contexts. The field + * names, represented as UTF-8, must not exceed 1,500 bytes and cannot be * empty. * * Field paths may be used in other contexts to refer to structured fields - * defined here. For `map_value`, the field path is represented by the simple - * or quoted field names of the containing fields, delimited by `.`. For - * example, the structured field - * `"foo" : { map_value: { "x&y" : { string_value: "hello" }}}` would be - * represented by the field path `foo.x&y`. + * defined here. For `map_value`, the field path is represented by a + * dot-delimited (`.`) string of segments. Each segment is either a simple + * field name (defined below) or a quoted field name. For example, the + * structured field `"foo" : { map_value: { "x&y" : { string_value: "hello" + * }}}` would be represented by the field path `` foo.`x&y` ``. * - * Within a field path, a quoted field name starts and ends with `` ` `` and + * A simple field name contains only characters `a` to `z`, `A` to `Z`, + * `0` to `9`, or `_`, and must not start with `0` to `9`. For example, + * `foo_bar_17`. + * + * A quoted field name starts and ends with `` ` `` and * may contain any character. Some characters, including `` ` ``, must be * escaped using a `\`. For example, `` `x&y` `` represents `x&y` and * `` `bak\`tik` `` represents `` bak`tik ``. @@ -1046,23 +1046,23 @@ public int getFieldsCount() { * * The map keys represent field names. * - * A simple field name contains only characters `a` to `z`, `A` to `Z`, - * `0` to `9`, or `_`, and must not start with `0` to `9`. For example, - * `foo_bar_17`. - * * Field names matching the regular expression `__.*__` are reserved. Reserved - * field names are forbidden except in certain documented contexts. The map - * keys, represented as UTF-8, must not exceed 1,500 bytes and cannot be + * field names are forbidden except in certain documented contexts. The field + * names, represented as UTF-8, must not exceed 1,500 bytes and cannot be * empty. * * Field paths may be used in other contexts to refer to structured fields - * defined here. For `map_value`, the field path is represented by the simple - * or quoted field names of the containing fields, delimited by `.`. For - * example, the structured field - * `"foo" : { map_value: { "x&y" : { string_value: "hello" }}}` would be - * represented by the field path `foo.x&y`. + * defined here. For `map_value`, the field path is represented by a + * dot-delimited (`.`) string of segments. Each segment is either a simple + * field name (defined below) or a quoted field name. For example, the + * structured field `"foo" : { map_value: { "x&y" : { string_value: "hello" + * }}}` would be represented by the field path `` foo.`x&y` ``. * - * Within a field path, a quoted field name starts and ends with `` ` `` and + * A simple field name contains only characters `a` to `z`, `A` to `Z`, + * `0` to `9`, or `_`, and must not start with `0` to `9`. For example, + * `foo_bar_17`. + * + * A quoted field name starts and ends with `` ` `` and * may contain any character. Some characters, including `` ` ``, must be * escaped using a `\`. For example, `` `x&y` `` represents `x&y` and * `` `bak\`tik` `` represents `` bak`tik ``. @@ -1091,23 +1091,23 @@ public java.util.Map getFields( * * The map keys represent field names. * - * A simple field name contains only characters `a` to `z`, `A` to `Z`, - * `0` to `9`, or `_`, and must not start with `0` to `9`. For example, - * `foo_bar_17`. - * * Field names matching the regular expression `__.*__` are reserved. Reserved - * field names are forbidden except in certain documented contexts. The map - * keys, represented as UTF-8, must not exceed 1,500 bytes and cannot be + * field names are forbidden except in certain documented contexts. The field + * names, represented as UTF-8, must not exceed 1,500 bytes and cannot be * empty. * * Field paths may be used in other contexts to refer to structured fields - * defined here. For `map_value`, the field path is represented by the simple - * or quoted field names of the containing fields, delimited by `.`. For - * example, the structured field - * `"foo" : { map_value: { "x&y" : { string_value: "hello" }}}` would be - * represented by the field path `foo.x&y`. + * defined here. For `map_value`, the field path is represented by a + * dot-delimited (`.`) string of segments. Each segment is either a simple + * field name (defined below) or a quoted field name. For example, the + * structured field `"foo" : { map_value: { "x&y" : { string_value: "hello" + * }}}` would be represented by the field path `` foo.`x&y` ``. * - * Within a field path, a quoted field name starts and ends with `` ` `` and + * A simple field name contains only characters `a` to `z`, `A` to `Z`, + * `0` to `9`, or `_`, and must not start with `0` to `9`. For example, + * `foo_bar_17`. + * + * A quoted field name starts and ends with `` ` `` and * may contain any character. Some characters, including `` ` ``, must be * escaped using a `\`. For example, `` `x&y` `` represents `x&y` and * `` `bak\`tik` `` represents `` bak`tik ``. @@ -1127,23 +1127,23 @@ public java.util.Map getFieldsM * * The map keys represent field names. * - * A simple field name contains only characters `a` to `z`, `A` to `Z`, - * `0` to `9`, or `_`, and must not start with `0` to `9`. For example, - * `foo_bar_17`. - * * Field names matching the regular expression `__.*__` are reserved. Reserved - * field names are forbidden except in certain documented contexts. The map - * keys, represented as UTF-8, must not exceed 1,500 bytes and cannot be + * field names are forbidden except in certain documented contexts. The field + * names, represented as UTF-8, must not exceed 1,500 bytes and cannot be * empty. * * Field paths may be used in other contexts to refer to structured fields - * defined here. For `map_value`, the field path is represented by the simple - * or quoted field names of the containing fields, delimited by `.`. For - * example, the structured field - * `"foo" : { map_value: { "x&y" : { string_value: "hello" }}}` would be - * represented by the field path `foo.x&y`. + * defined here. For `map_value`, the field path is represented by a + * dot-delimited (`.`) string of segments. Each segment is either a simple + * field name (defined below) or a quoted field name. For example, the + * structured field `"foo" : { map_value: { "x&y" : { string_value: "hello" + * }}}` would be represented by the field path `` foo.`x&y` ``. + * + * A simple field name contains only characters `a` to `z`, `A` to `Z`, + * `0` to `9`, or `_`, and must not start with `0` to `9`. For example, + * `foo_bar_17`. * - * Within a field path, a quoted field name starts and ends with `` ` `` and + * A quoted field name starts and ends with `` ` `` and * may contain any character. Some characters, including `` ` ``, must be * escaped using a `\`. For example, `` `x&y` `` represents `x&y` and * `` `bak\`tik` `` represents `` bak`tik ``. @@ -1171,23 +1171,23 @@ public java.util.Map getFieldsM * * The map keys represent field names. * - * A simple field name contains only characters `a` to `z`, `A` to `Z`, - * `0` to `9`, or `_`, and must not start with `0` to `9`. For example, - * `foo_bar_17`. - * * Field names matching the regular expression `__.*__` are reserved. Reserved - * field names are forbidden except in certain documented contexts. The map - * keys, represented as UTF-8, must not exceed 1,500 bytes and cannot be + * field names are forbidden except in certain documented contexts. The field + * names, represented as UTF-8, must not exceed 1,500 bytes and cannot be * empty. * * Field paths may be used in other contexts to refer to structured fields - * defined here. For `map_value`, the field path is represented by the simple - * or quoted field names of the containing fields, delimited by `.`. For - * example, the structured field - * `"foo" : { map_value: { "x&y" : { string_value: "hello" }}}` would be - * represented by the field path `foo.x&y`. + * defined here. For `map_value`, the field path is represented by a + * dot-delimited (`.`) string of segments. Each segment is either a simple + * field name (defined below) or a quoted field name. For example, the + * structured field `"foo" : { map_value: { "x&y" : { string_value: "hello" + * }}}` would be represented by the field path `` foo.`x&y` ``. * - * Within a field path, a quoted field name starts and ends with `` ` `` and + * A simple field name contains only characters `a` to `z`, `A` to `Z`, + * `0` to `9`, or `_`, and must not start with `0` to `9`. For example, + * `foo_bar_17`. + * + * A quoted field name starts and ends with `` ` `` and * may contain any character. Some characters, including `` ` ``, must be * escaped using a `\`. For example, `` `x&y` `` represents `x&y` and * `` `bak\`tik` `` represents `` bak`tik ``. @@ -1221,23 +1221,23 @@ public Builder clearFields() { * * The map keys represent field names. * - * A simple field name contains only characters `a` to `z`, `A` to `Z`, - * `0` to `9`, or `_`, and must not start with `0` to `9`. For example, - * `foo_bar_17`. - * * Field names matching the regular expression `__.*__` are reserved. Reserved - * field names are forbidden except in certain documented contexts. The map - * keys, represented as UTF-8, must not exceed 1,500 bytes and cannot be + * field names are forbidden except in certain documented contexts. The field + * names, represented as UTF-8, must not exceed 1,500 bytes and cannot be * empty. * * Field paths may be used in other contexts to refer to structured fields - * defined here. For `map_value`, the field path is represented by the simple - * or quoted field names of the containing fields, delimited by `.`. For - * example, the structured field - * `"foo" : { map_value: { "x&y" : { string_value: "hello" }}}` would be - * represented by the field path `foo.x&y`. + * defined here. For `map_value`, the field path is represented by a + * dot-delimited (`.`) string of segments. Each segment is either a simple + * field name (defined below) or a quoted field name. For example, the + * structured field `"foo" : { map_value: { "x&y" : { string_value: "hello" + * }}}` would be represented by the field path `` foo.`x&y` ``. + * + * A simple field name contains only characters `a` to `z`, `A` to `Z`, + * `0` to `9`, or `_`, and must not start with `0` to `9`. For example, + * `foo_bar_17`. * - * Within a field path, a quoted field name starts and ends with `` ` `` and + * A quoted field name starts and ends with `` ` `` and * may contain any character. Some characters, including `` ` ``, must be * escaped using a `\`. For example, `` `x&y` `` represents `x&y` and * `` `bak\`tik` `` represents `` bak`tik ``. @@ -1266,23 +1266,23 @@ public java.util.Map getMutable * * The map keys represent field names. * - * A simple field name contains only characters `a` to `z`, `A` to `Z`, - * `0` to `9`, or `_`, and must not start with `0` to `9`. For example, - * `foo_bar_17`. - * * Field names matching the regular expression `__.*__` are reserved. Reserved - * field names are forbidden except in certain documented contexts. The map - * keys, represented as UTF-8, must not exceed 1,500 bytes and cannot be + * field names are forbidden except in certain documented contexts. The field + * names, represented as UTF-8, must not exceed 1,500 bytes and cannot be * empty. * * Field paths may be used in other contexts to refer to structured fields - * defined here. For `map_value`, the field path is represented by the simple - * or quoted field names of the containing fields, delimited by `.`. For - * example, the structured field - * `"foo" : { map_value: { "x&y" : { string_value: "hello" }}}` would be - * represented by the field path `foo.x&y`. + * defined here. For `map_value`, the field path is represented by a + * dot-delimited (`.`) string of segments. Each segment is either a simple + * field name (defined below) or a quoted field name. For example, the + * structured field `"foo" : { map_value: { "x&y" : { string_value: "hello" + * }}}` would be represented by the field path `` foo.`x&y` ``. + * + * A simple field name contains only characters `a` to `z`, `A` to `Z`, + * `0` to `9`, or `_`, and must not start with `0` to `9`. For example, + * `foo_bar_17`. * - * Within a field path, a quoted field name starts and ends with `` ` `` and + * A quoted field name starts and ends with `` ` `` and * may contain any character. Some characters, including `` ` ``, must be * escaped using a `\`. For example, `` `x&y` `` represents `x&y` and * `` `bak\`tik` `` represents `` bak`tik ``. @@ -1309,23 +1309,23 @@ public Builder putFields(java.lang.String key, com.google.firestore.v1.Value val * * The map keys represent field names. * - * A simple field name contains only characters `a` to `z`, `A` to `Z`, - * `0` to `9`, or `_`, and must not start with `0` to `9`. For example, - * `foo_bar_17`. - * * Field names matching the regular expression `__.*__` are reserved. Reserved - * field names are forbidden except in certain documented contexts. The map - * keys, represented as UTF-8, must not exceed 1,500 bytes and cannot be + * field names are forbidden except in certain documented contexts. The field + * names, represented as UTF-8, must not exceed 1,500 bytes and cannot be * empty. * * Field paths may be used in other contexts to refer to structured fields - * defined here. For `map_value`, the field path is represented by the simple - * or quoted field names of the containing fields, delimited by `.`. For - * example, the structured field - * `"foo" : { map_value: { "x&y" : { string_value: "hello" }}}` would be - * represented by the field path `foo.x&y`. + * defined here. For `map_value`, the field path is represented by a + * dot-delimited (`.`) string of segments. Each segment is either a simple + * field name (defined below) or a quoted field name. For example, the + * structured field `"foo" : { map_value: { "x&y" : { string_value: "hello" + * }}}` would be represented by the field path `` foo.`x&y` ``. + * + * A simple field name contains only characters `a` to `z`, `A` to `Z`, + * `0` to `9`, or `_`, and must not start with `0` to `9`. For example, + * `foo_bar_17`. * - * Within a field path, a quoted field name starts and ends with `` ` `` and + * A quoted field name starts and ends with `` ` `` and * may contain any character. Some characters, including `` ` ``, must be * escaped using a `\`. For example, `` `x&y` `` represents `x&y` and * `` `bak\`tik` `` represents `` bak`tik ``. diff --git a/proto-google-cloud-firestore-v1/src/main/java/com/google/firestore/v1/DocumentOrBuilder.java b/proto-google-cloud-firestore-v1/src/main/java/com/google/firestore/v1/DocumentOrBuilder.java index f04f0d82a..6e6874008 100644 --- a/proto-google-cloud-firestore-v1/src/main/java/com/google/firestore/v1/DocumentOrBuilder.java +++ b/proto-google-cloud-firestore-v1/src/main/java/com/google/firestore/v1/DocumentOrBuilder.java @@ -58,23 +58,23 @@ public interface DocumentOrBuilder * * The map keys represent field names. * - * A simple field name contains only characters `a` to `z`, `A` to `Z`, - * `0` to `9`, or `_`, and must not start with `0` to `9`. For example, - * `foo_bar_17`. - * * Field names matching the regular expression `__.*__` are reserved. Reserved - * field names are forbidden except in certain documented contexts. The map - * keys, represented as UTF-8, must not exceed 1,500 bytes and cannot be + * field names are forbidden except in certain documented contexts. The field + * names, represented as UTF-8, must not exceed 1,500 bytes and cannot be * empty. * * Field paths may be used in other contexts to refer to structured fields - * defined here. For `map_value`, the field path is represented by the simple - * or quoted field names of the containing fields, delimited by `.`. For - * example, the structured field - * `"foo" : { map_value: { "x&y" : { string_value: "hello" }}}` would be - * represented by the field path `foo.x&y`. + * defined here. For `map_value`, the field path is represented by a + * dot-delimited (`.`) string of segments. Each segment is either a simple + * field name (defined below) or a quoted field name. For example, the + * structured field `"foo" : { map_value: { "x&y" : { string_value: "hello" + * }}}` would be represented by the field path `` foo.`x&y` ``. + * + * A simple field name contains only characters `a` to `z`, `A` to `Z`, + * `0` to `9`, or `_`, and must not start with `0` to `9`. For example, + * `foo_bar_17`. * - * Within a field path, a quoted field name starts and ends with `` ` `` and + * A quoted field name starts and ends with `` ` `` and * may contain any character. Some characters, including `` ` ``, must be * escaped using a `\`. For example, `` `x&y` `` represents `x&y` and * `` `bak\`tik` `` represents `` bak`tik ``. @@ -91,23 +91,23 @@ public interface DocumentOrBuilder * * The map keys represent field names. * - * A simple field name contains only characters `a` to `z`, `A` to `Z`, - * `0` to `9`, or `_`, and must not start with `0` to `9`. For example, - * `foo_bar_17`. - * * Field names matching the regular expression `__.*__` are reserved. Reserved - * field names are forbidden except in certain documented contexts. The map - * keys, represented as UTF-8, must not exceed 1,500 bytes and cannot be + * field names are forbidden except in certain documented contexts. The field + * names, represented as UTF-8, must not exceed 1,500 bytes and cannot be * empty. * * Field paths may be used in other contexts to refer to structured fields - * defined here. For `map_value`, the field path is represented by the simple - * or quoted field names of the containing fields, delimited by `.`. For - * example, the structured field - * `"foo" : { map_value: { "x&y" : { string_value: "hello" }}}` would be - * represented by the field path `foo.x&y`. + * defined here. For `map_value`, the field path is represented by a + * dot-delimited (`.`) string of segments. Each segment is either a simple + * field name (defined below) or a quoted field name. For example, the + * structured field `"foo" : { map_value: { "x&y" : { string_value: "hello" + * }}}` would be represented by the field path `` foo.`x&y` ``. + * + * A simple field name contains only characters `a` to `z`, `A` to `Z`, + * `0` to `9`, or `_`, and must not start with `0` to `9`. For example, + * `foo_bar_17`. * - * Within a field path, a quoted field name starts and ends with `` ` `` and + * A quoted field name starts and ends with `` ` `` and * may contain any character. Some characters, including `` ` ``, must be * escaped using a `\`. For example, `` `x&y` `` represents `x&y` and * `` `bak\`tik` `` represents `` bak`tik ``. @@ -127,23 +127,23 @@ public interface DocumentOrBuilder * * The map keys represent field names. * - * A simple field name contains only characters `a` to `z`, `A` to `Z`, - * `0` to `9`, or `_`, and must not start with `0` to `9`. For example, - * `foo_bar_17`. - * * Field names matching the regular expression `__.*__` are reserved. Reserved - * field names are forbidden except in certain documented contexts. The map - * keys, represented as UTF-8, must not exceed 1,500 bytes and cannot be + * field names are forbidden except in certain documented contexts. The field + * names, represented as UTF-8, must not exceed 1,500 bytes and cannot be * empty. * * Field paths may be used in other contexts to refer to structured fields - * defined here. For `map_value`, the field path is represented by the simple - * or quoted field names of the containing fields, delimited by `.`. For - * example, the structured field - * `"foo" : { map_value: { "x&y" : { string_value: "hello" }}}` would be - * represented by the field path `foo.x&y`. + * defined here. For `map_value`, the field path is represented by a + * dot-delimited (`.`) string of segments. Each segment is either a simple + * field name (defined below) or a quoted field name. For example, the + * structured field `"foo" : { map_value: { "x&y" : { string_value: "hello" + * }}}` would be represented by the field path `` foo.`x&y` ``. * - * Within a field path, a quoted field name starts and ends with `` ` `` and + * A simple field name contains only characters `a` to `z`, `A` to `Z`, + * `0` to `9`, or `_`, and must not start with `0` to `9`. For example, + * `foo_bar_17`. + * + * A quoted field name starts and ends with `` ` `` and * may contain any character. Some characters, including `` ` ``, must be * escaped using a `\`. For example, `` `x&y` `` represents `x&y` and * `` `bak\`tik` `` represents `` bak`tik ``. @@ -160,23 +160,23 @@ public interface DocumentOrBuilder * * The map keys represent field names. * - * A simple field name contains only characters `a` to `z`, `A` to `Z`, - * `0` to `9`, or `_`, and must not start with `0` to `9`. For example, - * `foo_bar_17`. - * * Field names matching the regular expression `__.*__` are reserved. Reserved - * field names are forbidden except in certain documented contexts. The map - * keys, represented as UTF-8, must not exceed 1,500 bytes and cannot be + * field names are forbidden except in certain documented contexts. The field + * names, represented as UTF-8, must not exceed 1,500 bytes and cannot be * empty. * * Field paths may be used in other contexts to refer to structured fields - * defined here. For `map_value`, the field path is represented by the simple - * or quoted field names of the containing fields, delimited by `.`. For - * example, the structured field - * `"foo" : { map_value: { "x&y" : { string_value: "hello" }}}` would be - * represented by the field path `foo.x&y`. + * defined here. For `map_value`, the field path is represented by a + * dot-delimited (`.`) string of segments. Each segment is either a simple + * field name (defined below) or a quoted field name. For example, the + * structured field `"foo" : { map_value: { "x&y" : { string_value: "hello" + * }}}` would be represented by the field path `` foo.`x&y` ``. + * + * A simple field name contains only characters `a` to `z`, `A` to `Z`, + * `0` to `9`, or `_`, and must not start with `0` to `9`. For example, + * `foo_bar_17`. * - * Within a field path, a quoted field name starts and ends with `` ` `` and + * A quoted field name starts and ends with `` ` `` and * may contain any character. Some characters, including `` ` ``, must be * escaped using a `\`. For example, `` `x&y` `` represents `x&y` and * `` `bak\`tik` `` represents `` bak`tik ``. @@ -197,23 +197,23 @@ com.google.firestore.v1.Value getFieldsOrDefault( * * The map keys represent field names. * - * A simple field name contains only characters `a` to `z`, `A` to `Z`, - * `0` to `9`, or `_`, and must not start with `0` to `9`. For example, - * `foo_bar_17`. - * * Field names matching the regular expression `__.*__` are reserved. Reserved - * field names are forbidden except in certain documented contexts. The map - * keys, represented as UTF-8, must not exceed 1,500 bytes and cannot be + * field names are forbidden except in certain documented contexts. The field + * names, represented as UTF-8, must not exceed 1,500 bytes and cannot be * empty. * * Field paths may be used in other contexts to refer to structured fields - * defined here. For `map_value`, the field path is represented by the simple - * or quoted field names of the containing fields, delimited by `.`. For - * example, the structured field - * `"foo" : { map_value: { "x&y" : { string_value: "hello" }}}` would be - * represented by the field path `foo.x&y`. + * defined here. For `map_value`, the field path is represented by a + * dot-delimited (`.`) string of segments. Each segment is either a simple + * field name (defined below) or a quoted field name. For example, the + * structured field `"foo" : { map_value: { "x&y" : { string_value: "hello" + * }}}` would be represented by the field path `` foo.`x&y` ``. + * + * A simple field name contains only characters `a` to `z`, `A` to `Z`, + * `0` to `9`, or `_`, and must not start with `0` to `9`. For example, + * `foo_bar_17`. * - * Within a field path, a quoted field name starts and ends with `` ` `` and + * A quoted field name starts and ends with `` ` `` and * may contain any character. Some characters, including `` ` ``, must be * escaped using a `\`. For example, `` `x&y` `` represents `x&y` and * `` `bak\`tik` `` represents `` bak`tik ``. diff --git a/proto-google-cloud-firestore-v1/src/main/java/com/google/firestore/v1/StructuredQuery.java b/proto-google-cloud-firestore-v1/src/main/java/com/google/firestore/v1/StructuredQuery.java index 2fdf3d3a3..d2d0b7869 100644 --- a/proto-google-cloud-firestore-v1/src/main/java/com/google/firestore/v1/StructuredQuery.java +++ b/proto-google-cloud-firestore-v1/src/main/java/com/google/firestore/v1/StructuredQuery.java @@ -23,6 +23,14 @@ * *
  * A Firestore query.
+ *
+ * The query stages are executed in the following order:
+ * 1. from
+ * 2. where
+ * 3. select
+ * 4. order_by + start_at + end_at
+ * 5. offset
+ * 6. limit
  * 
* * Protobuf type {@code google.firestore.v1.StructuredQuery} @@ -10417,6 +10425,14 @@ protected Builder newBuilderForType(com.google.protobuf.GeneratedMessageV3.Build * *
    * A Firestore query.
+   *
+   * The query stages are executed in the following order:
+   * 1. from
+   * 2. where
+   * 3. select
+   * 4. order_by + start_at + end_at
+   * 5. offset
+   * 6. limit
    * 
* * Protobuf type {@code google.firestore.v1.StructuredQuery} diff --git a/proto-google-cloud-firestore-v1/src/main/proto/google/firestore/v1/document.proto b/proto-google-cloud-firestore-v1/src/main/proto/google/firestore/v1/document.proto index 795200498..a8764a1a2 100644 --- a/proto-google-cloud-firestore-v1/src/main/proto/google/firestore/v1/document.proto +++ b/proto-google-cloud-firestore-v1/src/main/proto/google/firestore/v1/document.proto @@ -41,23 +41,23 @@ message Document { // // The map keys represent field names. // - // A simple field name contains only characters `a` to `z`, `A` to `Z`, - // `0` to `9`, or `_`, and must not start with `0` to `9`. For example, - // `foo_bar_17`. - // // Field names matching the regular expression `__.*__` are reserved. Reserved - // field names are forbidden except in certain documented contexts. The map - // keys, represented as UTF-8, must not exceed 1,500 bytes and cannot be + // field names are forbidden except in certain documented contexts. The field + // names, represented as UTF-8, must not exceed 1,500 bytes and cannot be // empty. // // Field paths may be used in other contexts to refer to structured fields - // defined here. For `map_value`, the field path is represented by the simple - // or quoted field names of the containing fields, delimited by `.`. For - // example, the structured field - // `"foo" : { map_value: { "x&y" : { string_value: "hello" }}}` would be - // represented by the field path `foo.x&y`. + // defined here. For `map_value`, the field path is represented by a + // dot-delimited (`.`) string of segments. Each segment is either a simple + // field name (defined below) or a quoted field name. For example, the + // structured field `"foo" : { map_value: { "x&y" : { string_value: "hello" + // }}}` would be represented by the field path `` foo.`x&y` ``. + // + // A simple field name contains only characters `a` to `z`, `A` to `Z`, + // `0` to `9`, or `_`, and must not start with `0` to `9`. For example, + // `foo_bar_17`. // - // Within a field path, a quoted field name starts and ends with `` ` `` and + // A quoted field name starts and ends with `` ` `` and // may contain any character. Some characters, including `` ` ``, must be // escaped using a `\`. For example, `` `x&y` `` represents `x&y` and // `` `bak\`tik` `` represents `` bak`tik ``. diff --git a/proto-google-cloud-firestore-v1/src/main/proto/google/firestore/v1/query.proto b/proto-google-cloud-firestore-v1/src/main/proto/google/firestore/v1/query.proto index b7d01c24e..09eefa241 100644 --- a/proto-google-cloud-firestore-v1/src/main/proto/google/firestore/v1/query.proto +++ b/proto-google-cloud-firestore-v1/src/main/proto/google/firestore/v1/query.proto @@ -30,6 +30,14 @@ option php_namespace = "Google\\Cloud\\Firestore\\V1"; option ruby_package = "Google::Cloud::Firestore::V1"; // A Firestore query. +// +// The query stages are executed in the following order: +// 1. from +// 2. where +// 3. select +// 4. order_by + start_at + end_at +// 5. offset +// 6. limit message StructuredQuery { // A selection of a collection, such as `messages as m1`. message CollectionSelector { From bcc366d67f396d9ccf660b6f330ad773eadf3c31 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Mon, 22 Jan 2024 19:52:23 +0100 Subject: [PATCH 15/20] test(deps): update dependency com.google.truth:truth to v1.3.0 (#1538) --- pom.xml | 2 +- samples/install-without-bom/pom.xml | 2 +- samples/native-image-sample/pom.xml | 2 +- samples/snapshot/pom.xml | 2 +- samples/snippets/pom.xml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 951ddd45c..4b6db78de 100644 --- a/pom.xml +++ b/pom.xml @@ -195,7 +195,7 @@ com.google.truth truth - 1.1.5 + 1.3.0 test diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml index 7956af70d..b98bd6b25 100644 --- a/samples/install-without-bom/pom.xml +++ b/samples/install-without-bom/pom.xml @@ -55,7 +55,7 @@ com.google.truth truth - 1.1.5 + 1.3.0 test diff --git a/samples/native-image-sample/pom.xml b/samples/native-image-sample/pom.xml index e5dff9f36..224b9a932 100644 --- a/samples/native-image-sample/pom.xml +++ b/samples/native-image-sample/pom.xml @@ -66,7 +66,7 @@ http://maven.apache.org/xsd/maven-4.0.0.xsd"> com.google.truth truth - 1.1.5 + 1.3.0 test diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml index bef2afa26..345b1109e 100644 --- a/samples/snapshot/pom.xml +++ b/samples/snapshot/pom.xml @@ -41,7 +41,7 @@ com.google.truth truth - 1.1.5 + 1.3.0 test diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml index e3e38aadd..94f1f32b4 100644 --- a/samples/snippets/pom.xml +++ b/samples/snippets/pom.xml @@ -69,7 +69,7 @@ com.google.truth truth - 1.1.5 + 1.3.0 test From 396c15ff27d9a68ef6ab9a777eedfd986c5c4513 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Tue, 23 Jan 2024 14:14:22 -0500 Subject: [PATCH 16/20] make some methods static: applyFieldMask() and convertToFieldPaths() --- .../main/java/com/google/cloud/firestore/UpdateBuilder.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java index 6f1166866..71649897c 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java @@ -304,7 +304,7 @@ private T writesAdd(WriteOperation operation) { } /** Removes all values in 'fields' that are not specified in 'fieldMask'. */ - private Map applyFieldMask( + private static Map applyFieldMask( Map fields, List fieldMask) { List remainingFields = new ArrayList<>(fieldMask); Map filteredData = @@ -324,7 +324,7 @@ private Map applyFieldMask( * Strips all values in 'fields' that are not specified in 'fieldMask'. Modifies 'fieldMask' * inline and removes all matched fields. */ - private Map applyFieldMask( + private static Map applyFieldMask( Map fields, List fieldMask, FieldPath root) { Map filteredMap = new HashMap<>(); @@ -345,7 +345,7 @@ private Map applyFieldMask( return filteredMap; } - private Map convertToFieldPaths( + private static Map convertToFieldPaths( @Nonnull Map fields, boolean splitOnDots) { Map fieldPaths = new HashMap<>(); From e1c701ae3dd0669c39dd6be1455c6342fef1cf74 Mon Sep 17 00:00:00 2001 From: Tom Andersen Date: Tue, 23 Jan 2024 14:17:37 -0500 Subject: [PATCH 17/20] Comment --- .../java/com/google/cloud/firestore/UpdateBuilder.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java index 71649897c..becd0dafd 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java @@ -622,10 +622,10 @@ ApiFuture> commit(@Nullable ByteString transactionId) { // Step 2 uses `forEach(..)` that is synchronized, therefore will be blocked // until any writes are complete. // - // Writes will `verifyNotCommit()` within synchronized block of code before - // appending writes. Since committed is set to true before accessing writes, - // we are ensured that no more writes will be appended after commit accesses - // writes. + // Writes will verify `committed==false` within synchronized block of code + // before appending writes. Since committed is set to true before accessing + // writes, we are ensured that no more writes will be appended after commit + // accesses writes. committed = true; CommitRequest request = buildCommitRequest(transactionId); From b54f0d0974a5c840b87a243c8ff2152722cc3477 Mon Sep 17 00:00:00 2001 From: Tom Andersen Date: Tue, 23 Jan 2024 14:27:47 -0500 Subject: [PATCH 18/20] Inline --- .../main/java/com/google/cloud/firestore/UpdateBuilder.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java index becd0dafd..8610163d4 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java @@ -74,10 +74,6 @@ public String toString() { protected volatile boolean committed; - boolean isCommitted() { - return committed; - } - UpdateBuilder(FirestoreImpl firestore) { this.firestore = firestore; } @@ -293,7 +289,7 @@ private T writesAdd(WriteOperation operation) { int writeIndex; synchronized (writes) { Preconditions.checkState( - !isCommitted(), + !committed, String.format( "Cannot modify a %s that has already been committed.", this.getClass().getSimpleName())); From f6cc7180953e99dedda0fb555526d538f9b0991f Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Tue, 23 Jan 2024 15:09:18 -0500 Subject: [PATCH 19/20] use explicit synchronization --- .../google/cloud/firestore/UpdateBuilder.java | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java index 8610163d4..f54e7a715 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java @@ -34,7 +34,6 @@ import io.opencensus.trace.AttributeValue; import io.opencensus.trace.Tracing; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -69,8 +68,9 @@ public String toString() { final FirestoreImpl firestore; - // This class can have writes added from multiple threads. - private final List writes = Collections.synchronizedList(new ArrayList<>()); + // All reads and writes on `writes` must be done in a block that is synchronized on `writes`; + // otherwise, you get undefined behavior. + private final List writes = new ArrayList<>(); protected volatile boolean committed; @@ -658,7 +658,9 @@ private CommitRequest buildCommitRequest(ByteString transactionId) { /** Checks whether any updates have been queued. */ boolean isEmpty() { - return writes.isEmpty(); + synchronized (writes) { + return writes.isEmpty(); + } } void forEach(Consumer consumer) { @@ -671,12 +673,19 @@ void forEach(Consumer consumer) { /** Get the number of writes. */ public int getMutationsSize() { - return writes.size(); + synchronized (writes) { + return writes.size(); + } } @Override public String toString() { + final String writesAsString; + synchronized (writes) { + writesAsString = writes.toString(); + } + return String.format( - "%s{writes=%s, committed=%s}", getClass().getSimpleName(), writes, committed); + "%s{writes=%s, committed=%s}", getClass().getSimpleName(), writesAsString, committed); } } From a8856960aa157b2ee7f0917b7fe49aecc4c960ba Mon Sep 17 00:00:00 2001 From: Tom Andersen Date: Wed, 24 Jan 2024 11:30:58 -0500 Subject: [PATCH 20/20] Review feedback --- .../cloud/firestore/BulkCommitBatch.java | 10 +++---- .../google/cloud/firestore/UpdateBuilder.java | 27 ++++++++++--------- .../google/cloud/firestore/ToStringTest.java | 3 ++- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/BulkCommitBatch.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/BulkCommitBatch.java index f9f7d7637..fd5e9ba26 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/BulkCommitBatch.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/BulkCommitBatch.java @@ -88,18 +88,18 @@ ApiFuture bulkCommit() { return ApiFutures.transformAsync( response, batchWriteResponse -> { + List> pendingUserCallbacks = new ArrayList<>(); + List writeResults = batchWriteResponse.getWriteResultsList(); List statuses = batchWriteResponse.getStatusList(); - int size = writeResults.size(); - List> pendingUserCallbacks = new ArrayList<>(size); - for (int i = 0; i < size; ++i) { + for (int i = 0; i < writeResults.size(); ++i) { + com.google.firestore.v1.WriteResult writeResult = writeResults.get(i); com.google.rpc.Status status = statuses.get(i); BulkWriterOperation operation = pendingOperations.get(i); Status code = Status.fromCodeValue(status.getCode()); if (code == Status.OK) { - com.google.firestore.v1.WriteResult writeResult = writeResults.get(i); pendingUserCallbacks.add( operation.onSuccess( new WriteResult(Timestamp.fromProto(writeResult.getUpdateTime())))); @@ -117,7 +117,7 @@ ApiFuture bulkCommit() { private BatchWriteRequest buildBatchWriteRequest() { BatchWriteRequest.Builder builder = BatchWriteRequest.newBuilder(); builder.setDatabase(firestore.getDatabaseName()); - forEach(builder::addWrites); + forEachWrite(builder::addWrites); return builder.build(); } diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java index f54e7a715..4e22c8a12 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java @@ -35,9 +35,11 @@ import io.opencensus.trace.Tracing; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.function.Consumer; @@ -55,9 +57,9 @@ static final class WriteOperation { final Write write; final DocumentReference documentReference; - WriteOperation(DocumentReference documentReference, Write.Builder write) { + WriteOperation(DocumentReference documentReference, Write write) { this.documentReference = documentReference; - this.write = write.build(); + this.write = write; } @Override @@ -157,7 +159,7 @@ private T performCreate( write.addAllUpdateTransforms(documentTransform.toPb()); } - return writesAdd(new WriteOperation(documentReference, write)); + return addWrite(documentReference, write); } /** @@ -282,10 +284,11 @@ private T performSet( write.setUpdateMask(documentMask.toPb()); } - return writesAdd(new WriteOperation(documentReference, write)); + return addWrite(documentReference, write); } - private T writesAdd(WriteOperation operation) { + private T addWrite(DocumentReference documentReference, Write.Builder write) { + WriteOperation operation = new WriteOperation(documentReference, write.build()); int writeIndex; synchronized (writes) { Preconditions.checkState( @@ -302,7 +305,7 @@ private T writesAdd(WriteOperation operation) { /** Removes all values in 'fields' that are not specified in 'fieldMask'. */ private static Map applyFieldMask( Map fields, List fieldMask) { - List remainingFields = new ArrayList<>(fieldMask); + Set remainingFields = new HashSet<>(fieldMask); Map filteredData = applyFieldMask(fields, remainingFields, FieldPath.empty()); @@ -310,7 +313,7 @@ private static Map applyFieldMask( throw new IllegalArgumentException( String.format( "Field masks contains invalid path. No data exist at field '%s'.", - remainingFields.get(0))); + remainingFields.iterator().next())); } return filteredData; @@ -321,7 +324,7 @@ private static Map applyFieldMask( * inline and removes all matched fields. */ private static Map applyFieldMask( - Map fields, List fieldMask, FieldPath root) { + Map fields, Set fieldMask, FieldPath root) { Map filteredMap = new HashMap<>(); for (Entry entry : fields.entrySet()) { @@ -565,7 +568,7 @@ public boolean allowTransform() { write.addAllUpdateTransforms(documentTransform.toPb()); } - return writesAdd(new WriteOperation(documentReference, write)); + return addWrite(documentReference, write); } /** @@ -601,7 +604,7 @@ private T performDelete( write.setCurrentDocument(precondition.toPb()); } - return writesAdd(new WriteOperation(documentReference, write)); + return addWrite(documentReference, write); } /** Commit the current batch. */ @@ -649,7 +652,7 @@ ApiFuture> commit(@Nullable ByteString transactionId) { private CommitRequest buildCommitRequest(ByteString transactionId) { CommitRequest.Builder builder = CommitRequest.newBuilder(); builder.setDatabase(firestore.getDatabaseName()); - forEach(builder::addWrites); + forEachWrite(builder::addWrites); if (transactionId != null) { builder.setTransaction(transactionId); } @@ -663,7 +666,7 @@ boolean isEmpty() { } } - void forEach(Consumer consumer) { + void forEachWrite(Consumer consumer) { synchronized (writes) { for (WriteOperation writeOperation : writes) { consumer.accept(writeOperation.write); diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/ToStringTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/ToStringTest.java index 9dca2518e..6779edd18 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/ToStringTest.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/ToStringTest.java @@ -86,7 +86,8 @@ public void testWriteOperation() { documentReference, Collections.singletonMap("key", "value"), UserDataConverter.NO_DELETES) - .toPb()) + .toPb() + .build()) .toString(); assertThat(toStringResult).startsWith("WriteOperation{"); assertThat(toStringResult)