From 11bdbccb9befaa871ee2f9f902fa279c86c4c250 Mon Sep 17 00:00:00 2001 From: Linchin Date: Thu, 5 Mar 2026 22:22:11 +0000 Subject: [PATCH 01/10] feat: Add `Rand` expression --- .../firestore_v1/pipeline_expressions.py | 11 +++++ .../tests/system/pipeline_e2e/math.yaml | 45 +++++++++++++++++++ .../unit/v1/test_pipeline_expressions.py | 6 +++ 3 files changed, 62 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 376d785901fc..277fe18a83d3 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 @@ -2029,3 +2029,14 @@ class CurrentTimestamp(FunctionExpression): def __init__(self): super().__init__("current_timestamp", [], use_infix_repr=False) + + +class Rand(FunctionExpression): + """Creates an expression that returns a pseudorandom float between 0.0 (inclusive) and 1.0 (exclusive). + + Returns: + A new `Expression` representing the random value. + """ + + def __init__(self): + super().__init__("rand", [], use_infix_repr=False) diff --git a/packages/google-cloud-firestore/tests/system/pipeline_e2e/math.yaml b/packages/google-cloud-firestore/tests/system/pipeline_e2e/math.yaml index 4d35f746d3f4..0ef0a1f427d6 100644 --- a/packages/google-cloud-firestore/tests/system/pipeline_e2e/math.yaml +++ b/packages/google-cloud-firestore/tests/system/pipeline_e2e/math.yaml @@ -306,4 +306,49 @@ tests: - fieldReferenceValue: rating - integerValue: '2' name: mod + name: select + - description: testRand + pipeline: + - Collection: books + - Limit: 1 + - Select: + - AliasedExpression: + - And: + - FunctionExpression.greater_than_or_equal: + - Rand: [] + - Constant: 0.0 + - FunctionExpression.less_than: + - Rand: [] + - Constant: 1.0 + - "is_valid_rand" + assert_results: + - is_valid_rand: true + assert_proto: + pipeline: + stages: + - args: + - referenceValue: /books + name: collection + - args: + - integerValue: '1' + name: limit + - args: + - mapValue: + fields: + is_valid_rand: + functionValue: + name: and + args: + - functionValue: + name: greater_than_or_equal + args: + - functionValue: + name: rand + - doubleValue: 0.0 + - functionValue: + name: less_than + args: + - functionValue: + name: rand + - doubleValue: 1.0 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 fe7beb460502..7b73de12099f 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 @@ -1583,3 +1583,9 @@ def test_maximum(self): assert repr(instance) == "Value.maximum()" infix_instance = arg1.maximum() assert infix_instance == instance + + def test_rand(self): + instance = expr.Rand() + assert instance.name == "rand" + assert instance.params == [] + assert repr(instance) == "Rand()" From 40a490b15450ebf6dda52a78677d2db937e4128e Mon Sep 17 00:00:00 2001 From: Linchin Date: Thu, 5 Mar 2026 22:33:20 +0000 Subject: [PATCH 02/10] feat: add trunc function expression --- .../firestore_v1/pipeline_expressions.py | 13 ++++ .../tests/system/pipeline_e2e/math.yaml | 63 +++++++++++++++++++ .../unit/v1/test_pipeline_expressions.py | 9 +++ 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 277fe18a83d3..7f4dda095de8 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 @@ -405,6 +405,19 @@ def sqrt(self) -> "Expression": """ return FunctionExpression("sqrt", [self]) + @expose_as_static + def trunc(self) -> "Expression": + """Creates an expression that truncates this expression towards zero. + + Example: + >>> # Truncate the 'value' field. + >>> Field.of("value").trunc() + + Returns: + A new `Expression` representing the truncated value. + """ + return FunctionExpression("trunc", [self]) + @expose_as_static def logical_maximum(self, *others: Expression | CONSTANT_TYPE) -> "Expression": """Creates an expression that returns the larger value between this expression diff --git a/packages/google-cloud-firestore/tests/system/pipeline_e2e/math.yaml b/packages/google-cloud-firestore/tests/system/pipeline_e2e/math.yaml index 0ef0a1f427d6..ca190a62d8e3 100644 --- a/packages/google-cloud-firestore/tests/system/pipeline_e2e/math.yaml +++ b/packages/google-cloud-firestore/tests/system/pipeline_e2e/math.yaml @@ -134,6 +134,69 @@ tests: - fieldReferenceValue: rating name: sqrt name: select + - description: testTrunc + pipeline: + - Collection: books + - Where: + - FunctionExpression.equal_any: + - Field: title + - - Constant: "To Kill a Mockingbird" # rating 4.2 + - Constant: "Pride and Prejudice" # rating 4.5 + - Constant: "The Lord of the Rings" # rating 4.7 + - Select: + - title + - AliasedExpression: + - FunctionExpression.trunc: + - Field: rating + - "trunc_rating" + - Sort: + - Ordering: + - Field: title + - ASCENDING + assert_results: + - title: "Pride and Prejudice" + trunc_rating: 4.0 + - title: "The Lord of the Rings" + trunc_rating: 4.0 + - title: "To Kill a Mockingbird" + trunc_rating: 4.0 + assert_proto: + pipeline: + stages: + - args: + - referenceValue: /books + name: collection + - args: + - functionValue: + args: + - fieldReferenceValue: title + - functionValue: + args: + - stringValue: "To Kill a Mockingbird" + - stringValue: "Pride and Prejudice" + - stringValue: "The Lord of the Rings" + name: array + name: equal_any + name: where + - args: + - mapValue: + fields: + title: + fieldReferenceValue: title + trunc_rating: + functionValue: + args: + - fieldReferenceValue: rating + name: trunc + name: select + - args: + - mapValue: + fields: + direction: + stringValue: ascending + expression: + fieldReferenceValue: title + name: sort - description: testRoundFunctionExpressionessions pipeline: - Collection: books 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 7b73de12099f..3084fac965b1 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 @@ -1418,6 +1418,15 @@ def test_sqrt(self): infix_instance = arg1.sqrt() assert infix_instance == instance + def test_trunc(self): + arg1 = self._make_arg("Value") + instance = Expression.trunc(arg1) + assert instance.name == "trunc" + assert instance.params == [arg1] + assert repr(instance) == "Value.trunc()" + infix_instance = arg1.trunc() + assert infix_instance == instance + def test_array_length(self): arg1 = self._make_arg("Array") instance = Expression.array_length(arg1) From bc888161eef964e0382a4f2a712db316ae4e098b Mon Sep 17 00:00:00 2001 From: Linchin Date: Fri, 6 Mar 2026 18:43:22 +0000 Subject: [PATCH 03/10] update trunc docstring --- .../google/cloud/firestore_v1/pipeline_expressions.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 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 7f4dda095de8..9311d6d145fa 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 @@ -407,11 +407,12 @@ def sqrt(self) -> "Expression": @expose_as_static def trunc(self) -> "Expression": - """Creates an expression that truncates this expression towards zero. + """Creates an expression that truncates a numeric value to the specified + number of decimal places. Example: - >>> # Truncate the 'value' field. - >>> Field.of("value").trunc() + >>> # Truncate the 'value' field to 2 decimal places. + >>> Field.of("value").trunc(2) Returns: A new `Expression` representing the truncated value. @@ -2045,10 +2046,10 @@ def __init__(self): class Rand(FunctionExpression): - """Creates an expression that returns a pseudorandom float between 0.0 (inclusive) and 1.0 (exclusive). + """Creates an expression that generates a random number between 0.0 and 1.0 but not including 1.0. Returns: - A new `Expression` representing the random value. + A new `Expression` representing the rand operation. """ def __init__(self): From 4e122d81985f59be1dfd9b92f443ca27823556e4 Mon Sep 17 00:00:00 2001 From: Linchin Date: Wed, 11 Mar 2026 21:18:35 +0000 Subject: [PATCH 04/10] update docstring --- .../google/cloud/firestore_v1/pipeline_expressions.py | 3 +-- 1 file changed, 1 insertion(+), 2 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 9311d6d145fa..b2b1bbeb9e05 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 @@ -407,8 +407,7 @@ def sqrt(self) -> "Expression": @expose_as_static def trunc(self) -> "Expression": - """Creates an expression that truncates a numeric value to the specified - number of decimal places. + """Function to truncate a numeric expression to the nearest whole number towards zero. Example: >>> # Truncate the 'value' field to 2 decimal places. From fb9c57c02179a312114e196e90aa56f2c4033b11 Mon Sep 17 00:00:00 2001 From: Linchin Date: Wed, 11 Mar 2026 21:58:52 +0000 Subject: [PATCH 05/10] add parameter for trunc --- .../cloud/firestore_v1/pipeline_expressions.py | 7 ++++--- .../tests/system/pipeline_e2e/math.yaml | 14 ++++++++++++++ .../tests/unit/v1/test_pipeline_expressions.py | 8 ++++++++ 3 files changed, 26 insertions(+), 3 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 b2b1bbeb9e05..0cff309433a1 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 @@ -406,17 +406,18 @@ def sqrt(self) -> "Expression": return FunctionExpression("sqrt", [self]) @expose_as_static - def trunc(self) -> "Expression": + def trunc(self, places: "Expression" | None = None) -> "Expression": """Function to truncate a numeric expression to the nearest whole number towards zero. Example: >>> # Truncate the 'value' field to 2 decimal places. - >>> Field.of("value").trunc(2) + >>> Field.of("value").trunc(PipelineSource.literals(2)) Returns: A new `Expression` representing the truncated value. """ - return FunctionExpression("trunc", [self]) + params = [self, places] if places is not None else [self] + return FunctionExpression("trunc", params) @expose_as_static def logical_maximum(self, *others: Expression | CONSTANT_TYPE) -> "Expression": diff --git a/packages/google-cloud-firestore/tests/system/pipeline_e2e/math.yaml b/packages/google-cloud-firestore/tests/system/pipeline_e2e/math.yaml index ca190a62d8e3..5b05c24aeb71 100644 --- a/packages/google-cloud-firestore/tests/system/pipeline_e2e/math.yaml +++ b/packages/google-cloud-firestore/tests/system/pipeline_e2e/math.yaml @@ -149,6 +149,11 @@ tests: - FunctionExpression.trunc: - Field: rating - "trunc_rating" + - AliasedExpression: + - FunctionExpression.trunc: + - Field: rating + - Constant: 1 + - "trunc_rating_with_places" - Sort: - Ordering: - Field: title @@ -156,10 +161,13 @@ tests: assert_results: - title: "Pride and Prejudice" trunc_rating: 4.0 + trunc_rating_with_places: 4.5 - title: "The Lord of the Rings" trunc_rating: 4.0 + trunc_rating_with_places: 4.7 - title: "To Kill a Mockingbird" trunc_rating: 4.0 + trunc_rating_with_places: 4.2 assert_proto: pipeline: stages: @@ -188,6 +196,12 @@ tests: args: - fieldReferenceValue: rating name: trunc + trunc_rating_with_places: + functionValue: + args: + - fieldReferenceValue: rating + - integerValue: '1' + name: trunc name: select - args: - mapValue: 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 3084fac965b1..eff8512346ca 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 @@ -1427,6 +1427,14 @@ def test_trunc(self): infix_instance = arg1.trunc() assert infix_instance == instance + places = self._make_arg("Places") + instance_with_places = Expression.trunc(arg1, places) + assert instance_with_places.name == "trunc" + assert instance_with_places.params == [arg1, places] + assert repr(instance_with_places) == "Value.trunc(Places)" + infix_instance_with_places = arg1.trunc(places) + assert infix_instance_with_places == instance_with_places + def test_array_length(self): arg1 = self._make_arg("Array") instance = Expression.array_length(arg1) From 920fb15c31ea2631627c44917c8f2de9e66ce0e4 Mon Sep 17 00:00:00 2001 From: Linchin Date: Wed, 11 Mar 2026 22:00:31 +0000 Subject: [PATCH 06/10] docstring --- .../google/cloud/firestore_v1/pipeline_expressions.py | 3 ++- 1 file changed, 2 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 0cff309433a1..f54c9059aa51 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 @@ -2046,7 +2046,8 @@ def __init__(self): class Rand(FunctionExpression): - """Creates an expression that generates a random number between 0.0 and 1.0 but not including 1.0. + """Creates an expression that generates a random number between 0.0 and 1.0 but not + including 1.0. Returns: A new `Expression` representing the rand operation. From 843a07d4b3c97aa9948b8cb8fb8efd757d5563fa Mon Sep 17 00:00:00 2001 From: Linchin Date: Wed, 18 Mar 2026 21:17:26 +0000 Subject: [PATCH 07/10] Update docstring --- .../google/cloud/firestore_v1/pipeline_expressions.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 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 f54c9059aa51..bdd03bc334b0 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 @@ -406,8 +406,10 @@ def sqrt(self) -> "Expression": return FunctionExpression("sqrt", [self]) @expose_as_static - def trunc(self, places: "Expression" | None = None) -> "Expression": - """Function to truncate a numeric expression to the nearest whole number towards zero. + def trunc(self, places: Expression | None = None) -> "Expression": + """Creates an expression that truncates the numeric value. If places is None, + truncates to an integer. Otherwise, truncates the numeric value to the + specified number of decimal places. Example: >>> # Truncate the 'value' field to 2 decimal places. From b296479c91c0cb9de9ca8d57fe5ae3a2886ddbe9 Mon Sep 17 00:00:00 2001 From: Linchin Date: Wed, 18 Mar 2026 21:23:22 +0000 Subject: [PATCH 08/10] add system test --- .../tests/system/pipeline_e2e/math.yaml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/google-cloud-firestore/tests/system/pipeline_e2e/math.yaml b/packages/google-cloud-firestore/tests/system/pipeline_e2e/math.yaml index 5b05c24aeb71..aaa9117b0626 100644 --- a/packages/google-cloud-firestore/tests/system/pipeline_e2e/math.yaml +++ b/packages/google-cloud-firestore/tests/system/pipeline_e2e/math.yaml @@ -152,7 +152,9 @@ tests: - AliasedExpression: - FunctionExpression.trunc: - Field: rating - - Constant: 1 + - FunctionExpression.add: + - Constant: 0 + - Constant: 1 - "trunc_rating_with_places" - Sort: - Ordering: @@ -200,7 +202,11 @@ tests: functionValue: args: - fieldReferenceValue: rating - - integerValue: '1' + - functionValue: + args: + - integerValue: '0' + - integerValue: '1' + name: add name: trunc name: select - args: From 5cd0c8281a6a1edb5506f861f58a2760b915133e Mon Sep 17 00:00:00 2001 From: Linchin Date: Wed, 18 Mar 2026 21:26:55 +0000 Subject: [PATCH 09/10] type hint --- .../google/cloud/firestore_v1/pipeline_expressions.py | 2 +- 1 file changed, 1 insertion(+), 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 bdd03bc334b0..7b7c888224bb 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 @@ -406,7 +406,7 @@ def sqrt(self) -> "Expression": return FunctionExpression("sqrt", [self]) @expose_as_static - def trunc(self, places: Expression | None = None) -> "Expression": + def trunc(self, places: Expression | int | None = None) -> "Expression": """Creates an expression that truncates the numeric value. If places is None, truncates to an integer. Otherwise, truncates the numeric value to the specified number of decimal places. From 06d3fb19e29596299fa36e21b296c5b4a0b64c09 Mon Sep 17 00:00:00 2001 From: Linchin Date: Wed, 18 Mar 2026 21:35:50 +0000 Subject: [PATCH 10/10] lint --- .../google/cloud/firestore_v1/pipeline_expressions.py | 6 +++++- 1 file changed, 5 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 8a95bec24017..2edd510070ca 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 @@ -418,7 +418,11 @@ def trunc(self, places: Expression | int | None = None) -> "Expression": Returns: A new `Expression` representing the truncated value. """ - params = [self, places] if places is not None else [self] + params = ( + [self, self._cast_to_expr_or_convert_to_constant(places)] + if places is not None + else [self] + ) return FunctionExpression("trunc", params) @expose_as_static