From 410953d8ba6ab269cd7a326cc41658b17a3be42a Mon Sep 17 00:00:00 2001 From: Mila <107142260+milaGGL@users.noreply.github.com> Date: Fri, 27 Feb 2026 14:28:23 -0500 Subject: [PATCH 1/2] add array expressions --- .../pipeline/expressions/Expression.java | 623 ++++++++++++++++++ .../cloud/firestore/it/ITPipelineTest.java | 609 +++++++++++++++++ .../example/firestore/PipelineSnippets.java | 143 ++++ 3 files changed, 1375 insertions(+) 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 363046c39..34431851b 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 @@ -2217,6 +2217,435 @@ public static Expression arrayLength(String arrayFieldName) { return arrayLength(field(arrayFieldName)); } + /** + * Creates an expression that returns the first element of an array. + * + * @param array The expression representing the array. + * @return A new {@link Expression} representing the first element of the array. + */ + @BetaApi + public static Expression arrayFirst(Expression array) { + return new FunctionExpression("array_first", ImmutableList.of(array)); + } + + /** + * Creates an expression that returns the first element of an array. + * + * @param arrayFieldName The field name of the array. + * @return A new {@link Expression} representing the first element of the array. + */ + @BetaApi + public static Expression arrayFirst(String arrayFieldName) { + return arrayFirst(field(arrayFieldName)); + } + + /** + * Creates an expression that returns the first n elements of an array. + * + * @param array The expression representing the array. + * @param n The Expression evaluates to the number of elements to return. + * @return A new {@link Expression} representing the first n elements of the array. + */ + @BetaApi + public static Expression arrayFirstN(Expression array, Expression n) { + return new FunctionExpression("array_first_n", ImmutableList.of(array, n)); + } + + /** + * Creates an expression that returns the first n elements of an array. + * + * @param array The expression representing the array. + * @param n The number of elements to return. + * @return A new {@link Expression} representing the first n elements of the array. + */ + @BetaApi + public static Expression arrayFirstN(Expression array, int n) { + return arrayFirstN(array, constant(n)); + } + + /** + * Creates an expression that returns the first n elements of an array. + * + * @param arrayFieldName The field name of the array. + * @param n The number of elements to return. + * @return A new {@link Expression} representing the first n elements of the array. + */ + @BetaApi + public static Expression arrayFirstN(String arrayFieldName, int n) { + return arrayFirstN(field(arrayFieldName), constant(n)); + } + + /** + * Creates an expression that returns the first n elements of an array. + * + * @param array The expression representing the array. + * @param n The Expression evaluates to the number of elements to return. + * @return A new {@link Expression} representing the first n elements of the array. + */ + @BetaApi + public static Expression arrayFirstN(String arrayFieldName, Expression n) { + return arrayFirstN(field(arrayFieldName), n); + } + + /** + * Creates an expression that returns the last element of an array. + * + * @param array The expression representing the array. + * @return A new {@link Expression} representing the last element of the array. + */ + @BetaApi + public static Expression arrayLast(Expression array) { + return new FunctionExpression("array_last", ImmutableList.of(array)); + } + + /** + * Creates an expression that returns the last element of an array. + * + * @param arrayFieldName The field name of the array. + * @return A new {@link Expression} representing the last element of the array. + */ + @BetaApi + public static Expression arrayLast(String arrayFieldName) { + return arrayLast(field(arrayFieldName)); + } + + /** + * Creates an expression that returns the last n elements of an array. + * + * @param array The expression representing the array. + * @param n The Expression evaluates to the number of elements to return. + * @return A new {@link Expression} representing the last n elements of the array. + */ + @BetaApi + public static Expression arrayLastN(Expression array, Expression n) { + return new FunctionExpression("array_last_n", ImmutableList.of(array, n)); + } + + /** + * Creates an expression that returns the last n elements of an array. + * + * @param array The expression representing the array. + * @param n The number of elements to return. + * @return A new {@link Expression} representing the last n elements of the array. + */ + @BetaApi + public static Expression arrayLastN(Expression array, int n) { + return arrayLastN(array, constant(n)); + } + + /** + * Creates an expression that returns the last n elements of an array. + * + * @param arrayFieldName The field name of the array. + * @param n The number of elements to return. + * @return A new {@link Expression} representing the last n elements of the array. + */ + @BetaApi + public static Expression arrayLastN(String arrayFieldName, int n) { + return arrayLastN(field(arrayFieldName), n); + } + + /** + * Creates an expression that returns the last n elements of an array. + * + * @param array The expression representing the array. + * @param n The Expression evaluates to the number of elements to return. + * @return A new {@link Expression} representing the last n elements of the array. + */ + @BetaApi + public static Expression arrayLastN(String arrayFieldName, Expression n) { + return arrayLastN(field(arrayFieldName), n); + } + + /** + * Creates an expression that returns the minimum value of an array. + * + * @param array The expression representing the array. + * @return A new {@link Expression} representing the minimum value of the array. + */ + @BetaApi + public static Expression arrayMinimum(Expression array) { + return new FunctionExpression("minimum", ImmutableList.of(array)); + } + + /** + * Creates an expression that returns the minimum value of an array. + * + * @param arrayFieldName The field name of the array. + * @return A new {@link Expression} representing the minimum value of the array. + */ + @BetaApi + public static Expression arrayMinimum(String arrayFieldName) { + return arrayMinimum(field(arrayFieldName)); + } + + /** + * Creates an expression that returns the n minimum values of an array. + * + * @param array The expression representing the array. + * @param n The number of elements to return. + * @return A new {@link Expression} representing the n minimum values of the array. + */ + @BetaApi + public static Expression arrayMinimumN(Expression array, Expression n) { + return new FunctionExpression("minimum_n", ImmutableList.of(array, n)); + } + + /** + * Creates an expression that returns the n minimum values of an array. + * + * @param array The expression representing the array. + * @param n The Expression evaluates to the number of elements to return. + * @return A new {@link Expression} representing the n minimum values of the array. + */ + @BetaApi + public static Expression arrayMinimumN(Expression array, int n) { + return arrayMinimumN(array, constant(n)); + } + + /** + * Creates an expression that returns the n minimum values of an array. + * + * @param arrayFieldName The field name of the array. + * @param n The number of elements to return. + * @return A new {@link Expression} representing the n minimum values of the array. + */ + @BetaApi + public static Expression arrayMinimumN(String arrayFieldName, int n) { + return arrayMinimumN(field(arrayFieldName), n); + } + + /** + * Creates an expression that returns the n minimum values of an array. + * + * @param array The expression representing the array. + * @param n The Expression evaluates to the number of elements to return. + * @return A new {@link Expression} representing the n minimum values of the array. + */ + @BetaApi + public static Expression arrayMinimumN(String arrayFieldName, Expression n) { + return arrayMinimumN(field(arrayFieldName), n); + } + + /** + * Creates an expression that returns the maximum value of an array. + * + * @param array The expression representing the array. + * @return A new {@link Expression} representing the maximum value of the array. + */ + @BetaApi + public static Expression arrayMaximum(Expression array) { + return new FunctionExpression("maximum", ImmutableList.of(array)); + } + + /** + * Creates an expression that returns the maximum value of an array. + * + * @param arrayFieldName The field name of the array. + * @return A new {@link Expression} representing the maximum value of the array. + */ + @BetaApi + public static Expression arrayMaximum(String arrayFieldName) { + return arrayMaximum(field(arrayFieldName)); + } + + /** + * Creates an expression that returns the n maximum values of an array. + * + * @param array The expression representing the array. + * @param n The number of elements to return. + * @return A new {@link Expression} representing the n maximum values of the array. + */ + @BetaApi + public static Expression arrayMaximumN(Expression array, Expression n) { + return new FunctionExpression("maximum_n", ImmutableList.of(array, n)); + } + + /** + * Creates an expression that returns the n maximum values of an array. + * + * @param array The expression representing the array. + * @param n The Expression evaluates to the number of elements to return. + * @return A new {@link Expression} representing the n maximum values of the array. + */ + @BetaApi + public static Expression arrayMaximumN(Expression array, int n) { + return arrayMaximumN(array, constant(n)); + } + + /** + * Creates an expression that returns the n maximum values of an array. + * + * @param arrayFieldName The field name of the array. + * @param n The number of elements to return. + * @return A new {@link Expression} representing the n maximum values of the array. + */ + @BetaApi + public static Expression arrayMaximumN(String arrayFieldName, int n) { + return arrayMaximumN(field(arrayFieldName), n); + } + + /** + * Creates an expression that returns the n maximum values of an array. + * + * @param arrayFieldName The field name of the array. + * @param n The Expression evaluates to the number of elements to return. + * @return A new {@link Expression} representing the n maximum values of the array. + */ + @BetaApi + public static Expression arrayMaximumN(String arrayFieldName, Expression n) { + return arrayMaximumN(field(arrayFieldName), n); + } + + /** + * Creates an expression that returns the index of the first occurrence of a value in an array. + * + * @param array The expression representing the array. + * @param value The value to search for. + * @return A new {@link Expression} representing the index. + */ + @BetaApi + public static Expression arrayIndexOf(Expression array, Expression value) { + return new FunctionExpression( + "array_index_of", + ImmutableList.of(array, toExprOrConstant(value), toExprOrConstant("first"))); + } + + /** + * Creates an expression that returns the index of the first occurrence of a value in an array. + * + * @param array The expression representing the array. + * @param value The value to search for. + * @return A new {@link Expression} representing the index. + */ + @BetaApi + public static Expression arrayIndexOf(Expression array, Object value) { + return arrayIndexOf(array, toExprOrConstant(value)); + } + + /** + * Creates an expression that returns the index of the first occurrence of a value in an array. + * + * @param arrayFieldName The field name of the array. + * @param value The value to search for. + * @return A new {@link Expression} representing the index. + */ + @BetaApi + public static Expression arrayIndexOf(String arrayFieldName, Object value) { + return arrayIndexOf(field(arrayFieldName), value); + } + + /** + * Creates an expression that returns the index of the first occurrence of a value in an array. + * + * @param arrayFieldName The field name of the array. + * @param value The value to search for. + * @return A new {@link Expression} representing the index. + */ + @BetaApi + public static Expression arrayIndexOf(String arrayFieldName, Expression value) { + return arrayIndexOf(field(arrayFieldName), value); + } + + /** + * Creates an expression that returns the index of the last occurrence of a value in an array. + * + * @param array The expression representing the array. + * @param value The value to search for. + * @return A new {@link Expression} representing the last index. + */ + @BetaApi + public static Expression arrayLastIndexOf(Expression array, Expression value) { + return new FunctionExpression( + "array_index_of", + ImmutableList.of(array, toExprOrConstant(value), toExprOrConstant("last"))); + } + + /** + * Creates an expression that returns the index of the last occurrence of a value in an array. + * + * @param array The expression representing the array. + * @param value The value to search for. + * @return A new {@link Expression} representing the last index. + */ + @BetaApi + public static Expression arrayLastIndexOf(Expression array, Object value) { + return arrayLastIndexOf(array, toExprOrConstant(value)); + } + + /** + * Creates an expression that returns the index of the last occurrence of a value in an array. + * + * @param arrayFieldName The field name of the array. + * @param value The value to search for. + * @return A new {@link Expression} representing the last index. + */ + @BetaApi + public static Expression arrayLastIndexOf(String arrayFieldName, Object value) { + return arrayLastIndexOf(field(arrayFieldName), value); + } + + /** + * Creates an expression that returns the index of the last occurrence of a value in an array. + * + * @param arrayFieldName The field name of the array. + * @param value The value to search for. + * @return A new {@link Expression} representing the last index. + */ + @BetaApi + public static Expression arrayLastIndexOf(String arrayFieldName, Expression value) { + return arrayLastIndexOf(field(arrayFieldName), value); + } + + /** + * Creates an expression that returns all indices of a value in an array. + * + * @param array The expression representing the array. + * @param value The value to search for. + * @return A new {@link Expression} representing the indices. + */ + @BetaApi + public static Expression arrayIndexOfAll(Expression array, Expression value) { + return new FunctionExpression( + "array_index_of_all", ImmutableList.of(array, toExprOrConstant(value))); + } + + /** + * Creates an expression that returns all indices of a value in an array. + * + * @param array The expression representing the array. + * @param value The value to search for. + * @return A new {@link Expression} representing the indices. + */ + @BetaApi + public static Expression arrayIndexOfAll(Expression array, Object value) { + return arrayIndexOfAll(array, toExprOrConstant(value)); + } + + /** + * Creates an expression that returns all indices of a value in an array. + * + * @param arrayFieldName The field name of the array. + * @param value The value to search for. + * @return A new {@link Expression} representing the indices. + */ + @BetaApi + public static Expression arrayIndexOfAll(String arrayFieldName, Object value) { + return arrayIndexOfAll(field(arrayFieldName), toExprOrConstant(value)); + } + + /** + * Creates an expression that returns all indices of a value in an array. + * + * @param arrayFieldName The field name of the array. + * @param value The value to search for. + * @return A new {@link Expression} representing the indices. + */ + @BetaApi + public static Expression arrayIndexOfAll(String arrayFieldName, Expression value) { + return arrayIndexOfAll(field(arrayFieldName), value); + } + /** * Creates an expression that returns an element from an array at a specified index. * @@ -4397,6 +4826,200 @@ public final Expression arrayConcat(Expression... otherArrays) { return arrayConcat(this, otherArrays); } + /** + * Returns the first element of an array. + * + * @return A new {@link Expression} representing the first element of the array. + */ + @BetaApi + public final Expression arrayFirst() { + return arrayFirst(this); + } + + /** + * Returns the first n elements of an array. + * + * @param n The number of elements to return. + * @return A new {@link Expression} representing the first n elements of the array. + */ + @BetaApi + public final Expression arrayFirstN(int n) { + return arrayFirstN(this, n); + } + + /** + * Returns the first n elements of an array. + * + * @param n The number of elements to return. + * @return A new {@link Expression} representing the first n elements of the array. + */ + @BetaApi + public final Expression arrayFirstN(Expression n) { + return arrayFirstN(this, n); + } + + /** + * Returns the last element of an array. + * + * @return A new {@link Expression} representing the last element of the array. + */ + @BetaApi + public final Expression arrayLast() { + return arrayLast(this); + } + + /** + * Returns the last n elements of an array. + * + * @param n The number of elements to return. + * @return A new {@link Expression} representing the last n elements of the array. + */ + @BetaApi + public final Expression arrayLastN(int n) { + return arrayLastN(this, n); + } + + /** + * Returns the last n elements of an array. + * + * @param n The number of elements to return. + * @return A new {@link Expression} representing the last n elements of the array. + */ + @BetaApi + public final Expression arrayLastN(Expression n) { + return arrayLastN(this, n); + } + + /** + * Returns the minimum value of an array. + * + * @return A new {@link Expression} representing the minimum value of the array. + */ + @BetaApi + public final Expression arrayMinimum() { + return arrayMinimum(this); + } + + /** + * Returns the n minimum values of an array. + * + * @param n The number of elements to return. + * @return A new {@link Expression} representing the n minimum values of the array. + */ + @BetaApi + public final Expression arrayMinimumN(int n) { + return arrayMinimumN(this, n); + } + + /** + * Returns the n minimum values of an array. + * + * @param n The number of elements to return. + * @return A new {@link Expression} representing the n minimum values of the array. + */ + @BetaApi + public final Expression arrayMinimumN(Expression n) { + return arrayMinimumN(this, n); + } + + /** + * Returns the maximum value of an array. + * + * @return A new {@link Expression} representing the maximum value of the array. + */ + @BetaApi + public final Expression arrayMaximum() { + return arrayMaximum(this); + } + + /** + * Returns the n maximum values of an array. + * + * @param n The number of elements to return. + * @return A new {@link Expression} representing the n maximum values of the array. + */ + @BetaApi + public final Expression arrayMaximumN(int n) { + return arrayMaximumN(this, n); + } + + /** + * Returns the n maximum values of an array. + * + * @param n The number of elements to return. + * @return A new {@link Expression} representing the n maximum values of the array. + */ + @BetaApi + public final Expression arrayMaximumN(Expression n) { + return arrayMaximumN(this, n); + } + + /** + * Returns the index of the first occurrence of a value in an array. + * + * @param value The value to search for. + * @return A new {@link Expression} representing the index. + */ + @BetaApi + public final Expression arrayIndexOf(Object value) { + return arrayIndexOf(this, value); + } + + /** + * Returns the index of the first occurrence of a value in an array. + * + * @param value The value to search for. + * @return A new {@link Expression} representing the index. + */ + @BetaApi + public final Expression arrayIndexOf(Expression value) { + return arrayIndexOf(this, value); + } + + /** + * Returns the index of the last occurrence of a value in an array. + * + * @param value The value to search for. + * @return A new {@link Expression} representing the last index. + */ + @BetaApi + public final Expression arrayLastIndexOf(Object value) { + return arrayLastIndexOf(this, value); + } + + /** + * Returns the index of the last occurrence of a value in an array. + * + * @param value The value to search for. + * @return A new {@link Expression} representing the last index. + */ + @BetaApi + public final Expression arrayLastIndexOf(Expression value) { + return arrayLastIndexOf(this, value); + } + + /** + * Returns all indices of a value in an array. + * + * @param value The value to search for. + * @return A new {@link Expression} representing the indices. + */ + @BetaApi + public final Expression arrayIndexOfAll(Object value) { + return arrayIndexOfAll(this, value); + } + + /** + * Returns all indices of a value in an array. + * + * @param value The value to search for. + * @return A new {@link Expression} representing the indices. + */ + @BetaApi + public final Expression arrayIndexOfAll(Expression value) { + return arrayIndexOfAll(this, value); + } + /** * Reverses the order of elements in the array. * 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 5f810332f..1a42fe494 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 @@ -30,7 +30,18 @@ import static com.google.cloud.firestore.pipeline.expressions.Expression.arrayContains; import static com.google.cloud.firestore.pipeline.expressions.Expression.arrayContainsAll; import static com.google.cloud.firestore.pipeline.expressions.Expression.arrayContainsAny; +import static com.google.cloud.firestore.pipeline.expressions.Expression.arrayFirst; +import static com.google.cloud.firestore.pipeline.expressions.Expression.arrayFirstN; import static com.google.cloud.firestore.pipeline.expressions.Expression.arrayGet; +import static com.google.cloud.firestore.pipeline.expressions.Expression.arrayIndexOf; +import static com.google.cloud.firestore.pipeline.expressions.Expression.arrayIndexOfAll; +import static com.google.cloud.firestore.pipeline.expressions.Expression.arrayLast; +import static com.google.cloud.firestore.pipeline.expressions.Expression.arrayLastIndexOf; +import static com.google.cloud.firestore.pipeline.expressions.Expression.arrayLastN; +import static com.google.cloud.firestore.pipeline.expressions.Expression.arrayMaximum; +import static com.google.cloud.firestore.pipeline.expressions.Expression.arrayMaximumN; +import static com.google.cloud.firestore.pipeline.expressions.Expression.arrayMinimum; +import static com.google.cloud.firestore.pipeline.expressions.Expression.arrayMinimumN; import static com.google.cloud.firestore.pipeline.expressions.Expression.arrayReverse; import static com.google.cloud.firestore.pipeline.expressions.Expression.ceil; import static com.google.cloud.firestore.pipeline.expressions.Expression.concat; @@ -73,6 +84,7 @@ import static com.google.cloud.firestore.pipeline.expressions.Expression.vectorLength; import static com.google.cloud.firestore.pipeline.expressions.Expression.xor; import static com.google.common.truth.Truth.assertThat; +import static java.util.Collections.emptyList; import static org.junit.Assert.assertThrows; import static org.junit.Assume.assumeFalse; @@ -787,6 +799,603 @@ public void testArrayLength() throws Exception { assertThat(data(results)).hasSize(10); } + @Test + public void testArrayFirst() throws Exception { + List results = + firestore + .pipeline() + .createFrom(collection) + .where(arrayFirst("tags").equal("adventure")) + .select("title") + .execute() + .get() + .getResults(); + + assertThat(data(results)).isEqualTo(Lists.newArrayList(map("title", "The Lord of the Rings"))); + + results = + firestore + .pipeline() + .createFrom(collection) + .where(field("tags").arrayFirst().equal("adventure")) + .select("title") + .execute() + .get() + .getResults(); + + assertThat(data(results)).isEqualTo(Lists.newArrayList(map("title", "The Lord of the Rings"))); + + // Test with empty/null/non-existent arrays + results = + firestore + .pipeline() + .createFrom(collection) + .where(equal("title", "The Lord of the Rings")) + .replaceWith( + Expression.map(map("empty", emptyList(), "nullval", Expression.nullValue()))) + .select( + arrayFirst("empty").as("emptyResult"), + arrayFirst("nullval").as("nullResult"), + arrayFirst("nonExistent").as("absentResult")) + .execute() + .get() + .getResults(); + + assertThat(data(results)) + .isEqualTo( + Lists.newArrayList( + // no emptyResult as arrayFirst returns UNSET for empty arrays + map("nullResult", null, "absentResult", null))); + } + + @Test + public void testArrayFirstN() throws Exception { + List results = + firestore + .pipeline() + .createFrom(collection) + .where(arrayFirstN("tags", 2).equal(Lists.newArrayList("adventure", "magic"))) + .select("title") + .execute() + .get() + .getResults(); + + assertThat(data(results)).isEqualTo(Lists.newArrayList(map("title", "The Lord of the Rings"))); + + results = + firestore + .pipeline() + .createFrom(collection) + .where( + field("tags") + .arrayFirstN(4) + .equal(Lists.newArrayList("adventure", "magic", "epic"))) + .select("title") + .execute() + .get() + .getResults(); + + assertThat(data(results)).isEqualTo(Lists.newArrayList(map("title", "The Lord of the Rings"))); + + // Test with empty/null/non-existent arrays + results = + firestore + .pipeline() + .createFrom(collection) + .where(equal("title", "The Lord of the Rings")) + .replaceWith( + Expression.map(map("empty", emptyList(), "nullval", Expression.nullValue()))) + .select( + arrayFirstN("empty", 2).as("emptyResult"), + arrayFirstN("nullval", 2).as("nullResult"), + arrayFirstN("nonExistent", 2).as("absentResult")) + .execute() + .get() + .getResults(); + + assertThat(data(results)) + .isEqualTo( + Lists.newArrayList( + map("emptyResult", emptyList(), "nullResult", null, "absentResult", null))); + } + + @Test + public void testArrayLast() throws Exception { + List results = + firestore + .pipeline() + .createFrom(collection) + .where(arrayLast("tags").equal("epic")) + .select("title") + .execute() + .get() + .getResults(); + + assertThat(data(results)).isEqualTo(Lists.newArrayList(map("title", "The Lord of the Rings"))); + + results = + firestore + .pipeline() + .createFrom(collection) + .where(field("tags").arrayLast().equal("epic")) + .select("title") + .execute() + .get() + .getResults(); + + assertThat(data(results)).isEqualTo(Lists.newArrayList(map("title", "The Lord of the Rings"))); + + // Test with empty/null/non-existent arrays + results = + firestore + .pipeline() + .createFrom(collection) + .where(equal("title", "The Lord of the Rings")) + .replaceWith( + Expression.map(map("empty", emptyList(), "nullval", Expression.nullValue()))) + .select( + arrayLast("empty").as("emptyResult"), + arrayLast("nullval").as("nullResult"), + arrayLast("nonExistent").as("absentResult")) + .execute() + .get() + .getResults(); + + assertThat(data(results)) + .isEqualTo( + Lists.newArrayList( + // no emptyResult as arrayLast returns UNSET for empty arrays + map("nullResult", null, "absentResult", null))); + } + + @Test + public void testArrayLastN() throws Exception { + List results = + firestore + .pipeline() + .createFrom(collection) + .where(arrayLastN("tags", 2).equal(Lists.newArrayList("magic", "epic"))) + .select("title") + .execute() + .get() + .getResults(); + + assertThat(data(results)).isEqualTo(Lists.newArrayList(map("title", "The Lord of the Rings"))); + + results = + firestore + .pipeline() + .createFrom(collection) + .where( + field("tags").arrayLastN(4).equal(Lists.newArrayList("adventure", "magic", "epic"))) + .select("title") + .execute() + .get() + .getResults(); + + assertThat(data(results)).isEqualTo(Lists.newArrayList(map("title", "The Lord of the Rings"))); + + // Test with empty/null/non-existent arrays + results = + firestore + .pipeline() + .createFrom(collection) + .where(equal("title", "The Lord of the Rings")) + .replaceWith( + Expression.map(map("empty", emptyList(), "nullval", Expression.nullValue()))) + .select( + arrayLastN("empty", 2).as("emptyResult"), + arrayLastN("nullval", 2).as("nullResult"), + arrayLastN("nonExistent", 2).as("absentResult")) + .execute() + .get() + .getResults(); + + assertThat(data(results)) + .isEqualTo( + Lists.newArrayList( + map("emptyResult", emptyList(), "nullResult", null, "absentResult", null))); + } + + @Test + public void testArrayMinimum() throws Exception { + List results = + firestore + .pipeline() + .createFrom(collection) + .where(arrayMinimum("tags").equal("adventure")) + .select("title") + .execute() + .get() + .getResults(); + + assertThat(data(results)) + .isEqualTo( + Lists.newArrayList( + map("title", "The Hitchhiker's Guide to the Galaxy"), + map("title", "The Lord of the Rings"))); + + results = + firestore + .pipeline() + .createFrom(collection) + .where(field("tags").arrayMinimum().equal("adventure")) + .select("title") + .execute() + .get() + .getResults(); + + assertThat(data(results)) + .isEqualTo( + Lists.newArrayList( + map("title", "The Hitchhiker's Guide to the Galaxy"), + map("title", "The Lord of the Rings"))); + + // Test with empty/null/non-existent arrays + results = + firestore + .pipeline() + .createFrom(collection) + .where(equal("title", "The Lord of the Rings")) + .replaceWith( + Expression.map( + map( + "empty", + emptyList(), + "nullval", + Expression.nullValue(), + "mixed", + Lists.newArrayList(1, "2", 3, "10")))) + .select( + arrayMinimum("empty").as("emptyResult"), + arrayMinimum("nullval").as("nullResult"), + arrayMinimum("nonExistent").as("absentResult"), + arrayMinimum("mixed").as("mixedResult")) + .execute() + .get() + .getResults(); + + assertThat(data(results)) + .isEqualTo( + Lists.newArrayList( + map( + "emptyResult", + null, + "nullResult", + null, + "absentResult", + null, + "mixedResult", + 1L))); + } + + @Test + public void testArrayMinimumN() throws Exception { + List results = + firestore + .pipeline() + .createFrom(collection) + .where(arrayMinimumN("tags", 2).equal(Lists.newArrayList("adventure", "epic"))) + .select("title") + .execute() + .get() + .getResults(); + + assertThat(data(results)).isEqualTo(Lists.newArrayList(map("title", "The Lord of the Rings"))); + + results = + firestore + .pipeline() + .createFrom(collection) + .where( + field("tags") + .arrayMinimumN(4) + .equal(Lists.newArrayList("adventure", "epic", "magic"))) + .select("title") + .execute() + .get() + .getResults(); + + assertThat(data(results)).isEqualTo(Lists.newArrayList(map("title", "The Lord of the Rings"))); + } + + @Test + public void testArrayMaximum() throws Exception { + List results = + firestore + .pipeline() + .createFrom(collection) + .where(arrayMaximum("tags").equal("magic")) + .select("title") + .execute() + .get() + .getResults(); + + assertThat(data(results)).isEqualTo(Lists.newArrayList(map("title", "The Lord of the Rings"))); + + results = + firestore + .pipeline() + .createFrom(collection) + .where(field("tags").arrayMaximum().equal("magic")) + .select("title") + .execute() + .get() + .getResults(); + + assertThat(data(results)).isEqualTo(Lists.newArrayList(map("title", "The Lord of the Rings"))); + + // Test with empty/null/non-existent and mixed types + results = + firestore + .pipeline() + .createFrom(collection) + .where(equal("title", "The Lord of the Rings")) + .replaceWith( + Expression.map( + map( + "empty", + emptyList(), + "nullval", + Expression.nullValue(), + "mixed", + Lists.newArrayList(1, "2", 3, "10")))) + .select( + arrayMaximum("empty").as("emptyResult"), + arrayMaximum("nullval").as("nullResult"), + arrayMaximum("nonExistent").as("absentResult"), + arrayMaximum("mixed").as("mixedResult")) + .execute() + .get() + .getResults(); + + assertThat(data(results)) + .isEqualTo( + Lists.newArrayList( + map( + "emptyResult", + null, + "nullResult", + null, + "absentResult", + null, + "mixedResult", + "2"))); + } + + @Test + public void testArrayMaximumN() throws Exception { + List results = + firestore + .pipeline() + .createFrom(collection) + .where(arrayMaximumN("tags", 2).equal(Lists.newArrayList("magic", "epic"))) + .select("title") + .execute() + .get() + .getResults(); + + assertThat(data(results)).isEqualTo(Lists.newArrayList(map("title", "The Lord of the Rings"))); + + results = + firestore + .pipeline() + .createFrom(collection) + .where( + field("tags") + .arrayMaximumN(4) + .equal(Lists.newArrayList("magic", "epic", "adventure"))) + .select("title") + .execute() + .get() + .getResults(); + + assertThat(data(results)).isEqualTo(Lists.newArrayList(map("title", "The Lord of the Rings"))); + } + + @Test + public void testArrayIndexOf() throws Exception { + List results = + firestore + .pipeline() + .createFrom(collection) + .where(equal("title", "The Lord of the Rings")) + .select( + arrayIndexOf("tags", "adventure").as("indexFirst"), + arrayIndexOf(field("tags"), "magic").as("indexSecond"), + field("tags").arrayIndexOf("epic").as("indexLast"), + arrayIndexOf("tags", "nonexistent").as("indexNone"), + arrayIndexOf("empty", "anything").as("indexEmpty")) + .execute() + .get() + .getResults(); + + assertThat(data(results)) + .isEqualTo( + Lists.newArrayList( + map( + "indexFirst", + 0L, + "indexSecond", + 1L, + "indexLast", + 2L, + "indexNone", + -1L, + "indexEmpty", + null))); + + // Test with duplicate values + results = + firestore + .pipeline() + .createFrom(collection) + .where(equal("title", "The Lord of the Rings")) + .replaceWith(Expression.map(map("arr", Lists.newArrayList(1, 2, 3, 2, 1)))) + .select(arrayIndexOf("arr", 2).as("firstIndex")) + .execute() + .get() + .getResults(); + + assertThat(data(results)).isEqualTo(Lists.newArrayList(map("firstIndex", 1L))); + + // Test with null value + results = + firestore + .pipeline() + .createFrom(collection) + .where(equal("title", "The Lord of the Rings")) + .replaceWith( + Expression.map(map("arr", Lists.newArrayList(1, null, 3, 2, 1), "nullArr", null))) + .select( + arrayIndexOf("arr", null).as("nullIndex"), + arrayIndexOf("nullArr", null).as("nullIndexNull")) + .execute() + .get() + .getResults(); + + assertThat(data(results)) + .isEqualTo(Lists.newArrayList(map("nullIndex", 1L, "nullIndexNull", null))); + } + + @Test + public void testArrayLastIndexOf() throws Exception { + List results = + firestore + .pipeline() + .createFrom(collection) + .where(equal("title", "The Lord of the Rings")) + .select( + arrayLastIndexOf("tags", "adventure").as("lastIndexFirst"), + arrayLastIndexOf(field("tags"), "magic").as("lastIndexSecond"), + field("tags").arrayLastIndexOf("epic").as("lastIndexLast"), + arrayLastIndexOf("tags", "nonexistent").as("lastIndexNone"), + arrayLastIndexOf("empty", "anything").as("lastIndexEmpty")) + .execute() + .get() + .getResults(); + + assertThat(data(results)) + .isEqualTo( + Lists.newArrayList( + map( + "lastIndexFirst", + 0L, + "lastIndexSecond", + 1L, + "lastIndexLast", + 2L, + "lastIndexNone", + -1L, + "lastIndexEmpty", + null))); + + // Test with duplicate values + results = + firestore + .pipeline() + .createFrom(collection) + .where(equal("title", "The Lord of the Rings")) + .replaceWith(Expression.map(map("arr", Lists.newArrayList(1, 2, 3, 2, 1)))) + .select(arrayLastIndexOf("arr", 2).as("lastIndex")) + .execute() + .get() + .getResults(); + + assertThat(data(results)).isEqualTo(Lists.newArrayList(map("lastIndex", 3L))); + + // Test with null value + results = + firestore + .pipeline() + .createFrom(collection) + .where(equal("title", "The Lord of the Rings")) + .replaceWith( + Expression.map(map("arr", Lists.newArrayList(1, null, 3, 2, 1), "nullArr", null))) + .select( + arrayLastIndexOf("arr", null).as("nullIndex"), + arrayLastIndexOf("nullArr", null).as("nullIndexNull")) + .execute() + .get() + .getResults(); + + assertThat(data(results)) + .isEqualTo(Lists.newArrayList(map("nullIndex", 1L, "nullIndexNull", null))); + } + + @Test + public void testArrayIndexOfAll() throws Exception { + List results = + firestore + .pipeline() + .createFrom(collection) + .where(equal("title", "The Lord of the Rings")) + .select( + arrayIndexOfAll("tags", "adventure").as("indicesFirst"), + arrayIndexOfAll(field("tags"), "magic").as("indicesSecond"), + field("tags").arrayIndexOfAll("epic").as("indicesLast"), + arrayIndexOfAll("tags", "nonexistent").as("indicesNone")) + .execute() + .get() + .getResults(); + + assertThat(data(results)) + .isEqualTo( + Lists.newArrayList( + map( + "indicesFirst", + Lists.newArrayList(0L), + "indicesSecond", + Lists.newArrayList(1L), + "indicesLast", + Lists.newArrayList(2L), + "indicesNone", + Lists.newArrayList()))); + + // Test with duplicate values + results = + firestore + .pipeline() + .createFrom(collection) + .where(equal("title", "The Lord of the Rings")) + .replaceWith(Expression.map(map("arr", Lists.newArrayList(1, 2, 3, 2, 1)))) + .select(arrayIndexOfAll("arr", 2).as("indices")) + .execute() + .get() + .getResults(); + + assertThat(data(results)) + .isEqualTo(Lists.newArrayList(map("indices", Lists.newArrayList(1L, 3L)))); + + // Test with null values + results = + firestore + .pipeline() + .createFrom(collection) + .where(equal("title", "The Lord of the Rings")) + .replaceWith( + Expression.map( + map("arr", Lists.newArrayList(1, null, 3, null, 1), "nullArr", null))) + .select( + arrayIndexOfAll("arr", null).as("indices"), + arrayIndexOfAll("nullArr", null).as("indicesNull"), + arrayIndexOfAll("nonExistentArray", null).as("indicesNonExistent")) + .execute() + .get() + .getResults(); + + assertThat(data(results)) + .isEqualTo( + Lists.newArrayList( + map( + "indices", + Lists.newArrayList(1L, 3L), + "indicesNull", + null, + "indicesNonExistent", + null))); + } + @Test public void testArrayConcat() throws Exception { List results = diff --git a/samples/preview-snippets/src/main/java/com/example/firestore/PipelineSnippets.java b/samples/preview-snippets/src/main/java/com/example/firestore/PipelineSnippets.java index cf6efe72d..9c6b4b6f3 100644 --- a/samples/preview-snippets/src/main/java/com/example/firestore/PipelineSnippets.java +++ b/samples/preview-snippets/src/main/java/com/example/firestore/PipelineSnippets.java @@ -924,6 +924,149 @@ void arrayLengthFunction() throws ExecutionException, InterruptedException { System.out.println(result.getResults()); } + void arrayFirstFunction() throws ExecutionException, InterruptedException { + // [START array_first] + Pipeline.Snapshot result = + firestore + .pipeline() + .collection("books") + .select(arrayFirst(field("genre")).as("firstGenre")) + .execute() + .get(); + // [END array_first] + System.out.println(result.getResults()); + } + + void arrayFirstNFunction() throws ExecutionException, InterruptedException { + // [START array_first_n] + Pipeline.Snapshot result = + firestore + .pipeline() + .collection("books") + .select(arrayFirstN(field("genre"), 2).as("firstTwoGenres")) + .execute() + .get(); + // [END array_first_n] + System.out.println(result.getResults()); + } + + void arrayLastFunction() throws ExecutionException, InterruptedException { + // [START array_last] + Pipeline.Snapshot result = + firestore + .pipeline() + .collection("books") + .select(arrayLast(field("genre")).as("lastGenre")) + .execute() + .get(); + // [END array_last] + System.out.println(result.getResults()); + } + + void arrayLastNFunction() throws ExecutionException, InterruptedException { + // [START array_last_n] + Pipeline.Snapshot result = + firestore + .pipeline() + .collection("books") + .select(arrayLastN(field("genre"), 2).as("lastTwoGenres")) + .execute() + .get(); + // [END array_last_n] + System.out.println(result.getResults()); + } + + void arrayMinimumFunction() throws ExecutionException, InterruptedException { + // [START array_minimum] + Pipeline.Snapshot result = + firestore + .pipeline() + .collection("books") + .select(arrayMinimum(field("genre")).as("minimumGenre")) + .execute() + .get(); + // [END array_minimum] + System.out.println(result.getResults()); + } + + void arrayMinimumNFunction() throws ExecutionException, InterruptedException { + // [START array_minimum_n] + Pipeline.Snapshot result = + firestore + .pipeline() + .collection("books") + .select(arrayMinimumN(field("genre"), 2).as("minimumTwoGenres")) + .execute() + .get(); + // [END array_minimum_n] + System.out.println(result.getResults()); + } + + void arrayMaximumFunction() throws ExecutionException, InterruptedException { + // [START array_maximum] + Pipeline.Snapshot result = + firestore + .pipeline() + .collection("books") + .select(arrayMaximum(field("genre")).as("maximumGenre")) + .execute() + .get(); + // [END array_maximum] + System.out.println(result.getResults()); + } + + void arrayMaximumNFunction() throws ExecutionException, InterruptedException { + // [START array_maximum_n] + Pipeline.Snapshot result = + firestore + .pipeline() + .collection("books") + .select(arrayMaximumN(field("genre"), 2).as("maximumTwoGenres")) + .execute() + .get(); + // [END array_maximum_n] + System.out.println(result.getResults()); + } + + void arrayIndexOfFunction() throws ExecutionException, InterruptedException { + // [START array_index_of] + Pipeline.Snapshot result = + firestore + .pipeline() + .collection("books") + .select(arrayIndexOf(field("genre"), "fantasy").as("genreIndex")) + .execute() + .get(); + // [END array_index_of] + System.out.println(result.getResults()); + } + + void arrayLastIndexOfFunction() throws ExecutionException, InterruptedException { + // [START array_last_index_of] + Pipeline.Snapshot result = + firestore + .pipeline() + .collection("books") + .select(arrayLastIndexOf(field("genre"), "fantasy").as("genreIndex")) + .execute() + .get(); + // [END array_last_index_of] + System.out.println(result.getResults()); + } + + void arrayIndexOfAllFunction() throws ExecutionException, InterruptedException { + // [START array_index_of_all] + Pipeline.Snapshot result = + firestore + .pipeline() + .collection("books") + .select(arrayIndexOfAll(field("genre"), "fantasy").as("genreIndex")) + .execute() + .get(); + // [END array_index_of_all] + System.out.println(result.getResults()); + } + void arrayReverseFunction() throws ExecutionException, InterruptedException { // [START array_reverse] Pipeline.Snapshot result = From 8472c0b3ca3e27923b8b24f60801d31e49ad6b65 Mon Sep 17 00:00:00 2001 From: cloud-java-bot Date: Fri, 27 Feb 2026 19:31:41 +0000 Subject: [PATCH 2/2] chore: generate libraries at Fri Feb 27 19:29:25 UTC 2026 --- .../main/java/com/example/firestore/PipelineSnippets.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/samples/preview-snippets/src/main/java/com/example/firestore/PipelineSnippets.java b/samples/preview-snippets/src/main/java/com/example/firestore/PipelineSnippets.java index 9c6b4b6f3..0d27fcf58 100644 --- a/samples/preview-snippets/src/main/java/com/example/firestore/PipelineSnippets.java +++ b/samples/preview-snippets/src/main/java/com/example/firestore/PipelineSnippets.java @@ -950,7 +950,7 @@ void arrayFirstNFunction() throws ExecutionException, InterruptedException { System.out.println(result.getResults()); } - void arrayLastFunction() throws ExecutionException, InterruptedException { + void arrayLastFunction() throws ExecutionException, InterruptedException { // [START array_last] Pipeline.Snapshot result = firestore @@ -1027,7 +1027,7 @@ void arrayMaximumNFunction() throws ExecutionException, InterruptedException { // [END array_maximum_n] System.out.println(result.getResults()); } - + void arrayIndexOfFunction() throws ExecutionException, InterruptedException { // [START array_index_of] Pipeline.Snapshot result = @@ -1053,7 +1053,7 @@ void arrayLastIndexOfFunction() throws ExecutionException, InterruptedException // [END array_last_index_of] System.out.println(result.getResults()); } - + void arrayIndexOfAllFunction() throws ExecutionException, InterruptedException { // [START array_index_of_all] Pipeline.Snapshot result =