Skip to content

Commit e649a90

Browse files
authored
feat(firestore): support object functions (#16132)
* map_set() * map_keys() * map_values() * map_entries()
1 parent c95e038 commit e649a90

File tree

3 files changed

+294
-0
lines changed

3 files changed

+294
-0
lines changed

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

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1285,6 +1285,33 @@ def map_get(self, key: str | Constant[str]) -> "Expression":
12851285
"map_get", [self, self._cast_to_expr_or_convert_to_constant(key)]
12861286
)
12871287

1288+
def map_set(self, key: str | Constant[str], value: Any) -> "Expression":
1289+
"""Creates an expression that returns a new map with the specified entries added or
1290+
updated.
1291+
1292+
Note:
1293+
`map_set` only performs shallow updates to the map. Setting a value to `None`
1294+
will retain the key with a `None` value. To remove a key entirely, use
1295+
`map_remove`.
1296+
1297+
Example:
1298+
>>> Map({"city": "London"}).map_set("city", "New York")
1299+
>>> Field.of("address").map_set("city", "Seattle")
1300+
1301+
Args:
1302+
key: The key to set in the map.
1303+
value: The value to associate with the key.
1304+
1305+
Returns:
1306+
A new `Expression` representing the map_set operation.
1307+
"""
1308+
args = [
1309+
self,
1310+
self._cast_to_expr_or_convert_to_constant(key),
1311+
self._cast_to_expr_or_convert_to_constant(value),
1312+
]
1313+
return FunctionExpression("map_set", args)
1314+
12881315
@expose_as_static
12891316
def map_remove(self, key: str | Constant[str]) -> "Expression":
12901317
"""Remove a key from a the map produced by evaluating this expression.
@@ -1328,6 +1355,58 @@ def map_merge(
13281355
)
13291356

13301357
@expose_as_static
1358+
def map_keys(self) -> "Expression":
1359+
"""Creates an expression that returns the keys of a map.
1360+
1361+
Note:
1362+
While the backend generally preserves insertion order, relying on the
1363+
order of the output array is not guaranteed and should be avoided.
1364+
1365+
Example:
1366+
>>> Map({"city": "London", "country": "UK"}).map_keys()
1367+
>>> Field.of("address").map_keys()
1368+
1369+
Returns:
1370+
A new `Expression` representing the keys of the map.
1371+
"""
1372+
return FunctionExpression("map_keys", [self])
1373+
1374+
@expose_as_static
1375+
def map_values(self) -> "Expression":
1376+
"""Creates an expression that returns the values of a map.
1377+
1378+
Note:
1379+
While the backend generally preserves insertion order, relying on the
1380+
order of the output array is not guaranteed and should be avoided.
1381+
1382+
Example:
1383+
>>> Map({"city": "London", "country": "UK"}).map_values()
1384+
>>> Field.of("address").map_values()
1385+
1386+
Returns:
1387+
A new `Expression` representing the values of the map.
1388+
"""
1389+
return FunctionExpression("map_values", [self])
1390+
1391+
@expose_as_static
1392+
def map_entries(self) -> "Expression":
1393+
"""Creates an expression that returns the entries of a map as an array of maps,
1394+
where each map contains a `"k"` property for the key and a `"v"` property for the value.
1395+
For example: `[{ "k": "key1", "v": "value1" }, ...]`.
1396+
1397+
Note:
1398+
While the backend generally preserves insertion order, relying on the
1399+
order of the output array is not guaranteed and should be avoided.
1400+
1401+
Example:
1402+
>>> Map({"city": "London", "country": "UK"}).map_entries()
1403+
>>> Field.of("address").map_entries()
1404+
1405+
Returns:
1406+
A new `Expression` representing the entries of the map.
1407+
"""
1408+
return FunctionExpression("map_entries", [self])
1409+
13311410
def regex_find(self, pattern: str | Constant[str] | Expression) -> "Expression":
13321411
"""Creates an expression that returns the first substring of a string expression that
13331412
matches a specified regular expression.

