Skip to content

Commit 4e4e6ef

Browse files
feat: Add support for the parent expression and fix integration tests error (#2351)
* Add support for the parent expression * chore: generate libraries at Mon Mar 30 16:02:57 UTC 2026 * Fix integration tests error due to changes in backend implementation * chore: generate libraries at Mon Mar 30 18:21:43 UTC 2026 * Fix nested field aggregations --------- Co-authored-by: cloud-java-bot <cloud-java-bot@google.com>
1 parent 5fd457b commit 4e4e6ef

3 files changed

Lines changed: 89 additions & 9 deletions

File tree

java-firestore/google-cloud-firestore/src/main/java/com/google/cloud/firestore/PipelineUtils.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -174,15 +174,19 @@ static BooleanExpression toPipelineBooleanExpr(FilterInternal f) {
174174
static AliasedAggregate toPipelineAggregatorTarget(AggregateField f) {
175175
String operator = f.getOperator();
176176
String fieldPath = f.getFieldPath();
177+
String alias = f.getAlias();
178+
if (alias.contains(".")) {
179+
alias = "`" + alias + "`";
180+
}
177181

178182
switch (operator) {
179183
case "sum":
180-
return Field.ofServerPath(fieldPath).sum().as(f.getAlias());
184+
return Field.ofServerPath(fieldPath).sum().as(alias);
181185

182186
case "count":
183-
return countAll().as(f.getAlias());
187+
return countAll().as(alias);
184188
case "average":
185-
return Field.ofServerPath(fieldPath).average().as(f.getAlias());
189+
return Field.ofServerPath(fieldPath).average().as(alias);
186190
default:
187191
// Handle the 'else' case appropriately in your Java code
188192
throw new IllegalArgumentException("Unsupported operator: " + operator);

java-firestore/google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/expressions/Expression.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4364,6 +4364,39 @@ public static Expression collectionId(String pathFieldName) {
43644364
return collectionId(field(pathFieldName));
43654365
}
43664366

4367+
/**
4368+
* Creates an expression that returns the parent document of a document reference.
4369+
*
4370+
* @param documentPath An expression that evaluates to a document path.
4371+
* @return A new {@link Expression} representing the parent operation.
4372+
*/
4373+
@BetaApi
4374+
public static Expression parent(Expression documentPath) {
4375+
return new FunctionExpression("parent", ImmutableList.of(documentPath));
4376+
}
4377+
4378+
/**
4379+
* Creates an expression that returns the parent document of a document reference.
4380+
*
4381+
* @param documentPath The string representation of the document path.
4382+
* @return A new {@link Expression} representing the parent operation.
4383+
*/
4384+
@BetaApi
4385+
public static Expression parent(String documentPath) {
4386+
return parent(constant(documentPath));
4387+
}
4388+
4389+
/**
4390+
* Creates an expression that returns the parent document of a document reference.
4391+
*
4392+
* @param docRef The {@link DocumentReference}.
4393+
* @return A new {@link Expression} representing the parent operation.
4394+
*/
4395+
@BetaApi
4396+
public static Expression parent(DocumentReference docRef) {
4397+
return parent(constant(docRef));
4398+
}
4399+
43674400
// Type Checking Functions
43684401
/**
43694402
* Creates an expression that checks if a field exists.
@@ -7016,6 +7049,16 @@ public final Expression collectionId() {
70167049
return collectionId(this);
70177050
}
70187051

7052+
/**
7053+
* Creates an expression that returns the parent document of a document reference.
7054+
*
7055+
* @return A new {@link Expression} representing the parent operation.
7056+
*/
7057+
@BetaApi
7058+
public final Expression parent() {
7059+
return parent(this);
7060+
}
7061+
70197062
/**
70207063
* Creates an expression that returns a string indicating the type of the value this expression
70217064
* evaluates to.

java-firestore/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITPipelineTest.java

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
import static com.google.cloud.firestore.pipeline.expressions.Expression.notEqual;
7373
import static com.google.cloud.firestore.pipeline.expressions.Expression.nullValue;
7474
import static com.google.cloud.firestore.pipeline.expressions.Expression.or;
75+
import static com.google.cloud.firestore.pipeline.expressions.Expression.parent;
7576
import static com.google.cloud.firestore.pipeline.expressions.Expression.pow;
7677
import static com.google.cloud.firestore.pipeline.expressions.Expression.rand;
7778
import static com.google.cloud.firestore.pipeline.expressions.Expression.regexMatch;
@@ -109,6 +110,7 @@
109110
import com.google.cloud.Timestamp;
110111
import com.google.cloud.firestore.Blob;
111112
import com.google.cloud.firestore.CollectionReference;
113+
import com.google.cloud.firestore.DocumentReference;
112114
import com.google.cloud.firestore.Firestore;
113115
import com.google.cloud.firestore.FirestoreOptions;
114116
import com.google.cloud.firestore.GeoPoint;
@@ -2295,7 +2297,7 @@ public void testChecks() throws Exception {
22952297
.select(
22962298
field("rating").equal(nullValue()).as("ratingIsNull"),
22972299
field("rating").equal(Double.NaN).as("ratingIsNaN"),
2298-
// arrayGet("title", 0) evaluates to UNSET so it is not an error
2300+
// arrayGet("title", 0) evaluates to ERROR
22992301
arrayGet("title", 0).isError().as("isError"),
23002302
arrayGet("title", 0).ifError(constant("was error")).as("ifError"),
23012303
field("foo").isAbsent().as("isAbsent"),
@@ -2316,7 +2318,9 @@ public void testChecks() throws Exception {
23162318
"ratingIsNaN",
23172319
false,
23182320
"isError",
2319-
false,
2321+
true,
2322+
"ifError",
2323+
"was error",
23202324
"isAbsent",
23212325
true,
23222326
"titleIsNotNull",
@@ -3535,8 +3539,8 @@ public void testNestedFields() throws Exception {
35353539
assertThat(data(results))
35363540
.isEqualTo(
35373541
Lists.newArrayList(
3538-
map("title", "The Hitchhiker's Guide to the Galaxy", "awards.hugo", true),
3539-
map("title", "Dune", "awards.hugo", true)));
3542+
map("title", "The Hitchhiker's Guide to the Galaxy", "awards", map("hugo", true)),
3543+
map("title", "Dune", "awards", map("hugo", true))));
35403544
}
35413545

35423546
@Test
@@ -3559,8 +3563,12 @@ public void testPipelineInTransactions() throws Exception {
35593563
assertThat(data(results))
35603564
.isEqualTo(
35613565
Lists.newArrayList(
3562-
map("title", "The Hitchhiker's Guide to the Galaxy", "awards.hugo", true),
3563-
map("title", "Dune", "awards.hugo", true)));
3566+
map(
3567+
"title",
3568+
"The Hitchhiker's Guide to the Galaxy",
3569+
"awards",
3570+
map("hugo", true)),
3571+
map("title", "Dune", "awards", map("hugo", true))));
35643572

35653573
transaction.update(collection.document("book1"), map("foo", "bar"));
35663574

@@ -4292,4 +4300,29 @@ public void disallowDuplicateAliasesAcrossStages() {
42924300
});
42934301
assertThat(exception).hasMessageThat().contains("Duplicate alias or field name");
42944302
}
4303+
4304+
@Test
4305+
public void testSupportsParent() throws Exception {
4306+
DocumentReference docRef =
4307+
collection.document("book4").collection("reviews").document("review1");
4308+
4309+
Pipeline pipeline =
4310+
firestore
4311+
.pipeline()
4312+
.collection(collection.getPath())
4313+
.limit(1)
4314+
.select(
4315+
parent(docRef).as("parentRefStatic"),
4316+
constant(docRef).parent().as("parentRefInstance"))
4317+
.select(
4318+
field("parentRefStatic").documentId().as("parentIdStatic"),
4319+
field("parentRefInstance").documentId().as("parentIdInstance"));
4320+
4321+
List<PipelineResult> results = pipeline.execute().get().getResults();
4322+
assertThat(results).hasSize(1);
4323+
Map<String, Object> data = results.get(0).getData();
4324+
4325+
assertThat(data.get("parentIdStatic")).isEqualTo("book4");
4326+
assertThat(data.get("parentIdInstance")).isEqualTo("book4");
4327+
}
42954328
}

0 commit comments

Comments
 (0)