Skip to content

Commit dc904ea

Browse files
authored
Firestore: don't omit originally-empty map values when processing timestamps. (#6050)
Closes #5944.
1 parent e1a7cfe commit dc904ea

2 files changed

Lines changed: 101 additions & 47 deletions

File tree

firestore/google/cloud/firestore_v1beta1/_helpers.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -851,6 +851,9 @@ def process_server_timestamp(document_data, split_on_dots=True):
851851
else:
852852
top_level_path = FieldPath.from_string(field_name)
853853
if isinstance(value, dict):
854+
if len(value) == 0:
855+
actual_data[field_name] = value
856+
continue
854857
sub_transform_paths, sub_data, sub_field_paths = (
855858
process_server_timestamp(value, False))
856859
for sub_transform_path in sub_transform_paths:

firestore/tests/unit/test__helpers.py

Lines changed: 98 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1377,6 +1377,27 @@ def test_field_updates(self):
13771377
expected_data = {'a': {'b': data['a']['b']}}
13781378
self.assertEqual(actual_data, expected_data)
13791379

1380+
def test_field_updates_w_empty_value(self):
1381+
import collections
1382+
from google.cloud.firestore_v1beta1 import _helpers
1383+
from google.cloud.firestore_v1beta1.constants import SERVER_TIMESTAMP
1384+
1385+
# "Cheat" and use OrderedDict-s so that iteritems() is deterministic.
1386+
data = collections.OrderedDict((
1387+
('a', {'b': 10}),
1388+
('c.d', {'e': SERVER_TIMESTAMP}),
1389+
('f.g', SERVER_TIMESTAMP),
1390+
('h', {}),
1391+
))
1392+
transform_paths, actual_data, field_paths = self._call_fut(data)
1393+
self.assertEqual(
1394+
transform_paths,
1395+
[_helpers.FieldPath('c', 'd', 'e'),
1396+
_helpers.FieldPath('f', 'g')])
1397+
1398+
expected_data = {'a': {'b': data['a']['b']}, 'h': {}}
1399+
self.assertEqual(actual_data, expected_data)
1400+
13801401

13811402
class Test_canonicalize_field_paths(unittest.TestCase):
13821403

@@ -1460,78 +1481,108 @@ def test_it(self):
14601481
class Test_pbs_for_set(unittest.TestCase):
14611482

14621483
@staticmethod
1463-
def _call_fut(document_path, document_data, option):
1484+
def _call_fut(document_path, document_data, merge=False, exists=None):
14641485
from google.cloud.firestore_v1beta1._helpers import pbs_for_set
14651486

1466-
return pbs_for_set(document_path, document_data, option)
1487+
return pbs_for_set(
1488+
document_path, document_data, merge=merge, exists=exists)
14671489

1468-
def _helper(self, merge=False, do_transform=False, **write_kwargs):
1469-
from google.cloud.firestore_v1beta1.constants import SERVER_TIMESTAMP
1470-
from google.cloud.firestore_v1beta1.gapic import enums
1471-
from google.cloud.firestore_v1beta1.proto import common_pb2
1490+
@staticmethod
1491+
def _make_write_w_document(document_path, **data):
14721492
from google.cloud.firestore_v1beta1.proto import document_pb2
14731493
from google.cloud.firestore_v1beta1.proto import write_pb2
1494+
from google.cloud.firestore_v1beta1._helpers import encode_dict
14741495

1475-
document_path = _make_ref_string(
1476-
u'little', u'town', u'of', u'ham')
1477-
field_name1 = 'cheese'
1478-
value1 = 1.5
1479-
field_name2 = 'crackers'
1480-
value2 = True
1481-
field_name3 = 'butter'
1496+
return write_pb2.Write(
1497+
update=document_pb2.Document(
1498+
name=document_path,
1499+
fields=encode_dict(data),
1500+
),
1501+
)
1502+
1503+
@staticmethod
1504+
def _make_write_w_transform(document_path, fields):
1505+
from google.cloud.firestore_v1beta1.proto import write_pb2
1506+
from google.cloud.firestore_v1beta1.gapic import enums
1507+
1508+
server_val = enums.DocumentTransform.FieldTransform.ServerValue
1509+
transforms = [
1510+
write_pb2.DocumentTransform.FieldTransform(
1511+
field_path=field, set_to_server_value=server_val.REQUEST_TIME)
1512+
for field in fields
1513+
]
1514+
1515+
return write_pb2.Write(
1516+
transform=write_pb2.DocumentTransform(
1517+
document=document_path,
1518+
field_transforms=transforms,
1519+
),
1520+
)
1521+
1522+
def _helper(self, merge=False, do_transform=False, exists=None,
1523+
empty_val=False):
1524+
from google.cloud.firestore_v1beta1.constants import SERVER_TIMESTAMP
1525+
from google.cloud.firestore_v1beta1.proto import common_pb2
14821526

