From 8a02c167a811c9bcb52c38dcfc56097a73957a16 Mon Sep 17 00:00:00 2001 From: Linchin Date: Wed, 18 Mar 2026 23:04:46 +0000 Subject: [PATCH 01/10] feat: support array_first, array_last, array_first_n, array_last_n --- .../firestore_v1/pipeline_expressions.py | 52 ++++++ .../tests/system/pipeline_e2e/array.yaml | 148 ++++++++++++++++++ .../unit/v1/test_pipeline_expressions.py | 42 +++++ 3 files changed, 242 insertions(+) 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..458671b8130b 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,58 @@ 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 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..416ebb2eb63a 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,152 @@ tests: - fieldReferenceValue: tags - integerValue: '-1' name: array_get + 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 \ No newline at end of file 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..cd40c5594b71 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,45 @@ 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 isinstance(instance.params[0], Constant) + assert instance.params[0].value == n + assert instance.params[1] == arg1 + assert repr(instance) == "Constant.of(2).array_first_n(Value)" + 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 isinstance(instance.params[0], Constant) + assert instance.params[0].value == n + assert instance.params[1] == arg1 + assert repr(instance) == "Constant.of(2).array_last_n(Value)" + infix_instance = arg1.array_last_n(n) + assert infix_instance == instance From bb7ddf55517e6616dc7409b260af8d278587a3d5 Mon Sep 17 00:00:00 2001 From: Linchin Date: Wed, 18 Mar 2026 23:13:15 +0000 Subject: [PATCH 02/10] fix unit test --- .../tests/unit/v1/test_pipeline_expressions.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) 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 cd40c5594b71..e8e439371e00 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 @@ -1643,10 +1643,10 @@ def test_array_first_n(self): n = 2 instance = Expression.array_first_n(arg1, n) assert instance.name == "array_first_n" - assert isinstance(instance.params[0], Constant) - assert instance.params[0].value == n - assert instance.params[1] == arg1 - assert repr(instance) == "Constant.of(2).array_first_n(Value)" + 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 @@ -1655,9 +1655,9 @@ def test_array_last_n(self): n = 2 instance = Expression.array_last_n(arg1, n) assert instance.name == "array_last_n" - assert isinstance(instance.params[0], Constant) - assert instance.params[0].value == n - assert instance.params[1] == arg1 - assert repr(instance) == "Constant.of(2).array_last_n(Value)" + 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 From 975c7e0224b6aeb535ec357adba220799b2c2b6e Mon Sep 17 00:00:00 2001 From: Linchin Date: Wed, 18 Mar 2026 23:26:49 +0000 Subject: [PATCH 03/10] support array_max, array_max_n, array_min, array_min_n --- .../firestore_v1/pipeline_expressions.py | 62 ++++++++++++++++++ .../tests/system/pipeline_e2e/array.yaml | 64 ++++++++++++++++++- .../unit/v1/test_pipeline_expressions.py | 42 ++++++++++++ 3 files changed, 167 insertions(+), 1 deletion(-) 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 458671b8130b..fbedbbf7215e 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 @@ -1542,6 +1542,68 @@ def array_last_n(self, n: int | "Expression") -> "Expression": """ 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 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 416ebb2eb63a..0bcc14be8e65 100644 --- a/packages/google-cloud-firestore/tests/system/pipeline_e2e/array.yaml +++ b/packages/google-cloud-firestore/tests/system/pipeline_e2e/array.yaml @@ -609,4 +609,66 @@ tests: - fieldReferenceValue: tags - integerValue: '2' name: array_last_n - name: select \ No newline at end of file + 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"] 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 e8e439371e00..87c450e9bb0d 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 @@ -1661,3 +1661,45 @@ def test_array_last_n(self): 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 From 35eba8d9e151851278c8d73c89f2f65d2f565649 Mon Sep 17 00:00:00 2001 From: Linchin Date: Wed, 18 Mar 2026 23:37:57 +0000 Subject: [PATCH 04/10] support array_slice --- .../firestore_v1/pipeline_expressions.py | 24 ++++++++++++++ .../tests/system/pipeline_e2e/array.yaml | 33 +++++++++++++++++++ .../unit/v1/test_pipeline_expressions.py | 29 ++++++++++++++++ 3 files changed, 86 insertions(+) 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 fbedbbf7215e..fc2e85617ce8 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 @@ -1604,6 +1604,30 @@ def array_minimum_n(self, n: int | "Expression") -> "Expression": """ 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": + """Creates an expression that returns a slice of an array. + + Example: + >>> # Slice array 'scores' starting at index 1 with length 2 + >>> Field.of("scores").array_slice(1, 2) + + Note: + Both offset and length allow negative values to represent wrap-around from the end + of the array. + + Args: + offset: The starting index of the slice. + 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 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 0bcc14be8e65..3e9a6108a8c9 100644 --- a/packages/google-cloud-firestore/tests/system/pipeline_e2e/array.yaml +++ b/packages/google-cloud-firestore/tests/system/pipeline_e2e/array.yaml @@ -672,3 +672,36 @@ tests: - "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"] 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 87c450e9bb0d..bd855c1cbac1 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 @@ -1703,3 +1703,32 @@ def test_array_minimum_n(self): 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 From dad5c0ded30fa8237529bb14db573ce17db9286a Mon Sep 17 00:00:00 2001 From: Linchin Date: Fri, 20 Mar 2026 23:48:32 +0000 Subject: [PATCH 05/10] update docstring for array_slice --- .../google/cloud/firestore_v1/pipeline_expressions.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) 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 fc2e85617ce8..8ce6873d1f6d 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 @@ -1606,18 +1606,15 @@ def array_minimum_n(self, n: int | "Expression") -> "Expression": @expose_as_static def array_slice(self, offset: int | "Expression", length: int | "Expression" | None = None) -> "Expression": - """Creates an expression that returns a slice of an array. + """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) - Note: - Both offset and length allow negative values to represent wrap-around from the end - of the array. - Args: - offset: The starting index of the slice. + 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: From 4e1e84730d3569406f8ee26e71af265e73cd5397 Mon Sep 17 00:00:00 2001 From: Linchin Date: Sat, 21 Mar 2026 00:09:02 +0000 Subject: [PATCH 06/10] feat: add array_index_of expression and corresponding unit and system tests. --- .../firestore_v1/pipeline_expressions.py | 27 +++++++++++++ .../tests/system/pipeline_e2e/array.yaml | 40 +++++++++++++++++++ .../unit/v1/test_pipeline_expressions.py | 15 +++++++ 3 files changed, 82 insertions(+) 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 8ce6873d1f6d..f33ee17d9c08 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 @@ -1625,6 +1625,33 @@ def array_slice(self, offset: int | "Expression", length: int | "Expression" | N 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 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 3e9a6108a8c9..bad1c67b3f81 100644 --- a/packages/google-cloud-firestore/tests/system/pipeline_e2e/array.yaml +++ b/packages/google-cloud-firestore/tests/system/pipeline_e2e/array.yaml @@ -705,3 +705,43 @@ tests: - "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 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 bd855c1cbac1..c35534ab45b1 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 @@ -1732,3 +1732,18 @@ def test_array_slice_2_args(self): 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 From 166f18026cc54b6a6a48118512b7a0da786a4c3d Mon Sep 17 00:00:00 2001 From: Linchin Date: Sat, 21 Mar 2026 00:28:19 +0000 Subject: [PATCH 07/10] feat: add `array_index_of_all` expression for pipeline queries with unit and system tests. --- .../firestore_v1/pipeline_expressions.py | 25 +++++++++ .../tests/system/pipeline_e2e/array.yaml | 55 +++++++++++++++++++ .../unit/v1/test_pipeline_expressions.py | 13 +++++ 3 files changed, 93 insertions(+) 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 f33ee17d9c08..0c87752dc04a 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 @@ -1652,6 +1652,31 @@ def array_index_of( ] ) + @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 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 bad1c67b3f81..e29ef0d6c2ed 100644 --- a/packages/google-cloud-firestore/tests/system/pipeline_e2e/array.yaml +++ b/packages/google-cloud-firestore/tests/system/pipeline_e2e/array.yaml @@ -745,3 +745,58 @@ tests: - 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 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 c35534ab45b1..0f25ccb3270f 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 @@ -1747,3 +1747,16 @@ def test_array_index_of(self): 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 From 755e643e9e66710fd0e1c577d193456f24fb0b8f Mon Sep 17 00:00:00 2001 From: Linchin Date: Sat, 21 Mar 2026 00:45:50 +0000 Subject: [PATCH 08/10] feat: Add `array_transform` function to `Expression` for transforming array elements with optional index. --- .../firestore_v1/pipeline_expressions.py | 33 +++++++ .../tests/system/pipeline_e2e/array.yaml | 98 +++++++++++++++++++ .../unit/v1/test_pipeline_expressions.py | 34 +++++++ 3 files changed, 165 insertions(+) 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 0c87752dc04a..a43e46eeb444 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 @@ -1677,6 +1677,39 @@ def array_index_of_all( ] ) + @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 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 e29ef0d6c2ed..7c4d1fea3a4e 100644 --- a/packages/google-cloud-firestore/tests/system/pipeline_e2e/array.yaml +++ b/packages/google-cloud-firestore/tests/system/pipeline_e2e/array.yaml @@ -800,3 +800,101 @@ tests: - 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 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 0f25ccb3270f..bc55e3fa8e27 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 @@ -1760,3 +1760,37 @@ def test_array_index_of_all(self): 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 + From 2bd56860b3e73ea01e85c54bac8e6b341cd5f074 Mon Sep 17 00:00:00 2001 From: Linchin Date: Sat, 21 Mar 2026 01:02:03 +0000 Subject: [PATCH 09/10] implement `array_filter` --- .../firestore_v1/pipeline_expressions.py | 22 +++++++++ .../tests/system/pipeline_e2e/array.yaml | 48 +++++++++++++++++++ .../unit/v1/test_pipeline_expressions.py | 15 ++++++ 3 files changed, 85 insertions(+) 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 a43e46eeb444..904e147cfac4 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 @@ -1709,6 +1709,28 @@ def array_transform( 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": 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 7c4d1fea3a4e..0e7f4b303eef 100644 --- a/packages/google-cloud-firestore/tests/system/pipeline_e2e/array.yaml +++ b/packages/google-cloud-firestore/tests/system/pipeline_e2e/array.yaml @@ -898,3 +898,51 @@ tests: - 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 bc55e3fa8e27..c67da914f6ec 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 @@ -1794,3 +1794,18 @@ def test_array_transform_with_index(self): 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 + From 496d8e2a086dd81d4ce19a34a02635f206b5f731 Mon Sep 17 00:00:00 2001 From: Linchin Date: Sat, 21 Mar 2026 01:03:13 +0000 Subject: [PATCH 10/10] lint --- .../firestore_v1/pipeline_expressions.py | 46 ++++++++++--------- .../unit/v1/test_pipeline_expressions.py | 19 ++++++-- 2 files changed, 38 insertions(+), 27 deletions(-) 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 904e147cfac4..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 @@ -1527,7 +1527,9 @@ def array_first_n(self, n: int | "Expression") -> "Expression": 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)]) + 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": @@ -1540,7 +1542,9 @@ def array_last_n(self, n: int | "Expression") -> "Expression": 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)]) + return FunctionExpression( + "array_last_n", [self, self._cast_to_expr_or_convert_to_constant(n)] + ) @expose_as_static def array_maximum(self) -> "Expression": @@ -1584,7 +1588,9 @@ def array_maximum_n(self, n: int | "Expression") -> "Expression": 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)]) + 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": @@ -1602,10 +1608,14 @@ def array_minimum_n(self, n: int | "Expression") -> "Expression": 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)]) + 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": + 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. @@ -1626,9 +1636,7 @@ def array_slice(self, offset: int | "Expression", length: int | "Expression" | N return FunctionExpression("array_slice", args) @expose_as_static - def array_index_of( - self, search: "Expression" | CONSTANT_TYPE - ) -> "Expression": + 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. @@ -1644,18 +1652,16 @@ def array_index_of( A new `Expression` representing the 'array_index_of' value. """ return FunctionExpression( - "array_index_of", + "array_index_of", [ - self, + self, self._cast_to_expr_or_convert_to_constant(search), - self._cast_to_expr_or_convert_to_constant("first") - ] + self._cast_to_expr_or_convert_to_constant("first"), + ], ) @expose_as_static - def array_index_of_all( - self, search: "Expression" | CONSTANT_TYPE - ) -> "Expression": + 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. @@ -1670,11 +1676,8 @@ def array_index_of_all( 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) - ] + "array_index_of_all", + [self, self._cast_to_expr_or_convert_to_constant(search)], ) @expose_as_static @@ -1704,7 +1707,7 @@ def array_transform( ] 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) @@ -1731,7 +1734,6 @@ def array_filter(self, element_alias: str, body: "Expression") -> "Expression": ], ) - @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/unit/v1/test_pipeline_expressions.py b/packages/google-cloud-firestore/tests/unit/v1/test_pipeline_expressions.py index c67da914f6ec..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 @@ -1744,7 +1744,10 @@ def test_array_index_of(self): 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'))" + assert ( + repr(instance) + == "Value.array_index_of(Constant.of('comedy'), Constant.of('first'))" + ) infix_instance = arg1.array_index_of(value) assert infix_instance == instance @@ -1781,7 +1784,9 @@ def test_array_transform_with_index(self): element_alias = "e" index_alias = "i" body = self._make_arg("BodyValue") - instance = Expression.array_transform(arg1, element_alias, body, index_alias=index_alias) + 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) @@ -1790,8 +1795,13 @@ def test_array_transform_with_index(self): 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 ( + 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): @@ -1808,4 +1818,3 @@ def test_array_filter(self): assert repr(instance) == "Value.array_filter(Constant.of('e'), BodyValue)" infix_instance = arg1.array_filter(element_alias, body) assert infix_instance == instance -