Skip to content

Commit 4c42276

Browse files
committed
implement ColumnComment compiles for SQL Server
Implemented DDL for column comment add/update/delete when using the :paramref:`.Operations.alter_column.comment` parameter with :meth:`.Operations.alter_column` on Microsoft SQL Server. Previously, these functions were not implemented for SQL Server and would raise ``UnsupportedCompilationError``. Fixes: #1755 Change-Id: Ia4a0f6cd44c4c4b691383b91ced5a9d898d79e46
1 parent 13c9b71 commit 4c42276

File tree

4 files changed

+220
-0
lines changed

4 files changed

+220
-0
lines changed

alembic/ddl/mssql.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from .base import AddColumn
2121
from .base import alter_column
2222
from .base import alter_table
23+
from .base import ColumnComment
2324
from .base import ColumnDefault
2425
from .base import ColumnName
2526
from .base import ColumnNullable
@@ -433,3 +434,89 @@ def visit_rename_table(
433434
format_table_name(compiler, element.table_name, element.schema),
434435
format_table_name(compiler, element.new_table_name, None),
435436
)
437+
438+
439+
def _add_column_comment(
440+
compiler: MSDDLCompiler,
441+
schema: Optional[str],
442+
tname: str,
443+
cname: str,
444+
comment: str,
445+
) -> str:
446+
schema_name = schema if schema else compiler.dialect.default_schema_name
447+
assert schema_name
448+
return (
449+
"exec sp_addextendedproperty 'MS_Description', {}, "
450+
"'schema', {}, 'table', {}, 'column', {}".format(
451+
compiler.sql_compiler.render_literal_value(
452+
comment, sqltypes.NVARCHAR()
453+
),
454+
compiler.preparer.quote_schema(schema_name),
455+
compiler.preparer.quote(tname),
456+
compiler.preparer.quote(cname),
457+
)
458+
)
459+
460+
461+
def _update_column_comment(
462+
compiler: MSDDLCompiler,
463+
schema: Optional[str],
464+
tname: str,
465+
cname: str,
466+
comment: str,
467+
) -> str:
468+
schema_name = schema if schema else compiler.dialect.default_schema_name
469+
assert schema_name
470+
return (
471+
"exec sp_updateextendedproperty 'MS_Description', {}, "
472+
"'schema', {}, 'table', {}, 'column', {}".format(
473+
compiler.sql_compiler.render_literal_value(
474+
comment, sqltypes.NVARCHAR()
475+
),
476+
compiler.preparer.quote_schema(schema_name),
477+
compiler.preparer.quote(tname),
478+
compiler.preparer.quote(cname),
479+
)
480+
)
481+
482+
483+
def _drop_column_comment(
484+
compiler: MSDDLCompiler, schema: Optional[str], tname: str, cname: str
485+
) -> str:
486+
schema_name = schema if schema else compiler.dialect.default_schema_name
487+
assert schema_name
488+
return (
489+
"exec sp_dropextendedproperty 'MS_Description', "
490+
"'schema', {}, 'table', {}, 'column', {}".format(
491+
compiler.preparer.quote_schema(schema_name),
492+
compiler.preparer.quote(tname),
493+
compiler.preparer.quote(cname),
494+
)
495+
)
496+
497+
498+
@compiles(ColumnComment, "mssql")
499+
def visit_column_comment(
500+
element: ColumnComment, compiler: MSDDLCompiler, **kw: Any
501+
) -> str:
502+
if element.comment is not None:
503+
if element.existing_comment is not None:
504+
return _update_column_comment(
505+
compiler,
506+
element.schema,
507+
element.table_name,
508+
element.column_name,
509+
element.comment,
510+
)
511+
else:
512+
return _add_column_comment(
513+
compiler,
514+
element.schema,
515+
element.table_name,
516+
element.column_name,
517+
element.comment,
518+
)
519+
else:
520+
return _drop_column_comment(
521+
compiler, element.schema, element.table_name, element.column_name
522+
)

alembic/testing/assertions.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ def _get_dialect(name):
132132
d.implicit_returning = True
133133
elif name == "mssql":
134134
d.legacy_schema_aliasing = False
135+
d.default_schema_name = "dbo"
135136
return d
136137

137138

docs/build/unreleased/1755.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.. change::
2+
:tags: bug, mssql
3+
:tickets: 1755
4+
5+
Implemented DDL for column comment add/update/delete when using the
6+
:paramref:`.Operations.alter_column.comment` parameter with
7+
:meth:`.Operations.alter_column` on Microsoft SQL Server. Previously,
8+
these functions were not implemented for SQL Server and would raise
9+
``UnsupportedCompilationError``.

