From f4598f87f88b79d429877cdc260750fcd9946ee5 Mon Sep 17 00:00:00 2001 From: Bastien Gerard Date: Fri, 27 Sep 2024 22:28:37 +0200 Subject: [PATCH] Fix text index on multiple fields , order independent (#2612) --- docs/changelog.rst | 1 + mongoengine/base/utils.py | 10 ++++++++++ mongoengine/document.py | 7 ++++++- tests/document/test_indexes.py | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 50 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 4beaa4a48..669f5b9b9 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -7,6 +7,7 @@ Changelog Development =========== - (Fill this out as you fix issues and develop your features). +- Fix Document.compare_indexes() not working correctly for text indexes on multiple fields #2612 - Add support for transaction through run_in_transaction (kudos to juannyG for this) #2569 Some considerations: - make sure to read https://www.mongodb.com/docs/manual/core/transactions-in-applications/#callback-api-vs-core-api diff --git a/mongoengine/base/utils.py b/mongoengine/base/utils.py index 7753ad50c..40f917965 100644 --- a/mongoengine/base/utils.py +++ b/mongoengine/base/utils.py @@ -20,3 +20,13 @@ def __get__(self, instance, owner): def __set__(self, instance, value): raise AttributeError("Can not set attribute LazyRegexCompiler") + + +class NonOrderedList(list): + """Simple utility class to compare lists without considering order (useful in context of indexes)""" + + def __eq__(self, other): + if isinstance(other, list): + # Compare sorted versions of the lists + return sorted(self) == sorted(other) + return False diff --git a/mongoengine/document.py b/mongoengine/document.py index 26b9fac7e..4907589e9 100644 --- a/mongoengine/document.py +++ b/mongoengine/document.py @@ -14,6 +14,7 @@ TopLevelDocumentMetaclass, get_document, ) +from mongoengine.base.utils import NonOrderedList from mongoengine.common import _import_class from mongoengine.connection import ( DEFAULT_CONNECTION_NAME, @@ -1043,9 +1044,13 @@ def compare_indexes(cls): # Useful for text indexes (but not only) index_type = info["key"][0][1] text_index_fields = info.get("weights").keys() - existing.append([(key, index_type) for key in text_index_fields]) + # Use NonOrderedList to avoid order comparison, see #2612 + existing.append( + NonOrderedList([(key, index_type) for key in text_index_fields]) + ) else: existing.append(info["key"]) + missing = [index for index in required if index not in existing] extra = [index for index in existing if index not in required] diff --git a/tests/document/test_indexes.py b/tests/document/test_indexes.py index 7ba18d588..02da590cb 100644 --- a/tests/document/test_indexes.py +++ b/tests/document/test_indexes.py @@ -66,6 +66,8 @@ class BlogPost(InheritFrom): for expected in expected_specs: assert expected["fields"] in info + assert BlogPost.compare_indexes() == {"missing": [], "extra": []} + def _index_test_inheritance(self, InheritFrom): class BlogPost(InheritFrom): date = DateTimeField(db_field="addDate", default=datetime.now) @@ -949,6 +951,8 @@ class MyDoc(Document): ]["key"] assert info["provider_ids.foo_1_provider_ids.bar_1"]["sparse"] + assert MyDoc.compare_indexes() == {"missing": [], "extra": []} + def test_text_indexes(self): class Book(Document): title = DictField() @@ -968,6 +972,8 @@ class Book(Document): assert "ref_id_hashed" in indexes assert ("ref_id", "hashed") in indexes["ref_id_hashed"]["key"] + assert Book.compare_indexes() == {"missing": [], "extra": []} + def test_indexes_after_database_drop(self): """ Test to ensure that indexes are not re-created on a collection @@ -1045,6 +1051,8 @@ class TestChildDoc(TestDoc): TestDoc.ensure_indexes() TestChildDoc.ensure_indexes() + assert TestDoc.compare_indexes() == {"missing": [], "extra": []} + index_info = TestDoc._get_collection().index_information() for key in index_info: del index_info[key][ @@ -1082,9 +1090,34 @@ class TestDoc(Document): TestDoc.drop_collection() TestDoc.ensure_indexes() + assert TestDoc.compare_indexes() == {"missing": [], "extra": []} + index_info = TestDoc._get_collection().index_information() assert "shard_1_1__cls_1_txt_1_1" in index_info + def test_compare_indexes_works_with_compound_text_indexes(self): + """The order of the fields in case of text indexes don't matter + so it's important to ensure that the compare_indexes method works that way + https://github.com/MongoEngine/mongoengine/issues/2612 + """ + + class Sample1(Document): + a = StringField() + b = StringField() + + meta = {"indexes": [{"fields": ["$a", "$b"]}]} + + class Sample2(Document): + a = StringField() + b = StringField() + + meta = {"indexes": [{"fields": ["$b", "$a"]}]} + + Sample1.drop_collection() + Sample2.drop_collection() + assert Sample1.compare_indexes() == {"missing": [], "extra": []} + assert Sample2.compare_indexes() == {"missing": [], "extra": []} + if __name__ == "__main__": unittest.main()