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
9 changes: 7 additions & 2 deletions haystack/dataclasses/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,16 @@ def __eq__(self, other: object) -> bool:
"""
Compares Documents for equality.

Two Documents are considered equals if their dictionary representation is identical.
Two Documents are considered equal if their field values, including the `meta` mapping, are
identical.

The unflattened dictionary representation is used so that metadata keys that collide with
top-level Document fields (e.g. ``id``, ``content``, ``score``) are preserved during the
comparison instead of being silently overwritten by the Document's own fields. See #11969.
"""
if type(self) != type(other):
return False
return self.to_dict() == other.to_dict()
return self.to_dict(flatten=False) == other.to_dict(flatten=False)

def __post_init__(self) -> None:
"""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
fixes:
- |
Fixed `Document.__eq__` reporting two Documents as equal when their `meta` mapping contained
keys that collide with top-level Document fields such as `id`, `content`, or `score`. The
equality comparison now uses the unflattened dictionary representation, so metadata keys
that share a name with a Document field are preserved during comparison instead of being
silently overwritten by the Document's own field values.
35 changes: 35 additions & 0 deletions test/dataclasses/test_document.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,41 @@ def test_basic_equality_id():
assert doc1 != doc2


def test_equality_preserves_meta_keys_colliding_with_top_level_fields():
"""
Regression test for #11969.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

  • no need to mention the issue
  • shorter docstring explanation


Equality must not be reported for two Documents whose `meta` contains keys that collide with
top-level Document fields (``id``, ``content``, ``score``). Previously ``Document.__eq__``
compared the flattened ``to_dict()`` output, which silently overwrote colliding meta keys
with the Document's own field values during comparison.
"""
doc1 = Document(id="same_id", content="hello", meta={"id": "different1"})
doc2 = Document(id="same_id", content="hello", meta={"id": "different2"})

assert doc1 != doc2

doc3 = Document(content="hello", meta={"score": 0.5})
doc4 = Document(content="hello", meta={"score": 0.9})
assert doc3 != doc4

doc5 = Document(content="hello", meta={"content": "from_meta"})
doc6 = Document(content="hello", meta={"content": "also_from_meta_but_different"})
assert doc5 != doc6
Comment on lines +140 to +146

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
doc3 = Document(content="hello", meta={"score": 0.5})
doc4 = Document(content="hello", meta={"score": 0.9})
assert doc3 != doc4
doc5 = Document(content="hello", meta={"content": "from_meta"})
doc6 = Document(content="hello", meta={"content": "also_from_meta_but_different"})
assert doc5 != doc6
doc3 = Document(id="same_id", content="hello", meta={"score": 0.5})
doc4 = Document(id="same_id", content="hello", meta={"score": 0.9})
assert doc3 != doc4
doc5 = Document(id="same_id", content="hello", meta={"content": "from_meta"})
doc6 = Document(id="same_id", content="hello", meta={"content": "different_from_meta"})
assert doc5 != doc6



def test_equality_with_colliding_meta_keys_when_meta_actually_matches():
"""
Companion to the regression test for #11969.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

no need to mention the issue


When two Documents have the same top-level fields AND the same colliding meta key, they
must still be considered equal.
"""
doc1 = Document(id="same_id", content="hello", meta={"id": "x"})
doc2 = Document(id="same_id", content="hello", meta={"id": "x"})
assert doc1 == doc2


def test_id_is_independent_of_meta_key_order():
doc1 = Document(content="hello", meta={"a": 1, "b": 2})
doc2 = Document(content="hello", meta={"b": 2, "a": 1})
Expand Down
Loading