Skip to content

Commit 8773895

Browse files
committed
implement SchemaEditor.add/remove_index()
And also creating indexes in create_model() and add_field(), and removing them in remove_field().
1 parent 821ae79 commit 8773895

File tree

4 files changed

+109
-32
lines changed

4 files changed

+109
-32
lines changed

django_mongodb/features.py

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -70,29 +70,13 @@ class DatabaseFeatures(BaseDatabaseFeatures):
7070
"backends.tests.ThreadTests.test_closing_non_shared_connections",
7171
"backends.tests.ThreadTests.test_default_connection_thread_local",
7272
# AddField
73-
"schema.tests.SchemaTests.test_add_indexed_charfield",
7473
"schema.tests.SchemaTests.test_add_unique_charfield",
75-
# Add/RemoveIndex
76-
"migrations.test_operations.OperationTests.test_add_index",
77-
"migrations.test_operations.OperationTests.test_alter_field_with_index",
78-
"migrations.test_operations.OperationTests.test_remove_index",
79-
"migrations.test_operations.OperationTests.test_rename_index",
80-
"migrations.test_operations.OperationTests.test_rename_index_unknown_unnamed_index",
81-
"migrations.test_operations.OperationTests.test_rename_index_unnamed_index",
82-
"schema.tests.SchemaTests.test_add_remove_index",
83-
"schema.tests.SchemaTests.test_composed_desc_index_with_fk",
84-
"schema.tests.SchemaTests.test_composed_index_with_fk",
85-
"schema.tests.SchemaTests.test_create_index_together",
86-
"schema.tests.SchemaTests.test_order_index",
87-
"schema.tests.SchemaTests.test_text_field_with_db_index",
8874
# AlterField
8975
"schema.tests.SchemaTests.test_alter_field_add_index_to_integerfield",
90-
"schema.tests.SchemaTests.test_alter_field_fk_keeps_index",
9176
"schema.tests.SchemaTests.test_alter_field_fk_to_o2o",
9277
"schema.tests.SchemaTests.test_alter_field_o2o_keeps_unique",
9378
"schema.tests.SchemaTests.test_alter_field_o2o_to_fk",
9479
"schema.tests.SchemaTests.test_alter_int_pk_to_int_unique",
95-
"schema.tests.SchemaTests.test_alter_not_unique_field_to_primary_key",
9680
# AlterField (db_index)
9781
"schema.tests.SchemaTests.test_alter_renames_index",
9882
"schema.tests.SchemaTests.test_indexes",
@@ -114,9 +98,6 @@ class DatabaseFeatures(BaseDatabaseFeatures):
11498
"schema.tests.SchemaTests.test_composed_constraint_with_fk",
11599
"schema.tests.SchemaTests.test_remove_ignored_unique_constraint_not_create_fk_index",
116100
"schema.tests.SchemaTests.test_unique_constraint",
117-
# subclasses of BaseDatabaseIntrospection may require a get_constraints() method
118-
"migrations.test_operations.OperationTests.test_add_func_unique_constraint",
119-
"migrations.test_operations.OperationTests.test_remove_func_unique_constraint",
120101
}
121102
# $bitAnd, #bitOr, and $bitXor are new in MongoDB 6.3.
122103
_django_test_expected_failures_bitwise = {
@@ -220,6 +201,7 @@ def django_test_expected_failures(self):
220201
"get_or_create.tests.GetOrCreateThroughManyToMany.test_something",
221202
"get_or_create.tests.UpdateOrCreateTests.test_manual_primary_key_test",
222203
"get_or_create.tests.UpdateOrCreateTestsWithManualPKs.test_create_with_duplicate_primary_key",
204+
"introspection.tests.IntrospectionTests.test_get_constraints_unique_indexes_orders",
223205
"model_fields.test_filefield.FileFieldTests.test_unique_when_same_filename",
224206
"one_to_one.tests.OneToOneTests.test_multiple_o2o",
225207
"queries.test_bulk_update.BulkUpdateTests.test_database_routing_batch_atomicity",
@@ -611,14 +593,8 @@ def django_test_expected_failures(self):
611593
"introspection.tests.IntrospectionTests.test_get_table_description_types",
612594
"introspection.tests.IntrospectionTests.test_smallautofield",
613595
},
614-
"DatabaseIntrospection.get_constraints() not implemented.": {
615-
"introspection.tests.IntrospectionTests.test_get_constraints",
616-
"introspection.tests.IntrospectionTests.test_get_constraints_index_types",
617-
"introspection.tests.IntrospectionTests.test_get_constraints_indexes_orders",
618-
"introspection.tests.IntrospectionTests.test_get_constraints_unique_indexes_orders",
619-
"introspection.tests.IntrospectionTests.test_get_primary_key_column",
620-
},
621596
"MongoDB can't introspect primary key.": {
597+
"introspection.tests.IntrospectionTests.test_get_primary_key_column",
622598
"schema.tests.SchemaTests.test_alter_primary_key_the_same_name",
623599
"schema.tests.SchemaTests.test_primary_key",
624600
},

