@@ -93,7 +93,40 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
93
93
sql_rename_table = "EXEC sp_rename %(old_table)s, %(new_table)s"
94
94
sql_create_unique_null = "CREATE UNIQUE INDEX %(name)s ON %(table)s(%(columns)s) " \
95
95
"WHERE %(columns)s IS NOT NULL"
96
-
96
+ sql_alter_table_comment = """
97
+ IF NOT EXISTS (SELECT NULL FROM sys.extended_properties ep
98
+ WHERE ep.major_id = OBJECT_ID('%(table)s')
99
+ AND ep.name = 'MS_Description'
100
+ AND ep.minor_id = 0)
101
+ EXECUTE sp_addextendedproperty
102
+ @name = 'MS_Description', @value = %(comment)s,
103
+ @level0type = 'SCHEMA', @level0name = 'dbo',
104
+ @level1type = 'TABLE', @level1name = %(table)s
105
+ ELSE
106
+ EXECUTE sp_updateextendedproperty
107
+ @name = 'MS_Description', @value = %(comment)s,
108
+ @level0type = 'SCHEMA', @level0name = 'dbo',
109
+ @level1type = 'TABLE', @level1name = %(table)s
110
+ """
111
+ sql_alter_column_comment = """
112
+ IF NOT EXISTS (SELECT NULL FROM sys.extended_properties ep
113
+ WHERE ep.major_id = OBJECT_ID('%(table)s')
114
+ AND ep.name = 'MS_Description'
115
+ AND ep.minor_id = (SELECT column_id FROM sys.columns
116
+ WHERE name = '%(column)s'
117
+ AND object_id = OBJECT_ID('%(table)s')))
118
+ EXECUTE sp_addextendedproperty
119
+ @name = 'MS_Description', @value = %(comment)s,
120
+ @level0type = 'SCHEMA', @level0name = 'dbo',
121
+ @level1type = 'TABLE', @level1name = %(table)s,
122
+ @level2type = 'COLUMN', @level2name = %(column)s
123
+ ELSE
124
+ EXECUTE sp_updateextendedproperty
125
+ @name = 'MS_Description', @value = %(comment)s,
126
+ @level0type = 'SCHEMA', @level0name = 'dbo',
127
+ @level1type = 'TABLE', @level1name = %(table)s,
128
+ @level2type = 'COLUMN', @level2name = %(column)s
129
+ """
97
130
_deferred_unique_indexes = defaultdict (list )
98
131
99
132
def _alter_column_default_sql (self , model , old_field , new_field , drop = False ):
@@ -138,7 +171,18 @@ def _alter_column_default_sql(self, model, old_field, new_field, drop=False):
138
171
},
139
172
params ,
140
173
)
141
-
174
+
175
+ def _alter_column_comment_sql (self , model , new_field , new_type , new_db_comment ):
176
+ return (
177
+ self .sql_alter_column_comment
178
+ % {
179
+ "table" : self .quote_name (model ._meta .db_table ),
180
+ "column" : new_field .column ,
181
+ "comment" : self ._comment_sql (new_db_comment ),
182
+ },
183
+ [],
184
+ )
185
+
142
186
def _alter_column_null_sql (self , model , old_field , new_field ):
143
187
"""
144
188
Hook to specialize column null alteration.
@@ -316,7 +360,19 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type,
316
360
317
361
# Drop any FK constraints, we'll remake them later
318
362
fks_dropped = set ()
319
- if old_field .remote_field and old_field .db_constraint :
363
+ if (
364
+ old_field .remote_field
365
+ and old_field .db_constraint
366
+ and (django_version < (4 ,2 )
367
+ or
368
+ (django_version >= (4 , 2 )
369
+ and self ._field_should_be_altered (
370
+ old_field ,
371
+ new_field ,
372
+ ignore = {"db_comment" })
373
+ )
374
+ )
375
+ ):
320
376
# Drop index, SQL Server requires explicit deletion
321
377
if not hasattr (new_field , 'db_constraint' ) or not new_field .db_constraint :
322
378
index_names = self ._constraint_names (model , [old_field .column ], index = True )
@@ -446,8 +502,11 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type,
446
502
actions = []
447
503
null_actions = []
448
504
post_actions = []
449
- # Type change?
450
- if old_type != new_type :
505
+ # Type or comment change?
506
+ if old_type != new_type or (django_version >= (4 , 2 ) and
507
+ self .connection .features .supports_comments
508
+ and old_field .db_comment != new_field .db_comment
509
+ ):
451
510
if django_version >= (4 , 2 ):
452
511
fragment , other_actions = self ._alter_column_type_sql (
453
512
model , old_field , new_field , new_type , old_collation = None , new_collation = None
@@ -922,6 +981,19 @@ def add_field(self, model, field):
922
981
"changes" : changes_sql ,
923
982
}
924
983
self .execute (sql , params )
984
+ # Add field comment, if required.
985
+ if django_version >= (4 , 2 ):
986
+ if (
987
+ field .db_comment
988
+ and self .connection .features .supports_comments
989
+ and not self .connection .features .supports_comments_inline
990
+ ):
991
+ field_type = db_params ["type" ]
992
+ self .execute (
993
+ * self ._alter_column_comment_sql (
994
+ model , field , field_type , field .db_comment
995
+ )
996
+ )
925
997
# Add an index, if required
926
998
self .deferred_sql .extend (self ._field_indexes_sql (model , field ))
927
999
# Add any FK constraints later
@@ -1129,6 +1201,23 @@ def create_model(self, model):
1129
1201
# Prevent using [] as params, in the case a literal '%' is used in the definition
1130
1202
self .execute (sql , params or None )
1131
1203
1204
+ if django_version >= (4 , 2 ) and self .connection .features .supports_comments :
1205
+ # Add table comment.
1206
+ if model ._meta .db_table_comment :
1207
+ self .alter_db_table_comment (model , None , model ._meta .db_table_comment )
1208
+ # Add column comments.
1209
+ if not self .connection .features .supports_comments_inline :
1210
+ for field in model ._meta .local_fields :
1211
+ if field .db_comment :
1212
+ field_db_params = field .db_parameters (
1213
+ connection = self .connection
1214
+ )
1215
+ field_type = field_db_params ["type" ]
1216
+ self .execute (
1217
+ * self ._alter_column_comment_sql (
1218
+ model , field , field_type , field .db_comment
1219
+ )
1220
+ )
1132
1221
# Add any field index and index_together's (deferred as SQLite3 _remake_table needs it)
1133
1222
self .deferred_sql .extend (self ._model_indexes_sql (model ))
1134
1223
self .deferred_sql = list (set (self .deferred_sql ))
0 commit comments