diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/Pipeline.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/Pipeline.java index c8cb17cc0..4ca0f19b3 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/Pipeline.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/Pipeline.java @@ -25,6 +25,9 @@ import com.google.api.gax.rpc.ResponseObserver; import com.google.api.gax.rpc.StreamController; import com.google.cloud.Timestamp; +import com.google.cloud.firestore.pipeline.stages.AggregateOptions; +import com.google.cloud.firestore.pipeline.stages.PipelineOptions; +import com.google.cloud.firestore.pipeline.stages.GenericOptions; import com.google.cloud.firestore.pipeline.expressions.Accumulator; import com.google.cloud.firestore.pipeline.expressions.Expr; import com.google.cloud.firestore.pipeline.expressions.ExprWithAlias; @@ -44,7 +47,6 @@ import com.google.cloud.firestore.pipeline.stages.RemoveFields; import com.google.cloud.firestore.pipeline.stages.Replace; import com.google.cloud.firestore.pipeline.stages.Sample; -import com.google.cloud.firestore.pipeline.stages.SampleOptions; import com.google.cloud.firestore.pipeline.stages.Select; import com.google.cloud.firestore.pipeline.stages.Sort; import com.google.cloud.firestore.pipeline.stages.Stage; @@ -69,6 +71,7 @@ import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; +import javax.annotation.Nonnull; import javax.annotation.Nullable; /** @@ -502,12 +505,10 @@ public Pipeline distinct(Selectable... selectables) { *
{@code
    * // Find books with similar "topicVectors" to the given targetVector
    * firestore.pipeline().collection("books")
-   *     .findNearest("topicVectors", targetVector, FindNearest.DistanceMeasure.cosine(),
-   *        FindNearestOptions
-   *          .builder()
-   *          .limit(10)
-   *          .distanceField("distance")
-   *          .build());
+   *     .findNearest("topicVectors", targetVector, FindNearest.DistanceMeasure.COSINE,
+   *        FindNearestOptions.DEFAULT
+   *          .withLimit(10)
+   *          .withDistanceField("distance"));
    * }
* * @param fieldName The name of the field containing the vector data. This field should store @@ -540,12 +541,11 @@ public Pipeline findNearest( *
{@code
    * // Find books with similar "topicVectors" to the given targetVector
    * firestore.pipeline().collection("books")
-   *     .findNearest(Field.of("topicVectors"), targetVector, FindNearest.DistanceMeasure.cosine(),
-   *        FindNearestOptions
-   *          .builder()
-   *          .limit(10)
-   *          .distanceField("distance")
-   *          .build());
+   *     .findNearest(
+   *        FindNearest.of(Field.of("topicVectors"), targetVector, FindNearest.DistanceMeasure.COSINE),
+   *        FindNearestOptions.DEFAULT
+   *          .withLimit(10)
+   *          .withDistanceField("distance"));
    * }
* * @param property The expression that evaluates to a vector value using the stage inputs. @@ -590,7 +590,7 @@ public Pipeline findNearest( */ @BetaApi public Pipeline sort(Ordering... orders) { - return append(new Sort(orders)); + return append(new Sort(ImmutableList.copyOf(orders))); } /** @@ -683,8 +683,7 @@ public Pipeline replace(Selectable field) { */ @BetaApi public Pipeline sample(int limit) { - SampleOptions options = SampleOptions.docLimit(limit); - return sample(options); + return sample(Sample.withDocLimit(limit)); } /** @@ -698,19 +697,19 @@ public Pipeline sample(int limit) { *
{@code
    * // Sample 10 books, if available.
    * firestore.pipeline().collection("books")
-   *     .sample(SampleOptions.docLimit(10));
+   *     .sample(Sample.withDocLimit(10));
    *
    * // Sample 50% of books.
    * firestore.pipeline().collection("books")
-   *     .sample(SampleOptions.percentage(0.5));
+   *     .sample(Sample.withPercentage(0.5));
    * }
* - * @param options The {@code SampleOptions} specifies how sampling is performed. + * @param sample The {@code Sample} specifies how sampling is performed. * @return A new {@code Pipeline} object with this stage appended to the stage list. */ @BetaApi - public Pipeline sample(SampleOptions options) { - return append(new Sample(options)); + public Pipeline sample(Sample sample) { + return append(sample); } /** @@ -756,21 +755,21 @@ public Pipeline union(Pipeline other) { * * // Emit a book document for each tag of the book. * firestore.pipeline().collection("books") - * .unnest("tags"); + * .unnest("tags", "tag"); * * // Output: - * // { "title": "The Hitchhiker's Guide to the Galaxy", "tags": "comedy", ... } - * // { "title": "The Hitchhiker's Guide to the Galaxy", "tags": "space", ... } - * // { "title": "The Hitchhiker's Guide to the Galaxy", "tags": "adventure", ... } + * // { "title": "The Hitchhiker's Guide to the Galaxy", "tag": "comedy", ... } + * // { "title": "The Hitchhiker's Guide to the Galaxy", "tag": "space", ... } + * // { "title": "The Hitchhiker's Guide to the Galaxy", "tag": "adventure", ... } * } * * @param fieldName The name of the field containing the array. * @return A new {@code Pipeline} object with this stage appended to the stage list. */ @BetaApi - public Pipeline unnest(String fieldName) { + public Pipeline unnest(String fieldName, String alias) { // return unnest(Field.of(fieldName)); - return append(new Unnest(Field.of(fieldName))); + return append(new Unnest(Field.of(fieldName), alias)); } // /** @@ -829,12 +828,12 @@ public Pipeline unnest(String fieldName) { * * // Emit a book document for each tag of the book. * firestore.pipeline().collection("books") - * .unnest("tags", UnnestOptions.indexField("tagIndex")); + * .unnest("tags", "tag", Unnest.Options.DEFAULT.withIndexField("tagIndex")); * * // Output: - * // { "title": "The Hitchhiker's Guide to the Galaxy", "tagIndex": 0, "tags": "comedy", ... } - * // { "title": "The Hitchhiker's Guide to the Galaxy", "tagIndex": 1, "tags": "space", ... } - * // { "title": "The Hitchhiker's Guide to the Galaxy", "tagIndex": 2, "tags": "adventure", ... } + * // { "title": "The Hitchhiker's Guide to the Galaxy", "tagIndex": 0, "tag": "comedy", ... } + * // { "title": "The Hitchhiker's Guide to the Galaxy", "tagIndex": 1, "tag": "space", ... } + * // { "title": "The Hitchhiker's Guide to the Galaxy", "tagIndex": 2, "tag": "adventure", ... } * } * * @param fieldName The name of the field containing the array. @@ -842,9 +841,9 @@ public Pipeline unnest(String fieldName) { * @return A new {@code Pipeline} object with this stage appended to the stage list. */ @BetaApi - public Pipeline unnest(String fieldName, UnnestOptions options) { + public Pipeline unnest(String fieldName, String alias, UnnestOptions options) { // return unnest(Field.of(fieldName), options); - return append(new Unnest(Field.of(fieldName), options)); + return append(new Unnest(Field.of(fieldName), alias, options)); } // /** @@ -905,12 +904,13 @@ public Pipeline unnest(String fieldName, UnnestOptions options) { * * @param name The unique name of the generic stage to add. * @param params A map of parameters to configure the generic stage's behavior. + * @param optionalParams Named optional parameters to configure the generic stage's behavior. * @return A new {@code Pipeline} object with this stage appended to the stage list. */ @BetaApi - public Pipeline genericStage(String name, List params) { + public Pipeline genericStage(String name, List params, GenericOptions optionalParams) { // Implementation for genericStage (add the GenericStage if needed) - return append(new GenericStage(name, params)); // Assuming GenericStage takes a list of params + return append(new GenericStage(name, params, optionalParams)); // Assuming GenericStage takes a list of params } /** @@ -946,7 +946,12 @@ public Pipeline genericStage(String name, List params) { */ @BetaApi public ApiFuture> execute() { - return execute((ByteString) null, (com.google.protobuf.Timestamp) null); + return execute(PipelineOptions.DEFAULT, (ByteString) null, (com.google.protobuf.Timestamp) null); + } + + @BetaApi + public ApiFuture> execute(PipelineOptions options) { + return execute(options, (ByteString) null, (com.google.protobuf.Timestamp) null); } /** @@ -996,7 +1001,7 @@ public ApiFuture> execute() { */ @BetaApi public void execute(ApiStreamObserver observer) { - executeInternal(null, null, observer); + executeInternal(PipelineOptions.DEFAULT, null, null, observer); } // @BetaApi @@ -1016,10 +1021,13 @@ public void execute(ApiStreamObserver observer) { // } ApiFuture> execute( - @Nullable final ByteString transactionId, @Nullable com.google.protobuf.Timestamp readTime) { + @Nonnull PipelineOptions options, + @Nullable final ByteString transactionId, + @Nullable com.google.protobuf.Timestamp readTime) { SettableApiFuture> futureResult = SettableApiFuture.create(); executeInternal( + options, transactionId, readTime, new PipelineResultObserver() { @@ -1045,13 +1053,17 @@ public void onError(Throwable t) { } void executeInternal( + @Nonnull PipelineOptions options, @Nullable final ByteString transactionId, @Nullable com.google.protobuf.Timestamp readTime, ApiStreamObserver observer) { ExecutePipelineRequest.Builder request = ExecutePipelineRequest.newBuilder() .setDatabase(rpcContext.getDatabaseName()) - .setStructuredPipeline(StructuredPipeline.newBuilder().setPipeline(toProto()).build()); + .setStructuredPipeline(StructuredPipeline.newBuilder() + .setPipeline(toProto()) + .putAllOptions(StageUtils.toMap(options)) + .build()); if (transactionId != null) { request.setTransaction(transactionId); @@ -1164,18 +1176,18 @@ public void onComplete() { rpcContext.streamRequest(request, observer, rpcContext.getClient().executePipelineCallable()); } -} -@InternalExtensionOnly -abstract class PipelineResultObserver implements ApiStreamObserver { - private Timestamp executionTime; // Remove optional since Java doesn't have it + @InternalExtensionOnly + static abstract class PipelineResultObserver implements ApiStreamObserver { + private Timestamp executionTime; // Remove optional since Java doesn't have it - public void onCompleted(Timestamp executionTime) { - this.executionTime = executionTime; - this.onCompleted(); - } + public void onCompleted(Timestamp executionTime) { + this.executionTime = executionTime; + this.onCompleted(); + } - public Timestamp getExecutionTime() { // Add getter for executionTime - return executionTime; + public Timestamp getExecutionTime() { // Add getter for executionTime + return executionTime; + } } } diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/PipelineSource.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/PipelineSource.java index d2fb06d30..18fbeff93 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/PipelineSource.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/PipelineSource.java @@ -20,6 +20,9 @@ import com.google.api.core.InternalApi; import com.google.cloud.firestore.pipeline.stages.Collection; import com.google.cloud.firestore.pipeline.stages.CollectionGroup; +import com.google.cloud.firestore.pipeline.stages.CollectionGroupOptions; +import com.google.cloud.firestore.pipeline.stages.CollectionHints; +import com.google.cloud.firestore.pipeline.stages.CollectionOptions; import com.google.cloud.firestore.pipeline.stages.Database; import com.google.cloud.firestore.pipeline.stages.Documents; import com.google.common.base.Preconditions; @@ -45,7 +48,7 @@ * } */ @BetaApi -public class PipelineSource { +public final class PipelineSource { private final FirestoreRpcContext rpcContext; @InternalApi @@ -62,7 +65,13 @@ public class PipelineSource { @Nonnull @BetaApi public Pipeline collection(@Nonnull String path) { - return new Pipeline(this.rpcContext, new Collection(path)); + return collection(path, CollectionOptions.DEFAULT); + } + + @Nonnull + @BetaApi + public Pipeline collection(@Nonnull String path, CollectionOptions options) { + return new Pipeline(this.rpcContext, new Collection(path, options)); } /** @@ -78,11 +87,17 @@ public Pipeline collection(@Nonnull String path) { @Nonnull @BetaApi public Pipeline collectionGroup(@Nonnull String collectionId) { + return collectionGroup(collectionId, CollectionGroupOptions.DEFAULT); + } + + @Nonnull + @BetaApi + public Pipeline collectionGroup(@Nonnull String collectionId, CollectionGroupOptions options) { Preconditions.checkArgument( !collectionId.contains("/"), "Invalid collectionId '%s'. Collection IDs must not contain '/'.", collectionId); - return new Pipeline(this.rpcContext, new CollectionGroup(collectionId)); + return new Pipeline(this.rpcContext, new CollectionGroup(collectionId, options)); } /** diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/PipelineUtils.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/PipelineUtils.java index 2b12c6e90..fa88e79f0 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/PipelineUtils.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/PipelineUtils.java @@ -22,6 +22,7 @@ import static com.google.cloud.firestore.pipeline.expressions.Function.inAny; import static com.google.cloud.firestore.pipeline.expressions.Function.not; import static com.google.cloud.firestore.pipeline.expressions.Function.or; +import static com.google.cloud.firestore.pipeline.expressions.FunctionUtils.exprToValue; import com.google.api.core.InternalApi; import com.google.cloud.firestore.Query.ComparisonFilterInternal; @@ -38,6 +39,7 @@ import com.google.cloud.firestore.pipeline.expressions.Selectable; import com.google.common.collect.Lists; import com.google.firestore.v1.Cursor; +import com.google.firestore.v1.MapValue; import com.google.firestore.v1.Value; import java.util.HashMap; import java.util.List; @@ -51,6 +53,36 @@ public static Value encodeValue(Object value) { return UserDataConverter.encodeValue(FieldPath.empty(), value, UserDataConverter.ARGUMENT); } + @InternalApi + public static Value encodeValue(Expr value) { + return exprToValue(value); + } + + @InternalApi + public static Value encodeValue(String value) { + return Value.newBuilder().setStringValue(value).build(); + } + + @InternalApi + public static Value encodeValue(boolean value) { + return Value.newBuilder().setBooleanValue(value).build(); + } + + @InternalApi + public static Value encodeValue(long value) { + return Value.newBuilder().setIntegerValue(value).build(); + } + + @InternalApi + public static Value encodeValue(double value) { + return Value.newBuilder().setDoubleValue(value).build(); + } + + @InternalApi + public static Value encodeValue(Map options) { + return Value.newBuilder().setMapValue(MapValue.newBuilder().putAllFields(options).build()).build(); + } + @InternalApi static FilterCondition toPipelineFilterCondition(FilterInternal f) { if (f instanceof ComparisonFilterInternal) { diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/ReadTimeTransaction.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/ReadTimeTransaction.java index 762ada8e1..093625a4d 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/ReadTimeTransaction.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/ReadTimeTransaction.java @@ -18,6 +18,7 @@ import com.google.api.core.ApiFuture; import com.google.api.core.ApiFutures; +import com.google.cloud.firestore.pipeline.stages.PipelineOptions; import com.google.cloud.firestore.telemetry.TraceUtil; import com.google.common.base.Preconditions; import com.google.common.util.concurrent.MoreExecutors; @@ -129,7 +130,7 @@ public ApiFuture get(@Nonnull AggregateQuery query) { @Override public ApiFuture> execute(@Nonnull Pipeline pipeline) { try (TraceUtil.Scope ignored = transactionTraceContext.makeCurrent()) { - return pipeline.execute(null, readTime); + return pipeline.execute(PipelineOptions.DEFAULT, null, readTime); } } diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/ServerSideTransaction.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/ServerSideTransaction.java index ddbcc2bec..df1828210 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/ServerSideTransaction.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/ServerSideTransaction.java @@ -19,6 +19,7 @@ import com.google.api.core.ApiFuture; import com.google.api.core.ApiFutures; import com.google.cloud.firestore.TransactionOptions.TransactionOptionsType; +import com.google.cloud.firestore.pipeline.stages.PipelineOptions; import com.google.cloud.firestore.telemetry.TraceUtil; import com.google.common.base.Preconditions; import com.google.common.util.concurrent.MoreExecutors; @@ -265,7 +266,7 @@ public ApiFuture get(@Nonnull AggregateQuery query) { @Override public ApiFuture> execute(@Nonnull Pipeline pipeline) { try (TraceUtil.Scope ignored = transactionTraceContext.makeCurrent()) { - return pipeline.execute(transactionId, null); + return pipeline.execute(PipelineOptions.DEFAULT, transactionId, null); } } } diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UserDataConverter.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UserDataConverter.java index 1d64b772d..9fee83ede 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UserDataConverter.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UserDataConverter.java @@ -43,6 +43,7 @@ class UserDataConverter { static final Value NULL_VALUE = Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build(); + private static final Logger LOGGER = Logger.getLogger(UserDataConverter.class.getName()); /** Controls the behavior for field deletes. */ diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/AbstractOptions.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/AbstractOptions.java new file mode 100644 index 000000000..2d7a64a3f --- /dev/null +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/AbstractOptions.java @@ -0,0 +1,93 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.firestore.pipeline.stages; + +import static com.google.cloud.firestore.PipelineUtils.encodeValue; + +import com.google.cloud.firestore.PipelineUtils; +import com.google.cloud.firestore.pipeline.expressions.Expr; +import com.google.cloud.firestore.pipeline.expressions.Field; +import com.google.cloud.firestore.pipeline.expressions.FunctionUtils; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import com.google.firestore.v1.Value; +import java.util.Arrays; +import java.util.List; + +/** + * Parent class to Pipeline and Stage options. + * + *

Provides a base set of `wither` methods for adding undefined options. + *

Standardizes structure of options for uniform encoding and handling. + *

Intentionally package-private to prevent extension outside of library. + * + * @param Subclass type. + */ +abstract class AbstractOptions { + + protected final InternalOptions options; + + AbstractOptions(InternalOptions options) { + this.options = options; + } + + abstract T self(InternalOptions options); + + public final T with(String key, String value) { + return with(key, encodeValue(value)); + } + + public final T with(String key, boolean value) { + return with(key, encodeValue(value)); + } + + public final T with(String key, long value) { + return with(key, encodeValue(value)); + } + + public final T with(String key, double value) { + return with(key, encodeValue(value)); + } + + public final T with(String key, Field value) { + return with(key, value.toProto()); + } + + protected final T with(String key, Value value) { + return self(options.with(key, value)); + } + + protected final T with(String key, String[] values) { + return self(options.with(key, Arrays.stream(values).map(PipelineUtils::encodeValue)::iterator)); + } + + protected final T with(String key, List expressions) { + return self(options.with(key, Lists.transform(expressions, FunctionUtils::exprToValue))); + } + + protected final T with(String key, AbstractOptions subSection) { + return self(options.with(key, subSection.options)); + } + + public final T withSection(String key, GenericOptions subSection) { + return with(key, subSection); + } + + final ImmutableMap toMap() { + return options.options; + } +} diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/AddFields.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/AddFields.java index bec9411bd..b7963fbb5 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/AddFields.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/AddFields.java @@ -20,22 +20,23 @@ import com.google.api.core.InternalApi; import com.google.cloud.firestore.pipeline.expressions.Expr; -import com.google.firestore.v1.Pipeline; +import com.google.firestore.v1.Value; +import java.util.Collections; import java.util.Map; @InternalApi public final class AddFields extends Stage { - private static final String name = "add_fields"; private final Map fields; @InternalApi public AddFields(Map fields) { + super("add_fields", InternalOptions.EMPTY); this.fields = fields; } @Override - Pipeline.Stage toStageProto() { - return Pipeline.Stage.newBuilder().setName(name).addArgs(encodeValue(fields)).build(); + Iterable toStageArgs() { + return Collections.singletonList(encodeValue(fields)); } } diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Aggregate.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Aggregate.java index 520c274b2..327c232da 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Aggregate.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Aggregate.java @@ -24,53 +24,57 @@ import com.google.cloud.firestore.pipeline.expressions.Expr; import com.google.cloud.firestore.pipeline.expressions.ExprWithAlias; import com.google.cloud.firestore.pipeline.expressions.Selectable; -import com.google.firestore.v1.Pipeline; +import com.google.firestore.v1.Value; import java.util.Arrays; import java.util.Collections; import java.util.Map; import java.util.stream.Collectors; +import javax.annotation.Nonnull; @BetaApi public final class Aggregate extends Stage { - private static final String name = "aggregate"; private final Map groups; private final Map accumulators; @BetaApi public Aggregate withGroups(String... fields) { - return new Aggregate(PipelineUtils.fieldNamesToMap(fields), this.accumulators); + return new Aggregate(PipelineUtils.fieldNamesToMap(fields), this.accumulators, AggregateOptions.DEFAULT); } @BetaApi public Aggregate withGroups(Selectable... selectables) { - return new Aggregate(PipelineUtils.selectablesToMap(selectables), this.accumulators); + return new Aggregate(PipelineUtils.selectablesToMap(selectables), this.accumulators, AggregateOptions.DEFAULT); } @BetaApi public static Aggregate withAccumulators(ExprWithAlias... accumulators) { - if (accumulators.length == 0) { - throw new IllegalArgumentException( - "Must specify at least one accumulator for aggregate() stage. There is a distinct() stage if only distinct group values are needed."); - } - return new Aggregate( Collections.emptyMap(), Arrays.stream(accumulators) - .collect(Collectors.toMap(ExprWithAlias::getAlias, ExprWithAlias::getExpr))); + .collect(Collectors.toMap(ExprWithAlias::getAlias, ExprWithAlias::getExpr)), + AggregateOptions.DEFAULT); } - private Aggregate(Map groups, Map accumulators) { + @BetaApi + public Aggregate withOptions(@Nonnull AggregateOptions options) { + return new Aggregate(groups, accumulators, options); + } + + private Aggregate(Map groups, Map accumulators, + AggregateOptions options) { + super("aggregate", options.options); + if (accumulators.isEmpty()) { + throw new IllegalArgumentException( + "Must specify at least one accumulator for aggregate() stage. There is a distinct() stage if only distinct group values are needed."); + } + this.groups = groups; this.accumulators = accumulators; } @Override - Pipeline.Stage toStageProto() { - return Pipeline.Stage.newBuilder() - .setName(name) - .addArgs(encodeValue(accumulators)) - .addArgs(encodeValue(groups)) - .build(); + Iterable toStageArgs() { + return Arrays.asList(encodeValue(accumulators), encodeValue(groups)); } } diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/AggregateHints.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/AggregateHints.java new file mode 100644 index 000000000..8ca2a3dd1 --- /dev/null +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/AggregateHints.java @@ -0,0 +1,35 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.firestore.pipeline.stages; + +public final class AggregateHints extends AbstractOptions { + + public static AggregateHints DEFAULT = new AggregateHints(InternalOptions.EMPTY); + + public AggregateHints(InternalOptions options) { + super(options); + } + + @Override + AggregateHints self(InternalOptions options) { + return new AggregateHints(options); + } + + public AggregateHints withForceStreamableEnabled() { + return with("force_streamable", true); + } +} diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/AggregateOptions.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/AggregateOptions.java new file mode 100644 index 000000000..69f576d6d --- /dev/null +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/AggregateOptions.java @@ -0,0 +1,35 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.firestore.pipeline.stages; + +public final class AggregateOptions extends AbstractOptions { + + public static AggregateOptions DEFAULT = new AggregateOptions(InternalOptions.EMPTY); + + public AggregateOptions(InternalOptions options) { + super(options); + } + + @Override + AggregateOptions self(InternalOptions options) { + return new AggregateOptions(options); + } + + public AggregateOptions withHints(AggregateHints hints) { + return with("hints", hints); + } +} diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Collection.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Collection.java index 1dbcfd028..0d049b0ee 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Collection.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Collection.java @@ -17,18 +17,17 @@ package com.google.cloud.firestore.pipeline.stages; import com.google.api.core.InternalApi; -import com.google.firestore.v1.Pipeline; import com.google.firestore.v1.Value; +import java.util.Collections; import javax.annotation.Nonnull; @InternalApi public final class Collection extends Stage { - private static final String name = "collection"; @Nonnull private final String path; - @InternalApi - public Collection(@Nonnull String path) { + public Collection(@Nonnull String path, CollectionOptions options) { + super("collection", options.options); if (!path.startsWith("/")) { this.path = "/" + path; } else { @@ -36,11 +35,12 @@ public Collection(@Nonnull String path) { } } + public Collection withOptions(CollectionOptions options) { + return new Collection(path, options); + } + @Override - Pipeline.Stage toStageProto() { - return Pipeline.Stage.newBuilder() - .setName(name) - .addArgs(Value.newBuilder().setReferenceValue(path).build()) - .build(); + Iterable toStageArgs() { + return Collections.singleton(Value.newBuilder().setReferenceValue(path).build()); } } diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/CollectionGroup.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/CollectionGroup.java index 186b06c09..5df5d4df9 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/CollectionGroup.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/CollectionGroup.java @@ -19,26 +19,28 @@ import static com.google.cloud.firestore.PipelineUtils.encodeValue; import com.google.api.core.InternalApi; -import com.google.firestore.v1.Pipeline; +import com.google.common.collect.ImmutableList; import com.google.firestore.v1.Value; @InternalApi public final class CollectionGroup extends Stage { - private static final String name = "collection_group"; private final String collectionId; @InternalApi - public CollectionGroup(String collectionId) { + public CollectionGroup(String collectionId, CollectionGroupOptions options) { + super("collection_group", options.options); this.collectionId = collectionId; } + public CollectionGroup withOptions(CollectionGroupOptions options) { + return new CollectionGroup(collectionId, options); + } + @Override - Pipeline.Stage toStageProto() { - return Pipeline.Stage.newBuilder() - .setName(name) - .addArgs(Value.newBuilder().setReferenceValue("").build()) - .addArgs(encodeValue(collectionId)) - .build(); + Iterable toStageArgs() { + return ImmutableList.of( + Value.newBuilder().setReferenceValue("").build(), + encodeValue(collectionId)); } } diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/CollectionGroupOptions.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/CollectionGroupOptions.java new file mode 100644 index 000000000..44f82baf1 --- /dev/null +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/CollectionGroupOptions.java @@ -0,0 +1,35 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.firestore.pipeline.stages; + +public final class CollectionGroupOptions extends AbstractOptions { + + public static final CollectionGroupOptions DEFAULT = new CollectionGroupOptions(InternalOptions.EMPTY); + + CollectionGroupOptions(InternalOptions options) { + super(options); + } + + @Override + CollectionGroupOptions self(InternalOptions options) { + return new CollectionGroupOptions(options); + } + + public CollectionGroupOptions withHints(CollectionHints hints) { + return with("hints", hints); + } +} diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/CollectionHints.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/CollectionHints.java new file mode 100644 index 000000000..c96927974 --- /dev/null +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/CollectionHints.java @@ -0,0 +1,39 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.firestore.pipeline.stages; + +public final class CollectionHints extends AbstractOptions { + + public static CollectionHints DEFAULT = new CollectionHints(InternalOptions.EMPTY); + + CollectionHints(InternalOptions options) { + super(options); + } + + @Override + CollectionHints self(InternalOptions options) { + return new CollectionHints(options); + } + + public CollectionHints withForceIndex(String value) { + return with("forceIndex", value); + } + + public CollectionHints withIgnoreIndexFields(String... values) { + return with("ignore_index_fields", values); + } +} diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/CollectionOptions.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/CollectionOptions.java new file mode 100644 index 000000000..9e9006a78 --- /dev/null +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/CollectionOptions.java @@ -0,0 +1,36 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.firestore.pipeline.stages; + +public final class CollectionOptions extends AbstractOptions { + + public static final CollectionOptions DEFAULT = new CollectionOptions(InternalOptions.EMPTY); + + CollectionOptions(InternalOptions options) { + super(options); + } + + @Override + CollectionOptions self(InternalOptions options) { + return new CollectionOptions(options); + } + + public CollectionOptions withHints(CollectionHints hints) { + return with("hints", hints); + } + +} diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Database.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Database.java index 8fd98ca03..121cde8f5 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Database.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Database.java @@ -17,17 +17,19 @@ package com.google.cloud.firestore.pipeline.stages; import com.google.api.core.InternalApi; -import com.google.firestore.v1.Pipeline; +import com.google.firestore.v1.Value; +import java.util.Collections; @InternalApi public final class Database extends Stage { - private static final String name = "database"; @InternalApi - public Database() {} + public Database() { + super("database", InternalOptions.EMPTY); + } @Override - Pipeline.Stage toStageProto() { - return Pipeline.Stage.newBuilder().setName(name).build(); + Iterable toStageArgs() { + return Collections.emptyList(); } } diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Distinct.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Distinct.java index eafbdce68..03e7b486a 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Distinct.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Distinct.java @@ -21,22 +21,23 @@ import com.google.api.core.BetaApi; import com.google.api.core.InternalApi; import com.google.cloud.firestore.pipeline.expressions.Expr; -import com.google.firestore.v1.Pipeline; +import com.google.firestore.v1.Value; +import java.util.Collections; import java.util.Map; @BetaApi public final class Distinct extends Stage { - private static final String name = "distinct"; private final Map groups; @InternalApi public Distinct(Map groups) { + super("distinct", InternalOptions.EMPTY); this.groups = groups; } @Override - Pipeline.Stage toStageProto() { - return Pipeline.Stage.newBuilder().setName(name).addArgs(encodeValue(groups)).build(); + Iterable toStageArgs() { + return Collections.singletonList(encodeValue(groups)); } } diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Documents.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Documents.java index f20d79898..5a26a35d9 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Documents.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Documents.java @@ -18,7 +18,9 @@ import com.google.api.core.InternalApi; import com.google.cloud.firestore.DocumentReference; -import com.google.firestore.v1.Pipeline; +import com.google.cloud.firestore.PipelineUtils; +import com.google.common.collect.Iterables; +import com.google.firestore.v1.Value; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @@ -26,11 +28,11 @@ @InternalApi public final class Documents extends Stage { - private static final String name = "documents"; private List documents; @InternalApi Documents(List documents) { + super("documents", InternalOptions.EMPTY); this.documents = documents; } @@ -41,11 +43,7 @@ public static Documents of(DocumentReference... documents) { } @Override - Pipeline.Stage toStageProto() { - Pipeline.Stage.Builder builder = Pipeline.Stage.newBuilder().setName(name); - for (String document : documents) { - builder.addArgsBuilder().setStringValue(document); - } - return builder.build(); + Iterable toStageArgs() { + return Iterables.transform(documents, PipelineUtils::encodeValue); } } diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/FindNearest.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/FindNearest.java index 2b41de29b..9cc75339b 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/FindNearest.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/FindNearest.java @@ -21,105 +21,47 @@ import com.google.api.core.BetaApi; import com.google.api.core.InternalApi; import com.google.cloud.firestore.pipeline.expressions.Expr; -import com.google.firestore.v1.Pipeline; +import com.google.common.collect.ImmutableList; +import com.google.firestore.v1.Value; @BetaApi public final class FindNearest extends Stage { - public interface DistanceMeasure { + public final static class DistanceMeasure { - enum Type { - EUCLIDEAN, - COSINE, - DOT_PRODUCT - } - - static DistanceMeasure euclidean() { - return new EuclideanDistanceMeasure(); - } - - static DistanceMeasure cosine() { - return new CosineDistanceMeasure(); - } - - static DistanceMeasure dotProduct() { - return new DotProductDistanceMeasure(); - } - - static DistanceMeasure generic(String name) { - return new GenericDistanceMeasure(name); - } - - @InternalApi - String toProtoString(); - } - - public static class EuclideanDistanceMeasure implements DistanceMeasure { + final String protoString; - @Override - @InternalApi - public String toProtoString() { - return "euclidean"; + private DistanceMeasure(String protoString) { + this.protoString = protoString; } - } - - public static class CosineDistanceMeasure implements DistanceMeasure { - - @Override - @InternalApi - public String toProtoString() { - return "cosine"; - } - } - - public static class DotProductDistanceMeasure implements DistanceMeasure { - - @Override - @InternalApi - public String toProtoString() { - return "dot_product"; - } - } - - public static class GenericDistanceMeasure implements DistanceMeasure { - - String name; - public GenericDistanceMeasure(String name) { - this.name = name; + public static final DistanceMeasure EUCLIDEAN = new DistanceMeasure("euclidean"); + public static final DistanceMeasure COSINE = new DistanceMeasure("cosine"); + public static final DistanceMeasure DOT_PRODUCT = new DistanceMeasure("dot_product"); + public static DistanceMeasure generic(String name) { + return new DistanceMeasure(name); } - @Override - @InternalApi - public String toProtoString() { - return name; + Value toProto() { + return encodeValue(protoString); } } - private static final String name = "find_nearest"; private final Expr property; private final double[] vector; private final DistanceMeasure distanceMeasure; - private final FindNearestOptions options; @InternalApi public FindNearest( Expr property, double[] vector, DistanceMeasure distanceMeasure, FindNearestOptions options) { + super("find_nearest", options.options); this.property = property; this.vector = vector; this.distanceMeasure = distanceMeasure; - this.options = options; } @Override - Pipeline.Stage toStageProto() { - return Pipeline.Stage.newBuilder() - .setName(name) - .addArgs(encodeValue(property)) - .addArgs(encodeValue(vector)) - .addArgs(encodeValue(distanceMeasure.toProtoString())) - .putOptions("limit", encodeValue(options.getLimit())) - .putOptions("distance_field", encodeValue(options.getDistanceField())) - .build(); + Iterable toStageArgs() { + return ImmutableList.of(encodeValue(property), encodeValue(vector), distanceMeasure.toProto()); } } diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/FindNearestOptions.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/FindNearestOptions.java index 1c57ac76e..03c0652c0 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/FindNearestOptions.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/FindNearestOptions.java @@ -18,50 +18,30 @@ import com.google.api.core.BetaApi; import com.google.cloud.firestore.pipeline.expressions.Field; -import javax.annotation.Nullable; @BetaApi -public class FindNearestOptions { +public final class FindNearestOptions extends AbstractOptions { - @Nullable private final Long limit; + public static FindNearestOptions DEFAULT = new FindNearestOptions(InternalOptions.EMPTY); - @Nullable private final Field distanceField; - - private FindNearestOptions(Long limit, Field distanceField) { - this.limit = limit; - this.distanceField = distanceField; + private FindNearestOptions(InternalOptions options) { + super(options); } - public static Builder builder() { - return new Builder(); + @Override + FindNearestOptions self(InternalOptions options) { + return new FindNearestOptions(options); } - @Nullable - public Long getLimit() { - return limit; + public FindNearestOptions withLimit(long limit) { + return with("limit", limit); } - @Nullable - public Field getDistanceField() { - return distanceField; + public FindNearestOptions withDistanceField(Field distanceField) { + return with("distance_field", distanceField); } - public static class Builder { - @Nullable private Long limit; - @Nullable private Field distanceField; - - public Builder limit(Long limit) { - this.limit = limit; - return this; - } - - public Builder distanceField(Field distanceField) { - this.distanceField = distanceField; - return this; - } - - public FindNearestOptions build() { - return new FindNearestOptions(limit, distanceField); - } + public FindNearestOptions withDistanceField(String distanceField) { + return withDistanceField(Field.of(distanceField)); } } diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/GenericOptions.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/GenericOptions.java new file mode 100644 index 000000000..f05e5f0ac --- /dev/null +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/GenericOptions.java @@ -0,0 +1,55 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.firestore.pipeline.stages; + +import static com.google.cloud.firestore.PipelineUtils.encodeValue; + +import com.google.cloud.firestore.pipeline.expressions.Field; + +public final class GenericOptions extends AbstractOptions { + + public static GenericOptions DEFAULT = new GenericOptions(InternalOptions.EMPTY); + + public static GenericOptions of(String key, String value) { + return new GenericOptions(InternalOptions.of(key, encodeValue(value))); + } + + public static GenericOptions of(String key, boolean value) { + return new GenericOptions(InternalOptions.of(key, encodeValue(value))); + } + + public static GenericOptions of(String key, long value) { + return new GenericOptions(InternalOptions.of(key, encodeValue(value))); + } + + public static GenericOptions of(String key, double value) { + return new GenericOptions(InternalOptions.of(key, encodeValue(value))); + } + + public static GenericOptions of(String key, Field value) { + return new GenericOptions(InternalOptions.of(key, value.toProto())); + } + + GenericOptions(InternalOptions options) { + super(options); + } + + @Override + protected GenericOptions self(InternalOptions options) { + return new GenericOptions(options); + } +} diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/GenericStage.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/GenericStage.java index d2595f12c..dc0adf00a 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/GenericStage.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/GenericStage.java @@ -19,27 +19,24 @@ import static com.google.cloud.firestore.PipelineUtils.encodeValue; import com.google.api.core.InternalApi; -import com.google.firestore.v1.Pipeline; +import com.google.cloud.firestore.PipelineUtils; +import com.google.common.collect.Iterables; +import com.google.firestore.v1.Value; import java.util.List; @InternalApi public final class GenericStage extends Stage { - private final String name; private List params; @InternalApi - public GenericStage(String name, List params) { - this.name = name; + public GenericStage(String name, List params, GenericOptions optionalParams) { + super(name, optionalParams.options); this.params = params; } @Override - Pipeline.Stage toStageProto() { - Pipeline.Stage.Builder builder = Pipeline.Stage.newBuilder().setName(name); - for (Object param : params) { - builder.addArgs(encodeValue(param)); - } - return builder.build(); + Iterable toStageArgs() { + return Iterables.transform(params, PipelineUtils::encodeValue); } } diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/InternalOptions.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/InternalOptions.java new file mode 100644 index 000000000..bad786c64 --- /dev/null +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/InternalOptions.java @@ -0,0 +1,67 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.firestore.pipeline.stages; + +import com.google.common.collect.ImmutableMap; +import com.google.firestore.v1.ArrayValue; +import com.google.firestore.v1.MapValue; +import com.google.firestore.v1.Value; + +/** + * Wither style Key/Value options object. + * + * Basic `wither` functionality built upon `ImmutableMap`. Exposes methods to + * construct, augment, and encode Kay/Value pairs. The wrapped collection + * `ImmutableMap` is an implementation detail, not to be exposed, since more + * efficient implementations are possible. + */ +final class InternalOptions { + + public static final InternalOptions EMPTY = new InternalOptions(ImmutableMap.of()); + + final ImmutableMap options; + + InternalOptions(ImmutableMap options) { + this.options = options; + } + + public static InternalOptions of(String key, Value value) { + return new InternalOptions(ImmutableMap.of(key, value)); + } + + InternalOptions with(String key, Value value) { + ImmutableMap.Builder builder = ImmutableMap.builderWithExpectedSize( + options.size() + 1); + builder.putAll(options); + builder.put(key, value); + return new InternalOptions(builder.buildKeepingLast()); + } + + InternalOptions with(String key, Iterable values) { + ArrayValue arrayValue = ArrayValue.newBuilder().addAllValues(values).build(); + return with(key, Value.newBuilder().setArrayValue(arrayValue).build()); + } + + InternalOptions with(String key, InternalOptions value) { + return with(key, value.toValue()); + } + + private Value toValue() { + MapValue mapValue = MapValue.newBuilder().putAllFields(options).build(); + return Value.newBuilder().setMapValue(mapValue).build(); + } +} diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Limit.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Limit.java index 5d73400ff..8723b980c 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Limit.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Limit.java @@ -19,21 +19,22 @@ import static com.google.cloud.firestore.PipelineUtils.encodeValue; import com.google.api.core.InternalApi; -import com.google.firestore.v1.Pipeline; +import com.google.firestore.v1.Value; +import java.util.Collections; @InternalApi public final class Limit extends Stage { - private static final String name = "limit"; private final int limit; @InternalApi public Limit(int limit) { + super("limit", InternalOptions.EMPTY); this.limit = limit; } @Override - Pipeline.Stage toStageProto() { - return Pipeline.Stage.newBuilder().setName(name).addArgs(encodeValue(limit)).build(); + Iterable toStageArgs() { + return Collections.singletonList(encodeValue(limit)); } } diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Offset.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Offset.java index 49bb0aed4..63a96812a 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Offset.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Offset.java @@ -19,21 +19,22 @@ import static com.google.cloud.firestore.PipelineUtils.encodeValue; import com.google.api.core.InternalApi; -import com.google.firestore.v1.Pipeline; +import com.google.firestore.v1.Value; +import java.util.Collections; @InternalApi public final class Offset extends Stage { - private static final String name = "offset"; private final int offset; @InternalApi public Offset(int offset) { + super("offset", InternalOptions.EMPTY); this.offset = offset; } @Override - Pipeline.Stage toStageProto() { - return Pipeline.Stage.newBuilder().setName(name).addArgs(encodeValue(offset)).build(); + Iterable toStageArgs() { + return Collections.singletonList(encodeValue(offset)); } } diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/PipelineOptions.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/PipelineOptions.java new file mode 100644 index 000000000..591c7b465 --- /dev/null +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/PipelineOptions.java @@ -0,0 +1,61 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.firestore.pipeline.stages; + +import com.google.cloud.firestore.PipelineUtils; +import com.google.firestore.v1.Value; + +public final class PipelineOptions extends AbstractOptions { + + public static PipelineOptions DEFAULT = new PipelineOptions(InternalOptions.EMPTY); + + public enum ExecutionMode { + EXECUTE("execute"), + EXPLAIN("explain"), + PROFILE("profile"); + + private final Value value; + ExecutionMode(String profile) { + value = PipelineUtils.encodeValue(profile); + } + } + + PipelineOptions(InternalOptions options) { + super(options); + } + + @Override + PipelineOptions self(InternalOptions options) { + return new PipelineOptions(options); + } + + public PipelineOptions withExecutionMode(ExecutionMode mode) { + return with("execution_mode", mode.value); + } + + public PipelineOptions withIndexRecommendationEnabled() { + return with("index_recommendation", true); + } + + public PipelineOptions withShowAlternativePlanEnabled() { + return with("show_alternative_plans", true); + } + + public PipelineOptions withRedactEnabled() { + return with("redact", true); + } +} diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/RemoveFields.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/RemoveFields.java index 794f28a8f..613f1bf0e 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/RemoveFields.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/RemoveFields.java @@ -20,25 +20,22 @@ import com.google.cloud.firestore.PipelineUtils; import com.google.cloud.firestore.pipeline.expressions.Field; import com.google.common.collect.ImmutableList; -import com.google.firestore.v1.Pipeline; +import com.google.common.collect.Iterables; +import com.google.firestore.v1.Value; @InternalApi public final class RemoveFields extends Stage { - private static final String name = "remove_fields"; private final ImmutableList fields; @InternalApi public RemoveFields(ImmutableList fields) { + super("remove_fields", InternalOptions.EMPTY); this.fields = fields; } @Override - Pipeline.Stage toStageProto() { - Pipeline.Stage.Builder builder = Pipeline.Stage.newBuilder().setName(name); - for (Field field : fields) { - builder.addArgs(PipelineUtils.encodeValue(field)); - } - return builder.build(); + Iterable toStageArgs() { + return Iterables.transform(fields, PipelineUtils::encodeValue); } } diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Replace.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Replace.java index 6be322564..d4f99f368 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Replace.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Replace.java @@ -19,13 +19,12 @@ import static com.google.cloud.firestore.PipelineUtils.encodeValue; import com.google.cloud.firestore.pipeline.expressions.Selectable; -import com.google.firestore.v1.Pipeline; +import com.google.common.collect.ImmutableList; import com.google.firestore.v1.Value; import javax.annotation.Nonnull; public class Replace extends Stage { - private static final String name = "replace"; private final Selectable field; private final Mode mode; @@ -46,16 +45,13 @@ public Replace(@Nonnull Selectable field) { } public Replace(@Nonnull Selectable field, @Nonnull Mode mode) { + super("replace", InternalOptions.EMPTY); this.field = field; this.mode = mode; } @Override - Pipeline.Stage toStageProto() { - return Pipeline.Stage.newBuilder() - .setName(name) - .addArgs(encodeValue(field)) - .addArgs(mode.value) - .build(); + Iterable toStageArgs() { + return ImmutableList.of(encodeValue(field), mode.value); } } diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Sample.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Sample.java index f30597a21..9115fe203 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Sample.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Sample.java @@ -16,21 +16,54 @@ package com.google.cloud.firestore.pipeline.stages; +import static com.google.cloud.firestore.PipelineUtils.encodeValue; + +import com.google.api.core.BetaApi; import com.google.api.core.InternalApi; -import com.google.firestore.v1.Pipeline; +import com.google.common.collect.ImmutableList; +import com.google.firestore.v1.Value; +import javax.annotation.Nonnull; public final class Sample extends Stage { - private static final String name = "sample"; - private final SampleOptions options; + private final Number size; + private final Mode mode; + + public enum Mode { + DOCUMENTS(encodeValue("documents")), + PERCENT(encodeValue("percent")); + + public final Value value; + + Mode(Value value) { + this.value = value; + } + } + + @BetaApi + public static Sample withPercentage(double percentage) { + return new Sample(percentage, Mode.PERCENT, SampleOptions.DEFAULT); + } + + @BetaApi + public static Sample withDocLimit(int documents) { + return new Sample(documents, Mode.DOCUMENTS, SampleOptions.DEFAULT); + } + + @BetaApi + public Sample withOptions(@Nonnull SampleOptions options) { + return new Sample(size, mode, options); + } @InternalApi - public Sample(SampleOptions options) { - this.options = options; + private Sample(Number size, Mode mode, SampleOptions options) { + super("sample", options.options); + this.size = size; + this.mode = mode; } @Override - Pipeline.Stage toStageProto() { - return Pipeline.Stage.newBuilder().setName(name).addAllArgs(options.getProtoArgs()).build(); + Iterable toStageArgs() { + return ImmutableList.of(encodeValue(size), mode.value); } } diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/SampleOptions.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/SampleOptions.java index fe7524dd6..c57a59d7a 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/SampleOptions.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/SampleOptions.java @@ -16,41 +16,16 @@ package com.google.cloud.firestore.pipeline.stages; -import static com.google.cloud.firestore.PipelineUtils.encodeValue; +public final class SampleOptions extends AbstractOptions { -import com.google.common.collect.ImmutableList; -import com.google.firestore.v1.Value; + public static SampleOptions DEFAULT = new SampleOptions(InternalOptions.EMPTY); -public class SampleOptions { - - private final Number n; - private final Mode mode; - - private SampleOptions(Number n, Mode mode) { - this.n = n; - this.mode = mode; - } - - public enum Mode { - DOCUMENTS(Value.newBuilder().setStringValue("documents").build()), - PERCENT(Value.newBuilder().setStringValue("percent").build()); - - public final Value value; - - Mode(Value value) { - this.value = value; - } - } - - public static SampleOptions percentage(double percentage) { - return new SampleOptions(percentage, Mode.PERCENT); - } - - public static SampleOptions docLimit(int documents) { - return new SampleOptions(documents, Mode.DOCUMENTS); + public SampleOptions(InternalOptions options) { + super(options); } - Iterable getProtoArgs() { - return ImmutableList.of(encodeValue(n), mode.value); + @Override + SampleOptions self(InternalOptions options) { + return new SampleOptions(options); } } diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Select.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Select.java index 74763f9b0..311608a23 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Select.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Select.java @@ -20,22 +20,23 @@ import com.google.api.core.InternalApi; import com.google.cloud.firestore.pipeline.expressions.Expr; -import com.google.firestore.v1.Pipeline; +import com.google.firestore.v1.Value; +import java.util.Collections; import java.util.Map; @InternalApi public final class Select extends Stage { - private static final String name = "select"; private final Map projections; @InternalApi public Select(Map projections) { + super("select", InternalOptions.EMPTY); this.projections = projections; } @Override - Pipeline.Stage toStageProto() { - return Pipeline.Stage.newBuilder().setName(name).addArgs(encodeValue(projections)).build(); + Iterable toStageArgs() { + return Collections.singletonList(encodeValue(projections)); } } diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Sort.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Sort.java index 70d92750f..c89266118 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Sort.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Sort.java @@ -18,8 +18,9 @@ import com.google.api.core.InternalApi; import com.google.cloud.firestore.pipeline.expressions.Ordering; -import com.google.common.collect.Lists; -import com.google.firestore.v1.Pipeline; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.firestore.v1.Value; import java.util.List; public final class Sort extends Stage { @@ -28,16 +29,13 @@ public final class Sort extends Stage { private final List orders; @InternalApi - public Sort(Ordering... orders) { - this.orders = Lists.newArrayList(orders); + public Sort(ImmutableList orders) { + super("sort", InternalOptions.EMPTY); + this.orders = orders; } @Override - Pipeline.Stage toStageProto() { - Pipeline.Stage.Builder builder = Pipeline.Stage.newBuilder().setName(name); - for (Ordering order : orders) { - builder.addArgs(order.toProto()); - } - return builder.build(); + Iterable toStageArgs() { + return Iterables.transform(orders, Ordering::toProto); } } diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Stage.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Stage.java index a148d126b..8f82195f7 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Stage.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Stage.java @@ -16,11 +16,28 @@ package com.google.cloud.firestore.pipeline.stages; +import com.google.firestore.v1.Pipeline; +import com.google.firestore.v1.Value; + /** Parent to all stages. */ public abstract class Stage { + protected final String name; + final InternalOptions options; + /** Constructor is package-private to prevent extension. */ - Stage() {} + Stage(String name, InternalOptions options) { + this.name = name; + this.options = options; + } + + final Pipeline.Stage toStageProto() { + return Pipeline.Stage.newBuilder() + .setName(name) + .addAllArgs(toStageArgs()) + .putAllOptions(options.options) + .build(); + } - abstract com.google.firestore.v1.Pipeline.Stage toStageProto(); + abstract Iterable toStageArgs(); } diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/StageUtils.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/StageUtils.java index d538e3dd1..bcb7e5ee0 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/StageUtils.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/StageUtils.java @@ -17,6 +17,8 @@ package com.google.cloud.firestore.pipeline.stages; import com.google.api.core.InternalApi; +import com.google.common.collect.ImmutableMap; +import com.google.firestore.v1.Value; @InternalApi public final class StageUtils { @@ -24,4 +26,10 @@ public final class StageUtils { public static com.google.firestore.v1.Pipeline.Stage toStageProto(Stage stage) { return stage.toStageProto(); } + + @SuppressWarnings("ClassEscapesDefinedScope") + @InternalApi + public static ImmutableMap toMap(AbstractOptions options) { + return options.options.options; + } } diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Union.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Union.java index 4014e926a..d54a650f4 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Union.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Union.java @@ -17,20 +17,20 @@ package com.google.cloud.firestore.pipeline.stages; import com.google.cloud.firestore.Pipeline; +import com.google.firestore.v1.Value; +import java.util.Collections; -public class Union extends Stage { +public final class Union extends Stage { - private static final String name = "union"; private final Pipeline other; public Union(Pipeline other) { + super("union", InternalOptions.EMPTY); this.other = other; } @Override - com.google.firestore.v1.Pipeline.Stage toStageProto() { - com.google.firestore.v1.Pipeline.Stage.Builder builder = - com.google.firestore.v1.Pipeline.Stage.newBuilder().setName(name); - return builder.addArgs(other.toProtoValue()).build(); + Iterable toStageArgs() { + return Collections.singletonList(other.toProtoValue()); } } diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Unnest.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Unnest.java index aaddc12b2..3d7a3cfee 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Unnest.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Unnest.java @@ -19,32 +19,30 @@ import static com.google.cloud.firestore.PipelineUtils.encodeValue; import com.google.cloud.firestore.pipeline.expressions.Field; -import com.google.firestore.v1.Pipeline; +import com.google.common.collect.ImmutableList; +import com.google.firestore.v1.Value; import javax.annotation.Nonnull; -public class Unnest extends Stage { +public final class Unnest extends Stage { - private static final String name = "unnest"; private final Field field; - private final UnnestOptions options; + private final String alias; - public Unnest(Field field) { + public Unnest(@Nonnull Field field, @Nonnull String alias) { + super("unnest", InternalOptions.EMPTY); this.field = field; - this.options = null; + this.alias = alias; } - public Unnest(@Nonnull Field field, @Nonnull UnnestOptions options) { + public Unnest(@Nonnull Field field, @Nonnull String alias, @Nonnull UnnestOptions options) { + super("unnest", options.options); this.field = field; - this.options = options; + this.alias = alias; } @Override - Pipeline.Stage toStageProto() { - Pipeline.Stage.Builder builder = - Pipeline.Stage.newBuilder().setName(name).addArgs(encodeValue(field)); - if (options != null) { - builder.addArgs(encodeValue(options.indexField)); - } - return builder.build(); + Iterable toStageArgs() { + return ImmutableList.of(encodeValue(field), encodeValue(alias)); } + } diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/UnnestOptions.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/UnnestOptions.java index 3ca12d792..59ab0cb2d 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/UnnestOptions.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/UnnestOptions.java @@ -18,15 +18,20 @@ import javax.annotation.Nonnull; -public class UnnestOptions { +public final class UnnestOptions extends AbstractOptions { - final String indexField; + public static UnnestOptions DEFAULT = new UnnestOptions(InternalOptions.EMPTY); - public static UnnestOptions indexField(@Nonnull String indexField) { - return new UnnestOptions(indexField); + public UnnestOptions withIndexField(@Nonnull String indexField) { + return with("index_field", indexField); } - private UnnestOptions(String indexField) { - this.indexField = indexField; + @Override + UnnestOptions self(InternalOptions options) { + return new UnnestOptions(options); + } + + private UnnestOptions(InternalOptions options) { + super(options); } } diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Where.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Where.java index 36da8f12e..02511e4fa 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Where.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Where.java @@ -20,21 +20,22 @@ import com.google.api.core.InternalApi; import com.google.cloud.firestore.pipeline.expressions.FilterCondition; -import com.google.firestore.v1.Pipeline; +import com.google.firestore.v1.Value; +import java.util.Collections; @InternalApi public final class Where extends Stage { - private static final String name = "where"; private final FilterCondition condition; @InternalApi public Where(FilterCondition condition) { + super("where", InternalOptions.EMPTY); this.condition = condition; } @Override - Pipeline.Stage toStageProto() { - return Pipeline.Stage.newBuilder().setName(name).addArgs(encodeValue(condition)).build(); + Iterable toStageArgs() { + return Collections.singletonList(encodeValue(condition)); } } diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITPipelineTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITPipelineTest.java index 550d04fa1..822d21834 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITPipelineTest.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITPipelineTest.java @@ -41,6 +41,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; +import com.google.api.core.ApiFuture; import com.google.cloud.firestore.CollectionReference; import com.google.cloud.firestore.LocalFirestoreHelper; import com.google.cloud.firestore.Pipeline; @@ -49,7 +50,15 @@ import com.google.cloud.firestore.pipeline.expressions.Field; import com.google.cloud.firestore.pipeline.expressions.Function; import com.google.cloud.firestore.pipeline.stages.Aggregate; -import com.google.cloud.firestore.pipeline.stages.SampleOptions; +import com.google.cloud.firestore.pipeline.stages.AggregateHints; +import com.google.cloud.firestore.pipeline.stages.AggregateOptions; +import com.google.cloud.firestore.pipeline.stages.CollectionHints; +import com.google.cloud.firestore.pipeline.stages.CollectionOptions; +import com.google.cloud.firestore.pipeline.stages.FindNearest; +import com.google.cloud.firestore.pipeline.stages.FindNearestOptions; +import com.google.cloud.firestore.pipeline.stages.PipelineOptions; +import com.google.cloud.firestore.pipeline.stages.PipelineOptions.ExecutionMode; +import com.google.cloud.firestore.pipeline.stages.Sample; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; @@ -261,7 +270,7 @@ public void testAggregates() throws Exception { firestore .pipeline() .collection(collection.getPath()) - .aggregate(countAll().as("count")) + .aggregate(AggregateOptions.DEFAULT, countAll().as("count")) .execute() .get(); assertThat(data(results)).isEqualTo(Lists.newArrayList(map("count", 10L))); @@ -1137,7 +1146,7 @@ public void testSampleLimit() throws Exception { @Test public void testSamplePercentage() throws Exception { List results = - collection.pipeline().sample(SampleOptions.percentage(0.6)).execute().get(); + collection.pipeline().sample(Sample.withPercentage(0.6)).execute().get(); assertThat(results).hasSize(6); } @@ -1156,10 +1165,48 @@ public void testUnnest() throws Exception { collection .pipeline() .where(eq(Field.of("title"), "The Hitchhiker's Guide to the Galaxy")) - .unnest("tags") + .unnest("tags", "tag") .execute() .get(); assertThat(results).hasSize(3); } + + @Test + public void testOptions() { + // This is just example of execute and stage options. + PipelineOptions opts = PipelineOptions.DEFAULT + .withIndexRecommendationEnabled() + .withExecutionMode(ExecutionMode.PROFILE); + + double[] vector = {1.0, 2.0, 3.0}; + + Pipeline pipeline = firestore.pipeline() + .collection( + "/k", + // Remove Hints overload - can be added later. + CollectionOptions.DEFAULT + .withHints(CollectionHints.DEFAULT + .withForceIndex("abcdef") + .with("foo", "bar")) + .with("foo", "bar") + ) + .findNearest("topicVectors", vector, FindNearest.DistanceMeasure.COSINE, + FindNearestOptions.DEFAULT + .withLimit(10) + .withDistanceField("distance") + .with("foo", "bar")) + .aggregate( + Aggregate + .withAccumulators(avg("rating").as("avg_rating")) + .withGroups("genre") + .withOptions(AggregateOptions.DEFAULT + .withHints(AggregateHints.DEFAULT + .withForceStreamableEnabled() + .with("foo", "bar")) + .with("foo", "bar")) + ); + + pipeline.execute(opts); + } }