diff --git a/firebase-firestore/CHANGELOG.md b/firebase-firestore/CHANGELOG.md index 1cb1ab4ebf6..d3dd4a0d0fe 100644 --- a/firebase-firestore/CHANGELOG.md +++ b/firebase-firestore/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +- [feature] Added support for Pipeline expressions `arraySlice`, `arraySliceToEnd`, `arrayFilter`, `arrayTransform` and `arrayTransformWithIndex`. + [#7989](https://github.com/firebase/firebase-android-sdk/pull/7989) - [feature] Added support for `parent` Pipeline expression. [#7999](https://github.com/firebase/firebase-android-sdk/pull/7999) - [feature] Added support for Pipeline expressions `ifNull` and `coalesce`. diff --git a/firebase-firestore/api.txt b/firebase-firestore/api.txt index 20227de22fd..a3cfa76cf1e 100644 --- a/firebase-firestore/api.txt +++ b/firebase-firestore/api.txt @@ -848,6 +848,9 @@ package com.google.firebase.firestore.pipeline { method public static final com.google.firebase.firestore.pipeline.BooleanExpression arrayContainsAny(String arrayFieldName, com.google.firebase.firestore.pipeline.Expression arrayExpression); method public static final com.google.firebase.firestore.pipeline.BooleanExpression arrayContainsAny(String arrayFieldName, java.util.List values); method public final com.google.firebase.firestore.pipeline.BooleanExpression arrayContainsAny(java.util.List values); + method public static final com.google.firebase.firestore.pipeline.Expression arrayFilter(com.google.firebase.firestore.pipeline.Expression array, String alias, com.google.firebase.firestore.pipeline.BooleanExpression filter); + method public final com.google.firebase.firestore.pipeline.Expression arrayFilter(String alias, com.google.firebase.firestore.pipeline.BooleanExpression filter); + method public static final com.google.firebase.firestore.pipeline.Expression arrayFilter(String arrayFieldName, String alias, com.google.firebase.firestore.pipeline.BooleanExpression filter); method public final com.google.firebase.firestore.pipeline.Expression arrayFirst(); method public static final com.google.firebase.firestore.pipeline.Expression arrayFirst(com.google.firebase.firestore.pipeline.Expression array); method public static final com.google.firebase.firestore.pipeline.Expression arrayFirst(String arrayFieldName); @@ -905,9 +908,27 @@ package com.google.firebase.firestore.pipeline { method public final com.google.firebase.firestore.pipeline.Expression arrayReverse(); method public static final com.google.firebase.firestore.pipeline.Expression arrayReverse(com.google.firebase.firestore.pipeline.Expression array); method public static final com.google.firebase.firestore.pipeline.Expression arrayReverse(String arrayFieldName); + method public final com.google.firebase.firestore.pipeline.Expression arraySlice(com.google.firebase.firestore.pipeline.Expression offset, com.google.firebase.firestore.pipeline.Expression length); + method public static final com.google.firebase.firestore.pipeline.Expression arraySlice(com.google.firebase.firestore.pipeline.Expression array, com.google.firebase.firestore.pipeline.Expression offset, com.google.firebase.firestore.pipeline.Expression length); + method public static final com.google.firebase.firestore.pipeline.Expression arraySlice(com.google.firebase.firestore.pipeline.Expression array, int offset, int length); + method public final com.google.firebase.firestore.pipeline.Expression arraySlice(int offset, int length); + method public static final com.google.firebase.firestore.pipeline.Expression arraySlice(String arrayFieldName, com.google.firebase.firestore.pipeline.Expression offset, com.google.firebase.firestore.pipeline.Expression length); + method public static final com.google.firebase.firestore.pipeline.Expression arraySlice(String arrayFieldName, int offset, int length); + method public final com.google.firebase.firestore.pipeline.Expression arraySliceToEnd(com.google.firebase.firestore.pipeline.Expression offset); + method public static final com.google.firebase.firestore.pipeline.Expression arraySliceToEnd(com.google.firebase.firestore.pipeline.Expression array, com.google.firebase.firestore.pipeline.Expression offset); + method public static final com.google.firebase.firestore.pipeline.Expression arraySliceToEnd(com.google.firebase.firestore.pipeline.Expression array, int offset); + method public final com.google.firebase.firestore.pipeline.Expression arraySliceToEnd(int offset); + method public static final com.google.firebase.firestore.pipeline.Expression arraySliceToEnd(String arrayFieldName, com.google.firebase.firestore.pipeline.Expression offset); + method public static final com.google.firebase.firestore.pipeline.Expression arraySliceToEnd(String arrayFieldName, int offset); method public final com.google.firebase.firestore.pipeline.Expression arraySum(); method public static final com.google.firebase.firestore.pipeline.Expression arraySum(com.google.firebase.firestore.pipeline.Expression array); method public static final com.google.firebase.firestore.pipeline.Expression arraySum(String arrayFieldName); + method public static final com.google.firebase.firestore.pipeline.Expression arrayTransform(com.google.firebase.firestore.pipeline.Expression array, String elementAlias, com.google.firebase.firestore.pipeline.Expression transform); + method public final com.google.firebase.firestore.pipeline.Expression arrayTransform(String elementAlias, com.google.firebase.firestore.pipeline.Expression transform); + method public static final com.google.firebase.firestore.pipeline.Expression arrayTransform(String arrayFieldName, String elementAlias, com.google.firebase.firestore.pipeline.Expression transform); + method public static final com.google.firebase.firestore.pipeline.Expression arrayTransformWithIndex(com.google.firebase.firestore.pipeline.Expression array, String elementAlias, String indexAlias, com.google.firebase.firestore.pipeline.Expression transform); + method public final com.google.firebase.firestore.pipeline.Expression arrayTransformWithIndex(String elementAlias, String indexAlias, com.google.firebase.firestore.pipeline.Expression transform); + method public static final com.google.firebase.firestore.pipeline.Expression arrayTransformWithIndex(String arrayFieldName, String elementAlias, String indexAlias, com.google.firebase.firestore.pipeline.Expression transform); method public final com.google.firebase.firestore.pipeline.BooleanExpression asBoolean(); method public final com.google.firebase.firestore.pipeline.Ordering ascending(); method public final com.google.firebase.firestore.pipeline.AggregateFunction average(); @@ -1470,6 +1491,8 @@ package com.google.firebase.firestore.pipeline { method public com.google.firebase.firestore.pipeline.BooleanExpression arrayContainsAny(com.google.firebase.firestore.pipeline.Expression array, java.util.List values); method public com.google.firebase.firestore.pipeline.BooleanExpression arrayContainsAny(String arrayFieldName, com.google.firebase.firestore.pipeline.Expression arrayExpression); method public com.google.firebase.firestore.pipeline.BooleanExpression arrayContainsAny(String arrayFieldName, java.util.List values); + method public com.google.firebase.firestore.pipeline.Expression arrayFilter(com.google.firebase.firestore.pipeline.Expression array, String alias, com.google.firebase.firestore.pipeline.BooleanExpression filter); + method public com.google.firebase.firestore.pipeline.Expression arrayFilter(String arrayFieldName, String alias, com.google.firebase.firestore.pipeline.BooleanExpression filter); method public com.google.firebase.firestore.pipeline.Expression arrayFirst(com.google.firebase.firestore.pipeline.Expression array); method public com.google.firebase.firestore.pipeline.Expression arrayFirst(String arrayFieldName); method public com.google.firebase.firestore.pipeline.Expression arrayFirstN(com.google.firebase.firestore.pipeline.Expression array, com.google.firebase.firestore.pipeline.Expression n); @@ -1508,8 +1531,20 @@ package com.google.firebase.firestore.pipeline { method public com.google.firebase.firestore.pipeline.Expression arrayMinimumN(String arrayFieldName, int n); method public com.google.firebase.firestore.pipeline.Expression arrayReverse(com.google.firebase.firestore.pipeline.Expression array); method public com.google.firebase.firestore.pipeline.Expression arrayReverse(String arrayFieldName); + method public com.google.firebase.firestore.pipeline.Expression arraySlice(com.google.firebase.firestore.pipeline.Expression array, com.google.firebase.firestore.pipeline.Expression offset, com.google.firebase.firestore.pipeline.Expression length); + method public com.google.firebase.firestore.pipeline.Expression arraySlice(com.google.firebase.firestore.pipeline.Expression array, int offset, int length); + method public com.google.firebase.firestore.pipeline.Expression arraySlice(String arrayFieldName, com.google.firebase.firestore.pipeline.Expression offset, com.google.firebase.firestore.pipeline.Expression length); + method public com.google.firebase.firestore.pipeline.Expression arraySlice(String arrayFieldName, int offset, int length); + method public com.google.firebase.firestore.pipeline.Expression arraySliceToEnd(com.google.firebase.firestore.pipeline.Expression array, com.google.firebase.firestore.pipeline.Expression offset); + method public com.google.firebase.firestore.pipeline.Expression arraySliceToEnd(com.google.firebase.firestore.pipeline.Expression array, int offset); + method public com.google.firebase.firestore.pipeline.Expression arraySliceToEnd(String arrayFieldName, com.google.firebase.firestore.pipeline.Expression offset); + method public com.google.firebase.firestore.pipeline.Expression arraySliceToEnd(String arrayFieldName, int offset); method public com.google.firebase.firestore.pipeline.Expression arraySum(com.google.firebase.firestore.pipeline.Expression array); method public com.google.firebase.firestore.pipeline.Expression arraySum(String arrayFieldName); + method public com.google.firebase.firestore.pipeline.Expression arrayTransform(com.google.firebase.firestore.pipeline.Expression array, String elementAlias, com.google.firebase.firestore.pipeline.Expression transform); + method public com.google.firebase.firestore.pipeline.Expression arrayTransform(String arrayFieldName, String elementAlias, com.google.firebase.firestore.pipeline.Expression transform); + method public com.google.firebase.firestore.pipeline.Expression arrayTransformWithIndex(com.google.firebase.firestore.pipeline.Expression array, String elementAlias, String indexAlias, com.google.firebase.firestore.pipeline.Expression transform); + method public com.google.firebase.firestore.pipeline.Expression arrayTransformWithIndex(String arrayFieldName, String elementAlias, String indexAlias, com.google.firebase.firestore.pipeline.Expression transform); method public com.google.firebase.firestore.pipeline.Expression bitAnd(com.google.firebase.firestore.pipeline.Expression bits, byte[] bitsOther); method public com.google.firebase.firestore.pipeline.Expression bitAnd(com.google.firebase.firestore.pipeline.Expression bits, com.google.firebase.firestore.pipeline.Expression bitsOther); method public com.google.firebase.firestore.pipeline.Expression bitAnd(String bitsFieldName, byte[] bitsOther); diff --git a/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/PipelineTest.java b/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/PipelineTest.java index 19ec9d98746..2504ef2c6f1 100644 --- a/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/PipelineTest.java +++ b/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/PipelineTest.java @@ -20,6 +20,7 @@ import static com.google.firebase.firestore.pipeline.Expression.array; import static com.google.firebase.firestore.pipeline.Expression.arrayContains; import static com.google.firebase.firestore.pipeline.Expression.arrayContainsAny; +import static com.google.firebase.firestore.pipeline.Expression.arrayFilter; import static com.google.firebase.firestore.pipeline.Expression.arrayFirst; import static com.google.firebase.firestore.pipeline.Expression.arrayFirstN; import static com.google.firebase.firestore.pipeline.Expression.arrayIndexOf; @@ -31,6 +32,10 @@ import static com.google.firebase.firestore.pipeline.Expression.arrayMaximumN; import static com.google.firebase.firestore.pipeline.Expression.arrayMinimum; import static com.google.firebase.firestore.pipeline.Expression.arrayMinimumN; +import static com.google.firebase.firestore.pipeline.Expression.arraySlice; +import static com.google.firebase.firestore.pipeline.Expression.arraySliceToEnd; +import static com.google.firebase.firestore.pipeline.Expression.arrayTransform; +import static com.google.firebase.firestore.pipeline.Expression.arrayTransformWithIndex; import static com.google.firebase.firestore.pipeline.Expression.collectionId; import static com.google.firebase.firestore.pipeline.Expression.concat; import static com.google.firebase.firestore.pipeline.Expression.constant; @@ -50,6 +55,7 @@ import static com.google.firebase.firestore.pipeline.Expression.logicalMinimum; import static com.google.firebase.firestore.pipeline.Expression.map; import static com.google.firebase.firestore.pipeline.Expression.mapGet; +import static com.google.firebase.firestore.pipeline.Expression.multiply; import static com.google.firebase.firestore.pipeline.Expression.nor; import static com.google.firebase.firestore.pipeline.Expression.not; import static com.google.firebase.firestore.pipeline.Expression.notEqual; @@ -69,6 +75,7 @@ import static com.google.firebase.firestore.pipeline.Expression.timestampTruncateWithTimezone; import static com.google.firebase.firestore.pipeline.Expression.trunc; import static com.google.firebase.firestore.pipeline.Expression.truncToPrecision; +import static com.google.firebase.firestore.pipeline.Expression.variable; import static com.google.firebase.firestore.pipeline.Expression.vector; import static com.google.firebase.firestore.pipeline.Ordering.ascending; import static com.google.firebase.firestore.pipeline.Ordering.descending; @@ -100,6 +107,7 @@ import com.google.firebase.firestore.pipeline.RawStage; import com.google.firebase.firestore.pipeline.UnnestOptions; import com.google.firebase.firestore.testutil.IntegrationTestUtil; +import java.util.Arrays; import java.util.Calendar; import java.util.Collections; import java.util.Date; @@ -966,6 +974,181 @@ public void arrayMaximumNWorks() { ImmutableList.of("magic", "epic", "adventure"))); } + @Test + public void arrayFilterWorks() { + Task execute = + firestore + .pipeline() + .collection(randomCol) + .where(equal("title", "The Lord of the Rings")) + .select( + field("tags") + .arrayFilter("tag", notEqual(variable("tag"), "magic")) + .alias("notMagicTags"), + arrayFilter("tags", "tag", notEqual(variable("tag"), "epic")).alias("notEpicTags"), + field("tags") + .arrayFilter("tag", equal(variable("tag"), "romance")) + .alias("noMatchingTags")) + .execute(); + assertThat(waitFor(execute).getResults()) + .comparingElementsUsing(DATA_CORRESPONDENCE) + .containsExactly( + ImmutableMap.of( + "notMagicTags", + ImmutableList.of("adventure", "epic"), + "notEpicTags", + ImmutableList.of("adventure", "magic"), + "noMatchingTags", + ImmutableList.of())); + } + + @Test + public void arrayFilterWithMixedTypesAndNullsWorks() { + Task execute = + firestore + .pipeline() + .collection(randomCol) + .limit(1) + .replaceWith( + map( + ImmutableMap.of( + "arr", + ImmutableList.of( + 1, + "foo", + Expression.nullValue(), + 20.0, + "bar", + 30, + "40", + Expression.nullValue())))) + .select( + field("arr") + .arrayFilter("element", greaterThan(variable("element"), 10)) + .alias("filtered")) + .execute(); + assertThat(waitFor(execute).getResults()) + .comparingElementsUsing(DATA_CORRESPONDENCE) + .containsExactly(ImmutableMap.of("filtered", ImmutableList.of(20.0, 30L))); + } + + @Test + public void supportsArrayTransformAndArrayTransformWithIndex() { + Task execute = + firestore + .pipeline() + .collection(randomCol) + .limit(1) + .replaceWith(map(ImmutableMap.of("arr", Arrays.asList(10, 20, 30)))) + .select( + arrayTransform("arr", "element", multiply(variable("element"), 10)) + .alias("staticTransform"), + field("arr") + .arrayTransform("element", multiply(variable("element"), 10)) + .alias("instanceTransform"), + arrayTransformWithIndex( + "arr", "element", "i", add(variable("element"), variable("i"))) + .alias("staticTransformWithIndex"), + field("arr") + .arrayTransformWithIndex( + "element", "i", add(variable("element"), variable("i"))) + .alias("instanceTransformWithIndex")) + .execute(); + assertThat(waitFor(execute).getResults()) + .comparingElementsUsing(DATA_CORRESPONDENCE) + .containsExactly( + ImmutableMap.of( + "staticTransform", + ImmutableList.of(100L, 200L, 300L), + "instanceTransform", + ImmutableList.of(100L, 200L, 300L), + "staticTransformWithIndex", + ImmutableList.of(10L, 21L, 32L), + "instanceTransformWithIndex", + ImmutableList.of(10L, 21L, 32L))); + } + + @Test + public void supportsArrayTransformWithEmptyArrayAndNulls() { + Task execute = + firestore + .pipeline() + .collection(randomCol) + .limit(1) + .replaceWith( + map(ImmutableMap.of("arr", Arrays.asList(1, null, 3), "empty", ImmutableList.of()))) + .select( + field("arr") + .arrayTransform("element", add(variable("element"), 1)) + .alias("transformedWithNulls"), + field("empty") + .arrayTransform("element", add(variable("element"), 1)) + .alias("transformedEmpty"), + field("arr") + .arrayTransformWithIndex( + "element", "idx", add(variable("element"), variable("idx"))) + .alias("transformedWithIndex"), + field("empty") + .arrayTransformWithIndex( + "element", "idx", add(variable("element"), variable("idx"))) + .alias("transformedEmptyWithIndex")) + .execute(); + Map expectedMap = new HashMap<>(); + expectedMap.put("transformedWithNulls", Arrays.asList(2L, null, 4L)); + expectedMap.put("transformedEmpty", ImmutableList.of()); + expectedMap.put("transformedWithIndex", Arrays.asList(1L, null, 5L)); + expectedMap.put("transformedEmptyWithIndex", ImmutableList.of()); + assertThat(waitFor(execute).getResults()) + .comparingElementsUsing(DATA_CORRESPONDENCE) + .containsExactly(expectedMap); + } + + @Test + public void arraySliceWorks() { + Task execute = + firestore + .pipeline() + .collection(randomCol) + .where(equal("title", "The Lord of the Rings")) + .select( + arraySlice("tags", 1, 1).alias("staticMethodSlice"), + arraySliceToEnd("tags", 1).alias("staticMethodSliceToEnd"), + field("tags").arraySlice(1, 1).alias("instanceMethodSlice"), + field("tags").arraySliceToEnd(1).alias("instanceMethodSliceToEnd"), + field("tags").arraySlice(1, 10).alias("overflowLength"), + field("tags").arraySlice(-1, 1).alias("negativeOffset"), + field("tags").arraySliceToEnd(-1).alias("negativeOffsetSliceToEnd"), + field("tags").arraySliceToEnd(10).alias("overflowOffset"), + field("tags").arraySliceToEnd(-10).alias("negativeOverflowOffset")) + .execute(); + assertThat(waitFor(execute).getResults()) + .comparingElementsUsing(DATA_CORRESPONDENCE) + .containsExactly( + mapOfEntries( + entry("staticMethodSlice", ImmutableList.of("magic")), + entry("staticMethodSliceToEnd", ImmutableList.of("magic", "epic")), + entry("instanceMethodSlice", ImmutableList.of("magic")), + entry("instanceMethodSliceToEnd", ImmutableList.of("magic", "epic")), + entry("overflowLength", ImmutableList.of("magic", "epic")), + entry("overflowOffset", ImmutableList.of()), + entry("negativeOffset", ImmutableList.of("epic")), + entry("negativeOffsetSliceToEnd", ImmutableList.of("epic")), + entry("negativeOverflowOffset", ImmutableList.of("adventure", "magic", "epic")))); + } + + @Test + public void arraySliceThrowsErrorForNegativeLength() { + Task execute = + firestore + .pipeline() + .collection(randomCol) + .where(equal("title", "The Lord of the Rings")) + .select(arraySlice("tags", 1, -1).alias("negativeLengthSlice")) + .execute(); + Exception exception = assertThrows(Exception.class, () -> waitFor(execute)); + assertThat(exception).hasMessageThat().contains("length must be non-negative"); + } + @Test public void arrayIndexOfWorks() { Task execute = diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/pipeline/expressions.kt b/firebase-firestore/src/main/java/com/google/firebase/firestore/pipeline/expressions.kt index d6dbd02accf..1d0e9b087a5 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/pipeline/expressions.kt +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/pipeline/expressions.kt @@ -6132,6 +6132,308 @@ abstract class Expression internal constructor() { fun arrayReverse(arrayFieldName: String): Expression = FunctionExpression("array_reverse", evaluateArrayReverse, arrayFieldName) + /** + * Filters an [array] expression based on a predicate. + * + * ```kotlin + * // Filter 'scores' array to include only values greater than 50 + * arrayFilter(field("scores"), "score", greaterThan(variable("score"), 50)) + * ``` + * + * @param array The array expression to filter. + * @param alias The alias to use for the current element in the filter expression. + * @param filter The predicate boolean expression used to filter the elements. + * @return A new [Expression] representing the arrayFilter operation. + */ + @JvmStatic + fun arrayFilter(array: Expression, alias: String, filter: BooleanExpression): Expression = + FunctionExpression("array_filter", notImplemented, array, constant(alias), filter) + + /** + * Filters an array field based on a predicate. + * + * ```kotlin + * // Filter 'scores' array to include only values greater than 50 + * arrayFilter("scores", "score", greaterThan(variable("score"), 50)) + * ``` + * + * @param arrayFieldName The name of field that contains array to filter. + * @param alias The alias to use for the current element in the filter expression. + * @param filter The predicate boolean expression used to filter the elements. + * @return A new [Expression] representing the arrayFilter operation. + */ + @JvmStatic + fun arrayFilter(arrayFieldName: String, alias: String, filter: BooleanExpression): Expression = + FunctionExpression("array_filter", notImplemented, arrayFieldName, constant(alias), filter) + + /** + * Creates an expression that applies a provided transformation to each element in an array. + * + * ```kotlin + * // Transform 'scores' array by multiplying each score by 10 + * arrayTransform(field("scores"), "score", multiply(variable("score"), 10)) + * ``` + * + * @param array The array expression to transform. + * @param elementAlias The alias to use for the current element in the transform expression. + * @param transform The expression used to transform the elements. + * @return A new [Expression] representing the arrayTransform operation. + */ + @JvmStatic + fun arrayTransform(array: Expression, elementAlias: String, transform: Expression): Expression = + FunctionExpression( + "array_transform", + notImplemented, + array, + constant(elementAlias), + transform + ) + + /** + * Creates an expression that applies a provided transformation to each element in an array. + * + * ```kotlin + * // Transform 'scores' array by multiplying each score by 10 + * arrayTransform("scores", "score", multiply(variable("score"), 10)) + * ``` + * + * @param arrayFieldName The name of field that contains array to transform. + * @param elementAlias The alias to use for the current element in the transform expression. + * @param transform The expression used to transform the elements. + * @return A new [Expression] representing the arrayTransform operation. + */ + @JvmStatic + fun arrayTransform( + arrayFieldName: String, + elementAlias: String, + transform: Expression + ): Expression = + FunctionExpression( + "array_transform", + notImplemented, + arrayFieldName, + constant(elementAlias), + transform + ) + + /** + * Creates an expression that applies a provided transformation to each element in an array, + * providing the element's index to the transformation expression. + * + * ```kotlin + * // Transform 'scores' array by adding the index + * arrayTransformWithIndex(field("scores"), "score", "i", add(variable("score"), variable("i"))) + * ``` + * + * @param array The array expression to transform. + * @param elementAlias The alias to use for the current element in the transform expression. + * @param indexAlias The alias to use for the current index. + * @param transform The expression used to transform the elements. + * @return A new [Expression] representing the arrayTransformWithIndex operation. + */ + @JvmStatic + fun arrayTransformWithIndex( + array: Expression, + elementAlias: String, + indexAlias: String, + transform: Expression + ): Expression = + FunctionExpression( + "array_transform", + notImplemented, + array, + constant(elementAlias), + constant(indexAlias), + transform + ) + + /** + * Creates an expression that applies a provided transformation to each element in an array, + * providing the element's index to the transformation expression. + * + * ```kotlin + * // Transform 'scores' array by adding the index + * arrayTransformWithIndex("scores", "score", "i", add(variable("score"), variable("i"))) + * ``` + * + * @param arrayFieldName The name of field that contains array to transform. + * @param elementAlias The alias to use for the current element in the transform expression. + * @param indexAlias The alias to use for the current index. + * @param transform The expression used to transform the elements. + * @return A new [Expression] representing the arrayTransformWithIndex operation. + */ + @JvmStatic + fun arrayTransformWithIndex( + arrayFieldName: String, + elementAlias: String, + indexAlias: String, + transform: Expression + ): Expression = + FunctionExpression( + "array_transform", + notImplemented, + arrayFieldName, + constant(elementAlias), + constant(indexAlias), + transform + ) + + /** + * Creates an expression that returns a slice of an [array] expression to its end. + * + * ```kotlin + * // Get elements from the 'items' array starting from index 2 + * arraySliceToEnd(field("items"), 2) + * ``` + * + * @param array The array expression. + * @param offset The starting index. + * @return A new [Expression] representing the arraySliceToEnd operation. + */ + @JvmStatic + fun arraySliceToEnd(array: Expression, offset: Int): Expression = + FunctionExpression("array_slice", notImplemented, array, toExprOrConstant(offset)) + + /** + * Creates an expression that returns a slice of an [array] expression to its end. + * + * ```kotlin + * // Get elements from the 'items' array starting at an offset defined by a field + * arraySliceToEnd(field("items"), field("startIdx")) + * ``` + * + * @param array The array expression. + * @param offset The starting index. + * @return A new [Expression] representing the arraySliceToEnd operation. + */ + @JvmStatic + fun arraySliceToEnd(array: Expression, offset: Expression): Expression = + FunctionExpression("array_slice", notImplemented, array, toExprOrConstant(offset)) + + /** + * Creates an expression that returns a slice of an array field to its end. + * + * ```kotlin + * // Get elements from the 'items' array starting from index 2 + * arraySliceToEnd("items", 2) + * ``` + * + * @param arrayFieldName The name of field that contains the array. + * @param offset The starting index. + * @return A new [Expression] representing the arraySliceToEnd operation. + */ + @JvmStatic + fun arraySliceToEnd(arrayFieldName: String, offset: Int): Expression = + FunctionExpression("array_slice", notImplemented, arrayFieldName, toExprOrConstant(offset)) + + /** + * Creates an expression that returns a slice of an array field to its end. + * + * ```kotlin + * // Get elements from the 'items' array starting at an offset defined by a field + * arraySliceToEnd("items", field("startIdx")) + * ``` + * + * @param arrayFieldName The name of field that contains the array. + * @param offset The starting index. + * @return A new [Expression] representing the arraySliceToEnd operation. + */ + @JvmStatic + fun arraySliceToEnd(arrayFieldName: String, offset: Expression): Expression = + FunctionExpression("array_slice", notImplemented, arrayFieldName, toExprOrConstant(offset)) + + /** + * Creates an expression that returns a slice of an [array] expression. + * + * ```kotlin + * // Get 5 elements from the 'items' array starting from index 2 + * arraySlice(field("items"), 2, 5) + * ``` + * + * @param array The array expression. + * @param offset The starting index. + * @param length The number of elements to return. + * @return A new [Expression] representing the arraySlice operation. + */ + @JvmStatic + fun arraySlice(array: Expression, offset: Int, length: Int): Expression = + FunctionExpression( + "array_slice", + notImplemented, + array, + toExprOrConstant(offset), + toExprOrConstant(length) + ) + + /** + * Creates an expression that returns a slice of an array field. + * + * ```kotlin + * // Get 5 elements from the 'items' array starting from index 2 + * arraySlice("items", 2, 5) + * ``` + * + * @param arrayFieldName The name of field that contains the array. + * @param offset The starting index. + * @param length The number of elements to return. + * @return A new [Expression] representing the arraySlice operation. + */ + @JvmStatic + fun arraySlice(arrayFieldName: String, offset: Int, length: Int): Expression = + FunctionExpression( + "array_slice", + notImplemented, + arrayFieldName, + toExprOrConstant(offset), + toExprOrConstant(length) + ) + + /** + * Creates an expression that returns a slice of an [array] expression. + * + * ```kotlin + * // Get elements from the 'items' array using expressions for offset and length + * arraySlice(field("items"), field("startIdx"), field("length")) + * ``` + * + * @param array The array expression. + * @param offset The starting index. + * @param length The number of elements to return. + * @return A new [Expression] representing the arraySlice operation. + */ + @JvmStatic + fun arraySlice(array: Expression, offset: Expression, length: Expression): Expression = + FunctionExpression( + "array_slice", + notImplemented, + array, + toExprOrConstant(offset), + toExprOrConstant(length) + ) + + /** + * Creates an expression that returns a slice of an array field. + * + * ```kotlin + * // Get elements from the 'items' array using expressions for offset and length + * arraySlice("items", field("startIdx"), field("length")) + * ``` + * + * @param arrayFieldName The name of field that contains the array. + * @param offset The starting index. + * @param length The number of elements to return. + * @return A new [Expression] representing the arraySlice operation. + */ + @JvmStatic + fun arraySlice(arrayFieldName: String, offset: Expression, length: Expression): Expression = + FunctionExpression( + "array_slice", + notImplemented, + arrayFieldName, + toExprOrConstant(offset), + toExprOrConstant(length) + ) + /** * Creates an expression that returns the sum of the elements in an array. * @@ -9875,6 +10177,108 @@ abstract class Expression internal constructor() { */ fun arrayReverse() = Companion.arrayReverse(this) + /** + * Filters this array expression based on a predicate. + * + * ```kotlin + * // Filter 'scores' array to include only values greater than 50 + * field("scores").arrayFilter("score", greaterThan(variable("score"), 50)) + * ``` + * + * @param alias The alias to use for the current element in the filter expression. + * @param filter The predicate boolean expression used to filter the elements. + * @return A new [Expression] representing the arrayFilter operation. + */ + fun arrayFilter(alias: String, filter: BooleanExpression) = + Companion.arrayFilter(this, alias, filter) + + /** + * Creates an expression that applies a provided transformation to each element in an array. + * + * ```kotlin + * // Transform 'scores' array by multiplying each score by 10 + * field("scores").arrayTransform("score", multiply(variable("score"), 10)) + * ``` + * + * @param elementAlias The alias to use for the current element in the transform expression. + * @param transform The expression used to transform the elements. + * @return A new [Expression] representing the arrayTransform operation. + */ + fun arrayTransform(elementAlias: String, transform: Expression) = + Companion.arrayTransform(this, elementAlias, transform) + + /** + * Creates an expression that applies a provided transformation to each element in an array, + * providing the element's index to the transformation expression. + * + * ```kotlin + * // Transform 'scores' array by adding the index + * field("scores").arrayTransformWithIndex("score", "i", add(variable("score"), variable("i"))) + * ``` + * + * @param elementAlias The alias to use for the current element in the transform expression. + * @param indexAlias The alias to use for the current index. + * @param transform The expression used to transform the elements. + * @return A new [Expression] representing the arrayTransformWithIndex operation. + */ + fun arrayTransformWithIndex(elementAlias: String, indexAlias: String, transform: Expression) = + Companion.arrayTransformWithIndex(this, elementAlias, indexAlias, transform) + + /** + * Creates an expression that returns a slice of this array expression to its end. + * + * ```kotlin + * // Get elements from the 'items' array starting from index 2 + * field("items").arraySliceToEnd(2) + * ``` + * + * @param offset The starting index. + * @return A new [Expression] representing the arraySliceToEnd operation. + */ + fun arraySliceToEnd(offset: Int) = Companion.arraySliceToEnd(this, offset) + + /** + * Creates an expression that returns a slice of this array expression to its end. + * + * ```kotlin + * // Get elements from the 'items' array starting from the value of the 'offset' field + * field("items").arraySliceToEnd(field("offset")) + * ``` + * + * @param offset The starting index. + * @return A new [Expression] representing the arraySliceToEnd operation. + */ + fun arraySliceToEnd(offset: Expression) = Companion.arraySliceToEnd(this, offset) + + /** + * Creates an expression that returns a slice of this array expression. + * + * ```kotlin + * // Get 5 elements from the 'items' array starting from index 2 + * field("items").arraySlice(2, 5) + * ``` + * + * @param offset The starting index. + * @param length The number of elements to return. + * @return A new [Expression] representing the arraySlice operation. + */ + fun arraySlice(offset: Int, length: Int) = Companion.arraySlice(this, offset, length) + + /** + * Creates an expression that returns a slice of this array expression. + * + * ```kotlin + * // Get elements from the 'items' array using expressions for offset and length + * field("items").arraySlice(field("offset"), field("length")) + * ``` + * + * @param offset The starting index. + * @param length The number of elements to return. + * @return A new [Expression] representing the arraySlice operation. + */ + fun arraySlice(offset: Expression, length: Expression) = + Companion.arraySlice(this, offset, length) + /** * Creates an expression that returns the sum of the elements in this array expression. *