django_mongodb/introspection.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,32 @@
11
from django.db.backends.base.introspection import BaseDatabaseIntrospection
2+
from django.db.models import Index
3+
from pymongo import ASCENDING, DESCENDING
24

35

46
class DatabaseIntrospection(BaseDatabaseIntrospection):
7+
ORDER_DIR = {ASCENDING: "ASC", DESCENDING: "DESC"}
8+
59
def table_names(self, cursor=None, include_views=False):
610
return sorted([x["name"] for x in self.connection.database.list_collections()])
11+
12+
def get_constraints(self, cursor, table_name):
13+
indexes = self.connection.get_collection(table_name).index_information()
14+
constraints = {}
15+
for name, details in indexes.items():
16+
# Remove underscore prefix from "_id" columns in primary key index.
17+
if is_primary_key := name == "_id_":
18+
name = "id"
19+
details["key"] = [("id", 1)]
20+
constraints[name] = {
21+
"check": False,
22+
"columns": [field for field, order in details["key"]],
23+
"definition": None,
24+
"foreign_key": None,
25+
"index": True,
26+
"orders": [self.ORDER_DIR[order] for field, order in details["key"]],
27+
"primary_key": is_primary_key,
28+
"type": Index.suffix,
29+
"unique": details.get("unique", False),
30+
"options": {},
31+
}
32+
return constraints

django_mongodb/schema.py

Lines changed: 78 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
2+
from django.db.models import Index
3+
from pymongo import ASCENDING, DESCENDING
4+
from pymongo.operations import IndexModel
25

36
from .query import wrap_database_errors
47

@@ -13,11 +16,30 @@ def get_database(self):
1316
@wrap_database_errors
1417
def create_model(self, model):
1518
self.get_database().create_collection(model._meta.db_table)
19+
self._create_model_indexes(model)
1620
# Make implicit M2M tables.
1721
for field in model._meta.local_many_to_many:
1822
if field.remote_field.through._meta.auto_created:
1923
self.create_model(field.remote_field.through)
2024

25+
def _create_model_indexes(self, model):
26+
"""
27+
Create all indexes (field indexes, index_together, Meta.indexes) for
28+
the specified model.
29+
"""
30+
if not model._meta.managed or model._meta.proxy or model._meta.swapped:
31+
return
32+
# Field indexes
33+
for field in model._meta.local_fields:
34+
if self._field_should_be_indexed(model, field):
35+
self._add_field_index(model, field)
36+
# Meta.index_together (RemovedInDjango51Warning)
37+
for field_names in model._meta.index_together:
38+
self._add_composed_index(model, field_names)
39+
# Meta.indexes
40+
for index in model._meta.indexes:
41+
self.add_index(model, index)
42+
2143
def delete_model(self, model):
2244
# Delete implicit M2m tables.
2345
for field in model._meta.local_many_to_many:
@@ -35,6 +57,9 @@ def add_field(self, model, field):
3557
self.get_collection(model._meta.db_table).update_many(
3658
{}, [{"$set": {column: self.effective_default(field)}}]
3759
)
60+
# Add an index, if required.
61+
if self._field_should_be_indexed(model, field):
62+
self._add_field_index(model, field)
3863

