Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions firebase-firestore/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Unreleased

- [feature] Added support for Pipeline expressions `ifNull` and `coalesce`.
[#7976](https://github.com/firebase/firebase-android-sdk/pull/7976)
- [feature] Pipeline operations are GA now.
- [feature] Added support for Pipeline expressions `nor` and `switchOn`.
[#7903](https://github.com/firebase/firebase-android-sdk/pull/7903)
Expand Down
15 changes: 15 additions & 0 deletions firebase-firestore/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -941,6 +941,9 @@ package com.google.firebase.firestore.pipeline {
method public final com.google.firebase.firestore.pipeline.Expression charLength();
method public static final com.google.firebase.firestore.pipeline.Expression charLength(com.google.firebase.firestore.pipeline.Expression expr);
method public static final com.google.firebase.firestore.pipeline.Expression charLength(String fieldName);
method public static final com.google.firebase.firestore.pipeline.Expression coalesce(com.google.firebase.firestore.pipeline.Expression expression, Object replacement, java.lang.Object... others);
method public final com.google.firebase.firestore.pipeline.Expression coalesce(Object replacement, java.lang.Object... others);
method public static final com.google.firebase.firestore.pipeline.Expression coalesce(String fieldName, Object replacement, java.lang.Object... others);
method public final com.google.firebase.firestore.pipeline.Expression collectionId();
method public static final com.google.firebase.firestore.pipeline.Expression collectionId(com.google.firebase.firestore.pipeline.Expression path);
method public static final com.google.firebase.firestore.pipeline.Expression collectionId(String pathField);
Expand Down Expand Up @@ -1056,6 +1059,12 @@ package com.google.firebase.firestore.pipeline {
method public static final com.google.firebase.firestore.pipeline.Expression ifError(com.google.firebase.firestore.pipeline.Expression tryExpr, com.google.firebase.firestore.pipeline.Expression catchExpr);
method public static final com.google.firebase.firestore.pipeline.Expression ifError(com.google.firebase.firestore.pipeline.Expression tryExpr, Object catchValue);
method public final com.google.firebase.firestore.pipeline.Expression ifError(Object catchValue);
method public final com.google.firebase.firestore.pipeline.Expression ifNull(com.google.firebase.firestore.pipeline.Expression elseExpression);
method public static final com.google.firebase.firestore.pipeline.Expression ifNull(com.google.firebase.firestore.pipeline.Expression ifExpr, com.google.firebase.firestore.pipeline.Expression elseExpr);
method public static final com.google.firebase.firestore.pipeline.Expression ifNull(com.google.firebase.firestore.pipeline.Expression ifExpr, Object elseValue);
method public final com.google.firebase.firestore.pipeline.Expression ifNull(Object elseValue);
method public static final com.google.firebase.firestore.pipeline.Expression ifNull(String ifFieldName, com.google.firebase.firestore.pipeline.Expression elseExpr);
method public static final com.google.firebase.firestore.pipeline.Expression ifNull(String ifFieldName, Object elseValue);
method public final com.google.firebase.firestore.pipeline.BooleanExpression isAbsent();
method public static final com.google.firebase.firestore.pipeline.BooleanExpression isAbsent(com.google.firebase.firestore.pipeline.Expression value);
method public static final com.google.firebase.firestore.pipeline.BooleanExpression isAbsent(String fieldName);
Expand Down Expand Up @@ -1407,6 +1416,8 @@ package com.google.firebase.firestore.pipeline {
method public com.google.firebase.firestore.pipeline.Expression ceil(String numericField);
method public com.google.firebase.firestore.pipeline.Expression charLength(com.google.firebase.firestore.pipeline.Expression expr);
method public com.google.firebase.firestore.pipeline.Expression charLength(String fieldName);
method public com.google.firebase.firestore.pipeline.Expression coalesce(com.google.firebase.firestore.pipeline.Expression expression, Object replacement, java.lang.Object... others);
method public com.google.firebase.firestore.pipeline.Expression coalesce(String fieldName, Object replacement, java.lang.Object... others);
method public com.google.firebase.firestore.pipeline.Expression collectionId(com.google.firebase.firestore.pipeline.Expression path);
method public com.google.firebase.firestore.pipeline.Expression collectionId(String pathField);
method public com.google.firebase.firestore.pipeline.Expression concat(com.google.firebase.firestore.pipeline.Expression first, com.google.firebase.firestore.pipeline.Expression second, java.lang.Object... others);
Expand Down Expand Up @@ -1486,6 +1497,10 @@ package com.google.firebase.firestore.pipeline {
method public com.google.firebase.firestore.pipeline.BooleanExpression ifError(com.google.firebase.firestore.pipeline.BooleanExpression tryExpr, com.google.firebase.firestore.pipeline.BooleanExpression catchExpr);
method public com.google.firebase.firestore.pipeline.Expression ifError(com.google.firebase.firestore.pipeline.Expression tryExpr, com.google.firebase.firestore.pipeline.Expression catchExpr);
method public com.google.firebase.firestore.pipeline.Expression ifError(com.google.firebase.firestore.pipeline.Expression tryExpr, Object catchValue);
method public com.google.firebase.firestore.pipeline.Expression ifNull(com.google.firebase.firestore.pipeline.Expression ifExpr, com.google.firebase.firestore.pipeline.Expression elseExpr);
method public com.google.firebase.firestore.pipeline.Expression ifNull(com.google.firebase.firestore.pipeline.Expression ifExpr, Object elseValue);
method public com.google.firebase.firestore.pipeline.Expression ifNull(String ifFieldName, com.google.firebase.firestore.pipeline.Expression elseExpr);
method public com.google.firebase.firestore.pipeline.Expression ifNull(String ifFieldName, Object elseValue);
method public com.google.firebase.firestore.pipeline.BooleanExpression isAbsent(com.google.firebase.firestore.pipeline.Expression value);
method public com.google.firebase.firestore.pipeline.BooleanExpression isAbsent(String fieldName);
method public com.google.firebase.firestore.pipeline.BooleanExpression isError(com.google.firebase.firestore.pipeline.Expression expr);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3169,6 +3169,82 @@ public void testIfAbsent() {
.containsExactly(ImmutableMap.of("res", "Frank Herbert"));
}

@Test
public void testIfNull() {
Task<Pipeline.Snapshot> execute =
firestore
.pipeline()
.collection(randomCol)
.limit(1)
.replaceWith(map(ImmutableMap.of("title", "foo", "name", Expression.nullValue())))
.select(
Expression.ifNull("title", "default title").alias("staticMethod"),
field("title").ifNull("default title").alias("instanceMethod"),
field("name").ifNull(field("title")).alias("nameOrTitle"),
field("name").ifNull("default name").alias("fieldIsNull"),
field("absent").ifNull("default name").alias("fieldIsAbsent"))
.execute();

assertThat(waitFor(execute).getResults())
.comparingElementsUsing(DATA_CORRESPONDENCE)
.containsExactly(
mapOfEntries(
entry("staticMethod", "foo"),
entry("instanceMethod", "foo"),
entry("nameOrTitle", "foo"),
entry("fieldIsNull", "default name"),
entry("fieldIsAbsent", "default name")));
}

@Test
public void testCoalesce() {
assumeFalse("Coalesce is not supported against the emulator.", isRunningAgainstEmulator());

Task<Pipeline.Snapshot> execute =
firestore
.pipeline()
.collection(randomCol)
.limit(1)
.replaceWith(
map(
ImmutableMap.of(
"numberValue",
1L,
"stringValue",
"unique_coalesce_hello",
"booleanValue",
false,
"nullValue",
Expression.nullValue(),
"nullValue2",
Expression.nullValue())))
.select(
Expression.coalesce(field("numberValue"), field("stringValue"))
.alias("staticMethod"),
field("numberValue").coalesce(field("stringValue")).alias("instanceMethod"),
Expression.coalesce(field("nullValue"), field("stringValue")).alias("firstIsNull"),
Expression.coalesce(field("nullValue"), field("nullValue2"), field("booleanValue"))
.alias("lastIsNotNull"),
Expression.coalesce(field("nullValue"), field("nullValue2")).alias("allFieldsNull"),
Expression.coalesce(field("nullValue"), field("nullValue2"), constant("default"))
.alias("allFieldsNullWithDefault"),
Expression.coalesce(field("absentField"), field("numberValue"), constant("default"))
.alias("withAbsentField"))
.execute();

assertThat(waitFor(execute).getResults())
.comparingElementsUsing(DATA_CORRESPONDENCE)
.containsExactly(
mapOfEntries(
entry("staticMethod", 1L),
entry("instanceMethod", 1L),
entry("firstIsNull", "unique_coalesce_hello"),
entry("lastIsNotNull", false),
entry("allFieldsNull", null),
entry("allFieldsNullWithDefault", "default"),
entry("withAbsentField", 1L)));
}

@Test
public void testCrossDatabaseRejection() {
FirebaseFirestore firestore2 = IntegrationTestUtil.testAlternateFirestore();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5921,6 +5921,120 @@ abstract class Expression internal constructor() {
fun ifAbsent(ifFieldName: String, elseValue: Any): Expression =
FunctionExpression("if_absent", notImplemented, ifFieldName, elseValue)

/**
* Creates an expression that returns the [elseExpr] argument if [ifExpr] evaluates to null,
* else return the result of the [ifExpr] argument evaluation.
*
* This function provides a fallback for both absent and explicit null values. In contrast,
* [ifAbsent] only triggers for missing fields.
*
* ```kotlin
* // Returns the user's preferred name, or if that is null, returns their full name.
* ifNull(field("preferredName"), field("fullName"))
* ```
*
* @param ifExpr The expression to check for null.
* @param elseExpr The expression that will be evaluated and returned if [ifExpr] is null.
* @return A new [Expression] representing the ifNull operation.
*/
@JvmStatic
fun ifNull(ifExpr: Expression, elseExpr: Expression): Expression =
FunctionExpression("if_null", notImplemented, ifExpr, elseExpr)

/**
* Creates an expression that returns the [elseValue] argument if [ifExpr] evaluates to null,
* else return the result of the [ifExpr] argument evaluation.
*
* This function provides a fallback for both absent and explicit null values. In contrast,
* [ifAbsent] only triggers for missing fields.
*
* ```kotlin
* // Returns the user's display name, or returns "Anonymous" if the field is null.
* ifNull(field("displayName"), "Anonymous")
* ```
*
* @param ifExpr The expression to check for null.
* @param elseValue The value that will be returned if [ifExpr] evaluates to null.
* @return A new [Expression] representing the ifNull operation.
*/
@JvmStatic
fun ifNull(ifExpr: Expression, elseValue: Any): Expression =
FunctionExpression("if_null", notImplemented, ifExpr, elseValue)

/**
* Creates an expression that returns the [elseExpr] argument if [ifFieldName] field is null,
* else return the value of the field.
*
* ```kotlin
* // Returns the user's preferred name, or if that is null, returns their full name.
* ifNull("preferredName", field("fullName"))
* ```
*
* @param ifFieldName The field to check for null.
* @param elseExpr The expression that will be evaluated and returned if [ifFieldName] is null.
* @return A new [Expression] representing the ifNull operation.
*/
@JvmStatic
fun ifNull(ifFieldName: String, elseExpr: Expression): Expression =
FunctionExpression("if_null", notImplemented, ifFieldName, elseExpr)

/**
* Creates an expression that returns the [elseValue] argument if [ifFieldName] field is null,
* else return the value of the field.
*
* ```kotlin
* // Returns the user's display name, or returns "Anonymous" if the field is null.
* ifNull("displayName", "Anonymous")
* ```
*
* @param ifFieldName The field to check for null.
* @param elseValue The value that will be returned if [ifFieldName] is null.
* @return A new [Expression] representing the ifNull operation.
*/
@JvmStatic
fun ifNull(ifFieldName: String, elseValue: Any): Expression =
FunctionExpression("if_null", notImplemented, ifFieldName, elseValue)

/**
* Creates an expression that returns the first non-null, non-absent argument, without
* evaluating the rest of the arguments. When all arguments are null or absent, returns the last
* argument.
*
* ```kotlin
* // Returns the value of the first non-null, non-absent field among 'preferredName', 'fullName',
* // or the last argument if all previous fields are null.
* coalesce(field("preferredName"), field("fullName"), constant("Anonymous"))
* ```
*
* @param expression The first expression to check for null.
* @param replacement The fallback expression or value if the first one is null.
* @param others Optional additional expressions to check if previous ones are null.
* @return A new [Expression] representing the coalesce operation.
*/
@JvmStatic
fun coalesce(expression: Expression, replacement: Any, vararg others: Any): Expression =
FunctionExpression("coalesce", notImplemented, expression, replacement, *others)

/**
* Creates an expression that returns the first non-null, non-absent argument, without
* evaluating the rest of the arguments. When all arguments are null or absent, returns the last
* argument.
*
* ```kotlin
* // Returns the value of the first non-null, non-absent field among 'preferredName', 'fullName',
* // or the last argument if all previous fields are null.
* coalesce("preferredName", field("fullName"), constant("Anonymous"))
* ```
*
* @param fieldName The name of the first field to check for null.
* @param replacement The fallback expression or value if the first one is null.
* @param others Optional additional expressions to check if previous ones are null.
* @return A new [Expression] representing the coalesce operation.
*/
@JvmStatic
fun coalesce(fieldName: String, replacement: Any, vararg others: Any): Expression =
FunctionExpression("coalesce", notImplemented, fieldName, replacement, *others)

/**
* Creates an expression that returns the collection ID from a path.
*
Expand Down Expand Up @@ -8193,6 +8307,58 @@ abstract class Expression internal constructor() {
*/
fun ifAbsent(elseValue: Any): Expression = Companion.ifAbsent(this, elseValue)

/**
* Creates an expression that returns the [elseExpression] argument if this expression evaluates
* to null, else return the result of this expression.
*
* This function provides a fallback for both absent and explicit null values. In contrast,
* [ifAbsent] only triggers for missing fields.
*
* ```kotlin
* // Returns the user's preferred name, or if that is null, returns their full name.
* field("preferredName").ifNull(field("fullName"))
* ```
*
* @param elseExpression The expression that will be evaluated and returned if this expression is
* null.
* @return A new [Expression] representing the ifNull operation.
*/
fun ifNull(elseExpression: Expression): Expression = Companion.ifNull(this, elseExpression)

/**
* Creates an expression that returns the [elseValue] argument if this expression evaluates to
* null, else return the result of this expression.
*
* This function provides a fallback for both absent and explicit null values. In contrast,
* [ifAbsent] only triggers for missing fields.
*
* ```kotlin
* // Returns the user's display name, or returns "Anonymous" if the field is null.
* field("displayName").ifNull("Anonymous")
* ```
*
* @param elseValue The value that will be returned if this expression evaluates to null.
* @return A new [Expression] representing the ifNull operation.
*/
fun ifNull(elseValue: Any): Expression = Companion.ifNull(this, elseValue)

/**
* Creates an expression that returns the first non-null, non-absent argument, without evaluating
* the rest of the arguments. When all arguments are null or absent, returns the last argument.
*
* ```kotlin
* // Returns the value of the first non-null, non-absent field among 'preferredName', 'fullName',
* // or the last argument if all previous fields are null.
* field("preferredName").coalesce(field("fullName"), "Anonymous")
* ```
*
* @param replacement The fallback expression or value if the first one is null.
* @param others Optional additional expressions to check if previous ones are null.
* @return A new [Expression] representing the coalesce operation.
*/
fun coalesce(replacement: Any, vararg others: Any): Expression =
Companion.coalesce(this, replacement, *others)

/**
* Creates an expression that checks if this expression produces an error.
*
Expand Down
Loading