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
47 changes: 42 additions & 5 deletions django_mongodb_backend/indexes.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,37 @@ class SearchIndex(Index):
suffix = "six"
_error_id_prefix = "django_mongodb_backend.indexes.SearchIndex"

def __init__(self, *, fields=(), name=None):
def __init__(
self, *, fields=(), field_mappings=None, name=None, analyzer=None, search_analyzer=None
):
if field_mappings and not isinstance(field_mappings, dict):
raise ValueError(
"field_mappings must be a dictionary mapping field names to their "
"Atlas Search field mappings."
)
if analyzer is not None and not isinstance(analyzer, str):
raise ValueError(f"analyzer must be a string. got type: {type(analyzer)}")
if search_analyzer is not None and not isinstance(search_analyzer, str):
raise ValueError(f"search_analyzer must be a string. got type: {type(search_analyzer)}")
self.field_mappings = field_mappings
self.analyzer = analyzer
self.search_analyzer = search_analyzer
if field_mappings:
if fields:
raise ValueError("Cannot provide fields and fields_mappings")
fields = [*self.field_mappings.keys()]
super().__init__(fields=fields, name=name)

def deconstruct(self):
path, args, kwargs = super().deconstruct()
if self.field_mappings is not None:
kwargs["field_mappings"] = self.field_mappings
if self.analyzer is not None:
kwargs["analyzer"] = self.analyzer
if self.search_analyzer is not None:
kwargs["search_analyzer"] = self.search_analyzer
return path, args, kwargs

def check(self, model, connection):
errors = []
if not connection.features.supports_atlas_search:
Expand Down Expand Up @@ -152,12 +180,21 @@ def get_pymongo_index_model(
return None
fields = {}
for field_name, _ in self.fields_orders:
field = model._meta.get_field(field_name)
type_ = self.search_index_data_types(field.db_type(schema_editor.connection))
field_path = column_prefix + model._meta.get_field(field_name).column
fields[field_path] = {"type": type_}
if self.field_mappings:
fields[field_path] = self.field_mappings[field_name]
else:
field = model._meta.get_field(field_name)
type_ = self.search_index_data_types(field.db_type(schema_editor.connection))
fields[field_path] = {"type": type_}
analyzers = {}
if self.analyzer is not None:
analyzers["analyzer"] = self.analyzer
if self.search_analyzer is not None:
analyzers["searchAnalyzer"] = self.search_analyzer
return SearchIndexModel(
definition={"mappings": {"dynamic": False, "fields": fields}}, name=self.name
definition={"mappings": {"dynamic": False, "fields": fields}, **analyzers},
name=self.name,
)


Expand Down
8 changes: 6 additions & 2 deletions django_mongodb_backend/introspection.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,12 @@ def _get_search_index_info(self, table_name):
type_ = VectorSearchIndex.suffix
options = details
else:
options = details["latestDefinition"]["mappings"]
columns = list(options.get("fields", {}).keys())
options = {
"analyzer": details["latestDefinition"].get("analyzer"),
"searchAnalyzer": details["latestDefinition"].get("searchAnalyzer"),
"mappings": details["latestDefinition"]["mappings"],
}
columns = list(options["mappings"].get("fields", {}).keys())
type_ = SearchIndex.suffix
constraints[details["name"]] = {
"check": False,
Expand Down
19 changes: 18 additions & 1 deletion docs/ref/models/indexes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ minutes, depending on the size of the collection.
``SearchIndex``
---------------

.. class:: SearchIndex(fields=(), name=None)
.. class:: SearchIndex(fields=(), field_mappings=None, name=None, analyzer=None, search_analyzer=None)

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

Use ``field_mappings`` (instead of ``fields``) to create an advanced search
index. ``field_mappings`` is a dictionary that maps field names to index
options (see ``definition["mappings"]["fields"]`` in the
:ref:`atlas:fts-static-mapping-example`).

If ``name`` isn't provided, one will be generated automatically. If you
need to reference the name in your search query and don't provide your own
name, you can lookup the generated one using ``Model._meta.indexes[0].name``
(substituting the name of your model as well as a different list index if
your model has multiple indexes).

Use ``analyzer`` or ``search_analyzer`` to configure the
indexing and searching analyzer, respectively, for
the search index definition. If these fields are not provided,
they will default to ``lucene.standard`` at the server level.
(See ``definition["mappings"]["analyzer"]``
and ``definition["mappings"]["searchAnalyzer"]``
in the :ref:`atlas:fts-static-mapping-example`).

.. versionchanged:: 5.2.2

The ``fields_mappings``, ``analyzer``, and ``search_analyzer`` arguments were added.

``VectorSearchIndex``
---------------------

Expand Down
3 changes: 2 additions & 1 deletion docs/releases/5.2.x.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ Django MongoDB Backend 5.2.x
New features
------------

- ...
- Added the ``field_mappings``, ``analyzer``, and ``search_analyzer``
arguments to :class:`.SearchIndex` to allow creating advanced indexes.

Bug fixes
---------
Expand Down
Loading