Skip to content

Commit 2135908

Browse files
committed
Proof of Concept INTPYTHON-729 with wait_for_ready added to index creation & deletion
1 parent 0b46a50 commit 2135908

File tree

2 files changed

+66
-10
lines changed

2 files changed

+66
-10
lines changed

django_mongodb_backend/indexes.py

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,22 @@ class SearchIndex(Index):
109109
suffix = "six"
110110
_error_id_prefix = "django_mongodb_backend.indexes.SearchIndex"
111111

112-
def __init__(self, *, fields=(), name=None):
112+
def __init__(self, *, fields=(), name=None, field_mappings=None):
113+
if field_mappings and not isinstance(field_mappings, dict):
114+
raise ValueError(
115+
"field_mappings must be a dictionary mapping field names to their "
116+
"Atlas Search field mappings."
117+
)
118+
self.field_mappings = field_mappings or {}
119+
120+
fields = list({*fields, *self.field_mappings.keys()})
113121
super().__init__(fields=fields, name=name)
114122

123+
def deconstruct(self):
124+
path, args, kwargs = super().deconstruct()
125+
kwargs["field_mappings"] = self.field_mappings
126+
return path, args, kwargs
127+
115128
def check(self, model, connection):
116129
errors = []
117130
if not connection.features.supports_atlas_search:
@@ -152,23 +165,39 @@ def get_pymongo_index_model(
152165
return None
153166
fields = {}
154167
for field_name, _ in self.fields_orders:
155-
field = model._meta.get_field(field_name)
156-
type_ = self.search_index_data_types(field.db_type(schema_editor.connection))
157168
field_path = column_prefix + model._meta.get_field(field_name).column
158-
fields[field_path] = {"type": type_}
169+
if field_name in self.field_mappings:
170+
fields[field_path] = self.field_mappings[field_name].copy()
171+
else:
172+
# If no field mapping is provided, use the default search index data type.
173+
field = model._meta.get_field(field_name)
174+
type_ = self.search_index_data_types(field.db_type(schema_editor.connection))
175+
fields[field_path] = {"type": type_}
159176
return SearchIndexModel(
160177
definition={"mappings": {"dynamic": False, "fields": fields}}, name=self.name
161178
)
162179

163180

181+
class DynamicSearchIndex(SearchIndex):
182+
suffix = "dsix"
183+
_error_id_prefix = "django_mongodb_backend.indexes.DynamicSearchIndex"
184+
185+
def get_pymongo_index_model(
186+
self, model, schema_editor, field=None, unique=False, column_prefix=""
187+
):
188+
if not schema_editor.connection.features.supports_atlas_search:
189+
return None
190+
return SearchIndexModel(definition={"mappings": {"dynamic": True}}, name=self.name)
191+
192+
164193
class VectorSearchIndex(SearchIndex):
165194
suffix = "vsi"
166195
_error_id_prefix = "django_mongodb_backend.indexes.VectorSearchIndex"
167196
VALID_FIELD_TYPES = frozenset(("boolean", "date", "number", "objectId", "string", "uuid"))
168197
VALID_SIMILARITIES = frozenset(("cosine", "dotProduct", "euclidean"))
169198

170-
def __init__(self, *, fields=(), name=None, similarities):
171-
super().__init__(fields=fields, name=name)
199+
def __init__(self, *, fields=(), name=None, similarities=(), fields_mappings=None):
200+
super().__init__(fields=fields, name=name, field_mappings=fields_mappings)
172201
self.similarities = similarities
173202
self._multiple_similarities = isinstance(similarities, tuple | list)
174203
for func in similarities if self._multiple_similarities else (similarities,):

django_mongodb_backend/schema.py

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from time import monotonic, sleep
2+
13
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
24
from django.db.models import Index, UniqueConstraint
35
from pymongo.operations import SearchIndexModel
@@ -28,6 +30,27 @@ def wrapper(self, model, *args, **kwargs):
2830
return wrapper
2931

3032

33+
def wait_until_index_ready(collection, index_name, timeout: float = 60, interval: float = 0.5):
34+
start = monotonic()
35+
while monotonic() - start < timeout:
36+
indexes = list(collection.list_search_indexes())
37+
for idx in indexes:
38+
if idx["name"] == index_name and idx["status"] == "READY":
39+
return True
40+
sleep(interval)
41+
raise TimeoutError(f"Index {index_name} not ready after {timeout} seconds")
42+
43+
44+
def wait_until_index_delete(collection, index_name, timeout: float = 60, interval: float = 0.5):
45+
start = monotonic()
46+
while monotonic() - start < timeout:
47+
indexes = list(collection.list_search_indexes())
48+
if all(idx["name"] != index_name for idx in indexes):
49+
return True
50+
sleep(interval)
51+
raise TimeoutError(f"Index {index_name} not deleted after {timeout} seconds")
52+
53+
3154
class BaseSchemaEditor(BaseDatabaseSchemaEditor):
3255
def get_collection(self, name):
3356
if self.collect_sql:
@@ -269,10 +292,12 @@ def add_index(
269292
)
270293
if idx:
271294
model = parent_model or model
295+
collection = self.get_collection(model._meta.db_table)
272296
if isinstance(idx, SearchIndexModel):
273-
self.get_collection(model._meta.db_table).create_search_index(idx)
297+
collection.create_search_index(idx)
298+
wait_until_index_ready(collection, index.name)
274299
else:
275-
self.get_collection(model._meta.db_table).create_indexes([idx])
300+
collection.create_indexes([idx])
276301

277302
def _add_composed_index(self, model, field_names, column_prefix="", parent_model=None):
278303
"""Add an index on the given list of field_names."""
@@ -290,12 +315,14 @@ def _add_field_index(self, model, field, *, column_prefix=""):
290315
def remove_index(self, model, index):
291316
if index.contains_expressions:
292317
return
318+
collection = self.get_collection(model._meta.db_table)
293319
if isinstance(index, SearchIndex):
294320
# Drop the index if it's supported.
295321
if self.connection.features.supports_atlas_search:
296-
self.get_collection(model._meta.db_table).drop_search_index(index.name)
322+
collection.drop_search_index(index.name)
323+
wait_until_index_delete(collection, index.name)
297324
else:
298-
self.get_collection(model._meta.db_table).drop_index(index.name)
325+
collection.drop_index(index.name)
299326

300327
def _remove_composed_index(
301328
self, model, field_names, constraint_kwargs, column_prefix="", parent_model=None

0 commit comments

Comments
 (0)