3964
def _alter_field(
4065
self,
@@ -66,21 +91,68 @@ def remove_field(self, model, field):
6691
# Unset field on existing documents.
6792
if column := field.column:
6893
self.get_collection(model._meta.db_table).update_many({}, {"$unset": {column: ""}})
94+
if self._field_should_be_indexed(model, field):
95+
self._remove_field_index(model, field)
6996

7097
def alter_index_together(self, model, old_index_together, new_index_together):
7198
pass
7299

73100
def alter_unique_together(self, model, old_unique_together, new_unique_together):
74101
pass
75102

76-
def add_index(self, model, index):
77-
pass
78-
79-
def rename_index(self, model, old_index, new_index):
80-
pass
103+
def add_index(self, model, index, field=None):
104+
if index.contains_expressions:
105+
return
106+
index_orders = (
107+
[(field.column, ASCENDING)]
108+
if field
109+
else [
110+
# order is "" if ASCENDING or "DESC" if DESCENDING (see
111+
# django.db.models.indexes.Index.fields_orders).
112+
(model._meta.get_field(field_name).column, ASCENDING if order == "" else DESCENDING)
113+
for field_name, order in index.fields_orders
114+
]
115+
)
116+
idx = IndexModel(index_orders, name=index.name)
117+
self.get_collection(model._meta.db_table).create_indexes([idx])
118+
119+
def _add_composed_index(self, model, field_names):
120+
"""Add an index on the given list of field_names."""
121+
idx = Index(fields=field_names)
122+
idx.set_name_with_model(model)
123+
self.add_index(model, idx)
124+
125+
def _add_field_index(self, model, field):
126+
"""Add an index on a field with db_index=True."""
127+
index = Index(fields=[field.name])
128+
index.name = self._create_index_name(model._meta.db_table, [field.column])
129+
self.add_index(model, index, field=field)
81130

82131
def remove_index(self, model, index):
83-
pass
132+
if index.contains_expressions:
133+
return
134+
self.get_collection(model._meta.db_table).drop_index(index.name)
135+
136+
def _remove_field_index(self, model, field):
137+
"""Remove a field's db_index=True index."""
138+
collection = self.get_collection(model._meta.db_table)
139+
meta_index_names = {index.name for index in model._meta.indexes}
140+
index_names = self._constraint_names(
141+
model,
142+
[field.column],
143+
index=True,
144+
# Retrieve only BTREE indexes since this is what's created with
145+
# db_index=True.
146+
type_=Index.suffix,
147+
exclude=meta_index_names,
148+
)
149+
if len(index_names) != 1:
150+
num_found = len(index_names)
151+
raise ValueError(
152+
f"Found wrong number ({num_found}) of constraints for "
153+
f"{model._meta.db_table}.{field.column}."
154+
)
155+
collection.drop_index(index_names[0])
84156

85157
def add_constraint(self, model, constraint):
86158
pass

django_mongodb/utils.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,10 @@ def wrapper(self, *args, **kwargs):
8282
# These are the operations that this backend uses.
8383
aggregate = logging_wrapper("aggregate")
8484
create_collection = logging_wrapper("create_collection")
85+
create_indexes = logging_wrapper("create_indexes")
8586
drop = logging_wrapper("drop")
87+
drop_index = logging_wrapper("drop_index")
88+
index_information = logging_wrapper("index_information")
8689
insert_many = logging_wrapper("insert_many")
8790
delete_many = logging_wrapper("delete_many")
8891
rename = logging_wrapper("rename")

0 commit comments

Comments
 (0)