1
1
from django .db .backends .base .schema import BaseDatabaseSchemaEditor
2
- from django .db .models import Index
2
+ from django .db .models import Index , UniqueConstraint
3
3
from pymongo import ASCENDING , DESCENDING
4
4
from pymongo .operations import IndexModel
5
5
@@ -29,18 +29,23 @@ def create_model(self, model):
29
29
30
30
def _create_model_indexes (self , model ):
31
31
"""
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.
34
34
"""
35
35
if not model ._meta .managed or model ._meta .proxy or model ._meta .swapped :
36
36
return
37
- # Field indexes
37
+ # Field indexes and uniques
38
38
for field in model ._meta .local_fields :
39
39
if self ._field_should_be_indexed (model , field ):
40
40
self ._add_field_index (model , field )
41
+ elif self ._field_should_have_unique (field ):
42
+ self ._add_field_unique (model , field )
41
43
# Meta.index_together (RemovedInDjango51Warning)
42
44
for field_names in model ._meta .index_together :
43
45
self ._add_composed_index (model , field_names )
46
+ # Meta.constraints
47
+ for constraint in model ._meta .constraints :
48
+ self .add_constraint (model , constraint )
44
49
# Meta.indexes
45
50
for index in model ._meta .indexes :
46
51
self .add_index (model , index )
@@ -62,9 +67,11 @@ def add_field(self, model, field):
62
67
self .get_collection (model ._meta .db_table ).update_many (
63
68
{}, [{"$set" : {column : self .effective_default (field )}}]
64
69
)
65
- # Add an index, if required.
70
+ # Add an index or unique , if required.
66
71
if self ._field_should_be_indexed (model , field ):
67
72
self ._add_field_index (model , field )
73
+ elif self ._field_should_have_unique (field ):
74
+ self ._add_field_unique (model , field )
68
75
69
76
def _alter_field (
70
77
self ,
@@ -110,6 +117,8 @@ def remove_field(self, model, field):
110
117
self .get_collection (model ._meta .db_table ).update_many ({}, {"$unset" : {column : "" }})
111
118
if self ._field_should_be_indexed (model , field ):
112
119
self ._remove_field_index (model , field )
120
+ elif self ._field_should_have_unique (field ):
121
+ self ._remove_field_unique (model , field )
113
122
114
123
def alter_index_together (self , model , old_index_together , new_index_together ):
115
124
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):
124
133
def alter_unique_together (self , model , old_unique_together , new_unique_together ):
125
134
pass
126
135
127
- def add_index (self , model , index , field = None ):
136
+ def add_index (self , model , index , field = None , unique = False ):
128
137
if index .contains_expressions :
129
138
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 }
130
151
index_orders = (
131
152
[(field .column , ASCENDING )]
132
153
if field
@@ -137,7 +158,7 @@ def add_index(self, model, index, field=None):
137
158
for field_name , order in index .fields_orders
138
159
]
139
160
)
140
- idx = IndexModel (index_orders , name = index .name )
161
+ idx = IndexModel (index_orders , name = index .name , ** kwargs )
141
162
self .get_collection (model ._meta .db_table ).create_indexes ([idx ])
142
163
143
164
def _add_composed_index (self , model , field_names ):
@@ -202,13 +223,57 @@ def _remove_field_index(self, model, field):
202
223
)
203
224
collection .drop_index (index_names [0 ])
204
225
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 )
207
241
208
242
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 ])
210
270
211
271
def alter_db_table (self , model , old_db_table , new_db_table ):
212
272
if old_db_table == new_db_table :
213
273
return
214
274
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