1527+
document_path = _make_ref_string(u'little', u'town', u'of', u'ham')
14831528
document_data = {
1484-
field_name1: value1,
1485-
field_name2: value2,
1529+
'cheese': 1.5,
1530+
'crackers': True,
14861531
}
1532+
14871533
if do_transform:
1488-
document_data[field_name3] = SERVER_TIMESTAMP
1534+
document_data['butter'] = SERVER_TIMESTAMP
14891535

1490-
write_pbs = self._call_fut(document_path, document_data, merge)
1536+
if empty_val:
1537+
document_data['mustard'] = {}
14911538

1492-
expected_update_pb = write_pb2.Write(
1493-
update=document_pb2.Document(
1494-
name=document_path,
1495-
fields={
1496-
field_name1: _value_pb(double_value=value1),
1497-
field_name2: _value_pb(boolean_value=value2),
1498-
},
1499-
),
1500-
**write_kwargs
1501-
)
1502-
expected_pbs = [expected_update_pb]
1539+
write_pbs = self._call_fut(
1540+
document_path, document_data, merge, exists)
1541+
1542+
if empty_val:
1543+
update_pb = self._make_write_w_document(
1544+
document_path, cheese=1.5, crackers=True, mustard={},
1545+
)
1546+
else:
1547+
update_pb = self._make_write_w_document(
1548+
document_path, cheese=1.5, crackers=True,
1549+
)
1550+
expected_pbs = [update_pb]
15031551

15041552
if merge:
1505-
field_paths = [field_name1, field_name2]
1506-
mask = common_pb2.DocumentMask(field_paths=sorted(field_paths))
1507-
expected_pbs[0].update_mask.CopyFrom(mask)
1553+
field_paths = sorted(['cheese', 'crackers'])
1554+
update_pb.update_mask.CopyFrom(
1555+
common_pb2.DocumentMask(field_paths=field_paths))
1556+
1557+
if exists is not None:
1558+
update_pb.current_document.CopyFrom(
1559+
common_pb2.Precondition(exists=exists))
15081560

15091561
if do_transform:
1510-
server_val = enums.DocumentTransform.FieldTransform.ServerValue
1511-
expected_transform_pb = write_pb2.Write(
1512-
transform=write_pb2.DocumentTransform(
1513-
document=document_path,
1514-
field_transforms=[
1515-
write_pb2.DocumentTransform.FieldTransform(
1516-
field_path=field_name3,
1517-
set_to_server_value=server_val.REQUEST_TIME,
1518-
),
1519-
],
1520-
),
1521-
)
1522-
expected_pbs.append(expected_transform_pb)
1562+
expected_pbs.append(
1563+
self._make_write_w_transform(document_path, fields=['butter']))
15231564

15241565
self.assertEqual(write_pbs, expected_pbs)
15251566

1526-
def test_without_option(self):
1567+
def test_without_merge(self):
15271568
self._helper()
15281569

1529-
def test_with_merge_option(self):
1570+
def test_with_merge(self):
15301571
self._helper(merge=True)
15311572

1532-
def test_update_and_transform(self):
1573+
def test_with_exists_false(self):
1574+
self._helper(exists=False)
1575+
1576+
def test_with_exists_true(self):
1577+
self._helper(exists=True)
1578+
1579+
def test_w_transform(self):
15331580
self._helper(do_transform=True)
15341581

1582+
def test_w_transform_and_empty_value(self):
1583+
# Exercise #5944
1584+
self._helper(do_transform=True, empty_val=True)
1585+
15351586

15361587
class Test_pbs_for_update(unittest.TestCase):
15371588

0 commit comments

Comments
 (0)