Skip to content

Commit 4615c41

Browse files
feat(firestore): add new pipeline expressions (#16151)
Adds the following expressions: - cmp - timestamp_trunc - timestamp_diff - timestamp_extract - if_null - type - is_type
1 parent 4e80530 commit 4615c41

7 files changed

Lines changed: 720 additions & 20 deletions

File tree

‎packages/google-cloud-firestore/google/cloud/firestore_v1/pipeline_expressions.py‎

Lines changed: 265 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,75 @@
4848
)
4949

5050

51+
classTimeUnit(str, Enum):
52+
"""Enumeration of the different time units supported by the Firestore backend."""
53+
54+
MICROSECOND="microsecond"
55+
MILLISECOND="millisecond"
56+
SECOND="second"
57+
MINUTE="minute"
58+
HOUR="hour"
59+
DAY="day"
60+
61+
62+
classTimeGranularity(str, Enum):
63+
"""Enumeration of the different time granularities supported by the Firestore backend."""
64+
65+
# Inherit from TimeUnit
66+
MICROSECOND=TimeUnit.MICROSECOND.value
67+
MILLISECOND=TimeUnit.MILLISECOND.value
68+
SECOND=TimeUnit.SECOND.value
69+
MINUTE=TimeUnit.MINUTE.value
70+
HOUR=TimeUnit.HOUR.value
71+
DAY=TimeUnit.DAY.value
72+
73+
# Additional granularities
74+
WEEK="week"
75+
WEEK_MONDAY="week(monday)"
76+
WEEK_TUESDAY="week(tuesday)"
77+
WEEK_WEDNESDAY="week(wednesday)"
78+
WEEK_THURSDAY="week(thursday)"
79+
WEEK_FRIDAY="week(friday)"
80+
WEEK_SATURDAY="week(saturday)"
81+
WEEK_SUNDAY="week(sunday)"
82+
ISOWEEK="isoweek"
83+
MONTH="month"
84+
QUARTER="quarter"
85+
YEAR="year"
86+
ISOYEAR="isoyear"
87+
88+
89+
classTimePart(str, Enum):
90+
"""Enumeration of the different time parts supported by the Firestore backend."""
91+
92+
# Inherit from TimeUnit
93+
MICROSECOND=TimeUnit.MICROSECOND.value
94+
MILLISECOND=TimeUnit.MILLISECOND.value
95+
SECOND=TimeUnit.SECOND.value
96+
MINUTE=TimeUnit.MINUTE.value
97+
HOUR=TimeUnit.HOUR.value
98+
DAY=TimeUnit.DAY.value
99+
100+
# Inherit from TimeGranularity
101+
WEEK=TimeGranularity.WEEK.value
102+
WEEK_MONDAY=TimeGranularity.WEEK_MONDAY.value
103+
WEEK_TUESDAY=TimeGranularity.WEEK_TUESDAY.value
104+
WEEK_WEDNESDAY=TimeGranularity.WEEK_WEDNESDAY.value
105+
WEEK_THURSDAY=TimeGranularity.WEEK_THURSDAY.value
106+
WEEK_FRIDAY=TimeGranularity.WEEK_FRIDAY.value
107+
WEEK_SATURDAY=TimeGranularity.WEEK_SATURDAY.value
108+
WEEK_SUNDAY=TimeGranularity.WEEK_SUNDAY.value
109+
ISOWEEK=TimeGranularity.ISOWEEK.value
110+
MONTH=TimeGranularity.MONTH.value
111+
QUARTER=TimeGranularity.QUARTER.value
112+
YEAR=TimeGranularity.YEAR.value
113+
ISOYEAR=TimeGranularity.ISOYEAR.value
114+
115+
# Additional parts
116+
DAY_OF_WEEK="dayofweek"
117+
DAY_OF_YEAR="dayofyear"
118+
119+
51120
classOrdering:
52121
"""Represents the direction for sorting results in a pipeline."""
53122

@@ -90,6 +159,31 @@ def _to_pb(self) -> Value:
90159
)
91160

92161

