Skip to content
Open
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 @@ -214,6 +214,32 @@ def encode_value(value) -> types.document.Value:
if isinstance(value, _BSONType):
return encode_value(value._to_map_value())

# Duck-type native PyMongo / third-party BSON objects
if hasattr(value, "__class__"):
cls_name = value.__class__.__name__
if cls_name == "ObjectId" and hasattr(value, "binary"):
return encode_value({"__oid__": str(value).lower()})
if cls_name == "Decimal128" and hasattr(value, "to_decimal"):
return encode_value({"__decimal128__": str(value)})
if cls_name == "Regex" and hasattr(value, "pattern"):
opts = getattr(value, "flags", "") or getattr(value, "options", "")
return encode_value(
{"__regex__": {"pattern": value.pattern, "options": str(opts)}}
)
if cls_name == "Timestamp" and hasattr(value, "time") and hasattr(value, "inc"):
return encode_value(
{
"__request_timestamp__": {
"seconds": value.time,
"increment": value.inc,
}
}
)
if cls_name == "MinKey":
return encode_value({"__min__": None})
if cls_name == "MaxKey":
return encode_value({"__max__": None})
Comment on lines +218 to +241

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The encode_value function is a critical hot path called recursively for every field of every document during serialization. While the proposed change aims to optimize standard types using an O(1) set lookup, any changes to this performance-critical code path must be validated and benchmarked to ensure they do not degrade performance or eliminate fast-path optimizations (such as the overhead of double getattr calls). Please run benchmarks to verify the performance impact of this change.

    cls_name = getattr(getattr(value, "__class__", None), "__name__", None)
    if cls_name in {"ObjectId", "Decimal128", "Regex", "Timestamp", "MinKey", "MaxKey"}:
        if cls_name == "ObjectId" and hasattr(value, "binary"):
            return encode_value({"__oid__": str(value).lower()})
        elif cls_name == "Decimal128" and hasattr(value, "to_decimal"):
            return encode_value({"__decimal128__": str(value)})
        elif cls_name == "Regex" and hasattr(value, "pattern"):
            opts = getattr(value, "flags", "") or getattr(value, "options", "")
            return encode_value(
                {"__regex__": {"pattern": value.pattern, "options": str(opts)}}
            )
        elif cls_name == "Timestamp" and hasattr(value, "time") and hasattr(value, "inc"):
            return encode_value(
                {
                    "__request_timestamp__": {
                        "seconds": value.time,
                        "increment": value.inc,
                    }
                }
            )
        elif cls_name == "MinKey":
            return encode_value({"__min__": None})
        elif cls_name == "MaxKey":
            return encode_value({"__max__": None})
References
  1. For performance-critical code paths executed on every request, validate and benchmark any proposed readability simplifications to ensure they do not degrade performance or eliminate fast-path optimizations.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of encoding all of these special strings here, we should be able to rely on the BSONType class, which has this knowledge built in already. We should have a simple way to find the matching BSONType for this PyMongo class, and a simple way to convert any BSONType to a Value. Then we can just string it together as something like BSONType._from_cls(value)._encode_value()


if isinstance(value, GeoPoint):
return document.Value(geo_point_value=value.to_protobuf())

Expand Down
30 changes: 30 additions & 0 deletions packages/google-cloud-firestore/tests/unit/v1/test__helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,36 @@ def test_geopoint_to_protobuf():
assert result == geo_pt_pb


def test_encode_value_pymongo_duck_typing():
from google.cloud.firestore_v1._helpers import encode_value

class ObjectId:
def __init__(self, val):
self.val = val
self.binary = b"12bytes_raw_"

def __str__(self):
return self.val

class Decimal128:
def __init__(self, val):
self.val = val

def to_decimal(self):
return self.val

def __str__(self):
return self.val

oid_obj = ObjectId("507f191e810c19729de860ea")
oid_pb = encode_value(oid_obj)
assert oid_pb.map_value.fields["__oid__"].string_value == "507f191e810c19729de860ea"

dec_obj = Decimal128("123.45")
dec_pb = encode_value(dec_obj)
assert dec_pb.map_value.fields["__decimal128__"].string_value == "123.45"


def test_geopoint___eq__w_same_value():
lat = 0.015625
lng = 20.03125
Expand Down
Loading