@@ -93,7 +93,40 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
9393 sql_rename_table = "EXEC sp_rename %(old_table)s, %(new_table)s"
9494 sql_create_unique_null = "CREATE UNIQUE INDEX %(name)s ON %(table)s(%(columns)s) " \
9595 "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+ """
97130 _deferred_unique_indexes = defaultdict (list )
98131
99132 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):
138171 },
139172 params ,
140173 )
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+
142186 def _alter_column_null_sql (self , model , old_field , new_field ):
143187 """
144188 Hook to specialize column null alteration.
@@ -316,7 +360,19 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type,
316360
317361 # Drop any FK constraints, we'll remake them later
318362 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+ ):
320376 # Drop index, SQL Server requires explicit deletion
321377 if not hasattr (new_field , 'db_constraint' ) or not new_field .db_constraint :
322378 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,
446502 actions = []
447503 null_actions = []
448504 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+ ):
451510 if django_version >= (4 , 2 ):
452511 fragment , other_actions = self ._alter_column_type_sql (
453512 model , old_field , new_field , new_type , old_collation = None , new_collation = None
@@ -922,6 +981,19 @@ def add_field(self, model, field):
922981 "changes" : changes_sql ,
923982 }
924983 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+ )
925997 # Add an index, if required
926998 self .deferred_sql .extend (self ._field_indexes_sql (model , field ))
927999 # Add any FK constraints later
@@ -1129,6 +1201,23 @@ def create_model(self, model):
11291201 # Prevent using [] as params, in the case a literal '%' is used in the definition
11301202 self .execute (sql , params or None )
11311203
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+ )
11321221 # Add any field index and index_together's (deferred as SQLite3 _remake_table needs it)
11331222 self .deferred_sql .extend (self ._model_indexes_sql (model ))
11341223 self .deferred_sql = list (set (self .deferred_sql ))
0 commit comments