tests/test_mssql.py

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,71 @@ def test_alter_column_identity_add_not_supported(self, sd, esd):
533533
existing_server_default=esd(),
534534
)
535535

536+
def test_alter_column_add_comment(self):
537+
context = op_fixture("mssql")
538+
op.alter_column(
539+
"t1",
540+
"c1",
541+
existing_type=String(50),
542+
comment="c1 comment",
543+
)
544+
context.assert_contains(
545+
"exec sp_addextendedproperty 'MS_Description', "
546+
"N'c1 comment', 'schema', dbo, 'table', t1, 'column', c1"
547+
)
548+
549+
def test_alter_column_update_comment(self):
550+
context = op_fixture("mssql")
551+
op.alter_column(
552+
"t1",
553+
"c1",
554+
existing_type=String(50),
555+
comment="updated comment",
556+
existing_comment="old comment",
557+
)
558+
context.assert_contains(
559+
"exec sp_updateextendedproperty 'MS_Description', "
560+
"N'updated comment', 'schema', dbo, 'table', t1, 'column', c1"
561+
)
562+
563+
def test_alter_column_add_comment_schema(self):
564+
context = op_fixture("mssql")
565+
op.alter_column(
566+
"t1",
567+
"c1",
568+
existing_type=String(50),
569+
comment="c1 comment",
570+
schema="xyz",
571+
)
572+
context.assert_contains("'schema', xyz, 'table', t1, 'column', c1")
573+
574+
def test_alter_column_add_comment_quoting(self):
575+
context = op_fixture("mssql")
576+
op.alter_column(
577+
"user",
578+
"theme",
579+
existing_type=String(20),
580+
comment="Column comment with 'quotes'",
581+
existing_nullable=True,
582+
schema="dbo",
583+
)
584+
context.assert_contains("N'Column comment with ''quotes'''")
585+
context.assert_contains("'table', [user], 'column', theme")
586+
587+
def test_alter_column_drop_comment(self):
588+
context = op_fixture("mssql")
589+
op.alter_column(
590+
"t1",
591+
"c1",
592+
existing_type=String(50),
593+
comment=None,
594+
existing_comment="existing comment",
595+
)
596+
context.assert_contains(
597+
"exec sp_dropextendedproperty 'MS_Description', "
598+
"'schema', dbo, 'table', t1, 'column', c1"
599+
)
600+
536601

537602
class RoundTripTest(TestBase):
538603
__backend__ = True
@@ -605,3 +670,61 @@ def test_issue_1744(self, ops_context, connection, metadata, op):
605670
ops_context.drop_column(
606671
"access", "created_at", mssql_drop_default=True
607672
)
673+
674+
@testing.variation("op", ["add", "update", "drop"])
675+
def test_column_comment(
676+
self, ops_context, connection, metadata, op: testing.Variation
677+
):
678+
"""test #1755"""
679+
t = Table(
680+
"t",
681+
metadata,
682+
Column("id", Integer, primary_key=True),
683+
Column("data", String(50)),
684+
)
685+
t.create(connection)
686+
687+
if op.update or op.drop:
688+
ops_context.alter_column(
689+
"t",
690+
"data",
691+
existing_type=String(50),
692+
comment="initial comment",
693+
)
694+
695+
if op.add:
696+
ops_context.alter_column(
697+
"t", "data", existing_type=String(50), comment="data comment"
698+
)
699+
expected = "data comment"
700+
elif op.update:
701+
ops_context.alter_column(
702+
"t",
703+
"data",
704+
existing_type=String(50),
705+
comment="updated comment",
706+
existing_comment="initial comment",
707+
)
708+
expected = "updated comment"
709+
elif op.drop:
710+
ops_context.alter_column(
711+
"t",
712+
"data",
713+
existing_type=String(50),
714+
comment=None,
715+
existing_comment="initial comment",
716+
)
717+
expected = None
718+
else:
719+
op.fail()
720+
721+
# Verify expected result
722+
result = connection.execute(
723+
text(
724+
"SELECT value FROM sys.extended_properties "
725+
"WHERE major_id = OBJECT_ID('t') "
726+
"AND minor_id = COLUMNPROPERTY(major_id, 'data', 'ColumnId') "
727+
"AND name = 'MS_Description'"
728+
)
729+
).scalar()
730+
eq_(result, expected)

0 commit comments

Comments
 (0)