This expression is useful when you need to access the entire document as a map, or pass the
+ * document itself to a function or subquery.
+ *
+ *
params;
- FunctionExpression(String name, List extends Expression> params) {
+ @InternalApi
+ public FunctionExpression(String name, List extends Expression> params) {
this.name = name;
this.params = Collections.unmodifiableList(params);
}
diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/expressions/PipelineValueExpression.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/expressions/PipelineValueExpression.java
new file mode 100644
index 000000000..9094a1e94
--- /dev/null
+++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/expressions/PipelineValueExpression.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.expressions;
+
+import com.google.api.core.InternalApi;
+import com.google.cloud.firestore.Pipeline;
+import com.google.firestore.v1.Value;
+
+/** Internal expression representing a pipeline value. */
+@InternalApi
+public final class PipelineValueExpression extends Expression {
+ private final Pipeline pipeline;
+
+ public PipelineValueExpression(Pipeline pipeline) {
+ this.pipeline = pipeline;
+ }
+
+ @Override
+ protected Value toProto() {
+ return pipeline.toProtoValue();
+ }
+}
diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/expressions/Variable.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/expressions/Variable.java
new file mode 100644
index 000000000..7f16dfcb5
--- /dev/null
+++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/expressions/Variable.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2025 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.expressions;
+
+import com.google.api.core.InternalApi;
+import com.google.firestore.v1.Value;
+
+/**
+ * Internal expression representing a variable reference.
+ *
+ * This evaluates to the value of a variable defined in a pipeline context.
+ */
+@InternalApi
+final class Variable extends Expression {
+ private final String name;
+
+ Variable(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public Value toProto() {
+ return Value.newBuilder().setVariableReferenceValue(name).build();
+ }
+}
diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Define.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Define.java
new file mode 100644
index 000000000..699e909f1
--- /dev/null
+++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Define.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2025 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.api.core.InternalApi;
+import com.google.cloud.firestore.pipeline.expressions.Expression;
+import com.google.firestore.v1.Value;
+import java.util.Collections;
+import java.util.Map;
+
+@InternalApi
+public final class Define extends Stage {
+
+ private final Map expressions;
+
+ @InternalApi
+ public Define(Map expressions) {
+ super("let", InternalOptions.EMPTY);
+ this.expressions = expressions;
+ }
+
+ @Override
+ Iterable toStageArgs() {
+ return Collections.singletonList(encodeValue(expressions));
+ }
+}
diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Subcollection.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Subcollection.java
new file mode 100644
index 000000000..f451e1c71
--- /dev/null
+++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Subcollection.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2025 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.api.core.InternalApi;
+import com.google.firestore.v1.Value;
+import java.util.Collections;
+
+@InternalApi
+public final class Subcollection extends Stage {
+
+ private final String path;
+
+ @InternalApi
+ public Subcollection(String path) {
+ super("subcollection", InternalOptions.EMPTY);
+ this.path = path;
+ }
+
+ @Override
+ Iterable toStageArgs() {
+ return Collections.singletonList(encodeValue(path));
+ }
+}
diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITPipelineSubqueryTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITPipelineSubqueryTest.java
new file mode 100644
index 000000000..ad4d44225
--- /dev/null
+++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITPipelineSubqueryTest.java
@@ -0,0 +1,1139 @@
+/*
+ * 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.it;
+
+import static com.google.cloud.firestore.FieldValue.vector;
+import static com.google.cloud.firestore.it.ITQueryTest.map;
+import static com.google.cloud.firestore.pipeline.expressions.Expression.and;
+import static com.google.cloud.firestore.pipeline.expressions.Expression.constant;
+import static com.google.cloud.firestore.pipeline.expressions.Expression.currentDocument;
+import static com.google.cloud.firestore.pipeline.expressions.Expression.equal;
+import static com.google.cloud.firestore.pipeline.expressions.Expression.field;
+import static com.google.cloud.firestore.pipeline.expressions.Expression.or;
+import static com.google.cloud.firestore.pipeline.expressions.Expression.variable;
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeFalse;
+
+import com.google.cloud.firestore.CollectionReference;
+import com.google.cloud.firestore.LocalFirestoreHelper;
+import com.google.cloud.firestore.Pipeline;
+import com.google.cloud.firestore.PipelineResult;
+import com.google.cloud.firestore.pipeline.expressions.AggregateFunction;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.stream.Collectors;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ITPipelineSubqueryTest extends ITBaseTest {
+ private CollectionReference collection;
+ private Map> bookDocs;
+
+ public CollectionReference testCollectionWithDocs(Map> docs)
+ throws ExecutionException, InterruptedException, TimeoutException {
+ CollectionReference collection = firestore.collection(LocalFirestoreHelper.autoId());
+ for (Map.Entry> doc : docs.entrySet()) {
+ collection.document(doc.getKey()).set(doc.getValue()).get(5, TimeUnit.SECONDS);
+ }
+ return collection;
+ }
+
+ List