Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ class Expression(ABC):
"""Represents an expression that can be evaluated to a value within the
execution of a pipeline.

Expressionessions are the building blocks for creating complex queries and
Expressions are the building blocks for creating complex queries and
transformations in Firestore pipelines. They can represent:

- **Field references:** Access values from document fields.
Expand Down Expand Up @@ -761,6 +761,9 @@ def array_get(self, offset: Expression | int) -> "FunctionExpression":
Creates an expression that indexes into an array from the beginning or end and returns the
element. A negative offset starts from the end.

If the expression is evaluated against a non-array type, it evaluates to an error. See `offset`
for an alternative that evaluates to unset instead.

Example:
>>> Array([1,2,3]).array_get(0)

Expand All @@ -774,6 +777,26 @@ def array_get(self, offset: Expression | int) -> "FunctionExpression":
"array_get", [self, self._cast_to_expr_or_convert_to_constant(offset)]
)

@expose_as_static
def offset(self, offset: Expression | int) -> "FunctionExpression":
"""
Creates an expression that indexes into an array from the beginning or end and returns the
element. A negative offset starts from the end.
If the expression is evaluated against a non-array type, it evaluates to unset.

Example:
>>> Array([1,2,3]).offset(0)

Args:
offset: the index of the element to return

Returns:
A new `Expression` representing the `offset` operation.
"""
return FunctionExpression(
"offset", [self, self._cast_to_expr_or_convert_to_constant(offset)]
)

@expose_as_static
def array_contains(
self, element: Expression | CONSTANT_TYPE
Expand Down Expand Up @@ -877,6 +900,70 @@ def array_reverse(self) -> "Expression":
"""
return FunctionExpression("array_reverse", [self])

@expose_as_static
def array_filter(
self,
filter_expr: "BooleanExpression",
element_alias: str | Constant[str],
index_alias: str | Constant[str] | None = None,
) -> "Expression":
"""Filters an array based on a predicate.

Example:
>>> # Filter the 'tags' array to only include the tag "comedy"
>>> Field.of("tags").array_filter(Variable("tag").equal("comedy"), "tag")
>>> # Filter the 'tags' array to only include elements after the first element (index > 0)
>>> Field.of("tags").array_filter(Variable("i").greater_than(0), element_alias="tag", index_alias="i")

Args:
filter_expr: The predicate boolean expression used to filter the elements.
element_alias: A string or string constant used to refer to the current array
element as a variable within the filter expression.
index_alias: An optional string or string constant used to refer to the index
of the current array element as a variable within the filter expression.

Returns:
A new `Expression` representing the filtered array.
"""
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(filter_expr)

return FunctionExpression("array_filter", args)

@expose_as_static
def array_transform(
self,
transform_expr: "Expression",
element_alias: str | Constant[str],
index_alias: str | Constant[str] | None = None,
) -> "Expression":
"""Creates an expression that applies a provided transformation to each element in an array.

Example:
>>> # Convert each tag in the 'tags' array to uppercase
>>> Field.of("tags").array_transform(Variable("tag").to_upper(), "tag")
>>> # Append the index to each tag in the 'tags' array
>>> Field.of("tags").array_transform(Variable("tag").string_concat(Variable("i")), element_alias="tag", index_alias="i")

Args:
transform_expr: The expression used to transform the elements.
element_alias: A string or string constant used to refer to the current array
element as a variable within the transform expression.
index_alias: An optional string or string constant used to refer to the index
of the current array element as a variable within the transform expression.

Returns:
A new `Expression` representing the transformed array.
"""
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(transform_expr)

return FunctionExpression("array_transform", args)