packages/google-cloud-firestore/tests/system/pipeline_e2e/map.yaml

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,67 @@ tests:
107107
title:
108108
fieldReferenceValue: title
109109
name: select
110+
- description: testMapSet
111+
pipeline:
112+
- Collection: books
113+
- Where:
114+
- FunctionExpression.equal:
115+
- Field: title
116+
- Constant: "Dune"
117+
- Select:
118+
- AliasedExpression:
119+
- FunctionExpression.map_set:
120+
- Field: awards
121+
- "new_award"
122+
- Constant: true
123+
- "awards_set"
124+
assert_results:
125+
- awards_set:
126+
hugo: true
127+
nebula: true
128+
new_award: true
129+
assert_proto:
130+
pipeline:
131+
stages:
132+
- args:
133+
- referenceValue: /books
134+
name: collection
135+
- args:
136+
- functionValue:
137+
args:
138+
- fieldReferenceValue: title
139+
- stringValue: "Dune"
140+
name: equal
141+
name: where
142+
- args:
143+
- mapValue:
144+
fields:
145+
awards_set:
146+
functionValue:
147+
name: map_set
148+
args:
149+
- fieldReferenceValue: awards
150+
- stringValue: "new_award"
151+
- booleanValue: true
152+
name: select
153+
- description: testMapSetNone
154+
pipeline:
155+
- Collection: books
156+
- Where:
157+
- FunctionExpression.equal:
158+
- Field: title
159+
- Constant: "Dune"
160+
- Select:
161+
- AliasedExpression:
162+
- FunctionExpression.map_set:
163+
- Field: awards
164+
- "hugo"
165+
- Constant: null
166+
- "awards_set"
167+
assert_results:
168+
- awards_set:
169+
hugo: null
170+
nebula: true
110171
- description: testMapRemove
111172
pipeline:
112173
- Collection: books
@@ -267,3 +328,119 @@ tests:
267328
a: "orig"
268329
b: "new"
269330
c: "new"
331+
- description: testMapKeys
332+
pipeline:
333+
- Collection: books
334+
- Where:
335+
- FunctionExpression.equal:
336+
- Field: title
337+
- Constant: "Dune"
338+
- Select:
339+
- AliasedExpression:
340+
- FunctionExpression.map_keys:
341+
- Field: awards
342+
- "award_keys"
343+
assert_results:
344+
- award_keys:
345+
- hugo
346+
- nebula
347+
assert_proto:
348+
pipeline:
349+
stages:
350+
- args:
351+
- referenceValue: /books
352+
name: collection
353+
- args:
354+
- functionValue:
355+
args:
356+
- fieldReferenceValue: title
357+
- stringValue: "Dune"
358+
name: equal
359+
name: where
360+
- args:
361+
- mapValue:
362+
fields:
363+
award_keys:
364+
functionValue:
365+
name: map_keys
366+
args:
367+
- fieldReferenceValue: awards
368+
name: select
369+
- description: testMapValues
370+
pipeline:
371+
- Collection: books
372+
- Where:
373+
- FunctionExpression.equal:
374+
- Field: title
375+
- Constant: "Dune"
376+
- Select:
377+
- AliasedExpression:
378+
- FunctionExpression.map_values:
379+
- Field: awards
380+
- "award_values"
381+
assert_results:
382+
- award_values:
383+
- true
384+
- true
385+
assert_proto:
386+
pipeline:
387+
stages:
388+
- args:
389+
- referenceValue: /books
390+
name: collection
391+
- args:
392+
- functionValue:
393+
args:
394+
- fieldReferenceValue: title
395+
- stringValue: "Dune"
396+
name: equal
397+
name: where
398+
- args:
399+
- mapValue:
400+
fields:
401+
award_values:
402+
functionValue:
403+
name: map_values
404+
args:
405+
- fieldReferenceValue: awards
406+
name: select
407+
- description: testMapEntries
408+
pipeline:
409+
- Collection: books
410+
- Where:
411+
- FunctionExpression.equal:
412+
- Field: title
413+
- Constant: "Dune"
414+
- Select:
415+
- AliasedExpression:
416+
- FunctionExpression.map_entries:
417+
- Field: awards
418+
- "award_entries"
419+
assert_results:
420+
- award_entries:
421+
- k: hugo
422+
v: true
423+
- k: nebula
424+
v: true
425+
assert_proto:
426+
pipeline:
427+
stages:
428+
- args:
429+
- referenceValue: /books
430+
name: collection
431+
- args:
432+
- functionValue:
433+
args:
434+
- fieldReferenceValue: title
435+
- stringValue: "Dune"
436+
name: equal
437+
name: where
438+
- args:
439+
- mapValue:
440+
fields:
441+
award_entries:
442+
functionValue:
443+
name: map_entries
444+
args:
445+
- fieldReferenceValue: awards
446+
name: select

packages/google-cloud-firestore/tests/unit/v1/test_pipeline_expressions.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1115,6 +1115,17 @@ def test_map_get(self):
11151115
infix_instance = arg1.map_get(Constant.of(arg2))
11161116
assert infix_instance == instance
11171117

1118+
def test_map_set(self):
1119+
arg1 = self._make_arg("Map")
1120+
arg2 = "key"
1121+
arg3 = "value"
1122+
instance = Expression.map_set(arg1, arg2, arg3)
1123+
assert instance.name == "map_set"
1124+
assert instance.params == [arg1, Constant.of(arg2), Constant.of(arg3)]
1125+
assert repr(instance) == "Map.map_set(Constant.of('key'), Constant.of('value'))"
1126+
infix_instance = arg1.map_set(Constant.of(arg2), arg3)
1127+
assert infix_instance == instance
1128+
11181129
def test_map_remove(self):
11191130
arg1 = self._make_arg("Map")
11201131
arg2 = "key"
@@ -1136,6 +1147,33 @@ def test_map_merge(self):
11361147
infix_instance = arg1.map_merge(arg2, arg3)
11371148
assert infix_instance == instance
11381149

1150+
def test_map_keys(self):
1151+
arg1 = self._make_arg("Map")
1152+
instance = Expression.map_keys(arg1)
1153+
assert instance.name == "map_keys"
1154+
assert instance.params == [arg1]
1155+
assert repr(instance) == "Map.map_keys()"
1156+
infix_instance = arg1.map_keys()
1157+
assert infix_instance == instance
1158+
1159+
def test_map_values(self):
1160+
arg1 = self._make_arg("Map")
1161+
instance = Expression.map_values(arg1)
1162+
assert instance.name == "map_values"
1163+
assert instance.params == [arg1]
1164+
assert repr(instance) == "Map.map_values()"
1165+
infix_instance = arg1.map_values()
1166+
assert infix_instance == instance
1167+
1168+
def test_map_entries(self):
1169+
arg1 = self._make_arg("Map")
1170+
instance = Expression.map_entries(arg1)
1171+
assert instance.name == "map_entries"
1172+
assert instance.params == [arg1]
1173+
assert repr(instance) == "Map.map_entries()"
1174+
infix_instance = arg1.map_entries()
1175+
assert infix_instance == instance
1176+
11391177
def test_mod(self):
11401178
arg1 = self._make_arg("Left")
11411179
arg2 = self._make_arg("Right")

0 commit comments

Comments
 (0)