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); 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..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 @@ -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; @@ -2295,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"), @@ -2316,7 +2318,9 @@ public void testChecks() throws Exception { "ratingIsNaN", false, "isError", - false, + true, + "ifError", + "was error", "isAbsent", true, "titleIsNotNull", @@ -3535,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 @@ -3559,8 +3563,12 @@ 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")); @@ -4292,4 +4300,29 @@ 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"); + } }