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+ if field :
143+ filter_expression [field .column ] = {"$type" : field .db_type (self .connection )}
144+ else :
145+ for field_name , _ in index .fields_orders :
146+ field_ = model ._meta .get_field (field_name )
147+ filter_expression [field_ .column ] = {"$type" : field_ .db_type (self .connection )}
148+ # Use partialFilterExpression to allow multiple null values for a
149+ # unique constraint.
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,58 @@ 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+ constraint = UniqueConstraint (
239+ fields = [field .name ], name = f"{ model ._meta .db_table } _{ field .column } _key"
240+ )
241+ self .add_constraint (model , constraint , field = field )
207242
208243 def remove_constraint (self , model , constraint ):
209- pass
244+ if isinstance (constraint , UniqueConstraint ) and self ._unique_supported (
245+ condition = constraint .condition ,
246+ deferrable = constraint .deferrable ,
247+ include = constraint .include ,
248+ expressions = constraint .expressions ,
249+ nulls_distinct = constraint .nulls_distinct ,
250+ ):
251+ idx = Index (fields = constraint .fields , name = constraint .name )
252+ self .remove_index (model , idx )
253+
254+ def _remove_field_unique (self , model , field ):
255+ # Find the unique constraint for this field
256+ meta_constraint_names = {constraint .name for constraint in model ._meta .constraints }
257+ constraint_names = self ._constraint_names (
258+ model ,
259+ [field .column ],
260+ unique = True ,
261+ primary_key = False ,
262+ exclude = meta_constraint_names ,
263+ )
264+ if len (constraint_names ) != 1 :
265+ num_found = len (constraint_names )
266+ raise ValueError (
267+ f"Found wrong number ({ num_found } ) of unique constraints for "
268+ f"{ model ._meta .db_table } .{ field .column } ."
269+ )
270+ self .get_collection (model ._meta .db_table ).drop_index (constraint_names [0 ])
210271
211272 def alter_db_table (self , model , old_db_table , new_db_table ):
212273 if old_db_table == new_db_table :
213274 return
214275 self .get_collection (old_db_table ).rename (new_db_table )
276+
277+ def _field_should_have_unique (self , field ):
278+ db_type = field .db_type (self .connection )
279+ # The _id column is automatically unique.
280+ return db_type and field .unique and field .column != "_id"
0 commit comments