From 746ded33cbce2b3c84abb7ab26b114052e7a0b35 Mon Sep 17 00:00:00 2001 From: Yvonne Pan <103622026+yvonnep165@users.noreply.github.com> Date: Mon, 30 Mar 2026 11:59:36 -0400 Subject: [PATCH 1/5] Add support for the parent expression --- .../pipeline/expressions/Expression.java | 43 +++++++++++++++++++ .../cloud/firestore/it/ITPipelineTest.java | 26 +++++++++++ 2 files changed, 69 insertions(+) diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/expressions/Expression.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/expressions/Expression.java index 752471c9a..dcd8c7d9d 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/expressions/Expression.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/expressions/Expression.java @@ -4364,6 +4364,39 @@ public static Expression collectionId(String pathFieldName) { return collectionId(field(pathFieldName)); } + /** + * Creates an expression that returns the parent document of a document reference. + * + * @param documentPath An expression that evaluates to a document path. + * @return A new {@link Expression} representing the parent operation. + */ + @BetaApi + public static Expression parent(Expression documentPath) { + return new FunctionExpression("parent", ImmutableList.of(documentPath)); + } + + /** + * Creates an expression that returns the parent document of a document reference. + * + * @param documentPath The string representation of the document path. + * @return A new {@link Expression} representing the parent operation. + */ + @BetaApi + public static Expression parent(String documentPath) { + return parent(constant(documentPath)); + } + + /** + * Creates an expression that returns the parent document of a document reference. + * + * @param docRef The {@link DocumentReference}. + * @return A new {@link Expression} representing the parent operation. + */ + @BetaApi + public static Expression parent(DocumentReference docRef) { + return parent(constant(docRef)); + } + // Type Checking Functions /** * Creates an expression that checks if a field exists. @@ -7016,6 +7049,16 @@ public final Expression collectionId() { return collectionId(this); } + /** + * Creates an expression that returns the parent document of a document reference. + * + * @return A new {@link Expression} representing the parent operation. + */ + @BetaApi + public final Expression parent() { + return parent(this); + } + /** * Creates an expression that returns a string indicating the type of the value this expression * evaluates to. 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 2d257bce6..d4fdc16fa 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 @@ -72,6 +72,7 @@ import static com.google.cloud.firestore.pipeline.expressions.Expression.notEqual; import static com.google.cloud.firestore.pipeline.expressions.Expression.nullValue; import static com.google.cloud.firestore.pipeline.expressions.Expression.or; +import static com.google.cloud.firestore.pipeline.expressions.Expression.parent; import static com.google.cloud.firestore.pipeline.expressions.Expression.pow; import static com.google.cloud.firestore.pipeline.expressions.Expression.rand; import static com.google.cloud.firestore.pipeline.expressions.Expression.regexMatch; @@ -109,6 +110,7 @@ import com.google.cloud.Timestamp; import com.google.cloud.firestore.Blob; import com.google.cloud.firestore.CollectionReference; +import com.google.cloud.firestore.DocumentReference; import com.google.cloud.firestore.Firestore; import com.google.cloud.firestore.FirestoreOptions; import com.google.cloud.firestore.GeoPoint; @@ -4292,4 +4294,28 @@ public void disallowDuplicateAliasesAcrossStages() { }); assertThat(exception).hasMessageThat().contains("Duplicate alias or field name"); } + + @Test + public void testSupportsParent() throws Exception { + DocumentReference docRef = collection.document("book4").collection("reviews").document("review1"); + + Pipeline pipeline = + firestore + .pipeline() + .collection(collection.getPath()) + .limit(1) + .select( + parent(docRef).as("parentRefStatic"), + constant(docRef).parent().as("parentRefInstance")) + .select( + field("parentRefStatic").documentId().as("parentIdStatic"), + field("parentRefInstance").documentId().as("parentIdInstance")); + + List results = pipeline.execute().get().getResults(); + assertThat(results).hasSize(1); + Map data = results.get(0).getData(); + + assertThat(data.get("parentIdStatic")).isEqualTo("book4"); + assertThat(data.get("parentIdInstance")).isEqualTo("book4"); + } } From 135215f9f1251923df6de9e400500f7458fd1dbb Mon Sep 17 00:00:00 2001 From: cloud-java-bot Date: Mon, 30 Mar 2026 16:05:11 +0000 Subject: [PATCH 2/5] chore: generate libraries at Mon Mar 30 16:02:57 UTC 2026 --- .../java/com/google/cloud/firestore/it/ITPipelineTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 d4fdc16fa..aaed75a21 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 @@ -4297,7 +4297,8 @@ public void disallowDuplicateAliasesAcrossStages() { @Test public void testSupportsParent() throws Exception { - DocumentReference docRef = collection.document("book4").collection("reviews").document("review1"); + DocumentReference docRef = + collection.document("book4").collection("reviews").document("review1"); Pipeline pipeline = firestore From d721fa9d115fe822d06af983f0bb86216dd75961 Mon Sep 17 00:00:00 2001 From: Yvonne Pan <103622026+yvonnep165@users.noreply.github.com> Date: Mon, 30 Mar 2026 14:20:37 -0400 Subject: [PATCH 3/5] Fix integration tests error due to changes in backend implementation --- .../google/cloud/firestore/it/ITPipelineTest.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) 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 aaed75a21..b3267973a 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 @@ -2297,7 +2297,7 @@ public void testChecks() throws Exception { .select( field("rating").equal(nullValue()).as("ratingIsNull"), field("rating").equal(Double.NaN).as("ratingIsNaN"), - // arrayGet("title", 0) evaluates to UNSET so it is not an error + // arrayGet("title", 0) evaluates to ERROR arrayGet("title", 0).isError().as("isError"), arrayGet("title", 0).ifError(constant("was error")).as("ifError"), field("foo").isAbsent().as("isAbsent"), @@ -2318,7 +2318,9 @@ public void testChecks() throws Exception { "ratingIsNaN", false, "isError", - false, + true, + "ifError", + "was error", "isAbsent", true, "titleIsNotNull", @@ -3537,8 +3539,8 @@ public void testNestedFields() throws Exception { assertThat(data(results)) .isEqualTo( Lists.newArrayList( - map("title", "The Hitchhiker's Guide to the Galaxy", "awards.hugo", true), - map("title", "Dune", "awards.hugo", true))); + map("title", "The Hitchhiker's Guide to the Galaxy", "awards", map("hugo", true)), + map("title", "Dune", "awards", map("hugo", true)))); } @Test @@ -3561,8 +3563,8 @@ public void testPipelineInTransactions() throws Exception { assertThat(data(results)) .isEqualTo( Lists.newArrayList( - map("title", "The Hitchhiker's Guide to the Galaxy", "awards.hugo", true), - map("title", "Dune", "awards.hugo", true))); + map("title", "The Hitchhiker's Guide to the Galaxy", "awards", map("hugo", true)), + map("title", "Dune", "awards", map("hugo", true)))); transaction.update(collection.document("book1"), map("foo", "bar")); From 5fea6d968acfc503989a7f7b78db2ba94b1fd5b9 Mon Sep 17 00:00:00 2001 From: cloud-java-bot Date: Mon, 30 Mar 2026 18:23:55 +0000 Subject: [PATCH 4/5] chore: generate libraries at Mon Mar 30 18:21:43 UTC 2026 --- .../java/com/google/cloud/firestore/it/ITPipelineTest.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 b3267973a..5e5ca54a6 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 @@ -3563,7 +3563,11 @@ public void testPipelineInTransactions() throws Exception { assertThat(data(results)) .isEqualTo( Lists.newArrayList( - map("title", "The Hitchhiker's Guide to the Galaxy", "awards", map("hugo", true)), + map( + "title", + "The Hitchhiker's Guide to the Galaxy", + "awards", + map("hugo", true)), map("title", "Dune", "awards", map("hugo", true)))); transaction.update(collection.document("book1"), map("foo", "bar")); From 5c9999274dff2c9e240b2f702efb0ff7258b3fc5 Mon Sep 17 00:00:00 2001 From: Yvonne Pan <103622026+yvonnep165@users.noreply.github.com> Date: Mon, 30 Mar 2026 15:05:14 -0400 Subject: [PATCH 5/5] Fix nested field aggregations --- .../java/com/google/cloud/firestore/PipelineUtils.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) 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 b04f368a6..6a9ef738a 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 @@ -174,15 +174,19 @@ static BooleanExpression toPipelineBooleanExpr(FilterInternal f) { static AliasedAggregate toPipelineAggregatorTarget(AggregateField f) { String operator = f.getOperator(); String fieldPath = f.getFieldPath(); + String alias = f.getAlias(); + if (alias.contains(".")) { + alias = "`" + alias + "`"; + } switch (operator) { case "sum": - return Field.ofServerPath(fieldPath).sum().as(f.getAlias()); + return Field.ofServerPath(fieldPath).sum().as(alias); case "count": - return countAll().as(f.getAlias()); + return countAll().as(alias); case "average": - return Field.ofServerPath(fieldPath).average().as(f.getAlias()); + return Field.ofServerPath(fieldPath).average().as(alias); default: // Handle the 'else' case appropriately in your Java code throw new IllegalArgumentException("Unsupported operator: " + operator);