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 695051c20..f0b8b9e2e 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 @@ -370,6 +370,46 @@ public static BooleanExpression or(BooleanExpression condition, BooleanExpressio return new BooleanFunctionExpression("or", builder.build()); } + /** + * Creates an expression that performs a logical 'NOR' operation. + * + * @param condition The first {@link BooleanExpression}. + * @param conditions Additional {@link BooleanExpression}s. + * @return A new {@link BooleanExpression} representing the logical 'NOR' operation. + */ + @BetaApi + public static BooleanExpression nor( + BooleanExpression condition, BooleanExpression... conditions) { + ImmutableList.Builder builder = ImmutableList.builder(); + builder.add(condition); + builder.add(conditions); + System.out.println("nor: " + builder.build()); + return new BooleanFunctionExpression("nor", builder.build()); + } + + /** + * Creates an expression that evaluates to the result corresponding to the first true condition. + * + *

This function behaves like a `switch` statement. It accepts an alternating sequence of + * conditions and their corresponding results. If an odd number of arguments is provided, the + * final argument serves as a default fallback result. If no default is provided and no condition + * evaluates to true, it throws an error. + * + * @param condition The first {@link BooleanExpression}. + * @param result The result if the first condition is true. + * @param others Additional conditions and results, and optionally a default value. + * @return A new {@link Expression} representing the switchOn operation. + */ + @BetaApi + public static Expression switchOn( + BooleanExpression condition, Expression result, Object... others) { + ImmutableList.Builder builder = ImmutableList.builder(); + builder.add(condition); + builder.add(result); + builder.addAll(toArrayOfExprOrConstant(others)); + return new FunctionExpression("switch_on", builder.build()); + } + /** * Creates an expression that performs a logical 'XOR' operation. * 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 e14fa8a94..962ca1108 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 @@ -67,6 +67,7 @@ import static com.google.cloud.firestore.pipeline.expressions.Expression.logicalMinimum; import static com.google.cloud.firestore.pipeline.expressions.Expression.mapMerge; import static com.google.cloud.firestore.pipeline.expressions.Expression.mapRemove; +import static com.google.cloud.firestore.pipeline.expressions.Expression.nor; 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; @@ -79,6 +80,7 @@ import static com.google.cloud.firestore.pipeline.expressions.Expression.stringConcat; import static com.google.cloud.firestore.pipeline.expressions.Expression.substring; import static com.google.cloud.firestore.pipeline.expressions.Expression.subtract; +import static com.google.cloud.firestore.pipeline.expressions.Expression.switchOn; import static com.google.cloud.firestore.pipeline.expressions.Expression.timestampAdd; import static com.google.cloud.firestore.pipeline.expressions.Expression.timestampToUnixMicros; import static com.google.cloud.firestore.pipeline.expressions.Expression.timestampToUnixMillis; @@ -845,6 +847,78 @@ public void whereByOrCondition() throws Exception { map("title", "1984"))); } + @Test + public void whereByNorCondition() throws Exception { + List results = + firestore + .pipeline() + .createFrom(collection) + .where( + nor( + equal("genre", "Romance"), + equal("genre", "Dystopian"), + equal("genre", "Fantasy"), + greaterThan("published", 1949))) + .select("title") + .execute() + .get() + .getResults(); + + assertThat(data(results)) + .containsExactlyElementsIn( + Lists.newArrayList( + map("title", "Crime and Punishment"), + map("title", "The Great Gatsby"), + map("title", "Timestamp Book"))); + } + + @Test + public void selectWithSwitchOn() throws Exception { + List results = + firestore + .pipeline() + .createFrom(collection) + .limit(1) + .replaceWith(Expression.map(map("value", 2))) + .select( + switchOn(equal(field("value"), 2), constant("two"), constant("NA")).as("result1"), + switchOn(equal(field("value"), 3), constant("three"), constant("NA")).as("result2"), + switchOn( + equal(field("value"), 1), + constant("one"), + equal(field("value"), 2), + constant("two"), + equal(field("value"), 3), + constant("three"), + constant("default")) + .as("result3")) + .execute() + .get() + .getResults(); + assertThat(data(results)) + .isEqualTo(Lists.newArrayList(map("result1", "two", "result2", "NA", "result3", "two"))); + + // throws if no match and no default + ExecutionException exception = + assertThrows( + ExecutionException.class, + () -> + firestore + .pipeline() + .createFrom(collection) + .limit(1) + .replaceWith(Expression.map(map("value", 5))) + .select( + switchOn( + equal(field("value"), 1), constant("one"), + equal(field("value"), 2), constant("two")) + .as("result")) + .execute() + .get() + .getResults()); + assertThat(exception).hasMessageThat().contains("all switch cases evaluate to false"); + } + @Test public void testPipelineWithOffsetAndLimit() throws Exception { List results =