Skip to content

Commit 204e722

Browse files
Jibolatimgraham
andcommitted
INTPYTHON-729 Allow creating search indexes with field mappings and analyzers
Co-authored-by: Tim Graham <timograham@gmail.com>
1 parent 0648db1 commit 204e722

File tree

6 files changed

+246
-113
lines changed

6 files changed

+246
-113
lines changed

django_mongodb_backend/indexes.py

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,38 @@ 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__(
113+
self, *, fields=(), field_mappings=None, name=None, analyzer=None, search_analyzer=None
114+
):
115+
if field_mappings and not isinstance(field_mappings, dict):
116+
raise ValueError(
117+
"field_mappings must be a dictionary mapping field names to their "
118+
"Atlas Search index options."
119+
)
120+
if analyzer and not isinstance(analyzer, str):
121+
raise ValueError(f"analyzer must be a string; got: {type(analyzer)}.")
122+
if search_analyzer and not isinstance(search_analyzer, str):
123+
raise ValueError(f"search_analyzer must be a string; got: {type(search_analyzer)}.")
124+
self.field_mappings = field_mappings
125+
self.analyzer = analyzer
126+
self.search_analyzer = search_analyzer
127+
if field_mappings:
128+
if fields:
129+
raise ValueError("Cannot provide fields and field_mappings.")
130+
fields = [*self.field_mappings.keys()]
113131
super().__init__(fields=fields, name=name)
114132

133+
def deconstruct(self):
134+
path, args, kwargs = super().deconstruct()
135+
if self.field_mappings:
136+
kwargs["field_mappings"] = self.field_mappings
137+
del kwargs["fields"]
138+
if self.analyzer:
139+
kwargs["analyzer"] = self.analyzer
140+
if self.search_analyzer:
141+
kwargs["search_analyzer"] = self.search_analyzer
142+
return path, args, kwargs
143+
115144
def check(self, model, connection):
116145
errors = []
117146
if not connection.features.supports_atlas_search:
@@ -152,12 +181,21 @@ def get_pymongo_index_model(
152181
return None
153182
fields = {}
154183
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))
157184
field_path = column_prefix + model._meta.get_field(field_name).column
158-
fields[field_path] = {"type": type_}
185+
if self.field_mappings:
186+
fields[field_path] = self.field_mappings[field_name]
187+
else:
188+
field = model._meta.get_field(field_name)
189+
type_ = self.search_index_data_types(field.db_type(schema_editor.connection))
190+
fields[field_path] = {"type": type_}
191+
extra = {}
192+
if self.analyzer:
193+
extra["analyzer"] = self.analyzer
194+
if self.search_analyzer:
195+
extra["searchAnalyzer"] = self.search_analyzer
159196
return SearchIndexModel(
160-
definition={"mappings": {"dynamic": False, "fields": fields}}, name=self.name
197+
definition={"mappings": {"dynamic": False, "fields": fields}, **extra},
198+
name=self.name,
161199
)
162200

163201

django_mongodb_backend/introspection.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,12 @@ def _get_search_index_info(self, table_name):
4444
type_ = VectorSearchIndex.suffix
4545
options = details
4646
else:
47-
options = details["latestDefinition"]["mappings"]
48-
columns = list(options.get("fields", {}).keys())
47+
options = {
48+
"analyzer": details["latestDefinition"].get("analyzer"),
49+
"searchAnalyzer": details["latestDefinition"].get("searchAnalyzer"),
50+
"mappings": details["latestDefinition"]["mappings"],
51+
}
52+
columns = list(options["mappings"].get("fields", {}).keys())
4953
type_ = SearchIndex.suffix
5054
constraints[details["name"]] = {
5155
"check": False,

docs/ref/models/indexes.rst

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ minutes, depending on the size of the collection.
2626
``SearchIndex``
2727
---------------
2828

29-
.. class:: SearchIndex(fields=(), name=None)
29+
.. class:: SearchIndex(fields=(), field_mappings=None, name=None, analyzer=None, search_analyzer=None)
3030

3131
Creates a basic :doc:`search index <atlas:atlas-search/index-definitions>`
3232
on the given field(s).
@@ -35,12 +35,28 @@ minutes, depending on the size of the collection.
3535
supported. See the :ref:`Atlas documentation <atlas:bson-data-chart>` for a
3636
complete list of unsupported data types.
3737

38+
Use ``field_mappings`` (instead of ``fields``) to create a complex search
39+
index. ``field_mappings`` is a dictionary that maps field names to index
40+
options. It corresponds to ``definition["mappings"]["fields"]`` in the
41+
:ref:`atlas:fts-static-mapping-examples`.
42+
3843
If ``name`` isn't provided, one will be generated automatically. If you
3944
need to reference the name in your search query and don't provide your own
4045
name, you can lookup the generated one using ``Model._meta.indexes[0].name``
4146
(substituting the name of your model as well as a different list index if
4247
your model has multiple indexes).
4348

49+
Use ``analyzer`` and ``search_analyzer`` to configure the indexing and
50+
searching :doc:`analyzer <atlas:atlas-search/analyzers>`. If these options
51+
aren't provided, the server defaults to ``lucene.standard``. It corresponds
52+
to ``definition["analyzer"]`` and ``definition["searchAnalyzer"]`` in the
53+
:ref:`atlas:fts-static-mapping-examples`.
54+
55+
.. versionchanged:: 5.2.2
56+
57+
The ``field_mappings``, ``analyzer``, and ``search_analyzer`` arguments
58+
were added.
59+
4460
``VectorSearchIndex``
4561
---------------------
4662

docs/releases/5.2.x.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ Django MongoDB Backend 5.2.x
1010
New features
1111
------------
1212

13-
- ...
13+
- :class:`.SearchIndex`\'s new ``field_mappings``, ``analyzer``, and
14+
``search_analyzer`` arguments allow creating more complex indexes.
1415

1516
Bug fixes
1617
---------

0 commit comments

Comments
 (0)