From a90c0a193d237dfcd3a312474f7bafa47603500d Mon Sep 17 00:00:00 2001 From: Yvonne Pan <103622026+yvonnep165@users.noreply.github.com> Date: Mon, 30 Mar 2026 15:42:13 -0400 Subject: [PATCH 1/3] Add the parent expression --- firebase-firestore/CHANGELOG.md | 1 + firebase-firestore/api.txt | 7 +++ .../firebase/firestore/PipelineTest.java | 27 ++++++++++ .../firestore/pipeline/expressions.kt | 54 +++++++++++++++++++ 4 files changed, 89 insertions(+) diff --git a/firebase-firestore/CHANGELOG.md b/firebase-firestore/CHANGELOG.md index 52d77c8d351..2448575ad0b 100644 --- a/firebase-firestore/CHANGELOG.md +++ b/firebase-firestore/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +- [feature] Added support for `parent` Pipeline expression. - [feature] Added support for Pipeline expressions `nor` and `switchOn`. [#7903](https://github.com/firebase/firebase-android-sdk/pull/7903) - [feature] Added support for `first`, `last`, `arrayAgg`, and `arrayAggDistinct` Pipeline expressions. diff --git a/firebase-firestore/api.txt b/firebase-firestore/api.txt index f5f8ed7e6c8..10ade7c9172 100644 --- a/firebase-firestore/api.txt +++ b/firebase-firestore/api.txt @@ -1153,6 +1153,10 @@ package com.google.firebase.firestore.pipeline { method public final com.google.firebase.firestore.pipeline.BooleanExpression notEqualAny(java.util.List values); method public static final com.google.firebase.firestore.pipeline.Expression nullValue(); method public static final com.google.firebase.firestore.pipeline.BooleanExpression or(com.google.firebase.firestore.pipeline.BooleanExpression condition, com.google.firebase.firestore.pipeline.BooleanExpression... conditions); + method public final com.google.firebase.firestore.pipeline.Expression parent(); + method public static final com.google.firebase.firestore.pipeline.Expression parent(com.google.firebase.firestore.DocumentReference docRef); + method public static final com.google.firebase.firestore.pipeline.Expression parent(com.google.firebase.firestore.pipeline.Expression documentPath); + method public static final com.google.firebase.firestore.pipeline.Expression parent(String documentPath); method public final com.google.firebase.firestore.pipeline.Expression pow(com.google.firebase.firestore.pipeline.Expression exponent); method public static final com.google.firebase.firestore.pipeline.Expression pow(com.google.firebase.firestore.pipeline.Expression numericExpr, com.google.firebase.firestore.pipeline.Expression exponent); method public static final com.google.firebase.firestore.pipeline.Expression pow(com.google.firebase.firestore.pipeline.Expression numericExpr, Number exponent); @@ -1550,6 +1554,9 @@ package com.google.firebase.firestore.pipeline { method public com.google.firebase.firestore.pipeline.BooleanExpression notEqualAny(String fieldName, java.util.List values); method public com.google.firebase.firestore.pipeline.Expression nullValue(); method public com.google.firebase.firestore.pipeline.BooleanExpression or(com.google.firebase.firestore.pipeline.BooleanExpression condition, com.google.firebase.firestore.pipeline.BooleanExpression... conditions); + method public com.google.firebase.firestore.pipeline.Expression parent(com.google.firebase.firestore.DocumentReference docRef); + method public com.google.firebase.firestore.pipeline.Expression parent(com.google.firebase.firestore.pipeline.Expression documentPath); + method public com.google.firebase.firestore.pipeline.Expression parent(String documentPath); method public com.google.firebase.firestore.pipeline.Expression pow(com.google.firebase.firestore.pipeline.Expression numericExpr, com.google.firebase.firestore.pipeline.Expression exponent); method public com.google.firebase.firestore.pipeline.Expression pow(com.google.firebase.firestore.pipeline.Expression numericExpr, Number exponent); method public com.google.firebase.firestore.pipeline.Expression pow(String numericField, com.google.firebase.firestore.pipeline.Expression exponent); diff --git a/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/PipelineTest.java b/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/PipelineTest.java index f6f1f6d2d1e..bd2ad2a66d9 100644 --- a/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/PipelineTest.java +++ b/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/PipelineTest.java @@ -55,6 +55,7 @@ import static com.google.firebase.firestore.pipeline.Expression.notEqual; import static com.google.firebase.firestore.pipeline.Expression.nullValue; import static com.google.firebase.firestore.pipeline.Expression.or; +import static com.google.firebase.firestore.pipeline.Expression.parent; import static com.google.firebase.firestore.pipeline.Expression.rand; import static com.google.firebase.firestore.pipeline.Expression.split; import static com.google.firebase.firestore.pipeline.Expression.startsWith; @@ -3000,6 +3001,32 @@ public void testSupportsDocumentId() { .containsExactly(ImmutableMap.of("docId", "book4")); } + @Test + public void testSupportsParent() { + DocumentReference reviewRef = + randomCol.document("book4").collection("reviews").document("review1"); + + Task execute = + firestore + .pipeline() + .collection(randomCol.getPath()) + .limit(1) + .select( + parent(constant(reviewRef)).alias("parentRefStatic"), + constant(reviewRef).parent().alias("parentRefInstance")) + .select( + field("parentRefStatic").documentId().alias("parentIdStatic"), + field("parentRefInstance").documentId().alias("parentIdInstance")) + .execute(); + + assertThat(waitFor(execute).getResults()) + .comparingElementsUsing(DATA_CORRESPONDENCE) + .containsExactly( + ImmutableMap.of( + "parentIdStatic", "book4", + "parentIdInstance", "book4")); + } + @Test public void testDocumentsAsSource() { Task execute = diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/pipeline/expressions.kt b/firebase-firestore/src/main/java/com/google/firebase/firestore/pipeline/expressions.kt index 3ab61256422..3a7d259961e 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/pipeline/expressions.kt +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/pipeline/expressions.kt @@ -5986,6 +5986,47 @@ abstract class Expression internal constructor() { * @return A new [Expression] representing the documentId operation. */ @JvmStatic fun documentId(docRef: DocumentReference): Expression = documentId(constant(docRef)) + + /** + * Creates an expression that returns the parent document reference of a document reference. + * + * ```kotlin + * // Get the parent document reference of a document reference. + * parent(field("__path__")) + * ``` + * + * @param documentPath An expression evaluating to a document reference. + * @return A new [Expression] representing the parent operation. + */ + @JvmStatic + fun parent(documentPath: Expression): Expression = + FunctionExpression("parent", notImplemented, documentPath) + + /** + * Creates an expression that returns the parent document reference of a document reference. + * + * ```kotlin + * // Get the parent document reference of a document reference. + * parent("projects/p/databases/d/documents/c/d") + * ``` + * + * @param documentPath A string path to get the parent from. + * @return A new [Expression] representing the parent operation. + */ + @JvmStatic fun parent(documentPath: String): Expression = parent(constant(documentPath)) + + /** + * Creates an expression that returns the parent document reference of a document reference. + * + * ```kotlin + * // Get the parent document reference of a document reference. + * parent(myDocumentReference) + * ``` + * + * @param docRef A [DocumentReference] to get the parent from. + * @return A new [Expression] representing the parent operation. + */ + @JvmStatic fun parent(docRef: DocumentReference): Expression = parent(constant(docRef)) } /** @@ -6154,6 +6195,19 @@ abstract class Expression internal constructor() { */ fun documentId(): Expression = Companion.documentId(this) + /** + * Creates an expression that returns the parent document reference of this document reference + * expression. + * + * ```kotlin + * // Get the parent document reference of the 'path' field. + * field("path").parent() + * ``` + * + * @return A new [Expression] representing the parent operation. + */ + fun parent(): Expression = Companion.parent(this) + /** * Creates an expression that returns the collection ID from this path expression. * From 809499a9fc735e50bfab593961144873b53dbce0 Mon Sep 17 00:00:00 2001 From: Yvonne Pan <103622026+yvonnep165@users.noreply.github.com> Date: Mon, 30 Mar 2026 16:57:13 -0400 Subject: [PATCH 2/3] Update CHANGELOG.md --- firebase-firestore/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/firebase-firestore/CHANGELOG.md b/firebase-firestore/CHANGELOG.md index 919d70a2b55..5dfddfad5a5 100644 --- a/firebase-firestore/CHANGELOG.md +++ b/firebase-firestore/CHANGELOG.md @@ -1,6 +1,7 @@ # Unreleased - [feature] Added support for `parent` Pipeline expression. + [#7999](https://github.com/firebase/firebase-android-sdk/pull/7999) - [feature] Added support for `timestampTruncate`, `timestampDiff`, and `timestampExtract` Pipeline expressions. [#7955](https://github.com/firebase/firebase-android-sdk/pull/7955) - [feature] Pipeline operations are GA now. From def8826583ca7ddf0a19b196bd09e298fd0f3f66 Mon Sep 17 00:00:00 2001 From: Yvonne Pan <103622026+yvonnep165@users.noreply.github.com> Date: Mon, 30 Mar 2026 17:23:20 -0400 Subject: [PATCH 3/3] fix formatting error --- .../java/com/google/firebase/firestore/pipeline/expressions.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/pipeline/expressions.kt b/firebase-firestore/src/main/java/com/google/firebase/firestore/pipeline/expressions.kt index a83c91829cb..595d03450c0 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/pipeline/expressions.kt +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/pipeline/expressions.kt @@ -6899,7 +6899,7 @@ abstract class Expression internal constructor() { * @return A new [Expression] representing the parent operation. */ @JvmStatic fun parent(docRef: DocumentReference): Expression = parent(constant(docRef)) - + /** * Creates an expression that retrieves the value of a variable bound via [Pipeline.define]. *