Skip to content

Commit dcba644

Browse files
committed
run drop default constraint ahead of type
Fixed issue in SQL Server dialect where the DROP that's automatically emitted for existing default constraints during an ALTER COLUMN needs to take place before not just the modification of the column's default, but also before the column's type is changed. also vendor sqlalchemy's metadata fixture which otherwise does not integrate with alembic's version of the connection fixture Fixes: #1744 Change-Id: Ia756da024297354449f53e9f3254820dec542bef
1 parent 3edfb0f commit dcba644

File tree

4 files changed

+91
-9
lines changed

4 files changed

+91
-9
lines changed

alembic/ddl/mssql.py

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,27 @@ def alter_column(
140140
kw["server_default"] = server_default
141141
kw["existing_server_default"] = existing_server_default
142142

143+
# drop existing default constraints before changing type
144+
# or default, see issue #1744
145+
if (
146+
server_default is not False
147+
and used_default is False
148+
and (
149+
existing_server_default is not False or server_default is None
150+
)
151+
):
152+
self._exec(
153+
_ExecDropConstraint(
154+
table_name,
155+
column_name,
156+
"sys.default_constraints",
157+
schema,
158+
)
159+
)
160+
161+
# TODO: see why these two alter_columns can't be called
162+
# at once. joining them works but some of the mssql tests
163+
# seem to expect something different
143164
super().alter_column(
144165
table_name,
145166
column_name,
@@ -152,15 +173,6 @@ def alter_column(
152173
)
153174

154175
if server_default is not False and used_default is False:
155-
if existing_server_default is not False or server_default is None:
156-
self._exec(
157-
_ExecDropConstraint(
158-
table_name,
159-
column_name,
160-
"sys.default_constraints",
161-
schema,
162-
)
163-
)
164176
if server_default is not None:
165177
super().alter_column(
166178
table_name,

alembic/testing/fixtures.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from sqlalchemy.testing.fixtures import FutureEngineMixin
2727
from sqlalchemy.testing.fixtures import TablesTest as SQLAlchemyTablesTest
2828
from sqlalchemy.testing.fixtures import TestBase as SQLAlchemyTestBase
29+
from sqlalchemy.testing.util import drop_all_tables_from_metadata
2930

3031
import alembic
3132
from .assertions import _get_dialect
@@ -87,9 +88,41 @@ def as_sql_migration_context(self, connection):
8788

8889
@testing.fixture
8990
def connection(self):
91+
global _connection_fixture_connection
92+
9093
with config.db.connect() as conn:
94+
_connection_fixture_connection = conn
9195
yield conn
9296

97+
_connection_fixture_connection = None
98+
99+
@config.fixture()
100+
def metadata(self, request):
101+
"""Provide bound MetaData for a single test, dropping afterwards."""
102+
103+
from sqlalchemy.sql import schema
104+
105+
metadata = schema.MetaData()
106+
request.instance.metadata = metadata
107+
yield metadata
108+
del request.instance.metadata
109+
110+
if (
111+
_connection_fixture_connection
112+
and _connection_fixture_connection.in_transaction()
113+
):
114+
trans = _connection_fixture_connection.get_transaction()
115+
trans.rollback()
116+
with _connection_fixture_connection.begin():
117+
drop_all_tables_from_metadata(
118+
metadata, _connection_fixture_connection
119+
)
120+
else:
121+
drop_all_tables_from_metadata(metadata, config.db)
122+
123+
124+
_connection_fixture_connection = None
125+
93126

94127
class TablesTest(TestBase, SQLAlchemyTablesTest):
95128
pass

docs/build/unreleased/1744.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.. change::
2+
:tags: bug, mssql
3+
:tickets: 1744
4+
5+
Fixed issue in SQL Server dialect where the DROP that's automatically
6+
emitted for existing default constraints during an ALTER COLUMN needs to
7+
take place before not just the modification of the column's default, but
8+
also before the column's type is changed.

tests/test_mssql.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@
88
from sqlalchemy import CheckConstraint
99
from sqlalchemy import Column
1010
from sqlalchemy import Computed
11+
from sqlalchemy import DATE
12+
from sqlalchemy import DATETIME
1113
from sqlalchemy import exc
1214
from sqlalchemy import ForeignKey
15+
from sqlalchemy import func
1316
from sqlalchemy import Identity
1417
from sqlalchemy import inspect
1518
from sqlalchemy import Integer
@@ -20,6 +23,7 @@
2023

2124
from alembic import command
2225
from alembic import op
26+
from alembic import testing
2327
from alembic import util
2428
from alembic.testing import assert_raises_message
2529
from alembic.testing import combinations
@@ -576,3 +580,28 @@ def test_drop_col_with_check(self, ops_context, connection, tables):
576580

577581
# don't know if a default constraint can be explicitly named, but
578582
# the path is the same as the check constraint, so it should be good
583+
584+
@testing.variation("op", ["drop", "alter"])
585+
def test_issue_1744(self, ops_context, connection, metadata, op):
586+
access = Table(
587+
"access",
588+
metadata,
589+
Column("id", Integer, primary_key=True),
590+
Column("created_at", DATE, server_default=func.getdate()),
591+
)
592+
access.create(connection)
593+
594+
if op.alter:
595+
ops_context.alter_column(
596+
"access",
597+
"created_at",
598+
existing_type=DATETIME(),
599+
type_=DATETIME(timezone=True),
600+
server_default=func.getdate(),
601+
existing_nullable=False,
602+
existing_server_default=text("(getdate())"),
603+
)
604+
elif op.drop:
605+
ops_context.drop_column(
606+
"access", "created_at", mssql_drop_default=True
607+
)

0 commit comments

Comments
 (0)