diff --git a/packages/google-cloud-firestore/google/cloud/firestore_v1/pipeline_expressions.py b/packages/google-cloud-firestore/google/cloud/firestore_v1/pipeline_expressions.py index 325789f2358a..a97cd9feb73f 100644 --- a/packages/google-cloud-firestore/google/cloud/firestore_v1/pipeline_expressions.py +++ b/packages/google-cloud-firestore/google/cloud/firestore_v1/pipeline_expressions.py @@ -1490,6 +1490,250 @@ def last(self) -> "Expression": """ return AggregateFunction("last", [self]) + @expose_as_static + def array_first(self) -> "Expression": + """Creates an expression that returns the first element of an array. + + Example: + >>> # Select the first element of array 'colors' + >>> Field.of("colors").array_first() + + Returns: + A new `Expression` representing the first element of the array. + """ + return FunctionExpression("array_first", [self]) + + @expose_as_static + def array_last(self) -> "Expression": + """Creates an expression that returns the last element of an array. + + Example: + >>> # Select the last element of array 'colors' + >>> Field.of("colors").array_last() + + Returns: + A new `Expression` representing the last element of the array. + """ + return FunctionExpression("array_last", [self]) + + @expose_as_static + def array_first_n(self, n: int | "Expression") -> "Expression": + """Creates an expression that returns the first `n` elements of an array. + + Example: + >>> # Select the first 2 elements of array 'colors' + >>> Field.of("colors").array_first_n(2) + + Returns: + A new `Expression` representing the first `n` elements of the array. + """ + return FunctionExpression( + "array_first_n", [self, self._cast_to_expr_or_convert_to_constant(n)] + ) + + @expose_as_static + def array_last_n(self, n: int | "Expression") -> "Expression": + """Creates an expression that returns the last `n` elements of an array. + + Example: + >>> # Select the last 2 elements of array 'colors' + >>> Field.of("colors").array_last_n(2) + + Returns: + A new `Expression` representing the last `n` elements of the array. + """ + return FunctionExpression( + "array_last_n", [self, self._cast_to_expr_or_convert_to_constant(n)] + ) + + @expose_as_static + def array_maximum(self) -> "Expression": + """Creates an expression that returns the maximum element of an array. + + Example: + >>> # Select the maximum element of array 'scores' + >>> Field.of("scores").array_maximum() + + Returns: + A new `Expression` representing the maximum element of the array. + """ + return FunctionExpression("maximum", [self]) + + @expose_as_static + def array_minimum(self) -> "Expression": + """Creates an expression that returns the minimum element of an array. + + Example: + >>> # Select the minimum element of array 'scores' + >>> Field.of("scores").array_minimum() + + Returns: + A new `Expression` representing the minimum element of the array. + """ + return FunctionExpression("minimum", [self]) + + @expose_as_static + def array_maximum_n(self, n: int | "Expression") -> "Expression": + """Creates an expression that returns the maximum `n` elements of an array. + + Example: + >>> # Select the maximum 2 elements of array 'scores' + >>> Field.of("scores").array_maximum_n(2) + + Note: + Returns the n largest non-null elements in the array, in descending + order. This does not use a stable sort, meaning the order of equivalent + elements is undefined. + + Returns: + A new `Expression` representing the maximum `n` elements of the array. + """ + return FunctionExpression( + "maximum_n", [self, self._cast_to_expr_or_convert_to_constant(n)] + ) + + @expose_as_static + def array_minimum_n(self, n: int | "Expression") -> "Expression": + """Creates an expression that returns the minimum `n` elements of an array. + + Example: + >>> # Select the minimum 2 elements of array 'scores' + >>> Field.of("scores").array_minimum_n(2) + + Note: + Returns the n smallest non-null elements in the array, in ascending + order. This does not use a stable sort, meaning the order of equivalent + elements is undefined. + + Returns: + A new `Expression` representing the minimum `n` elements of the array. + """ + return FunctionExpression( + "minimum_n", [self, self._cast_to_expr_or_convert_to_constant(n)] + ) + + @expose_as_static + def array_slice( + self, offset: int | "Expression", length: int | "Expression" | None = None + ) -> "Expression": + """Ccreates an expression that returns a slice of an array starting from the specified + offset with a given length. + + Example: + >>> # Slice array 'scores' starting at index 1 with length 2 + >>> Field.of("scores").array_slice(1, 2) + + Args: + offset: the 0-based index of the first element to include. + length: The number of elements to include in the slice. If omitted, slices to the end. + + Returns: + A new `Expression` representing the slice of the array. + """ + args = [self, self._cast_to_expr_or_convert_to_constant(offset)] + if length is not None: + args.append(self._cast_to_expr_or_convert_to_constant(length)) + return FunctionExpression("array_slice", args) + + @expose_as_static + def array_index_of(self, search: "Expression" | CONSTANT_TYPE) -> "Expression": + """Creates an expression that returns the index of a value in an array. + + Returns -1 if the value is not found. + + Example: + >>> # Get the index of "comedy" in the 'tags' array + >>> Field.of("tags").array_index_of("comedy") + + Args: + search: The element (expression or constant) to find the index of. + + Returns: + A new `Expression` representing the 'array_index_of' value. + """ + return FunctionExpression( + "array_index_of", + [ + self, + self._cast_to_expr_or_convert_to_constant(search), + self._cast_to_expr_or_convert_to_constant("first"), + ], + ) + + @expose_as_static + def array_index_of_all(self, search: "Expression" | CONSTANT_TYPE) -> "Expression": + """Creates an expression that returns all indices of a value in an array. + Returns an empty array if the value is not found. + + Example: + >>> # Get all indices of "comedy" in the 'tags' array + >>> Field.of("tags").array_index_of_all("comedy") + + Args: + search: The element (expression or constant) to find the indices of. + + Returns: + A new `Expression` representing the 'array_index_of_all' value. + """ + return FunctionExpression( + "array_index_of_all", + [self, self._cast_to_expr_or_convert_to_constant(search)], + ) + + @expose_as_static + def array_transform( + self, element_alias: str, body: "Expression", index_alias: str | None = None + ) -> "Expression": + """Creates an expression that transforms elements of an array. + + Example: + >>> # Transform each element by adding 1. + >>> Field.of("nums").array_transform("e", Field.of("e").add(1)) + >>> + >>> # Transform each element by adding its index to it. + >>> Field.of("nums").array_transform("e", Field.of("e").add(Field.of("i")), index_alias="i") + + Args: + element_alias: The variable name to use for the current element within the body expression. + body: The expression to apply to each element. + index_alias: The variable name to use for the current index within the body expression. + + Returns: + A new `Expression` applying the transformation to the array elements. + """ + args = [ + self, + self._cast_to_expr_or_convert_to_constant(element_alias), + ] + if index_alias is not None: + args.append(self._cast_to_expr_or_convert_to_constant(index_alias)) + + args.append(self._cast_to_expr_or_convert_to_constant(body)) + + return FunctionExpression("array_transform", args) + + @expose_as_static + def array_filter(self, element_alias: str, body: "Expression") -> "Expression": + """ + Takes an array, evaluates a boolean expression on each element, and returns a new + array containing only the elements for which the expression evaluates to True. + + Args: + element_alias: Element variable name. + body: Boolean expression applied to each element. + + Returns: + Expression: The created FunctionExpression AST node. + """ + return FunctionExpression( + "array_filter", + [ + self, + self._cast_to_expr_or_convert_to_constant(element_alias), + self._cast_to_expr_or_convert_to_constant(body), + ], + ) + @expose_as_static def unix_micros_to_timestamp(self) -> "Expression": """Creates an expression that converts a number of microseconds since the epoch (1970-01-01 diff --git a/packages/google-cloud-firestore/tests/system/pipeline_e2e/array.yaml b/packages/google-cloud-firestore/tests/system/pipeline_e2e/array.yaml index f82f1cbc1564..0e7f4b303eef 100644 --- a/packages/google-cloud-firestore/tests/system/pipeline_e2e/array.yaml +++ b/packages/google-cloud-firestore/tests/system/pipeline_e2e/array.yaml @@ -461,4 +461,488 @@ tests: - fieldReferenceValue: tags - integerValue: '-1' name: array_get - name: select \ No newline at end of file + name: select + - description: testArrayFirst + pipeline: + - Collection: books + - Where: + - FunctionExpression.equal: + - Field: title + - Constant: "The Hitchhiker's Guide to the Galaxy" + - Select: + - AliasedExpression: + - FunctionExpression.array_first: + - Field: tags + - "firstTag" + assert_results: + - firstTag: "comedy" + assert_proto: + pipeline: + stages: + - args: + - referenceValue: /books + name: collection + - args: + - functionValue: + args: + - fieldReferenceValue: title + - stringValue: "The Hitchhiker's Guide to the Galaxy" + name: equal + name: where + - args: + - mapValue: + fields: + firstTag: + functionValue: + args: + - fieldReferenceValue: tags + name: array_first + name: select + - description: testArrayLast + pipeline: + - Collection: books + - Where: + - FunctionExpression.equal: + - Field: title + - Constant: "The Hitchhiker's Guide to the Galaxy" + - Select: + - AliasedExpression: + - FunctionExpression.array_last: + - Field: tags + - "lastTag" + assert_results: + - lastTag: "adventure" + assert_proto: + pipeline: + stages: + - args: + - referenceValue: /books + name: collection + - args: + - functionValue: + args: + - fieldReferenceValue: title + - stringValue: "The Hitchhiker's Guide to the Galaxy" + name: equal + name: where + - args: + - mapValue: + fields: + lastTag: + functionValue: + args: + - fieldReferenceValue: tags + name: array_last + name: select + - description: testArrayFirstN + pipeline: + - Collection: books + - Where: + - FunctionExpression.equal: + - Field: title + - Constant: "The Hitchhiker's Guide to the Galaxy" + - Select: + - AliasedExpression: + - FunctionExpression.array_first_n: + - Field: tags + - Constant: 2 + - "firstTags" + assert_results: + - firstTags: ["comedy", "space"] + assert_proto: + pipeline: + stages: + - args: + - referenceValue: /books + name: collection + - args: + - functionValue: + args: + - fieldReferenceValue: title + - stringValue: "The Hitchhiker's Guide to the Galaxy" + name: equal + name: where + - args: + - mapValue: + fields: + firstTags: + functionValue: + args: + - fieldReferenceValue: tags + - integerValue: '2' + name: array_first_n + name: select + - description: testArrayLastN + pipeline: + - Collection: books + - Where: + - FunctionExpression.equal: + - Field: title + - Constant: "The Hitchhiker's Guide to the Galaxy" + - Select: + - AliasedExpression: + - FunctionExpression.array_last_n: + - Field: tags + - Constant: 2 + - "lastTags" + assert_results: + - lastTags: ["space", "adventure"] + assert_proto: + pipeline: + stages: + - args: + - referenceValue: /books + name: collection + - args: + - functionValue: + args: + - fieldReferenceValue: title + - stringValue: "The Hitchhiker's Guide to the Galaxy" + name: equal + name: where + - args: + - mapValue: + fields: + lastTags: + functionValue: + args: + - fieldReferenceValue: tags + - integerValue: '2' + name: array_last_n + name: select + + - description: testArrayMaximum + pipeline: + - Collection: books + - Where: + - FunctionExpression.equal: + - Field: title + - Constant: "The Hitchhiker's Guide to the Galaxy" + - Select: + - AliasedExpression: + - FunctionExpression.array_maximum: + - Field: tags + - "maxTag" + assert_results: + - maxTag: "space" + + - description: testArrayMinimum + pipeline: + - Collection: books + - Where: + - FunctionExpression.equal: + - Field: title + - Constant: "The Hitchhiker's Guide to the Galaxy" + - Select: + - AliasedExpression: + - FunctionExpression.array_minimum: + - Field: tags + - "minTag" + assert_results: + - minTag: "adventure" + + - description: testArrayMaximumN + pipeline: + - Collection: books + - Where: + - FunctionExpression.equal: + - Field: title + - Constant: "The Hitchhiker's Guide to the Galaxy" + - Select: + - AliasedExpression: + - FunctionExpression.array_maximum_n: + - Field: tags + - Constant: 2 + - "maxTags" + assert_results: + - maxTags: ["space", "comedy"] + + - description: testArrayMinimumN + pipeline: + - Collection: books + - Where: + - FunctionExpression.equal: + - Field: title + - Constant: "The Hitchhiker's Guide to the Galaxy" + - Select: + - AliasedExpression: + - FunctionExpression.array_minimum_n: + - Field: tags + - Constant: 2 + - "minTags" + assert_results: + - minTags: ["adventure", "comedy"] + + - description: testArraySlice1Arg + pipeline: + - Collection: books + - Where: + - FunctionExpression.equal: + - Field: title + - Constant: "The Hitchhiker's Guide to the Galaxy" + - Select: + - AliasedExpression: + - FunctionExpression.array_slice: + - Field: tags + - Constant: 1 + - "sliced" + assert_results: + - sliced: ["space", "adventure"] + + - description: testArraySlice2Args + pipeline: + - Collection: books + - Where: + - FunctionExpression.equal: + - Field: title + - Constant: "The Hitchhiker's Guide to the Galaxy" + - Select: + - AliasedExpression: + - FunctionExpression.array_slice: + - Field: tags + - Constant: 1 + - Constant: 1 + - "sliced" + assert_results: + - sliced: ["space"] + + - description: testArrayIndexOf + pipeline: + - Collection: books + - Where: + - FunctionExpression.equal: + - Field: title + - Constant: "The Hitchhiker's Guide to the Galaxy" + - Select: + - AliasedExpression: + - FunctionExpression.array_index_of: + - Field: tags + - Constant: "space" + - "spaceIndex" + assert_results: + - spaceIndex: 1 + assert_proto: + pipeline: + stages: + - args: + - referenceValue: /books + name: collection + - args: + - functionValue: + args: + - fieldReferenceValue: title + - stringValue: "The Hitchhiker's Guide to the Galaxy" + name: equal + name: where + - args: + - mapValue: + fields: + spaceIndex: + functionValue: + args: + - fieldReferenceValue: tags + - stringValue: "space" + - stringValue: "first" + name: array_index_of + name: select + + - description: testArrayIndexOfAll + pipeline: + - Collection: books + - Sort: + - Ordering: + - Field: title + - ASCENDING + - Aggregate: + - AliasedExpression: + - FunctionExpression.array_agg: + - Field: genre + - "genre_array" + - Select: + - AliasedExpression: + - FunctionExpression.array_index_of_all: + - Field: genre_array + - Constant: "Science Fiction" + - "sciFiIndices" + assert_results: + - sciFiIndices: [2, 7] + assert_proto: + pipeline: + stages: + - args: + - referenceValue: /books + name: collection + - args: + - mapValue: + fields: + direction: + stringValue: ascending + expression: + fieldReferenceValue: title + name: sort + - args: + - mapValue: + fields: + genre_array: + functionValue: + name: array_agg + args: + - fieldReferenceValue: genre + - mapValue: {} + name: aggregate + - args: + - mapValue: + fields: + sciFiIndices: + functionValue: + args: + - fieldReferenceValue: genre_array + - stringValue: "Science Fiction" + name: array_index_of_all + name: select + + - description: testArrayTransform + pipeline: + - Collection: books + - Select: + - AliasedExpression: + - FunctionExpression.array_transform: + - Array: + - 1 + - 10 + - 100 + - "num" + - FunctionExpression.add: + - Field: "num" + - Constant: 1 + - "transformed" + - Limit: 1 + assert_results: + - transformed: [2, 11, 101] + assert_proto: + pipeline: + stages: + - args: + - referenceValue: /books + name: collection + - args: + - mapValue: + fields: + transformed: + functionValue: + name: array_transform + args: + - functionValue: + name: array + args: + - integerValue: '1' + - integerValue: '10' + - integerValue: '100' + - stringValue: num + - functionValue: + name: add + args: + - fieldReferenceValue: num + - integerValue: '1' + name: select + - args: + - integerValue: '1' + name: limit + + - description: testArrayTransformWithIndex + pipeline: + - Collection: books + - Select: + - AliasedExpression: + - FunctionExpression.array_transform: + - Array: + - 1 + - 10 + - 100 + - "num" + - FunctionExpression.multiply: # body + - Field: "num" + - Field: "idx" + - "idx" # index_alias + - "transformed" + - Limit: 1 + assert_results: + - transformed: [0, 10, 200] + assert_proto: + pipeline: + stages: + - args: + - referenceValue: /books + name: collection + - args: + - mapValue: + fields: + transformed: + functionValue: + name: array_transform + args: + - functionValue: + name: array + args: + - integerValue: '1' + - integerValue: '10' + - integerValue: '100' + - stringValue: num + - stringValue: idx + - functionValue: + name: multiply + args: + - fieldReferenceValue: num + - fieldReferenceValue: idx + name: select + - args: + - integerValue: '1' + name: limit + + - description: testArrayFilter + pipeline: + - Collection: books + - Select: + - AliasedExpression: + - FunctionExpression.array_filter: + - Array: + - 1 + - 10 + - 100 + - "num" + - FunctionExpression.greater_than: + - Field: "num" + - Constant: 9 + - "filtered" + - Limit: 1 + assert_results: + - filtered: [10, 100] + assert_proto: + pipeline: + stages: + - args: + - referenceValue: /books + name: collection + - args: + - mapValue: + fields: + filtered: + functionValue: + name: array_filter + args: + - functionValue: + name: array + args: + - integerValue: '1' + - integerValue: '10' + - integerValue: '100' + - stringValue: num + - functionValue: + name: greater_than + args: + - fieldReferenceValue: num + - integerValue: '9' + name: select + - args: + - integerValue: '1' + name: limit diff --git a/packages/google-cloud-firestore/tests/unit/v1/test_pipeline_expressions.py b/packages/google-cloud-firestore/tests/unit/v1/test_pipeline_expressions.py index 80738799f975..2f4cf9459bb6 100644 --- a/packages/google-cloud-firestore/tests/unit/v1/test_pipeline_expressions.py +++ b/packages/google-cloud-firestore/tests/unit/v1/test_pipeline_expressions.py @@ -1619,3 +1619,202 @@ def test_last(self): assert repr(instance) == "Value.last()" infix_instance = arg1.last() assert infix_instance == instance + + def test_array_first(self): + arg1 = self._make_arg("Value") + instance = Expression.array_first(arg1) + assert instance.name == "array_first" + assert instance.params == [arg1] + assert repr(instance) == "Value.array_first()" + infix_instance = arg1.array_first() + assert infix_instance == instance + + def test_array_last(self): + arg1 = self._make_arg("Value") + instance = Expression.array_last(arg1) + assert instance.name == "array_last" + assert instance.params == [arg1] + assert repr(instance) == "Value.array_last()" + infix_instance = arg1.array_last() + assert infix_instance == instance + + def test_array_first_n(self): + arg1 = self._make_arg("Value") + n = 2 + instance = Expression.array_first_n(arg1, n) + assert instance.name == "array_first_n" + assert instance.params[0] == arg1 + assert isinstance(instance.params[1], Constant) + assert instance.params[1].value == n + assert repr(instance) == "Value.array_first_n(Constant.of(2))" + infix_instance = arg1.array_first_n(n) + assert infix_instance == instance + + def test_array_last_n(self): + arg1 = self._make_arg("Value") + n = 2 + instance = Expression.array_last_n(arg1, n) + assert instance.name == "array_last_n" + assert instance.params[0] == arg1 + assert isinstance(instance.params[1], Constant) + assert instance.params[1].value == n + assert repr(instance) == "Value.array_last_n(Constant.of(2))" + infix_instance = arg1.array_last_n(n) + assert infix_instance == instance + + def test_array_maximum(self): + arg1 = self._make_arg("Value") + instance = Expression.array_maximum(arg1) + assert instance.name == "maximum" + assert instance.params == [arg1] + assert repr(instance) == "Value.maximum()" + infix_instance = arg1.array_maximum() + assert infix_instance == instance + + def test_array_minimum(self): + arg1 = self._make_arg("Value") + instance = Expression.array_minimum(arg1) + assert instance.name == "minimum" + assert instance.params == [arg1] + assert repr(instance) == "Value.minimum()" + infix_instance = arg1.array_minimum() + assert infix_instance == instance + + def test_array_maximum_n(self): + arg1 = self._make_arg("Value") + n = 2 + instance = Expression.array_maximum_n(arg1, n) + assert instance.name == "maximum_n" + assert instance.params[0] == arg1 + assert isinstance(instance.params[1], Constant) + assert instance.params[1].value == n + assert repr(instance) == "Value.maximum_n(Constant.of(2))" + infix_instance = arg1.array_maximum_n(n) + assert infix_instance == instance + + def test_array_minimum_n(self): + arg1 = self._make_arg("Value") + n = 2 + instance = Expression.array_minimum_n(arg1, n) + assert instance.name == "minimum_n" + assert instance.params[0] == arg1 + assert isinstance(instance.params[1], Constant) + assert instance.params[1].value == n + assert repr(instance) == "Value.minimum_n(Constant.of(2))" + infix_instance = arg1.array_minimum_n(n) + assert infix_instance == instance + + def test_array_slice_1_arg(self): + arg1 = self._make_arg("Value") + offset = 1 + instance = Expression.array_slice(arg1, offset) + assert instance.name == "array_slice" + assert instance.params[0] == arg1 + assert isinstance(instance.params[1], Constant) + assert instance.params[1].value == offset + assert len(instance.params) == 2 + assert repr(instance) == "Value.array_slice(Constant.of(1))" + infix_instance = arg1.array_slice(offset) + assert infix_instance == instance + + def test_array_slice_2_args(self): + arg1 = self._make_arg("Value") + offset = 1 + length = 2 + instance = Expression.array_slice(arg1, offset, length) + assert instance.name == "array_slice" + assert instance.params[0] == arg1 + assert isinstance(instance.params[1], Constant) + assert instance.params[1].value == offset + assert isinstance(instance.params[2], Constant) + assert instance.params[2].value == length + assert len(instance.params) == 3 + assert repr(instance) == "Value.array_slice(Constant.of(1), Constant.of(2))" + infix_instance = arg1.array_slice(offset, length) + assert infix_instance == instance + + def test_array_index_of(self): + arg1 = self._make_arg("Value") + value = "comedy" + instance = Expression.array_index_of(arg1, value) + assert instance.name == "array_index_of" + assert instance.params[0] == arg1 + assert isinstance(instance.params[1], Constant) + assert instance.params[1].value == value + assert isinstance(instance.params[2], Constant) + assert instance.params[2].value == "first" + assert len(instance.params) == 3 + assert ( + repr(instance) + == "Value.array_index_of(Constant.of('comedy'), Constant.of('first'))" + ) + infix_instance = arg1.array_index_of(value) + assert infix_instance == instance + + def test_array_index_of_all(self): + arg1 = self._make_arg("Value") + value = "comedy" + instance = Expression.array_index_of_all(arg1, value) + assert instance.name == "array_index_of_all" + assert instance.params[0] == arg1 + assert isinstance(instance.params[1], Constant) + assert instance.params[1].value == value + assert len(instance.params) == 2 + assert repr(instance) == "Value.array_index_of_all(Constant.of('comedy'))" + infix_instance = arg1.array_index_of_all(value) + assert infix_instance == instance + + def test_array_transform(self): + arg1 = self._make_arg("Value") + element_alias = "e" + body = self._make_arg("BodyValue") + instance = Expression.array_transform(arg1, element_alias, body) + assert instance.name == "array_transform" + assert instance.params[0] == arg1 + assert isinstance(instance.params[1], Constant) + assert instance.params[1].value == element_alias + assert instance.params[2] == body + assert len(instance.params) == 3 + assert repr(instance) == "Value.array_transform(Constant.of('e'), BodyValue)" + infix_instance = arg1.array_transform(element_alias, body) + assert infix_instance == instance + + def test_array_transform_with_index(self): + arg1 = self._make_arg("Value") + element_alias = "e" + index_alias = "i" + body = self._make_arg("BodyValue") + instance = Expression.array_transform( + arg1, element_alias, body, index_alias=index_alias + ) + assert instance.name == "array_transform" + assert instance.params[0] == arg1 + assert isinstance(instance.params[1], Constant) + assert instance.params[1].value == element_alias + assert isinstance(instance.params[2], Constant) + assert instance.params[2].value == index_alias + assert instance.params[3] == body + assert len(instance.params) == 4 + assert ( + repr(instance) + == "Value.array_transform(Constant.of('e'), Constant.of('i'), BodyValue)" + ) + infix_instance = arg1.array_transform( + element_alias, body, index_alias=index_alias + ) + assert infix_instance == instance + + def test_array_filter(self): + arg1 = self._make_arg("Value") + element_alias = "e" + body = self._make_arg("BodyValue") + instance = Expression.array_filter(arg1, element_alias, body) + assert instance.name == "array_filter" + assert instance.params[0] == arg1 + assert isinstance(instance.params[1], Constant) + assert instance.params[1].value == element_alias + assert instance.params[2] == body + assert len(instance.params) == 3 + assert repr(instance) == "Value.array_filter(Constant.of('e'), BodyValue)" + infix_instance = arg1.array_filter(element_alias, body) + assert infix_instance == instance