11from django .db .backends .base .schema import BaseDatabaseSchemaEditor
2- from django .db .models import Index
2+ from django .db .models import Index , UniqueConstraint
33from pymongo import ASCENDING , DESCENDING
44from pymongo .operations import IndexModel
55
@@ -29,18 +29,23 @@ def create_model(self, model):
2929
3030 def _create_model_indexes (self , model ):
3131 """
32- Create all indexes (field indexes, index_together , Meta.indexes) for
33- the specified model.
32+ Create all indexes (field indexes & uniques , Meta.index_together,
33+ Meta.constraints, Meta.indexes) for the specified model.
3434 """
3535 if not model ._meta .managed or model ._meta .proxy or model ._meta .swapped :
3636 return
37- # Field indexes
37+ # Field indexes and uniques
3838 for field in model ._meta .local_fields :
3939 if self ._field_should_be_indexed (model , field ):
4040 self ._add_field_index (model , field )
41+ elif self ._field_should_have_unique (field ):
42+ self ._add_field_unique (model , field )
4143 # Meta.index_together (RemovedInDjango51Warning)
4244 for field_names in model ._meta .index_together :
4345 self ._add_composed_index (model , field_names )
46+ # Meta.constraints
47+ for constraint in model ._meta .constraints :
48+ self .add_constraint (model , constraint )
4449 # Meta.indexes
4550 for index in model ._meta .indexes :
4651 self .add_index (model , index )
@@ -62,9 +67,11 @@ def add_field(self, model, field):
6267 self .get_collection (model ._meta .db_table ).update_many (
6368 {}, [{"$set" : {column : self .effective_default (field )}}]
6469 )
65- # Add an index, if required.
70+ # Add an index or unique , if required.
6671 if self ._field_should_be_indexed (model , field ):
6772 self ._add_field_index (model , field )
73+ elif self ._field_should_have_unique (field ):
74+ self ._add_field_unique (model , field )
6875
6976 def _alter_field (
7077 self ,
@@ -110,6 +117,8 @@ def remove_field(self, model, field):
110117 self .get_collection (model ._meta .db_table ).update_many ({}, {"$unset" : {column : "" }})
111118 if self ._field_should_be_indexed (model , field ):
112119 self ._remove_field_index (model , field )
120+ elif self ._field_should_have_unique (field ):
121+ self ._remove_field_unique (model , field )
113122
114123 def alter_index_together (self , model , old_index_together , new_index_together ):
115124 olds = {tuple (fields ) for fields in old_index_together }
@@ -124,9 +133,21 @@ def alter_index_together(self, model, old_index_together, new_index_together):
124133 def alter_unique_together (self , model , old_unique_together , new_unique_together ):
125134 pass
126135
127- def add_index (self , model , index , field = None ):
136+ def add_index (self , model , index , field = None , unique = False ):
128137 if index .contains_expressions :
129138 return
139+ kwargs = {}
140+ if unique :
141+ filter_expression = {}
142+ # Indexing on $type matches the value of most SQL databases by
143+ # allowing multiple null values for the unique constraint.
144+ if field :
145+ filter_expression [field .column ] = {"$type" : field .db_type (self .connection )}
146+ else :
147+ for field_name , _ in index .fields_orders :
148+ field_ = model ._meta .get_field (field_name )
149+ filter_expression [field_ .column ] = {"$type" : field_ .db_type (self .connection )}
150+ kwargs = {"partialFilterExpression" : filter_expression , "unique" : True }
130151 index_orders = (
131152 [(field .column , ASCENDING )]
132153 if field
@@ -137,7 +158,7 @@ def add_index(self, model, index, field=None):
137158 for field_name , order in index .fields_orders
138159 ]
139160 )
140- idx = IndexModel (index_orders , name = index .name )
161+ idx = IndexModel (index_orders , name = index .name , ** kwargs )
141162 self .get_collection (model ._meta .db_table ).create_indexes ([idx ])
142163
143164 def _add_composed_index (self , model , field_names ):
@@ -202,13 +223,57 @@ def _remove_field_index(self, model, field):
202223 )
203224 collection .drop_index (index_names [0 ])
204225
205- def add_constraint (self , model , constraint ):
206- pass
226+ def add_constraint (self , model , constraint , field = None ):
227+ if isinstance (constraint , UniqueConstraint ) and self ._unique_supported (
228+ condition = constraint .condition ,
229+ deferrable = constraint .deferrable ,
230+ include = constraint .include ,
231+ expressions = constraint .expressions ,
232+ nulls_distinct = constraint .nulls_distinct ,
233+ ):
234+ idx = Index (fields = constraint .fields , name = constraint .name )
235+ self .add_index (model , idx , field = field , unique = True )
236+
237+ def _add_field_unique (self , model , field ):
238+ name = str (self ._unique_constraint_name (model ._meta .db_table , [field .column ]))
239+ constraint = UniqueConstraint (fields = [field .name ], name = name )
240+ self .add_constraint (model , constraint , field = field )
207241
208242 def remove_constraint (self , model , constraint ):
209- pass
243+ if isinstance (constraint , UniqueConstraint ) and self ._unique_supported (
244+ condition = constraint .condition ,
245+ deferrable = constraint .deferrable ,
246+ include = constraint .include ,
247+ expressions = constraint .expressions ,
248+ nulls_distinct = constraint .nulls_distinct ,
249+ ):
250+ idx = Index (fields = constraint .fields , name = constraint .name )
251+ self .remove_index (model , idx )
252+
253+ def _remove_field_unique (self , model , field ):
254+ # Find the unique constraint for this field
255+ meta_constraint_names = {constraint .name for constraint in model ._meta .constraints }
256+ constraint_names = self ._constraint_names (
257+ model ,
258+ [field .column ],
259+ unique = True ,
260+ primary_key = False ,
261+ exclude = meta_constraint_names ,
262+ )
263+ if len (constraint_names ) != 1 :
264+ num_found = len (constraint_names )
265+ raise ValueError (
266+ f"Found wrong number ({ num_found } ) of unique constraints for "
267+ f"{ model ._meta .db_table } .{ field .column } ."
268+ )
269+ self .get_collection (model ._meta .db_table ).drop_index (constraint_names [0 ])
210270
211271 def alter_db_table (self , model , old_db_table , new_db_table ):
212272 if old_db_table == new_db_table :
213273 return
214274 self .get_collection (old_db_table ).rename (new_db_table )
275+
276+ def _field_should_have_unique (self , field ):
277+ db_type = field .db_type (self .connection )
278+ # The _id column is automatically unique.
279+ return db_type and field .unique and field .column != "_id"
0 commit comments