Skip to content
This repository was archived by the owner on Aug 30, 2026. It is now read-only.
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,100 @@ public static Expression ifAbsent(String ifFieldName, Object elseValue) {
return ifAbsent(field(ifFieldName), toExprOrConstant(elseValue));
}

/**
* Creates an expression that returns a default value if an expression evaluates to null.
*
* <p>Note: This function provides a fallback for both absent and explicit null values. In
* contrast, {@link ifAbsent} only triggers for missing fields.
*
* @param ifExpr The expression to check.
* @param elseExpression The default expression that will be evaluated and returned.
* @return A new {@link Expression} representing the ifNull operation.
*/
@BetaApi
public static Expression ifNull(Expression ifExpr, Expression elseExpression) {
return new FunctionExpression("if_null", ImmutableList.of(ifExpr, elseExpression));
}

/**
* Creates an expression that returns a default value if an expression evaluates to null.
*
* <p>Note: This function provides a fallback for both absent and explicit null values. In
* contrast, {@link ifAbsent} only triggers for missing fields.
*
* @param ifExpr The expression to check.
* @param elseValue The default value that will be returned.
* @return A new {@link Expression} representing the ifNull operation.
*/
@BetaApi
public static Expression ifNull(Expression ifExpr, Object elseValue) {
return ifNull(ifExpr, toExprOrConstant(elseValue));
}

/**
* Creates an expression that returns a default value if a field is null.
*
* <p>Note: This function provides a fallback for both absent and explicit null values. In
* contrast, {@link ifAbsent} only triggers for missing fields.
*
* @param ifFieldName The field to check.
* @param elseExpression The default expression that will be evaluated and returned.
* @return A new {@link Expression} representing the ifNull operation.
*/
@BetaApi
public static Expression ifNull(String ifFieldName, Expression elseExpression) {
return ifNull(field(ifFieldName), elseExpression);
}

/**
* Creates an expression that returns a default value if a field is null.
*
* <p>Note: This function provides a fallback for both absent and explicit null values. In
* contrast, {@link ifAbsent} only triggers for missing fields.
*
* @param ifFieldName The field to check.
* @param elseValue The default value that will be returned.
* @return A new {@link Expression} representing the ifNull operation.
*/
@BetaApi
public static Expression ifNull(String ifFieldName, Object elseValue) {
return ifNull(field(ifFieldName), toExprOrConstant(elseValue));
}

/**
* 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.
*
* @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 {@link Expression} representing the coalesce operation.
*/
@BetaApi
public static Expression coalesce(Expression expression, Object replacement, Object... others) {
ImmutableList.Builder<Expression> args = ImmutableList.builder();
args.add(expression);
args.add(toExprOrConstant(replacement));
for (Object other : others) {
args.add(toExprOrConstant(other));
}
return new FunctionExpression("coalesce", args.build());
}

/**
* 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.
*
* @param firstFieldName 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 {@link Expression} representing the coalesce operation.
*/
@BetaApi
public static Expression coalesce(String firstFieldName, Object replacement, Object... others) {
return coalesce(field(firstFieldName), replacement, others);
}

/**
* Creates an expression that joins the elements of an array into a string.
*
Expand Down Expand Up @@ -5118,6 +5212,33 @@ public Expression ifAbsent(Object elseValue) {
return Expression.ifAbsent(this, elseValue);
}

/**
* Creates an expression that returns a default value if this expression evaluates null.
*
* <p>Note: This function provides a fallback for both absent and explicit null values. In
* contrast, {@link ifAbsent} only triggers for missing fields.
*
* @param elseValue The default value that will be returned.
* @return A new {@link Expression} representing the ifNull operation.
*/
@BetaApi
public Expression ifNull(Object elseValue) {
return Expression.ifNull(this, elseValue);
}

/**
* 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.
*
* @param second The next expression or literal to evaluate.
* @param others Additional expressions or literals to evaluate.
* @return A new {@link Expression} representing the coalesce operation.
*/
@BetaApi
public Expression coalesce(Object second, Object... others) {
return Expression.coalesce(this, second, others);
}

/**
* Creates an expression that joins the elements of this array expression into a string.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3185,6 +3185,92 @@ public void testIfAbsent() throws Exception {
assertThat(data(results)).containsExactly(map("res", "Frank Herbert"));
}

@Test
public void testIfNull() throws Exception {
List<PipelineResult> results =
firestore
.pipeline()
.collection(collection.getPath())
.limit(1)
.replaceWith(Expression.map(map("title", "foo", "name", null)))
.select(
Expression.ifNull("title", "default title").as("staticMethod"),
field("title").ifNull("default title").as("instanceMethod"),
field("name").ifNull(field("title")).as("nameOrTitle"),
field("name").ifNull("default name").as("fieldIsNull"),
field("absent").ifNull("default name").as("fieldIsAbsent"))
.execute()
.get()
.getResults();

assertThat(data(results))
.containsExactly(
map(
"staticMethod", "foo",
"instanceMethod", "foo",
"nameOrTitle", "foo",
"fieldIsNull", "default name",
"fieldIsAbsent", "default name"));
}

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

List<PipelineResult> results =
firestore
.pipeline()
.collection(collection.getPath())
.limit(1)
.replaceWith(
Expression.map(
map(
"numberValue",
1L,
"stringValue",
"hello",
"booleanValue",
false,
"nullValue",
null,
"nullValue2",
null)))
.select(
Expression.coalesce(field("numberValue"), field("stringValue")).as("staticMethod"),
field("numberValue").coalesce(field("stringValue")).as("instanceMethod"),
Expression.coalesce(field("nullValue"), field("stringValue")).as("firstIsNull"),
Expression.coalesce(field("nullValue"), field("nullValue2"), field("booleanValue"))
.as("lastIsNotNull"),
Expression.coalesce(field("nullValue"), field("nullValue2")).as("allFieldsNull"),
Expression.coalesce(field("nullValue"), field("nullValue2"), constant("default"))
.as("allFieldsNullWithDefault"),
Expression.coalesce(field("absentField"), field("numberValue"), constant("default"))
.as("withAbsentField"))
.execute()
.get()
.getResults();

assertThat(data(results))
.containsExactly(
map(
"staticMethod",
1L,
"instanceMethod",
1L,
"firstIsNull",
"hello",
"lastIsNotNull",
false,
"allFieldsNull",
null,
"allFieldsNullWithDefault",
"default",
"withAbsentField",
1L));
}

@Test
public void testJoin() throws Exception {
// Test join with a constant delimiter
Expand Down
Loading