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 .operations import IndexModel
4
4
5
5
from .query import wrap_database_errors
@@ -17,18 +17,23 @@ def create_model(self, model):
17
17
18
18
def _create_model_indexes (self , model ):
19
19
"""
20
- Create all indexes (field indexes, index_together, Meta.indexes) for
21
- the specified model.
20
+ Create all indexes (field indexes & uniques , index_together,
21
+ Meta.constraints, Meta.indexes) for the specified model.
22
22
"""
23
23
if not model ._meta .managed or model ._meta .proxy or model ._meta .swapped :
24
24
return
25
- # Field indexes
25
+ # Field indexes and uniques
26
26
for field in model ._meta .local_fields :
27
27
if self ._field_should_be_indexed (model , field ):
28
28
self ._add_field_index (model , field )
29
+ elif self ._field_should_have_unique (field ):
30
+ self ._add_field_unique (model , field )
29
31
# Meta.index_together (RemovedInDjango51Warning)
30
32
for field_names in model ._meta .index_together :
31
33
self ._add_composed_index (model , field_names )
34
+ # Meta.constraints
35
+ for constraint in model ._meta .constraints :
36
+ self .add_constraint (model , constraint )
32
37
# Meta.indexes
33
38
for index in model ._meta .indexes :
34
39
self .add_index (model , index )
@@ -50,9 +55,11 @@ def add_field(self, model, field):
50
55
self .connection .database [model ._meta .db_table ].update_many (
51
56
{}, [{"$set" : {column : self .effective_default (field )}}]
52
57
)
53
- # Add an index, if required.
58
+ # Add an index or unique , if required.
54
59
if self ._field_should_be_indexed (model , field ):
55
60
self ._add_field_index (model , field )
61
+ elif self ._field_should_have_unique (field ):
62
+ self ._add_field_unique (model , field )
56
63
57
64
def _alter_field (
58
65
self ,
@@ -102,6 +109,8 @@ def remove_field(self, model, field):
102
109
self .connection .database [model ._meta .db_table ].update_many ({}, {"$unset" : {column : "" }})
103
110
if self ._field_should_be_indexed (model , field ):
104
111
self ._remove_field_index (model , field )
112
+ elif self ._field_should_have_unique (field ):
113
+ self ._remove_field_unique (model , field )
105
114
106
115
def alter_index_together (self , model , old_index_together , new_index_together ):
107
116
olds = {tuple (fields ) for fields in old_index_together }
@@ -116,9 +125,19 @@ def alter_index_together(self, model, old_index_together, new_index_together):
116
125
def alter_unique_together (self , model , old_unique_together , new_unique_together ):
117
126
pass
118
127
119
- def add_index (self , model , index , field = None ):
128
+ def add_index (self , model , index , field = None , unique = False ):
120
129
if index .contains_expressions :
121
130
return
131
+ kwargs = {}
132
+ if unique :
133
+ filter_expression = {}
134
+ if field :
135
+ filter_expression [field .column ] = {"$type" : field .db_type (self .connection )}
136
+ else :
137
+ for field_name , _ in index .fields_orders :
138
+ field_ = model ._meta .get_field (field_name )
139
+ filter_expression [field_ .column ] = {"$type" : field_ .db_type (self .connection )}
140
+ kwargs = {"partialFilterExpression" : filter_expression , "unique" : True }
122
141
index_orders = (
123
142
[(field .column , 1 )]
124
143
if field
@@ -130,6 +149,7 @@ def add_index(self, model, index, field=None):
130
149
idx = IndexModel (
131
150
index_orders ,
132
151
name = index .name ,
152
+ ** kwargs ,
133
153
)
134
154
self .connection .database [model ._meta .db_table ].create_indexes ([idx ])
135
155
@@ -196,13 +216,56 @@ def _remove_field_index(self, model, field):
196
216
# is to look at its name (refs #28053).
197
217
collection .drop_index (index_name )
198
218
199
- def add_constraint (self , model , constraint ):
200
- pass
219
+ def add_constraint (self , model , constraint , field = None ):
220
+ if isinstance (constraint , UniqueConstraint ) and self ._unique_supported (
221
+ condition = constraint .condition ,
222
+ deferrable = constraint .deferrable ,
223
+ include = constraint .include ,
224
+ expressions = constraint .expressions ,
225
+ nulls_distinct = constraint .nulls_distinct ,
226
+ ):
227
+ idx = Index (fields = constraint .fields , name = constraint .name )
228
+ self .add_index (model , idx , field = field , unique = True )
229
+
230
+ def _add_field_unique (self , model , field ):
231
+ constraint = UniqueConstraint (
232
+ fields = [field .name ], name = f"{ model ._meta .db_table } _{ field .column } _key"
233
+ )
234
+ self .add_constraint (model , constraint , field = field )
201
235
202
236
def remove_constraint (self , model , constraint ):
203
- pass
237
+ if isinstance (constraint , UniqueConstraint ) and self ._unique_supported (
238
+ condition = constraint .condition ,
239
+ deferrable = constraint .deferrable ,
240
+ include = constraint .include ,
241
+ expressions = constraint .expressions ,
242
+ nulls_distinct = constraint .nulls_distinct ,
243
+ ):
244
+ idx = Index (fields = constraint .fields , name = constraint .name )
245
+ self .remove_index (model , idx )
246
+
247
+ def _remove_field_unique (self , model , field , strict = True ):
248
+ # Find the unique constraint for this field
249
+ meta_constraint_names = {constraint .name for constraint in model ._meta .constraints }
250
+ constraint_names = self ._constraint_names (
251
+ model ,
252
+ [field .column ],
253
+ unique = True ,
254
+ primary_key = False ,
255
+ exclude = meta_constraint_names ,
256
+ )
257
+ if strict and len (constraint_names ) != 1 :
258
+ raise ValueError (
259
+ f"Found wrong number ({ len (constraint_names )} ) of unique "
260
+ f"constraints for { model ._meta .db_table } .{ field .column } "
261
+ )
262
+ for constraint_name in constraint_names :
263
+ self .connection .database [model ._meta .db_table ].drop_index (constraint_name )
204
264
205
265
def alter_db_table (self , model , old_db_table , new_db_table ):
206
266
if old_db_table == new_db_table :
207
267
return
208
268
self .connection .database [old_db_table ].rename (new_db_table )
269
+
270
+ def _field_should_have_unique (self , field ):
271
+ return field .unique and field .column != "_id"
0 commit comments