@expose_as_static
def array_concat(
self, *other_arrays: Array | list[Expression | CONSTANT_TYPE] | Expression
Expand Down Expand Up @@ -938,7 +1025,7 @@ def is_absent(self) -> "BooleanExpression":
>>> Field.of("email").is_absent()

Returns:
A new `BooleanExpressionession` representing the isAbsent operation.
A new `BooleanExpression` representing the isAbsent operation.
"""
return BooleanExpression("is_absent", [self])

Expand Down Expand Up @@ -1006,6 +1093,76 @@ def exists(self) -> "BooleanExpression":
"""
return BooleanExpression("exists", [self])

@expose_as_static
def coalesce(self, *others: Expression | CONSTANT_TYPE) -> "Expression":
"""Creates an expression that evaluates to the first non-null, non-error value.

Example:
>>> # Return the "preferredName" field if it exists.
>>> # Otherwise, check the "fullName" field.
>>> # Otherwise, return the literal string "Anonymous".
>>> Field.of("preferredName").coalesce(Field.of("fullName"), "Anonymous")

>>> # Equivalent static call:
>>> Expression.coalesce(Field.of("preferredName"), Field.of("fullName"), "Anonymous")

Args:
*others: Additional expressions or constants to evaluate if the current
expression evaluates to null or error.

Returns:
An Expression representing the coalesce operation.
"""
return FunctionExpression(
"coalesce",
[self]
+ [Expression._cast_to_expr_or_convert_to_constant(x) for x in others],
)

@expose_as_static
def switch_on(
self, result: Expression | CONSTANT_TYPE, *others: Expression | CONSTANT_TYPE
) -> "Expression":
"""Creates an expression that evaluates to the result corresponding to the first true condition.

This function behaves like a `switch` statement. It accepts an alternating sequence of
conditions and their corresponding results. If an odd number of arguments is provided, the
final argument serves as a default fallback result. If no default is provided and no condition
evaluates to true, it throws an error.

Example:
>>> # Return "Pending" if status is 1, "Active" if status is 2, otherwise "Unknown"
>>> Field.of("status").equal(1).switch_on(
... "Pending", Field.of("status").equal(2), "Active", "Unknown"
... )

Args:
result: The result to return if this condition is true.
*others: Additional alternating conditions and results, optionally followed by a default value.

Returns:
An Expression representing the "switch_on" operation.
"""
return FunctionExpression(
"switch_on",
[self, Expression._cast_to_expr_or_convert_to_constant(result)]
+ [Expression._cast_to_expr_or_convert_to_constant(x) for x in others],
)

@expose_as_static
def storage_size(self) -> "Expression":
"""Calculates the Firestore storage size of a given value.

Mirrors the sizing rules detailed in Firebase/Firestore documentation.

Example:
>>> Field.of("content").storage_size()

Returns:
A new `Expression` representing the storage size.
"""
return FunctionExpression("storage_size", [self])

@expose_as_static
def sum(self) -> "Expression":
"""Creates an aggregation that calculates the sum of a numeric field across multiple stage inputs.
Expand Down Expand Up @@ -1366,6 +1523,7 @@ def join(self, delimeter: Expression | str) -> "Expression":
@expose_as_static
def map_get(self, key: str | Constant[str]) -> "Expression":
"""Accesses a value from the map produced by evaluating this expression.
If the expression is evaluated against a non-map type, it evaluates to an error.

Example:
>>> Map({"city": "London"}).map_get("city")
Expand Down Expand Up @@ -2808,6 +2966,22 @@ def __init__(self, *conditions: "BooleanExpression"):
super().__init__("or", conditions, use_infix_repr=False)


class Nor(BooleanExpression):
"""
Represents an expression that performs a logical 'NOR' operation on multiple filter conditions.

Example:
>>> # Check if neither the 'age' field is greater than 18 nor the 'city' field is "London"
>>> Nor(Field.of("age").greater_than(18), Field.of("city").equal("London"))

Args:
*conditions: The filter conditions to 'NOR' together.
"""

def __init__(self, *conditions: "BooleanExpression"):
super().__init__("nor", conditions, use_infix_repr=False)


class Xor(BooleanExpression):
"""
Represents an expression that performs a logical 'XOR' (exclusive OR) operation on multiple filter conditions.
Expand Down
Loading
Loading