1# Copyright 2025 Google LLC
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14"""
15.. warning::
16 **Preview API**: Firestore Pipelines is currently in preview and is
17 subject to potential breaking changes in future releases.
18"""
19
20from __future__ import annotations
21from typing import (
22 Any,
23 Generic,
24 TypeVar,
25 Sequence,
26)
27from abc import ABC
28from abc import abstractmethod
29from enum import Enum
30import datetime
31from google.cloud.firestore_v1.types.document import Value
32from google.cloud.firestore_v1.types.query import StructuredQuery as Query_pb
33from google.cloud.firestore_v1.vector import Vector
34from google.cloud.firestore_v1._helpers import GeoPoint
35from google.cloud.firestore_v1._helpers import encode_value
36from google.cloud.firestore_v1._helpers import decode_value
37
38CONSTANT_TYPE = TypeVar(
39 "CONSTANT_TYPE",
40 str,
41 int,
42 float,
43 bool,
44 datetime.datetime,
45 bytes,
46 GeoPoint,
47 Vector,
48 None,
49)
50
51
52class Ordering:
53 """Represents the direction for sorting results in a pipeline."""
54
55 class Direction(Enum):
56 ASCENDING = "ascending"
57 DESCENDING = "descending"
58
59 def __init__(self, expr, order_dir: Direction | str = Direction.ASCENDING):
60 """
61 Initializes an Ordering instance
62
63 Args:
64 expr (Expression | str): The expression or field path string to sort by.
65 If a string is provided, it's treated as a field path.
66 order_dir (Direction | str): The direction to sort in.
67 Defaults to ascending
68 """
69 self.expr = expr if isinstance(expr, Expression) else Field.of(expr)
70 self.order_dir = (
71 Ordering.Direction[order_dir.upper()]
72 if isinstance(order_dir, str)
73 else order_dir
74 )
75
76 def __repr__(self):
77 if self.order_dir is Ordering.Direction.ASCENDING:
78 order_str = ".ascending()"
79 else:
80 order_str = ".descending()"
81 return f"{self.expr!r}{order_str}"
82
83 def _to_pb(self) -> Value:
84 return Value(
85 map_value={
86 "fields": {
87 "direction": Value(string_value=self.order_dir.value),
88 "expression": self.expr._to_pb(),
89 }
90 }
91 )
92
93
94class Expression(ABC):
95 """Represents an expression that can be evaluated to a value within the
96 execution of a pipeline.
97
98 Expressionessions are the building blocks for creating complex queries and
99 transformations in Firestore pipelines. They can represent:
100
101 - **Field references:** Access values from document fields.
102 - **Literals:** Represent constant values (strings, numbers, booleans).
103 - **FunctionExpression calls:** Apply functions to one or more expressions.
104 - **Aggregations:** Calculate aggregate values (e.g., sum, average) over a set of documents.
105
106 The `Expression` class provides a fluent API for building expressions. You can chain
107 together method calls to create complex expressions.
108 """
109
110 def __repr__(self):
111 return f"{self.__class__.__name__}()"
112
113 @abstractmethod
114 def _to_pb(self) -> Value:
115 raise NotImplementedError
116
117 @staticmethod
118 def _cast_to_expr_or_convert_to_constant(
119 o: Any, include_vector=False
120 ) -> "Expression":
121 """Convert arbitrary object to an Expression."""
122 if isinstance(o, Expression):
123 return o
124 if isinstance(o, dict):
125 return Map(o)
126 if isinstance(o, list):
127 if include_vector and all([isinstance(i, (float, int)) for i in o]):
128 return Constant(Vector(o))
129 else:
130 return Array(o)
131 return Constant(o)
132
133 class expose_as_static:
134 """
135 Decorator to mark instance methods to be exposed as static methods as well as instance
136 methods.
137
138 When called statically, the first argument is converted to a Field expression if needed.
139
140 Example:
141 >>> Field.of("test").add(5)
142 >>> FunctionExpression.add("test", 5)
143 """
144
145 def __init__(self, instance_func):
146 self.instance_func = instance_func
147
148 def static_func(self, first_arg, *other_args, **kwargs):
149 if not isinstance(first_arg, (Expression, str)):
150 raise TypeError(
151 f"'{self.instance_func.__name__}' must be called on an Expression or a string representing a field. got {type(first_arg)}."
152 )
153 first_expr = (
154 Field.of(first_arg)
155 if not isinstance(first_arg, Expression)
156 else first_arg
157 )
158 return self.instance_func(first_expr, *other_args, **kwargs)
159
160 def __get__(self, instance, owner):
161 if instance is None:
162 return self.static_func
163 else:
164 return self.instance_func.__get__(instance, owner)
165
166 @expose_as_static
167 def add(self, other: Expression | float) -> "Expression":
168 """Creates an expression that adds this expression to another expression or constant.
169
170 Example:
171 >>> # Add the value of the 'quantity' field and the 'reserve' field.
172 >>> Field.of("quantity").add(Field.of("reserve"))
173 >>> # Add 5 to the value of the 'age' field
174 >>> Field.of("age").add(5)
175
176 Args:
177 other: The expression or constant value to add to this expression.
178
179 Returns:
180 A new `Expression` representing the addition operation.
181 """
182 return FunctionExpression(
183 "add", [self, self._cast_to_expr_or_convert_to_constant(other)]
184 )
185
186 @expose_as_static
187 def subtract(self, other: Expression | float) -> "Expression":
188 """Creates an expression that subtracts another expression or constant from this expression.
189
190 Example:
191 >>> # Subtract the 'discount' field from the 'price' field
192 >>> Field.of("price").subtract(Field.of("discount"))
193 >>> # Subtract 20 from the value of the 'total' field
194 >>> Field.of("total").subtract(20)
195
196 Args:
197 other: The expression or constant value to subtract from this expression.
198
199 Returns:
200 A new `Expression` representing the subtraction operation.
201 """
202 return FunctionExpression(
203 "subtract", [self, self._cast_to_expr_or_convert_to_constant(other)]
204 )
205
206 @expose_as_static
207 def multiply(self, other: Expression | float) -> "Expression":
208 """Creates an expression that multiplies this expression by another expression or constant.
209
210 Example:
211 >>> # Multiply the 'quantity' field by the 'price' field
212 >>> Field.of("quantity").multiply(Field.of("price"))
213 >>> # Multiply the 'value' field by 2
214 >>> Field.of("value").multiply(2)
215
216 Args:
217 other: The expression or constant value to multiply by.
218
219 Returns:
220 A new `Expression` representing the multiplication operation.
221 """
222 return FunctionExpression(
223 "multiply", [self, self._cast_to_expr_or_convert_to_constant(other)]
224 )
225
226 @expose_as_static
227 def divide(self, other: Expression | float) -> "Expression":
228 """Creates an expression that divides this expression by another expression or constant.
229
230 Example:
231 >>> # Divide the 'total' field by the 'count' field
232 >>> Field.of("total").divide(Field.of("count"))
233 >>> # Divide the 'value' field by 10
234 >>> Field.of("value").divide(10)
235
236 Args:
237 other: The expression or constant value to divide by.
238
239 Returns:
240 A new `Expression` representing the division operation.
241 """
242 return FunctionExpression(
243 "divide", [self, self._cast_to_expr_or_convert_to_constant(other)]
244 )
245
246 @expose_as_static
247 def mod(self, other: Expression | float) -> "Expression":
248 """Creates an expression that calculates the modulo (remainder) to another expression or constant.
249
250 Example:
251 >>> # Calculate the remainder of dividing the 'value' field by field 'divisor'.
252 >>> Field.of("value").mod(Field.of("divisor"))
253 >>> # Calculate the remainder of dividing the 'value' field by 5.
254 >>> Field.of("value").mod(5)
255
256 Args:
257 other: The divisor expression or constant.
258
259 Returns:
260 A new `Expression` representing the modulo operation.
261 """
262 return FunctionExpression(
263 "mod", [self, self._cast_to_expr_or_convert_to_constant(other)]
264 )
265
266 @expose_as_static
267 def abs(self) -> "Expression":
268 """Creates an expression that calculates the absolute value of this expression.
269
270 Example:
271 >>> # Get the absolute value of the 'change' field.
272 >>> Field.of("change").abs()
273
274 Returns:
275 A new `Expression` representing the absolute value.
276 """
277 return FunctionExpression("abs", [self])
278
279 @expose_as_static
280 def ceil(self) -> "Expression":
281 """Creates an expression that calculates the ceiling of this expression.
282
283 Example:
284 >>> # Get the ceiling of the 'value' field.
285 >>> Field.of("value").ceil()
286
287 Returns:
288 A new `Expression` representing the ceiling value.
289 """
290 return FunctionExpression("ceil", [self])
291
292 @expose_as_static
293 def exp(self) -> "Expression":
294 """Creates an expression that computes e to the power of this expression.
295
296 Example:
297 >>> # Compute e to the power of the 'value' field
298 >>> Field.of("value").exp()
299
300 Returns:
301 A new `Expression` representing the exponential value.
302 """
303 return FunctionExpression("exp", [self])
304
305 @expose_as_static
306 def floor(self) -> "Expression":
307 """Creates an expression that calculates the floor of this expression.
308
309 Example:
310 >>> # Get the floor of the 'value' field.
311 >>> Field.of("value").floor()
312
313 Returns:
314 A new `Expression` representing the floor value.
315 """
316 return FunctionExpression("floor", [self])
317
318 @expose_as_static
319 def ln(self) -> "Expression":
320 """Creates an expression that calculates the natural logarithm of this expression.
321
322 Example:
323 >>> # Get the natural logarithm of the 'value' field.
324 >>> Field.of("value").ln()
325
326 Returns:
327 A new `Expression` representing the natural logarithm.
328 """
329 return FunctionExpression("ln", [self])
330
331 @expose_as_static
332 def log(self, base: Expression | float) -> "Expression":
333 """Creates an expression that calculates the logarithm of this expression with a given base.
334
335 Example:
336 >>> # Get the logarithm of 'value' with base 2.
337 >>> Field.of("value").log(2)
338 >>> # Get the logarithm of 'value' with base from 'base_field'.
339 >>> Field.of("value").log(Field.of("base_field"))
340
341 Args:
342 base: The base of the logarithm.
343
344 Returns:
345 A new `Expression` representing the logarithm.
346 """
347 return FunctionExpression(
348 "log", [self, self._cast_to_expr_or_convert_to_constant(base)]
349 )
350
351 @expose_as_static
352 def log10(self) -> "Expression":
353 """Creates an expression that calculates the base 10 logarithm of this expression.
354
355 Example:
356 >>> Field.of("value").log10()
357
358 Returns:
359 A new `Expression` representing the logarithm.
360 """
361 return FunctionExpression("log10", [self])
362
363 @expose_as_static
364 def pow(self, exponent: Expression | float) -> "Expression":
365 """Creates an expression that calculates this expression raised to the power of the exponent.
366
367 Example:
368 >>> # Raise 'base_val' to the power of 2.
369 >>> Field.of("base_val").pow(2)
370 >>> # Raise 'base_val' to the power of 'exponent_val'.
371 >>> Field.of("base_val").pow(Field.of("exponent_val"))
372
373 Args:
374 exponent: The exponent.
375
376 Returns:
377 A new `Expression` representing the power operation.
378 """
379 return FunctionExpression(
380 "pow", [self, self._cast_to_expr_or_convert_to_constant(exponent)]
381 )
382
383 @expose_as_static
384 def round(self) -> "Expression":
385 """Creates an expression that rounds this expression to the nearest integer.
386
387 Example:
388 >>> # Round the 'value' field.
389 >>> Field.of("value").round()
390
391 Returns:
392 A new `Expression` representing the rounded value.
393 """
394 return FunctionExpression("round", [self])
395
396 @expose_as_static
397 def sqrt(self) -> "Expression":
398 """Creates an expression that calculates the square root of this expression.
399
400 Example:
401 >>> # Get the square root of the 'area' field.
402 >>> Field.of("area").sqrt()
403
404 Returns:
405 A new `Expression` representing the square root.
406 """
407 return FunctionExpression("sqrt", [self])
408
409 @expose_as_static
410 def logical_maximum(self, *others: Expression | CONSTANT_TYPE) -> "Expression":
411 """Creates an expression that returns the larger value between this expression
412 and another expression or constant, based on Firestore's value type ordering.
413
414 Firestore's value type ordering is described here:
415 https://cloud.google.com/firestore/docs/concepts/data-types#value_type_ordering
416
417 Example:
418 >>> # Returns the larger value between the 'discount' field and the 'cap' field.
419 >>> Field.of("discount").logical_maximum(Field.of("cap"))
420 >>> # Returns the larger value between the 'value' field and some ints
421 >>> Field.of("value").logical_maximum(10, 20, 30)
422
423 Args:
424 others: The other expression or constant values to compare with.
425
426 Returns:
427 A new `Expression` representing the logical maximum operation.
428 """
429 return FunctionExpression(
430 "maximum",
431 [self] + [self._cast_to_expr_or_convert_to_constant(o) for o in others],
432 infix_name_override="logical_maximum",
433 )
434
435 @expose_as_static
436 def logical_minimum(self, *others: Expression | CONSTANT_TYPE) -> "Expression":
437 """Creates an expression that returns the smaller value between this expression
438 and another expression or constant, based on Firestore's value type ordering.
439
440 Firestore's value type ordering is described here:
441 https://cloud.google.com/firestore/docs/concepts/data-types#value_type_ordering
442
443 Example:
444 >>> # Returns the smaller value between the 'discount' field and the 'floor' field.
445 >>> Field.of("discount").logical_minimum(Field.of("floor"))
446 >>> # Returns the smaller value between the 'value' field and some ints
447 >>> Field.of("value").logical_minimum(10, 20, 30)
448
449 Args:
450 others: The other expression or constant values to compare with.
451
452 Returns:
453 A new `Expression` representing the logical minimum operation.
454 """
455 return FunctionExpression(
456 "minimum",
457 [self] + [self._cast_to_expr_or_convert_to_constant(o) for o in others],
458 infix_name_override="logical_minimum",
459 )
460
461 @expose_as_static
462 def equal(self, other: Expression | CONSTANT_TYPE) -> "BooleanExpression":
463 """Creates an expression that checks if this expression is equal to another
464 expression or constant value.
465
466 Example:
467 >>> # Check if the 'age' field is equal to 21
468 >>> Field.of("age").equal(21)
469 >>> # Check if the 'city' field is equal to "London"
470 >>> Field.of("city").equal("London")
471
472 Args:
473 other: The expression or constant value to compare for equality.
474
475 Returns:
476 A new `Expression` representing the equality comparison.
477 """
478 return BooleanExpression(
479 "equal", [self, self._cast_to_expr_or_convert_to_constant(other)]
480 )
481
482 @expose_as_static
483 def not_equal(self, other: Expression | CONSTANT_TYPE) -> "BooleanExpression":
484 """Creates an expression that checks if this expression is not equal to another
485 expression or constant value.
486
487 Example:
488 >>> # Check if the 'status' field is not equal to "completed"
489 >>> Field.of("status").not_equal("completed")
490 >>> # Check if the 'country' field is not equal to "USA"
491 >>> Field.of("country").not_equal("USA")
492
493 Args:
494 other: The expression or constant value to compare for inequality.
495
496 Returns:
497 A new `Expression` representing the inequality comparison.
498 """
499 return BooleanExpression(
500 "not_equal", [self, self._cast_to_expr_or_convert_to_constant(other)]
501 )
502
503 @expose_as_static
504 def greater_than(self, other: Expression | CONSTANT_TYPE) -> "BooleanExpression":
505 """Creates an expression that checks if this expression is greater than another
506 expression or constant value.
507
508 Example:
509 >>> # Check if the 'age' field is greater than the 'limit' field
510 >>> Field.of("age").greater_than(Field.of("limit"))
511 >>> # Check if the 'price' field is greater than 100
512 >>> Field.of("price").greater_than(100)
513
514 Args:
515 other: The expression or constant value to compare for greater than.
516
517 Returns:
518 A new `Expression` representing the greater than comparison.
519 """
520 return BooleanExpression(
521 "greater_than", [self, self._cast_to_expr_or_convert_to_constant(other)]
522 )
523
524 @expose_as_static
525 def greater_than_or_equal(
526 self, other: Expression | CONSTANT_TYPE
527 ) -> "BooleanExpression":
528 """Creates an expression that checks if this expression is greater than or equal
529 to another expression or constant value.
530
531 Example:
532 >>> # Check if the 'quantity' field is greater than or equal to field 'requirement' plus 1
533 >>> Field.of("quantity").greater_than_or_equal(Field.of('requirement').add(1))
534 >>> # Check if the 'score' field is greater than or equal to 80
535 >>> Field.of("score").greater_than_or_equal(80)
536
537 Args:
538 other: The expression or constant value to compare for greater than or equal to.
539
540 Returns:
541 A new `Expression` representing the greater than or equal to comparison.
542 """
543 return BooleanExpression(
544 "greater_than_or_equal",
545 [self, self._cast_to_expr_or_convert_to_constant(other)],
546 )
547
548 @expose_as_static
549 def less_than(self, other: Expression | CONSTANT_TYPE) -> "BooleanExpression":
550 """Creates an expression that checks if this expression is less than another
551 expression or constant value.
552
553 Example:
554 >>> # Check if the 'age' field is less than 'limit'
555 >>> Field.of("age").less_than(Field.of('limit'))
556 >>> # Check if the 'price' field is less than 50
557 >>> Field.of("price").less_than(50)
558
559 Args:
560 other: The expression or constant value to compare for less than.
561
562 Returns:
563 A new `Expression` representing the less than comparison.
564 """
565 return BooleanExpression(
566 "less_than", [self, self._cast_to_expr_or_convert_to_constant(other)]
567 )
568
569 @expose_as_static
570 def less_than_or_equal(
571 self, other: Expression | CONSTANT_TYPE
572 ) -> "BooleanExpression":
573 """Creates an expression that checks if this expression is less than or equal to
574 another expression or constant value.
575
576 Example:
577 >>> # Check if the 'quantity' field is less than or equal to 20
578 >>> Field.of("quantity").less_than_or_equal(Constant.of(20))
579 >>> # Check if the 'score' field is less than or equal to 70
580 >>> Field.of("score").less_than_or_equal(70)
581
582 Args:
583 other: The expression or constant value to compare for less than or equal to.
584
585 Returns:
586 A new `Expression` representing the less than or equal to comparison.
587 """
588 return BooleanExpression(
589 "less_than_or_equal",
590 [self, self._cast_to_expr_or_convert_to_constant(other)],
591 )
592
593 @expose_as_static
594 def equal_any(
595 self, array: Array | Sequence[Expression | CONSTANT_TYPE] | Expression
596 ) -> "BooleanExpression":
597 """Creates an expression that checks if this expression is equal to any of the
598 provided values or expressions.
599
600 Example:
601 >>> # Check if the 'category' field is either "Electronics" or value of field 'primaryType'
602 >>> Field.of("category").equal_any(["Electronics", Field.of("primaryType")])
603
604 Args:
605 array: The values or expressions to check against.
606
607 Returns:
608 A new `Expression` representing the 'IN' comparison.
609 """
610 return BooleanExpression(
611 "equal_any",
612 [
613 self,
614 self._cast_to_expr_or_convert_to_constant(array),
615 ],
616 )
617
618 @expose_as_static
619 def not_equal_any(
620 self, array: Array | list[Expression | CONSTANT_TYPE] | Expression
621 ) -> "BooleanExpression":
622 """Creates an expression that checks if this expression is not equal to any of the
623 provided values or expressions.
624
625 Example:
626 >>> # Check if the 'status' field is neither "pending" nor "cancelled"
627 >>> Field.of("status").not_equal_any(["pending", "cancelled"])
628
629 Args:
630 array: The values or expressions to check against.
631
632 Returns:
633 A new `Expression` representing the 'NOT IN' comparison.
634 """
635 return BooleanExpression(
636 "not_equal_any",
637 [
638 self,
639 self._cast_to_expr_or_convert_to_constant(array),
640 ],
641 )
642
643 @expose_as_static
644 def array_get(self, offset: Expression | int) -> "FunctionExpression":
645 """
646 Creates an expression that indexes into an array from the beginning or end and returns the
647 element. A negative offset starts from the end.
648
649 Example:
650 >>> Array([1,2,3]).array_get(0)
651
652 Args:
653 offset: the index of the element to return
654
655 Returns:
656 A new `Expression` representing the `array_get` operation.
657 """
658 return FunctionExpression(
659 "array_get", [self, self._cast_to_expr_or_convert_to_constant(offset)]
660 )
661
662 @expose_as_static
663 def array_contains(
664 self, element: Expression | CONSTANT_TYPE
665 ) -> "BooleanExpression":
666 """Creates an expression that checks if an array contains a specific element or value.
667
668 Example:
669 >>> # Check if the 'sizes' array contains the value from the 'selectedSize' field
670 >>> Field.of("sizes").array_contains(Field.of("selectedSize"))
671 >>> # Check if the 'colors' array contains "red"
672 >>> Field.of("colors").array_contains("red")
673
674 Args:
675 element: The element (expression or constant) to search for in the array.
676
677 Returns:
678 A new `Expression` representing the 'array_contains' comparison.
679 """
680 return BooleanExpression(
681 "array_contains", [self, self._cast_to_expr_or_convert_to_constant(element)]
682 )
683
684 @expose_as_static
685 def array_contains_all(
686 self,
687 elements: Array | list[Expression | CONSTANT_TYPE] | Expression,
688 ) -> "BooleanExpression":
689 """Creates an expression that checks if an array contains all the specified elements.
690
691 Example:
692 >>> # Check if the 'tags' array contains both "news" and "sports"
693 >>> Field.of("tags").array_contains_all(["news", "sports"])
694 >>> # Check if the 'tags' array contains both of the values from field 'tag1' and "tag2"
695 >>> Field.of("tags").array_contains_all([Field.of("tag1"), "tag2"])
696
697 Args:
698 elements: The list of elements (expressions or constants) to check for in the array.
699
700 Returns:
701 A new `Expression` representing the 'array_contains_all' comparison.
702 """
703 return BooleanExpression(
704 "array_contains_all",
705 [
706 self,
707 self._cast_to_expr_or_convert_to_constant(elements),
708 ],
709 )
710
711 @expose_as_static
712 def array_contains_any(
713 self,
714 elements: Array | list[Expression | CONSTANT_TYPE] | Expression,
715 ) -> "BooleanExpression":
716 """Creates an expression that checks if an array contains any of the specified elements.
717
718 Example:
719 >>> # Check if the 'categories' array contains either values from field "cate1" or "cate2"
720 >>> Field.of("categories").array_contains_any([Field.of("cate1"), Field.of("cate2")])
721 >>> # Check if the 'groups' array contains either the value from the 'userGroup' field
722 >>> # or the value "guest"
723 >>> Field.of("groups").array_contains_any([Field.of("userGroup"), "guest"])
724
725 Args:
726 elements: The list of elements (expressions or constants) to check for in the array.
727
728 Returns:
729 A new `Expression` representing the 'array_contains_any' comparison.
730 """
731 return BooleanExpression(
732 "array_contains_any",
733 [
734 self,
735 self._cast_to_expr_or_convert_to_constant(elements),
736 ],
737 )
738
739 @expose_as_static
740 def array_length(self) -> "Expression":
741 """Creates an expression that calculates the length of an array.
742
743 Example:
744 >>> # Get the number of items in the 'cart' array
745 >>> Field.of("cart").array_length()
746
747 Returns:
748 A new `Expression` representing the length of the array.
749 """
750 return FunctionExpression("array_length", [self])
751
752 @expose_as_static
753 def array_reverse(self) -> "Expression":
754 """Creates an expression that returns the reversed content of an array.
755
756 Example:
757 >>> # Get the 'preferences' array in reversed order.
758 >>> Field.of("preferences").array_reverse()
759
760 Returns:
761 A new `Expression` representing the reversed array.
762 """
763 return FunctionExpression("array_reverse", [self])
764
765 @expose_as_static
766 def array_concat(
767 self, *other_arrays: Array | list[Expression | CONSTANT_TYPE] | Expression
768 ) -> "Expression":
769 """Creates an expression that concatenates an array expression with another array.
770
771 Example:
772 >>> # Combine the 'tags' array with a new array and an array field
773 >>> Field.of("tags").array_concat(["newTag1", "newTag2", Field.of("otherTag")])
774
775 Args:
776 array: The list of constants or expressions to concat with.
777
778 Returns:
779 A new `Expression` representing the concatenated array.
780 """
781 return FunctionExpression(
782 "array_concat",
783 [self]
784 + [self._cast_to_expr_or_convert_to_constant(arr) for arr in other_arrays],
785 )
786
787 @expose_as_static
788 def concat(self, *others: Expression | CONSTANT_TYPE) -> "Expression":
789 """Creates an expression that concatenates expressions together
790
791 Args:
792 *others: The expressions to concatenate.
793
794 Returns:
795 A new `Expression` representing the concatenated value.
796 """
797 return FunctionExpression(
798 "concat",
799 [self] + [self._cast_to_expr_or_convert_to_constant(o) for o in others],
800 )
801
802 @expose_as_static
803 def length(self) -> "Expression":
804 """
805 Creates an expression that calculates the length of the expression if it is a string, array, map, or blob.
806
807 Example:
808 >>> # Get the length of the 'name' field.
809 >>> Field.of("name").length()
810
811 Returns:
812 A new `Expression` representing the length of the expression.
813 """
814 return FunctionExpression("length", [self])
815
816 @expose_as_static
817 def is_absent(self) -> "BooleanExpression":
818 """Creates an expression that returns true if a value is absent. Otherwise, returns false even if
819 the value is null.
820
821 Example:
822 >>> # Check if the 'email' field is absent.
823 >>> Field.of("email").is_absent()
824
825 Returns:
826 A new `BooleanExpressionession` representing the isAbsent operation.
827 """
828 return BooleanExpression("is_absent", [self])
829
830 @expose_as_static
831 def if_absent(self, default_value: Expression | CONSTANT_TYPE) -> "Expression":
832 """Creates an expression that returns a default value if an expression evaluates to an absent value.
833
834 Example:
835 >>> # Return the value of the 'email' field, or "N/A" if it's absent.
836 >>> Field.of("email").if_absent("N/A")
837
838 Args:
839 default_value: The expression or constant value to return if this expression is absent.
840
841 Returns:
842 A new `Expression` representing the ifAbsent operation.
843 """
844 return FunctionExpression(
845 "if_absent",
846 [self, self._cast_to_expr_or_convert_to_constant(default_value)],
847 )
848
849 @expose_as_static
850 def is_error(self):
851 """Creates an expression that checks if a given expression produces an error
852
853 Example:
854 >>> # Resolves to True if an expression produces an error
855 >>> Field.of("value").divide("string").is_error()
856
857 Returns:
858 A new `Expression` representing the isError operation.
859 """
860 return FunctionExpression("is_error", [self])
861
862 @expose_as_static
863 def if_error(self, then_value: Expression | CONSTANT_TYPE) -> "Expression":
864 """Creates an expression that returns ``then_value`` if this expression evaluates to an error.
865 Otherwise, returns the value of this expression.
866
867 Example:
868 >>> # Resolves to 0 if an expression produces an error
869 >>> Field.of("value").divide("string").if_error(0)
870
871 Args:
872 then_value: The value to return if this expression evaluates to an error.
873
874 Returns:
875 A new `Expression` representing the ifError operation.
876 """
877 return FunctionExpression(
878 "if_error", [self, self._cast_to_expr_or_convert_to_constant(then_value)]
879 )
880
881 @expose_as_static
882 def exists(self) -> "BooleanExpression":
883 """Creates an expression that checks if a field exists in the document.
884
885 Example:
886 >>> # Check if the document has a field named "phoneNumber"
887 >>> Field.of("phoneNumber").exists()
888
889 Returns:
890 A new `Expression` representing the 'exists' check.
891 """
892 return BooleanExpression("exists", [self])
893
894 @expose_as_static
895 def sum(self) -> "Expression":
896 """Creates an aggregation that calculates the sum of a numeric field across multiple stage inputs.
897
898 Example:
899 >>> # Calculate the total revenue from a set of orders
900 >>> Field.of("orderAmount").sum().as_("totalRevenue")
901
902 Returns:
903 A new `AggregateFunction` representing the 'sum' aggregation.
904 """
905 return AggregateFunction("sum", [self])
906
907 @expose_as_static
908 def average(self) -> "Expression":
909 """Creates an aggregation that calculates the average (mean) of a numeric field across multiple
910 stage inputs.
911
912 Example:
913 >>> # Calculate the average age of users
914 >>> Field.of("age").average().as_("averageAge")
915
916 Returns:
917 A new `AggregateFunction` representing the 'avg' aggregation.
918 """
919 return AggregateFunction("average", [self])
920
921 @expose_as_static
922 def count(self) -> "Expression":
923 """Creates an aggregation that counts the number of stage inputs with valid evaluations of the
924 expression or field.
925
926 Example:
927 >>> # Count the total number of products
928 >>> Field.of("productId").count().as_("totalProducts")
929
930 Returns:
931 A new `AggregateFunction` representing the 'count' aggregation.
932 """
933 return AggregateFunction("count", [self])
934
935 @expose_as_static
936 def count_if(self) -> "Expression":
937 """Creates an aggregation that counts the number of values of the provided field or expression
938 that evaluate to True.
939
940 Example:
941 >>> # Count the number of adults
942 >>> Field.of("age").greater_than(18).count_if().as_("totalAdults")
943
944
945 Returns:
946 A new `AggregateFunction` representing the 'count_if' aggregation.
947 """
948 return AggregateFunction("count_if", [self])
949
950 @expose_as_static
951 def count_distinct(self) -> "Expression":
952 """Creates an aggregation that counts the number of distinct values of the
953 provided field or expression.
954
955 Example:
956 >>> # Count the total number of countries in the data
957 >>> Field.of("country").count_distinct().as_("totalCountries")
958
959 Returns:
960 A new `AggregateFunction` representing the 'count_distinct' aggregation.
961 """
962 return AggregateFunction("count_distinct", [self])
963
964 @expose_as_static
965 def minimum(self) -> "Expression":
966 """Creates an aggregation that finds the minimum value of a field across multiple stage inputs.
967
968 Example:
969 >>> # Find the lowest price of all products
970 >>> Field.of("price").minimum().as_("lowestPrice")
971
972 Returns:
973 A new `AggregateFunction` representing the 'minimum' aggregation.
974 """
975 return AggregateFunction("minimum", [self])
976
977 @expose_as_static
978 def maximum(self) -> "Expression":
979 """Creates an aggregation that finds the maximum value of a field across multiple stage inputs.
980
981 Example:
982 >>> # Find the highest score in a leaderboard
983 >>> Field.of("score").maximum().as_("highestScore")
984
985 Returns:
986 A new `AggregateFunction` representing the 'maximum' aggregation.
987 """
988 return AggregateFunction("maximum", [self])
989
990 @expose_as_static
991 def char_length(self) -> "Expression":
992 """Creates an expression that calculates the character length of a string.
993
994 Example:
995 >>> # Get the character length of the 'name' field
996 >>> Field.of("name").char_length()
997
998 Returns:
999 A new `Expression` representing the length of the string.
1000 """
1001 return FunctionExpression("char_length", [self])
1002
1003 @expose_as_static
1004 def byte_length(self) -> "Expression":
1005 """Creates an expression that calculates the byte length of a string in its UTF-8 form.
1006
1007 Example:
1008 >>> # Get the byte length of the 'name' field
1009 >>> Field.of("name").byte_length()
1010
1011 Returns:
1012 A new `Expression` representing the byte length of the string.
1013 """
1014 return FunctionExpression("byte_length", [self])
1015
1016 @expose_as_static
1017 def like(self, pattern: Expression | str) -> "BooleanExpression":
1018 """Creates an expression that performs a case-sensitive string comparison.
1019
1020 Example:
1021 >>> # Check if the 'title' field contains the word "guide" (case-sensitive)
1022 >>> Field.of("title").like("%guide%")
1023 >>> # Check if the 'title' field matches the pattern specified in field 'pattern'.
1024 >>> Field.of("title").like(Field.of("pattern"))
1025
1026 Args:
1027 pattern: The pattern (string or expression) to search for. You can use "%" as a wildcard character.
1028
1029 Returns:
1030 A new `Expression` representing the 'like' comparison.
1031 """
1032 return BooleanExpression(
1033 "like", [self, self._cast_to_expr_or_convert_to_constant(pattern)]
1034 )
1035
1036 @expose_as_static
1037 def regex_contains(self, regex: Expression | str) -> "BooleanExpression":
1038 """Creates an expression that checks if a string contains a specified regular expression as a
1039 substring.
1040
1041 Example:
1042 >>> # Check if the 'description' field contains "example" (case-insensitive)
1043 >>> Field.of("description").regex_contains("(?i)example")
1044 >>> # Check if the 'description' field contains the regular expression stored in field 'regex'
1045 >>> Field.of("description").regex_contains(Field.of("regex"))
1046
1047 Args:
1048 regex: The regular expression (string or expression) to use for the search.
1049
1050 Returns:
1051 A new `Expression` representing the 'contains' comparison.
1052 """
1053 return BooleanExpression(
1054 "regex_contains", [self, self._cast_to_expr_or_convert_to_constant(regex)]
1055 )
1056
1057 @expose_as_static
1058 def regex_match(self, regex: Expression | str) -> "BooleanExpression":
1059 """Creates an expression that checks if a string matches a specified regular expression.
1060
1061 Example:
1062 >>> # Check if the 'email' field matches a valid email pattern
1063 >>> Field.of("email").regex_match("[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}")
1064 >>> # Check if the 'email' field matches a regular expression stored in field 'regex'
1065 >>> Field.of("email").regex_match(Field.of("regex"))
1066
1067 Args:
1068 regex: The regular expression (string or expression) to use for the match.
1069
1070 Returns:
1071 A new `Expression` representing the regular expression match.
1072 """
1073 return BooleanExpression(
1074 "regex_match", [self, self._cast_to_expr_or_convert_to_constant(regex)]
1075 )
1076
1077 @expose_as_static
1078 def string_contains(self, substring: Expression | str) -> "BooleanExpression":
1079 """Creates an expression that checks if this string expression contains a specified substring.
1080
1081 Example:
1082 >>> # Check if the 'description' field contains "example".
1083 >>> Field.of("description").string_contains("example")
1084 >>> # Check if the 'description' field contains the value of the 'keyword' field.
1085 >>> Field.of("description").string_contains(Field.of("keyword"))
1086
1087 Args:
1088 substring: The substring (string or expression) to use for the search.
1089
1090 Returns:
1091 A new `Expression` representing the 'contains' comparison.
1092 """
1093 return BooleanExpression(
1094 "string_contains",
1095 [self, self._cast_to_expr_or_convert_to_constant(substring)],
1096 )
1097
1098 @expose_as_static
1099 def starts_with(self, prefix: Expression | str) -> "BooleanExpression":
1100 """Creates an expression that checks if a string starts with a given prefix.
1101
1102 Example:
1103 >>> # Check if the 'name' field starts with "Mr."
1104 >>> Field.of("name").starts_with("Mr.")
1105 >>> # Check if the 'fullName' field starts with the value of the 'firstName' field
1106 >>> Field.of("fullName").starts_with(Field.of("firstName"))
1107
1108 Args:
1109 prefix: The prefix (string or expression) to check for.
1110
1111 Returns:
1112 A new `Expression` representing the 'starts with' comparison.
1113 """
1114 return BooleanExpression(
1115 "starts_with", [self, self._cast_to_expr_or_convert_to_constant(prefix)]
1116 )
1117
1118 @expose_as_static
1119 def ends_with(self, postfix: Expression | str) -> "BooleanExpression":
1120 """Creates an expression that checks if a string ends with a given postfix.
1121
1122 Example:
1123 >>> # Check if the 'filename' field ends with ".txt"
1124 >>> Field.of("filename").ends_with(".txt")
1125 >>> # Check if the 'url' field ends with the value of the 'extension' field
1126 >>> Field.of("url").ends_with(Field.of("extension"))
1127
1128 Args:
1129 postfix: The postfix (string or expression) to check for.
1130
1131 Returns:
1132 A new `Expression` representing the 'ends with' comparison.
1133 """
1134 return BooleanExpression(
1135 "ends_with", [self, self._cast_to_expr_or_convert_to_constant(postfix)]
1136 )
1137
1138 @expose_as_static
1139 def string_concat(self, *elements: Expression | CONSTANT_TYPE) -> "Expression":
1140 """Creates an expression that concatenates string expressions, fields or constants together.
1141
1142 Example:
1143 >>> # Combine the 'firstName', " ", and 'lastName' fields into a single string
1144 >>> Field.of("firstName").string_concat(" ", Field.of("lastName"))
1145
1146 Args:
1147 *elements: The expressions or constants (typically strings) to concatenate.
1148
1149 Returns:
1150 A new `Expression` representing the concatenated string.
1151 """
1152 return FunctionExpression(
1153 "string_concat",
1154 [self] + [self._cast_to_expr_or_convert_to_constant(el) for el in elements],
1155 )
1156
1157 @expose_as_static
1158 def to_lower(self) -> "Expression":
1159 """Creates an expression that converts a string to lowercase.
1160
1161 Example:
1162 >>> # Convert the 'name' field to lowercase
1163 >>> Field.of("name").to_lower()
1164
1165 Returns:
1166 A new `Expression` representing the lowercase string.
1167 """
1168 return FunctionExpression("to_lower", [self])
1169
1170 @expose_as_static
1171 def to_upper(self) -> "Expression":
1172 """Creates an expression that converts a string to uppercase.
1173
1174 Example:
1175 >>> # Convert the 'title' field to uppercase
1176 >>> Field.of("title").to_upper()
1177
1178 Returns:
1179 A new `Expression` representing the uppercase string.
1180 """
1181 return FunctionExpression("to_upper", [self])
1182
1183 @expose_as_static
1184 def trim(self) -> "Expression":
1185 """Creates an expression that removes leading and trailing whitespace from a string.
1186
1187 Example:
1188 >>> # Trim whitespace from the 'userInput' field
1189 >>> Field.of("userInput").trim()
1190
1191 Returns:
1192 A new `Expression` representing the trimmed string.
1193 """
1194 return FunctionExpression("trim", [self])
1195
1196 @expose_as_static
1197 def string_reverse(self) -> "Expression":
1198 """Creates an expression that reverses a string.
1199
1200 Example:
1201 >>> # Reverse the 'userInput' field
1202 >>> Field.of("userInput").reverse()
1203
1204 Returns:
1205 A new `Expression` representing the reversed string.
1206 """
1207 return FunctionExpression("string_reverse", [self])
1208
1209 @expose_as_static
1210 def substring(
1211 self, position: Expression | int, length: Expression | int | None = None
1212 ) -> "Expression":
1213 """Creates an expression that returns a substring of the results of this expression.
1214
1215
1216 Example:
1217 >>> Field.of("description").substring(5, 10)
1218 >>> Field.of("description").substring(5)
1219
1220 Args:
1221 position: the index of the first character of the substring.
1222 length: the length of the substring. If not provided the substring
1223 will end at the end of the input.
1224
1225 Returns:
1226 A new `Expression` representing the extracted substring.
1227 """
1228 args = [self, self._cast_to_expr_or_convert_to_constant(position)]
1229 if length is not None:
1230 args.append(self._cast_to_expr_or_convert_to_constant(length))
1231 return FunctionExpression("substring", args)
1232
1233 @expose_as_static
1234 def join(self, delimeter: Expression | str) -> "Expression":
1235 """Creates an expression that joins the elements of an array into a string
1236
1237
1238 Example:
1239 >>> Field.of("tags").join(", ")
1240
1241 Args:
1242 delimiter: The delimiter to add between the elements of the array.
1243
1244 Returns:
1245 A new `Expression` representing the joined string.
1246 """
1247 return FunctionExpression(
1248 "join", [self, self._cast_to_expr_or_convert_to_constant(delimeter)]
1249 )
1250
1251 @expose_as_static
1252 def map_get(self, key: str | Constant[str]) -> "Expression":
1253 """Accesses a value from the map produced by evaluating this expression.
1254
1255 Example:
1256 >>> Map({"city": "London"}).map_get("city")
1257 >>> Field.of("address").map_get("city")
1258
1259 Args:
1260 key: The key to access in the map.
1261
1262 Returns:
1263 A new `Expression` representing the value associated with the given key in the map.
1264 """
1265 return FunctionExpression(
1266 "map_get", [self, self._cast_to_expr_or_convert_to_constant(key)]
1267 )
1268
1269 @expose_as_static
1270 def map_remove(self, key: str | Constant[str]) -> "Expression":
1271 """Remove a key from a the map produced by evaluating this expression.
1272
1273 Example:
1274 >>> Map({"city": "London"}).map_remove("city")
1275 >>> Field.of("address").map_remove("city")
1276
1277 Args:
1278 key: The key to remove in the map.
1279
1280 Returns:
1281 A new `Expression` representing the map_remove operation.
1282 """
1283 return FunctionExpression(
1284 "map_remove", [self, self._cast_to_expr_or_convert_to_constant(key)]
1285 )
1286
1287 @expose_as_static
1288 def map_merge(
1289 self,
1290 *other_maps: Map
1291 | dict[str | Constant[str], Expression | CONSTANT_TYPE]
1292 | Expression,
1293 ) -> "Expression":
1294 """Creates an expression that merges one or more dicts into a single map.
1295
1296 Example:
1297 >>> Map({"city": "London"}).map_merge({"country": "UK"}, {"isCapital": True})
1298 >>> Field.of("settings").map_merge({"enabled":True}, FunctionExpression.conditional(Field.of('isAdmin'), {"admin":True}, {}})
1299
1300 Args:
1301 *other_maps: Sequence of maps to merge into the resulting map.
1302
1303 Returns:
1304 A new `Expression` representing the value associated with the given key in the map.
1305 """
1306 return FunctionExpression(
1307 "map_merge",
1308 [self] + [self._cast_to_expr_or_convert_to_constant(m) for m in other_maps],
1309 )
1310
1311 @expose_as_static
1312 def cosine_distance(self, other: Expression | list[float] | Vector) -> "Expression":
1313 """Calculates the cosine distance between two vectors.
1314
1315 Example:
1316 >>> # Calculate the cosine distance between the 'userVector' field and the 'itemVector' field
1317 >>> Field.of("userVector").cosine_distance(Field.of("itemVector"))
1318 >>> # Calculate the Cosine distance between the 'location' field and a target location
1319 >>> Field.of("location").cosine_distance([37.7749, -122.4194])
1320
1321 Args:
1322 other: The other vector (represented as an Expression, list of floats, or Vector) to compare against.
1323
1324 Returns:
1325 A new `Expression` representing the cosine distance between the two vectors.
1326 """
1327 return FunctionExpression(
1328 "cosine_distance",
1329 [
1330 self,
1331 self._cast_to_expr_or_convert_to_constant(other, include_vector=True),
1332 ],
1333 )
1334
1335 @expose_as_static
1336 def euclidean_distance(
1337 self, other: Expression | list[float] | Vector
1338 ) -> "Expression":
1339 """Calculates the Euclidean distance between two vectors.
1340
1341 Example:
1342 >>> # Calculate the Euclidean distance between the 'location' field and a target location
1343 >>> Field.of("location").euclidean_distance([37.7749, -122.4194])
1344 >>> # Calculate the Euclidean distance between two vector fields: 'pointA' and 'pointB'
1345 >>> Field.of("pointA").euclidean_distance(Field.of("pointB"))
1346
1347 Args:
1348 other: The other vector (represented as an Expression, list of floats, or Vector) to compare against.
1349
1350 Returns:
1351 A new `Expression` representing the Euclidean distance between the two vectors.
1352 """
1353 return FunctionExpression(
1354 "euclidean_distance",
1355 [
1356 self,
1357 self._cast_to_expr_or_convert_to_constant(other, include_vector=True),
1358 ],
1359 )
1360
1361 @expose_as_static
1362 def dot_product(self, other: Expression | list[float] | Vector) -> "Expression":
1363 """Calculates the dot product between two vectors.
1364
1365 Example:
1366 >>> # Calculate the dot product between a feature vector and a target vector
1367 >>> Field.of("features").dot_product([0.5, 0.8, 0.2])
1368 >>> # Calculate the dot product between two document vectors: 'docVector1' and 'docVector2'
1369 >>> Field.of("docVector1").dot_product(Field.of("docVector2"))
1370
1371 Args:
1372 other: The other vector (represented as an Expression, list of floats, or Vector) to calculate dot product with.
1373
1374 Returns:
1375 A new `Expression` representing the dot product between the two vectors.
1376 """
1377 return FunctionExpression(
1378 "dot_product",
1379 [
1380 self,
1381 self._cast_to_expr_or_convert_to_constant(other, include_vector=True),
1382 ],
1383 )
1384
1385 @expose_as_static
1386 def vector_length(self) -> "Expression":
1387 """Creates an expression that calculates the length (dimension) of a Firestore Vector.
1388
1389 Example:
1390 >>> # Get the vector length (dimension) of the field 'embedding'.
1391 >>> Field.of("embedding").vector_length()
1392
1393 Returns:
1394 A new `Expression` representing the length of the vector.
1395 """
1396 return FunctionExpression("vector_length", [self])
1397
1398 @expose_as_static
1399 def timestamp_to_unix_micros(self) -> "Expression":
1400 """Creates an expression that converts a timestamp to the number of microseconds since the epoch
1401 (1970-01-01 00:00:00 UTC).
1402
1403 Truncates higher levels of precision by rounding down to the beginning of the microsecond.
1404
1405 Example:
1406 >>> # Convert the 'timestamp' field to microseconds since the epoch.
1407 >>> Field.of("timestamp").timestamp_to_unix_micros()
1408
1409 Returns:
1410 A new `Expression` representing the number of microseconds since the epoch.
1411 """
1412 return FunctionExpression("timestamp_to_unix_micros", [self])
1413
1414 @expose_as_static
1415 def unix_micros_to_timestamp(self) -> "Expression":
1416 """Creates an expression that converts a number of microseconds since the epoch (1970-01-01
1417 00:00:00 UTC) to a timestamp.
1418
1419 Example:
1420 >>> # Convert the 'microseconds' field to a timestamp.
1421 >>> Field.of("microseconds").unix_micros_to_timestamp()
1422
1423 Returns:
1424 A new `Expression` representing the timestamp.
1425 """
1426 return FunctionExpression("unix_micros_to_timestamp", [self])
1427
1428 @expose_as_static
1429 def timestamp_to_unix_millis(self) -> "Expression":
1430 """Creates an expression that converts a timestamp to the number of milliseconds since the epoch
1431 (1970-01-01 00:00:00 UTC).
1432
1433 Truncates higher levels of precision by rounding down to the beginning of the millisecond.
1434
1435 Example:
1436 >>> # Convert the 'timestamp' field to milliseconds since the epoch.
1437 >>> Field.of("timestamp").timestamp_to_unix_millis()
1438
1439 Returns:
1440 A new `Expression` representing the number of milliseconds since the epoch.
1441 """
1442 return FunctionExpression("timestamp_to_unix_millis", [self])
1443
1444 @expose_as_static
1445 def unix_millis_to_timestamp(self) -> "Expression":
1446 """Creates an expression that converts a number of milliseconds since the epoch (1970-01-01
1447 00:00:00 UTC) to a timestamp.
1448
1449 Example:
1450 >>> # Convert the 'milliseconds' field to a timestamp.
1451 >>> Field.of("milliseconds").unix_millis_to_timestamp()
1452
1453 Returns:
1454 A new `Expression` representing the timestamp.
1455 """
1456 return FunctionExpression("unix_millis_to_timestamp", [self])
1457
1458 @expose_as_static
1459 def timestamp_to_unix_seconds(self) -> "Expression":
1460 """Creates an expression that converts a timestamp to the number of seconds since the epoch
1461 (1970-01-01 00:00:00 UTC).
1462
1463 Truncates higher levels of precision by rounding down to the beginning of the second.
1464
1465 Example:
1466 >>> # Convert the 'timestamp' field to seconds since the epoch.
1467 >>> Field.of("timestamp").timestamp_to_unix_seconds()
1468
1469 Returns:
1470 A new `Expression` representing the number of seconds since the epoch.
1471 """
1472 return FunctionExpression("timestamp_to_unix_seconds", [self])
1473
1474 @expose_as_static
1475 def unix_seconds_to_timestamp(self) -> "Expression":
1476 """Creates an expression that converts a number of seconds since the epoch (1970-01-01 00:00:00
1477 UTC) to a timestamp.
1478
1479 Example:
1480 >>> # Convert the 'seconds' field to a timestamp.
1481 >>> Field.of("seconds").unix_seconds_to_timestamp()
1482
1483 Returns:
1484 A new `Expression` representing the timestamp.
1485 """
1486 return FunctionExpression("unix_seconds_to_timestamp", [self])
1487
1488 @expose_as_static
1489 def timestamp_add(
1490 self, unit: Expression | str, amount: Expression | float
1491 ) -> "Expression":
1492 """Creates an expression that adds a specified amount of time to this timestamp expression.
1493
1494 Example:
1495 >>> # Add a duration specified by the 'unit' and 'amount' fields to the 'timestamp' field.
1496 >>> Field.of("timestamp").timestamp_add(Field.of("unit"), Field.of("amount"))
1497 >>> # Add 1.5 days to the 'timestamp' field.
1498 >>> Field.of("timestamp").timestamp_add("day", 1.5)
1499
1500 Args:
1501 unit: The expression or string evaluating to the unit of time to add, must be one of
1502 'microsecond', 'millisecond', 'second', 'minute', 'hour', 'day'.
1503 amount: The expression or float representing the amount of time to add.
1504
1505 Returns:
1506 A new `Expression` representing the resulting timestamp.
1507 """
1508 return FunctionExpression(
1509 "timestamp_add",
1510 [
1511 self,
1512 self._cast_to_expr_or_convert_to_constant(unit),
1513 self._cast_to_expr_or_convert_to_constant(amount),
1514 ],
1515 )
1516
1517 @expose_as_static
1518 def timestamp_subtract(
1519 self, unit: Expression | str, amount: Expression | float
1520 ) -> "Expression":
1521 """Creates an expression that subtracts a specified amount of time from this timestamp expression.
1522
1523 Example:
1524 >>> # Subtract a duration specified by the 'unit' and 'amount' fields from the 'timestamp' field.
1525 >>> Field.of("timestamp").timestamp_subtract(Field.of("unit"), Field.of("amount"))
1526 >>> # Subtract 2.5 hours from the 'timestamp' field.
1527 >>> Field.of("timestamp").timestamp_subtract("hour", 2.5)
1528
1529 Args:
1530 unit: The expression or string evaluating to the unit of time to subtract, must be one of
1531 'microsecond', 'millisecond', 'second', 'minute', 'hour', 'day'.
1532 amount: The expression or float representing the amount of time to subtract.
1533
1534 Returns:
1535 A new `Expression` representing the resulting timestamp.
1536 """
1537 return FunctionExpression(
1538 "timestamp_subtract",
1539 [
1540 self,
1541 self._cast_to_expr_or_convert_to_constant(unit),
1542 self._cast_to_expr_or_convert_to_constant(amount),
1543 ],
1544 )
1545
1546 @expose_as_static
1547 def collection_id(self):
1548 """Creates an expression that returns the collection ID from a path.
1549
1550 Example:
1551 >>> # Get the collection ID from a path.
1552 >>> Field.of("__name__").collection_id()
1553
1554 Returns:
1555 A new `Expression` representing the collection ID.
1556 """
1557 return FunctionExpression("collection_id", [self])
1558
1559 @expose_as_static
1560 def document_id(self):
1561 """Creates an expression that returns the document ID from a path.
1562
1563 Example:
1564 >>> # Get the document ID from a path.
1565 >>> Field.of("__name__").document_id()
1566
1567 Returns:
1568 A new `Expression` representing the document ID.
1569 """
1570 return FunctionExpression("document_id", [self])
1571
1572 def ascending(self) -> Ordering:
1573 """Creates an `Ordering` that sorts documents in ascending order based on this expression.
1574
1575 Example:
1576 >>> # Sort documents by the 'name' field in ascending order
1577 >>> client.pipeline().collection("users").sort(Field.of("name").ascending())
1578
1579 Returns:
1580 A new `Ordering` for ascending sorting.
1581 """
1582 return Ordering(self, Ordering.Direction.ASCENDING)
1583
1584 def descending(self) -> Ordering:
1585 """Creates an `Ordering` that sorts documents in descending order based on this expression.
1586
1587 Example:
1588 >>> # Sort documents by the 'createdAt' field in descending order
1589 >>> client.pipeline().collection("users").sort(Field.of("createdAt").descending())
1590
1591 Returns:
1592 A new `Ordering` for descending sorting.
1593 """
1594 return Ordering(self, Ordering.Direction.DESCENDING)
1595
1596 def as_(self, alias: str) -> "AliasedExpression":
1597 """Assigns an alias to this expression.
1598
1599 Aliases are useful for renaming fields in the output of a stage or for giving meaningful
1600 names to calculated values.
1601
1602 Example:
1603 >>> # Calculate the total price and assign it the alias "totalPrice" and add it to the output.
1604 >>> client.pipeline().collection("items").add_fields(
1605 ... Field.of("price").multiply(Field.of("quantity")).as_("totalPrice")
1606 ... )
1607
1608 Args:
1609 alias: The alias to assign to this expression.
1610
1611 Returns:
1612 A new `Selectable` (typically an `AliasedExpression`) that wraps this
1613 expression and associates it with the provided alias.
1614 """
1615 return AliasedExpression(self, alias)
1616
1617
1618class Constant(Expression, Generic[CONSTANT_TYPE]):
1619 """Represents a constant literal value in an expression."""
1620
1621 def __init__(self, value: CONSTANT_TYPE):
1622 self.value: CONSTANT_TYPE = value
1623
1624 def __eq__(self, other):
1625 if not isinstance(other, Constant):
1626 return other == self.value
1627 else:
1628 return other.value == self.value
1629
1630 @staticmethod
1631 def of(value: CONSTANT_TYPE) -> Constant[CONSTANT_TYPE]:
1632 """Creates a constant expression from a Python value."""
1633 return Constant(value)
1634
1635 def __repr__(self):
1636 value_str = repr(self.value)
1637 if isinstance(self.value, float) and value_str == "nan":
1638 value_str = "math.nan"
1639 return f"Constant.of({value_str})"
1640
1641 def __hash__(self):
1642 return hash(self.value)
1643
1644 def _to_pb(self) -> Value:
1645 return encode_value(self.value)
1646
1647
1648class FunctionExpression(Expression):
1649 """A base class for expressions that represent function calls."""
1650
1651 def __init__(
1652 self,
1653 name: str,
1654 params: Sequence[Expression],
1655 *,
1656 use_infix_repr: bool = True,
1657 infix_name_override: str | None = None,
1658 ):
1659 self.name = name
1660 self.params = list(params)
1661 self._use_infix_repr = use_infix_repr
1662 self._infix_name_override = infix_name_override
1663
1664 def __repr__(self):
1665 """
1666 Most FunctionExpressions can be triggered infix. Eg: Field.of('age').greater_than(18).
1667
1668 Display them this way in the repr string where possible
1669 """
1670 if self._use_infix_repr:
1671 infix_name = self._infix_name_override or self.name
1672 if len(self.params) == 1:
1673 return f"{self.params[0]!r}.{infix_name}()"
1674 elif len(self.params) == 2:
1675 return f"{self.params[0]!r}.{infix_name}({self.params[1]!r})"
1676 else:
1677 return f"{self.params[0]!r}.{infix_name}({', '.join([repr(p) for p in self.params[1:]])})"
1678 return f"{self.__class__.__name__}({', '.join([repr(p) for p in self.params])})"
1679
1680 def __eq__(self, other):
1681 if not isinstance(other, FunctionExpression):
1682 return False
1683 else:
1684 return other.name == self.name and other.params == self.params
1685
1686 def _to_pb(self):
1687 return Value(
1688 function_value={
1689 "name": self.name,
1690 "args": [p._to_pb() for p in self.params],
1691 }
1692 )
1693
1694
1695class AggregateFunction(FunctionExpression):
1696 """A base class for aggregation functions that operate across multiple inputs."""
1697
1698
1699class Selectable(Expression):
1700 """Base class for expressions that can be selected or aliased in projection stages."""
1701
1702 def __eq__(self, other):
1703 if not isinstance(other, type(self)):
1704 return False
1705 else:
1706 return other._to_map() == self._to_map()
1707
1708 @abstractmethod
1709 def _to_map(self) -> tuple[str, Value]:
1710 """
1711 Returns a str: Value representation of the Selectable
1712 """
1713 raise NotImplementedError
1714
1715 @classmethod
1716 def _value_from_selectables(cls, *selectables: Selectable) -> Value:
1717 """
1718 Returns a Value representing a map of Selectables
1719 """
1720 return Value(
1721 map_value={
1722 "fields": {m[0]: m[1] for m in [s._to_map() for s in selectables]}
1723 }
1724 )
1725
1726 @staticmethod
1727 def _to_value(field_list: Sequence[Selectable]) -> Value:
1728 return Value(
1729 map_value={
1730 "fields": {m[0]: m[1] for m in [f._to_map() for f in field_list]}
1731 }
1732 )
1733
1734
1735T = TypeVar("T", bound=Expression)
1736
1737
1738class AliasedExpression(Selectable, Generic[T]):
1739 """Wraps an expression with an alias."""
1740
1741 def __init__(self, expr: T, alias: str):
1742 self.expr = expr
1743 self.alias = alias
1744
1745 def _to_map(self):
1746 return self.alias, self.expr._to_pb()
1747
1748 def __repr__(self):
1749 return f"{self.expr}.as_('{self.alias}')"
1750
1751 def _to_pb(self):
1752 return Value(map_value={"fields": {self.alias: self.expr._to_pb()}})
1753
1754
1755class Field(Selectable):
1756 """Represents a reference to a field within a document."""
1757
1758 DOCUMENT_ID = "__name__"
1759
1760 def __init__(self, path: str):
1761 """Initializes a Field reference.
1762
1763 Args:
1764 path: The dot-separated path to the field (e.g., "address.city").
1765 Use Field.DOCUMENT_ID for the document ID.
1766 """
1767 self.path = path
1768
1769 @staticmethod
1770 def of(path: str):
1771 """Creates a Field reference.
1772
1773 Args:
1774 path: The dot-separated path to the field (e.g., "address.city").
1775 Use Field.DOCUMENT_ID for the document ID.
1776
1777 Returns:
1778 A new Field instance.
1779 """
1780 return Field(path)
1781
1782 def _to_map(self):
1783 return self.path, self._to_pb()
1784
1785 def __repr__(self):
1786 return f"Field.of({self.path!r})"
1787
1788 def _to_pb(self):
1789 return Value(field_reference_value=self.path)
1790
1791
1792class BooleanExpression(FunctionExpression):
1793 """Filters the given data in some way."""
1794
1795 @staticmethod
1796 def _from_query_filter_pb(filter_pb, client):
1797 if isinstance(filter_pb, Query_pb.CompositeFilter):
1798 sub_filters = [
1799 BooleanExpression._from_query_filter_pb(f, client)
1800 for f in filter_pb.filters
1801 ]
1802 if filter_pb.op == Query_pb.CompositeFilter.Operator.OR:
1803 return Or(*sub_filters)
1804 elif filter_pb.op == Query_pb.CompositeFilter.Operator.AND:
1805 return And(*sub_filters)
1806 else:
1807 raise TypeError(
1808 f"Unexpected CompositeFilter operator type: {filter_pb.op}"
1809 )
1810 elif isinstance(filter_pb, Query_pb.UnaryFilter):
1811 field = Field.of(filter_pb.field.field_path)
1812 if filter_pb.op == Query_pb.UnaryFilter.Operator.IS_NAN:
1813 return And(field.exists(), field.equal(float("nan")))
1814 elif filter_pb.op == Query_pb.UnaryFilter.Operator.IS_NOT_NAN:
1815 return And(field.exists(), Not(field.equal(float("nan"))))
1816 elif filter_pb.op == Query_pb.UnaryFilter.Operator.IS_NULL:
1817 return And(field.exists(), field.equal(None))
1818 elif filter_pb.op == Query_pb.UnaryFilter.Operator.IS_NOT_NULL:
1819 return And(field.exists(), Not(field.equal(None)))
1820 else:
1821 raise TypeError(f"Unexpected UnaryFilter operator type: {filter_pb.op}")
1822 elif isinstance(filter_pb, Query_pb.FieldFilter):
1823 field = Field.of(filter_pb.field.field_path)
1824 value = decode_value(filter_pb.value, client)
1825 if filter_pb.op == Query_pb.FieldFilter.Operator.LESS_THAN:
1826 return And(field.exists(), field.less_than(value))
1827 elif filter_pb.op == Query_pb.FieldFilter.Operator.LESS_THAN_OR_EQUAL:
1828 return And(field.exists(), field.less_than_or_equal(value))
1829 elif filter_pb.op == Query_pb.FieldFilter.Operator.GREATER_THAN:
1830 return And(field.exists(), field.greater_than(value))
1831 elif filter_pb.op == Query_pb.FieldFilter.Operator.GREATER_THAN_OR_EQUAL:
1832 return And(field.exists(), field.greater_than_or_equal(value))
1833 elif filter_pb.op == Query_pb.FieldFilter.Operator.EQUAL:
1834 return And(field.exists(), field.equal(value))
1835 elif filter_pb.op == Query_pb.FieldFilter.Operator.NOT_EQUAL:
1836 # In Enterprise DBs NOT_EQUAL will match a field that does not exist,
1837 # therefore we do not want an existence filter for the NOT_EQUAL conversion
1838 # so the Query and Pipeline behavior are consistent in Enterprise.
1839 return field.not_equal(value)
1840 if filter_pb.op == Query_pb.FieldFilter.Operator.ARRAY_CONTAINS:
1841 return And(field.exists(), field.array_contains(value))
1842 elif filter_pb.op == Query_pb.FieldFilter.Operator.ARRAY_CONTAINS_ANY:
1843 return And(field.exists(), field.array_contains_any(value))
1844 elif filter_pb.op == Query_pb.FieldFilter.Operator.IN:
1845 return And(field.exists(), field.equal_any(value))
1846 elif filter_pb.op == Query_pb.FieldFilter.Operator.NOT_IN:
1847 # In Enterprise DBs NOT_IN will match a field that does not exist,
1848 # therefore we do not want an existence filter for the NOT_IN conversion
1849 # so the Query and Pipeline behavior are consistent in Enterprise.
1850 return field.not_equal_any(value)
1851 else:
1852 raise TypeError(f"Unexpected FieldFilter operator type: {filter_pb.op}")
1853 elif isinstance(filter_pb, Query_pb.Filter):
1854 # unwrap oneof
1855 f = (
1856 filter_pb.composite_filter
1857 or filter_pb.field_filter
1858 or filter_pb.unary_filter
1859 )
1860 return BooleanExpression._from_query_filter_pb(f, client)
1861 else:
1862 raise TypeError(f"Unexpected filter type: {type(filter_pb)}")
1863
1864
1865class Array(FunctionExpression):
1866 """
1867 Creates an expression that creates a Firestore array value from an input list.
1868
1869 Example:
1870 >>> Array(["bar", Field.of("baz")])
1871
1872 Args:
1873 elements: The input list to evaluate in the expression
1874 """
1875
1876 def __init__(self, elements: list[Expression | CONSTANT_TYPE]):
1877 if not isinstance(elements, list):
1878 raise TypeError("Array must be constructed with a list")
1879 converted_elements = [
1880 self._cast_to_expr_or_convert_to_constant(el) for el in elements
1881 ]
1882 super().__init__("array", converted_elements)
1883
1884 def __repr__(self):
1885 return f"Array({self.params})"
1886
1887
1888class Map(FunctionExpression):
1889 """
1890 Creates an expression that creates a Firestore map value from an input dict.
1891
1892 Example:
1893 >>> Expression.map({"foo": "bar", "baz": Field.of("baz")})
1894
1895 Args:
1896 elements: The input dict to evaluate in the expression
1897 """
1898
1899 def __init__(self, elements: dict[str | Constant[str], Expression | CONSTANT_TYPE]):
1900 element_list = []
1901 for k, v in elements.items():
1902 element_list.append(self._cast_to_expr_or_convert_to_constant(k))
1903 element_list.append(self._cast_to_expr_or_convert_to_constant(v))
1904 super().__init__("map", element_list)
1905
1906 def __repr__(self):
1907 formatted_params = [
1908 a.value if isinstance(a, Constant) else a for a in self.params
1909 ]
1910 d = {a: b for a, b in zip(formatted_params[::2], formatted_params[1::2])}
1911 return f"Map({d})"
1912
1913
1914class And(BooleanExpression):
1915 """
1916 Represents an expression that performs a logical 'AND' operation on multiple filter conditions.
1917
1918 Example:
1919 >>> # Check if the 'age' field is greater than 18 AND the 'city' field is "London" AND
1920 >>> # the 'status' field is "active"
1921 >>> And(Field.of("age").greater_than(18), Field.of("city").equal("London"), Field.of("status").equal("active"))
1922
1923 Args:
1924 *conditions: The filter conditions to 'AND' together.
1925 """
1926
1927 def __init__(self, *conditions: "BooleanExpression"):
1928 super().__init__("and", conditions, use_infix_repr=False)
1929
1930
1931class Not(BooleanExpression):
1932 """
1933 Represents an expression that negates a filter condition.
1934
1935 Example:
1936 >>> # Find documents where the 'completed' field is NOT true
1937 >>> Not(Field.of("completed").equal(True))
1938
1939 Args:
1940 condition: The filter condition to negate.
1941 """
1942
1943 def __init__(self, condition: BooleanExpression):
1944 super().__init__("not", [condition], use_infix_repr=False)
1945
1946
1947class Or(BooleanExpression):
1948 """
1949 Represents expression that performs a logical 'OR' operation on multiple filter conditions.
1950
1951 Example:
1952 >>> # Check if the 'age' field is greater than 18 OR the 'city' field is "London" OR
1953 >>> # the 'status' field is "active"
1954 >>> Or(Field.of("age").greater_than(18), Field.of("city").equal("London"), Field.of("status").equal("active"))
1955
1956 Args:
1957 *conditions: The filter conditions to 'OR' together.
1958 """
1959
1960 def __init__(self, *conditions: "BooleanExpression"):
1961 super().__init__("or", conditions, use_infix_repr=False)
1962
1963
1964class Xor(BooleanExpression):
1965 """
1966 Represents an expression that performs a logical 'XOR' (exclusive OR) operation on multiple filter conditions.
1967
1968 Example:
1969 >>> # Check if only one of the conditions is true: 'age' greater than 18, 'city' is "London",
1970 >>> # or 'status' is "active".
1971 >>> Xor(Field.of("age").greater_than(18), Field.of("city").equal("London"), Field.of("status").equal("active"))
1972
1973 Args:
1974 *conditions: The filter conditions to 'XOR' together.
1975 """
1976
1977 def __init__(self, conditions: Sequence["BooleanExpression"]):
1978 super().__init__("xor", conditions, use_infix_repr=False)
1979
1980
1981class Conditional(BooleanExpression):
1982 """
1983 Represents a conditional expression that evaluates to a 'then' expression if a condition is true
1984 and an 'else' expression if the condition is false.
1985
1986 Example:
1987 >>> # If 'age' is greater than 18, return "Adult"; otherwise, return "Minor".
1988 >>> Conditional(Field.of("age").greater_than(18), Constant.of("Adult"), Constant.of("Minor"));
1989
1990 Args:
1991 condition: The condition to evaluate.
1992 then_expr: The expression to return if the condition is true.
1993 else_expr: The expression to return if the condition is false
1994 """
1995
1996 def __init__(
1997 self, condition: BooleanExpression, then_expr: Expression, else_expr: Expression
1998 ):
1999 super().__init__(
2000 "conditional", [condition, then_expr, else_expr], use_infix_repr=False
2001 )
2002
2003
2004class Count(AggregateFunction):
2005 """
2006 Represents an aggregation that counts the number of stage inputs with valid evaluations of the
2007 expression or field.
2008
2009 Example:
2010 >>> # Count the total number of products
2011 >>> Field.of("productId").count().as_("totalProducts")
2012 >>> Count(Field.of("productId"))
2013 >>> Count().as_("count")
2014
2015 Args:
2016 expression: The expression or field to count. If None, counts all stage inputs.
2017 """
2018
2019 def __init__(self, expression: Expression | None = None):
2020 expression_list = [expression] if expression else []
2021 super().__init__("count", expression_list, use_infix_repr=bool(expression_list))
2022
2023
2024class CurrentTimestamp(FunctionExpression):
2025 """Creates an expression that returns the current timestamp
2026
2027 Returns:
2028 A new `Expression` representing the current timestamp.
2029 """
2030
2031 def __init__(self):
2032 super().__init__("current_timestamp", [], use_infix_repr=False)