162+
classType(str, Enum):
163+
"""Enumeration of the different types generated by the Firestore backend."""
164+
165+
NULL="null"
166+
ARRAY="array"
167+
BOOLEAN="boolean"
168+
BYTES="bytes"
169+
TIMESTAMP="timestamp"
170+
GEO_POINT="geo_point"
171+
NUMBER="number"
172+
INT32="int32"
173+
INT64="int64"
174+
FLOAT64="float64"
175+
DECIMAL128="decimal128"
176+
MAP="map"
177+
REFERENCE="reference"
178+
STRING="string"
179+
VECTOR="vector"
180+
MAX_KEY="max_key"
181+
MIN_KEY="min_key"
182+
OBJECT_ID="object_id"
183+
REGEX="regex"
184+
REQUEST_TIMESTAMP="request_timestamp"
185+
186+
93187
classExpression(ABC):
94188
"""Represents an expression that can be evaluated to a value within the
95189
execution of a pipeline.
@@ -120,6 +214,8 @@ def _cast_to_expr_or_convert_to_constant(
120214
"""Convert arbitrary object to an Expression."""
121215
ifisinstance(o, Expression):
122216
returno
217+
ifisinstance(o, Enum):
218+
o=o.value
123219
ifisinstance(o, dict):
124220
returnMap(o)
125221
ifisinstance(o, list):
@@ -2079,20 +2175,21 @@ def unix_seconds_to_timestamp(self) -> "Expression":
20792175

20802176
@expose_as_static
20812177
deftimestamp_add(
2082-
self, unit: Expression|str, amount: Expression|float
2178+
self, unit: TimeUnit|str|Expression, amount: Expression|float
20832179
) ->"Expression":
20842180
"""Creates an expression that adds a specified amount of time to this timestamp expression.
20852181
20862182
Example:
2183+
>>> # Add 1.5 days to the 'timestamp' field using TimeUnit enum.
2184+
>>> Field.of("timestamp").timestamp_add(TimeUnit.DAY, 1.5)
20872185
>>> # Add a duration specified by the 'unit' and 'amount' fields to the 'timestamp' field.
20882186
>>> Field.of("timestamp").timestamp_add(Field.of("unit"), Field.of("amount"))
2089-
>>> # Add 1.5 days to the 'timestamp' field.
2187+
>>> # Add 1.5 days to the 'timestamp' field using a string.
20902188
>>> Field.of("timestamp").timestamp_add("day", 1.5)
20912189
20922190
Args:
2093-
unit: The expression or string evaluating to the unit of time to add, must be one of
2094-
'microsecond', 'millisecond', 'second', 'minute', 'hour', 'day'.
2095-
amount: The expression or float representing the amount of time to add.
2191+
unit: The unit of time to add.
2192+
amount: The amount of time to add.
20962193
20972194
Returns:
20982195
A new `Expression` representing the resulting timestamp.
@@ -2108,20 +2205,21 @@ def timestamp_add(
21082205

21092206
@expose_as_static
21102207
deftimestamp_subtract(
2111-
self, unit: Expression|str, amount: Expression|float
2208+
self, unit: TimeUnit|str|Expression, amount: Expression|float
21122209
) ->"Expression":
21132210
"""Creates an expression that subtracts a specified amount of time from this timestamp expression.
21142211
21152212
Example:
2213+
>>> # Subtract 2.5 hours from the 'timestamp' field using TimeUnit enum.
2214+
>>> Field.of("timestamp").timestamp_subtract(TimeUnit.HOUR, 2.5)
21162215
>>> # Subtract a duration specified by the 'unit' and 'amount' fields from the 'timestamp' field.
21172216
>>> Field.of("timestamp").timestamp_subtract(Field.of("unit"), Field.of("amount"))
2118-
>>> # Subtract 2.5 hours from the 'timestamp' field.
2217+
>>> # Subtract 2.5 hours from the 'timestamp' field using a string.
21192218
>>> Field.of("timestamp").timestamp_subtract("hour", 2.5)
21202219
21212220
Args:
2122-
unit: The expression or string evaluating to the unit of time to subtract, must be one of
2123-
'microsecond', 'millisecond', 'second', 'minute', 'hour', 'day'.
2124-
amount: The expression or float representing the amount of time to subtract.
2221+
unit: The unit of time to subtract.
2222+
amount: The amount of time to subtract.
21252223
21262224
Returns:
21272225
A new `Expression` representing the resulting timestamp.
@@ -2206,6 +2304,163 @@ def as_(self, alias: str) -> "AliasedExpression":
22062304
"""
22072305
returnAliasedExpression(self, alias)
22082306

2307+
@expose_as_static
2308+
defcmp(self, other: Expression|CONSTANT_TYPE) ->"Expression":
2309+
"""Creates an expression that compares this expression to another expression.
2310+
2311+
Returns an integer:
2312+
* -1 if this expression is less than the other
2313+
* 0 if they are equal
2314+
* 1 if this expression is greater than the other
2315+
2316+
Example:
2317+
>>> # Compare the 'price' field to 10
2318+
>>> Field.of("price").cmp(10)
2319+
2320+
Args:
2321+
other: The value to compare against.
2322+
2323+
Returns:
2324+
A new `Expression` representing the comparison operation.
2325+
"""
2326+
returnFunctionExpression(
2327+
"cmp", [self, self._cast_to_expr_or_convert_to_constant(other)]
2328+
)
2329+
2330+
@expose_as_static
2331+
deftimestamp_trunc(
2332+
self,
2333+
granularity: TimeGranularity|Expression|str,
2334+
timezone: Expression|str|None=None,
2335+
) ->"Expression":
2336+
"""Creates an expression that truncates a timestamp to a specified granularity.
2337+
2338+
Example:
2339+
>>> # Truncate the 'createdAt' field to the day using TimeGranularity enum
2340+
>>> Field.of("createdAt").timestamp_trunc(TimeGranularity.DAY)
2341+
>>> # Truncate the 'createdAt' field to the day in the 'America/Los_Angeles' timezone
2342+
>>> Field.of("createdAt").timestamp_trunc(TimeGranularity.DAY, "America/Los_Angeles")
2343+
>>> # Truncate the 'createdAt' field to the day using a string
2344+
>>> Field.of("createdAt").timestamp_trunc("day")
2345+
2346+
Args:
2347+
granularity: The granularity to truncate to.
2348+
timezone: The optional timezone.
2349+
2350+
Returns:
2351+
A new `Expression` representing the timestamp_trunc operation.
2352+
"""
2353+
args= [self, self._cast_to_expr_or_convert_to_constant(granularity)]
2354+
iftimezoneisnotNone:
2355+
args.append(self._cast_to_expr_or_convert_to_constant(timezone))
2356+
returnFunctionExpression("timestamp_trunc", args)
2357+
2358+
@expose_as_static
2359+
deftimestamp_extract(
2360+
self,
2361+
part: TimePart|str|Expression,
2362+
timezone: str|Expression|None=None,
2363+
) ->"Expression":
2364+
"""Creates an expression that extracts a part of a timestamp.
2365+
2366+
Example:
2367+
>>> # Extract the year from the 'createdAt' field using TimePart enum
2368+
>>> Field.of("createdAt").timestamp_extract(TimePart.YEAR)
2369+
>>> # Extract the year from the 'createdAt' field in the 'America/Los_Angeles' timezone
2370+
>>> Field.of("createdAt").timestamp_extract(TimePart.YEAR, "America/Los_Angeles")
2371+
>>> # Extract the year from the 'createdAt' field using a string
2372+
>>> Field.of("createdAt").timestamp_extract("year")
2373+
2374+
Args:
2375+
part: The part to extract.
2376+
timezone: The optional timezone.
2377+
2378+
Returns:
2379+
A new `Expression` representing the timestamp_extract operation.
2380+
"""
2381+
args= [self, self._cast_to_expr_or_convert_to_constant(part)]
2382+
iftimezoneisnotNone:
2383+
args.append(self._cast_to_expr_or_convert_to_constant(timezone))
2384+
returnFunctionExpression("timestamp_extract", args)
2385+
2386+
@expose_as_static
2387+
deftimestamp_diff(
2388+
self, start: Expression|datetime.datetime, unit: TimeUnit|str|Expression
2389+
) ->"Expression":
2390+
"""Creates an expression that computes the difference between two timestamps in the specified unit.
2391+
2392+
Example:
2393+
>>> # Compute the difference in days between the 'end' field and the 'start' field using TimeUnit enum
2394+
>>> Field.of("end").timestamp_diff(Field.of("start"), TimeUnit.DAY)
2395+
>>> # Compute the difference in days using a string
2396+
>>> Field.of("end").timestamp_diff(Field.of("start"), "day")
2397+
2398+
Args:
2399+
start: The start timestamp.
2400+
unit: The unit of time.
2401+
2402+
Returns:
2403+
A new `Expression` representing the timestamp_diff operation.
2404+
"""
2405+
returnFunctionExpression(
2406+
"timestamp_diff",
2407+
[
2408+
self,
2409+
self._cast_to_expr_or_convert_to_constant(start),
2410+
self._cast_to_expr_or_convert_to_constant(unit),
2411+
],
2412+
)
2413+
2414+
@expose_as_static
2415+
defif_null(self, *others: Expression|CONSTANT_TYPE) ->"Expression":
2416+
"""Creates an expression that returns the first non-null expression from the provided arguments.
2417+
2418+
Example:
2419+
>>> # Return the 'nickname' field if not null, otherwise return 'firstName'
2420+
>>> Field.of("nickname").if_null(Field.of("firstName"))
2421+
2422+
Args:
2423+
*others: Additional expressions or constants to evaluate if the previous ones are null.
2424+
2425+
Returns:
2426+
A new `Expression` representing the if_null operation.
2427+
"""
2428+
returnFunctionExpression(
2429+
"if_null",
2430+
[self] + [self._cast_to_expr_or_convert_to_constant(o) foroinothers],
2431+
)
2432+
2433+
@expose_as_static
2434+
deftype(self) ->"Expression":
2435+
"""Creates an expression that returns the data type of this expression's result as a string.
2436+
2437+
Example:
2438+
>>> # Get the type of the 'title' field
2439+
>>> Field.of("title").type()
2440+
2441+
Returns:
2442+
A new `Expression` representing the type operation.
2443+
"""
2444+
returnFunctionExpression("type", [self])
2445+
2446+
@expose_as_static
2447+
defis_type(self, type_val: Type|str|Expression) ->"BooleanExpression":
2448+
"""Creates an expression that checks if the result is of the specified type.
2449+
2450+
Example:
2451+
>>> # Check if the 'price' field is a number
2452+
>>> Field.of("price").is_type("number")
2453+
2454+
Args:
2455+
type_val: The type string or expression to check against.
2456+
2457+
Returns:
2458+
A new `BooleanExpression` representing the is_type operation.
2459+
"""
2460+
returnBooleanExpression(
2461+
"is_type", [self, self._cast_to_expr_or_convert_to_constant(type_val)]
2462+
)
2463+
22092464

22102465
classConstant(Expression, Generic[CONSTANT_TYPE]):
22112466
"""Represents a constant literal value in an expression."""

0 commit comments

Comments
 (0)