Skip to content

Commit fa35ee9

Browse files
committed
revert primary_key=True change and replace with inline_primary_key
Reverted the behavior of :meth:`.Operations.add_column` that would automatically render the "PRIMARY KEY" keyword inline when a :class:`.Column` with ``primary_key=True`` is added. The automatic behavior, added in version 1.18.2, is now opt-in via the new :paramref:`.Operations.add_column.inline_primary_key` parameter. This change restores the ability to render a PostgreSQL SERIAL column, which is required to be ``primary_key=True``, while not impacting the ability to render a separate primary key constraint. This also provides consistency with the :paramref:`.Operations.add_column.inline_references` parameter and gives users explicit control over SQL generation. Fixes: #1232 Change-Id: Ia9333c1768caa5d99b0527d59fcc797af2c3249c
1 parent 588bdac commit fa35ee9

11 files changed

Lines changed: 127 additions & 13 deletions

File tree

alembic/ddl/base.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,11 +156,13 @@ def __init__(
156156
schema: Optional[Union[quoted_name, str]] = None,
157157
if_not_exists: Optional[bool] = None,
158158
inline_references: Optional[bool] = None,
159+
inline_primary_key: Optional[bool] = None,
159160
) -> None:
160161
super().__init__(name, schema=schema)
161162
self.column = column
162163
self.if_not_exists = if_not_exists
163164
self.inline_references = inline_references
165+
self.inline_primary_key = inline_primary_key
164166

165167

166168
class DropColumn(AlterTable):
@@ -203,6 +205,7 @@ def visit_add_column(element: AddColumn, compiler: DDLCompiler, **kw) -> str:
203205
element.column,
204206
if_not_exists=element.if_not_exists,
205207
inline_references=element.inline_references,
208+
inline_primary_key=element.inline_primary_key,
206209
**kw,
207210
),
208211
)
@@ -355,14 +358,15 @@ def add_column(
355358
column: Column[Any],
356359
if_not_exists: Optional[bool] = None,
357360
inline_references: Optional[bool] = None,
361+
inline_primary_key: Optional[bool] = None,
358362
**kw,
359363
) -> str:
360364
text = "ADD COLUMN %s%s" % (
361365
"IF NOT EXISTS " if if_not_exists else "",
362366
compiler.get_column_specification(column, **kw),
363367
)
364368

365-
if column.primary_key:
369+
if inline_primary_key and column.primary_key:
366370
text += " PRIMARY KEY"
367371

368372
# Handle inline REFERENCES if requested

alembic/ddl/impl.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,7 @@ def add_column(
387387
schema: Optional[Union[str, quoted_name]] = None,
388388
if_not_exists: Optional[bool] = None,
389389
inline_references: Optional[bool] = None,
390+
inline_primary_key: Optional[bool] = None,
390391
) -> None:
391392
self._exec(
392393
base.AddColumn(
@@ -395,6 +396,7 @@ def add_column(
395396
schema=schema,
396397
if_not_exists=if_not_exists,
397398
inline_references=inline_references,
399+
inline_primary_key=inline_primary_key,
398400
)
399401
)
400402

alembic/op.pyi

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ def add_column(
6565
schema: Optional[str] = None,
6666
if_not_exists: Optional[bool] = None,
6767
inline_references: Optional[bool] = None,
68+
inline_primary_key: Optional[bool] = None,
6869
) -> None:
6970
"""Issue an "add column" instruction using the current
7071
migration context.
@@ -163,6 +164,13 @@ def add_column(
163164
164165
.. versionadded:: 1.18.2
165166
167+
:param inline_primary_key: If True, renders the PRIMARY KEY constraint
168+
inline within the ADD COLUMN directive, rather than rendering it
169+
separately. This is a purely syntactic option and should only be
170+
used for single-column primary keys.
171+
172+
.. versionadded:: 1.18.4
173+
166174
"""
167175

168176
def alter_column(

alembic/operations/base.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,7 @@ def add_column(
631631
schema: Optional[str] = None,
632632
if_not_exists: Optional[bool] = None,
633633
inline_references: Optional[bool] = None,
634+
inline_primary_key: Optional[bool] = None,
634635
) -> None:
635636
"""Issue an "add column" instruction using the current
636637
migration context.
@@ -729,6 +730,13 @@ def add_column(
729730
730731
.. versionadded:: 1.18.2
731732
733+
:param inline_primary_key: If True, renders the PRIMARY KEY constraint
734+
inline within the ADD COLUMN directive, rather than rendering it
735+
separately. This is a purely syntactic option and should only be
736+
used for single-column primary keys.
737+
738+
.. versionadded:: 1.18.4
739+
732740
""" # noqa: E501
733741
...
734742

@@ -1691,6 +1699,7 @@ def add_column(
16911699
insert_after: Optional[str] = None,
16921700
if_not_exists: Optional[bool] = None,
16931701
inline_references: Optional[bool] = None,
1702+
inline_primary_key: Optional[bool] = None,
16941703
) -> None:
16951704
"""Issue an "add column" instruction using the current
16961705
batch migration context.

alembic/operations/ops.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2054,12 +2054,14 @@ def __init__(
20542054
schema: Optional[str] = None,
20552055
if_not_exists: Optional[bool] = None,
20562056
inline_references: Optional[bool] = None,
2057+
inline_primary_key: Optional[bool] = None,
20572058
**kw: Any,
20582059
) -> None:
20592060
super().__init__(table_name, schema=schema)
20602061
self.column = column
20612062
self.if_not_exists = if_not_exists
20622063
self.inline_references = inline_references
2064+
self.inline_primary_key = inline_primary_key
20632065
self.kw = kw
20642066

20652067
def reverse(self) -> DropColumnOp:
@@ -2100,6 +2102,7 @@ def add_column(
21002102
schema: Optional[str] = None,
21012103
if_not_exists: Optional[bool] = None,
21022104
inline_references: Optional[bool] = None,
2105+
inline_primary_key: Optional[bool] = None,
21032106
) -> None:
21042107
"""Issue an "add column" instruction using the current
21052108
migration context.
@@ -2198,6 +2201,13 @@ def add_column(
21982201
21992202
.. versionadded:: 1.18.2
22002203
2204+
:param inline_primary_key: If True, renders the PRIMARY KEY constraint
2205+
inline within the ADD COLUMN directive, rather than rendering it
2206+
separately. This is a purely syntactic option and should only be
2207+
used for single-column primary keys.
2208+
2209+
.. versionadded:: 1.18.4
2210+
22012211
"""
22022212

22032213
op = cls(
@@ -2206,6 +2216,7 @@ def add_column(
22062216
schema=schema,
22072217
if_not_exists=if_not_exists,
22082218
inline_references=inline_references,
2219+
inline_primary_key=inline_primary_key,
22092220
)
22102221
return operations.invoke(op)
22112222

@@ -2219,6 +2230,7 @@ def batch_add_column(
22192230
insert_after: Optional[str] = None,
22202231
if_not_exists: Optional[bool] = None,
22212232
inline_references: Optional[bool] = None,
2233+
inline_primary_key: Optional[bool] = None,
22222234
) -> None:
22232235
"""Issue an "add column" instruction using the current
22242236
batch migration context.
@@ -2241,6 +2253,7 @@ def batch_add_column(
22412253
schema=operations.impl.schema,
22422254
if_not_exists=if_not_exists,
22432255
inline_references=inline_references,
2256+
inline_primary_key=inline_primary_key,
22442257
**kw,
22452258
)
22462259
return operations.invoke(op)

alembic/operations/toimpl.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ def add_column(operations: "Operations", operation: "ops.AddColumnOp") -> None:
173173
schema = operation.schema
174174
kw = operation.kw
175175
inline_references = operation.inline_references
176+
inline_primary_key = operation.inline_primary_key
176177

177178
if column.table is not None:
178179
column = _copy(column)
@@ -184,6 +185,7 @@ def add_column(operations: "Operations", operation: "ops.AddColumnOp") -> None:
184185
schema=schema,
185186
if_not_exists=operation.if_not_exists,
186187
inline_references=inline_references,
188+
inline_primary_key=inline_primary_key,
187189
**kw,
188190
)
189191

docs/build/changelog.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,13 @@ Changelog
6565
status with any existing primary key constraint or particular backend
6666
limitations on adding columns to the primary key.
6767

68+
.. note::
69+
70+
As of version 1.18.4, this behavior has been amended to be opt-in
71+
via the new ``inline_primary_key`` parameter to
72+
:meth:`.Operations.add_column`, rather than occurring automatically
73+
when ``primary_key=True`` is set on the :class:`.Column` object.
74+
6875
.. change::
6976
:tags: bug, typing
7077
:tickets: 1669
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
.. change::
2+
:tags: bug, operations
3+
:tickets: 1232
4+
5+
Reverted the behavior of :meth:`.Operations.add_column` that would
6+
automatically render the "PRIMARY KEY" keyword inline when a
7+
:class:`.Column` with ``primary_key=True`` is added. The automatic
8+
behavior, added in version 1.18.2, is now opt-in via the new
9+
:paramref:`.Operations.add_column.inline_primary_key` parameter. This
10+
change restores the ability to render a PostgreSQL SERIAL column, which is
11+
required to be ``primary_key=True``, while not impacting the ability to
12+
render a separate primary key constraint. This also provides consistency
13+
with the :paramref:`.Operations.add_column.inline_references` parameter and
14+
gives users explicit control over SQL generation.
15+
16+
To render PRIMARY KEY inline, use the
17+
:paramref:`.Operations.add_column.inline_primary_key` parameter set to
18+
``True``::
19+
20+
op.add_column(
21+
"my_table",
22+
Column("id", Integer, primary_key=True),
23+
inline_primary_key=True
24+
)

tests/test_batch.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -940,6 +940,7 @@ def test_add_col(self):
940940
schema=None,
941941
if_not_exists=None,
942942
inline_references=None,
943+
inline_primary_key=None,
943944
)
944945
in batch.impl.operations.impl.mock_calls
945946
)
@@ -1716,11 +1717,21 @@ def test_drop_pk_col_readd_col(self):
17161717
pk_const = inspect(self.conn).get_pk_constraint("foo")
17171718
eq_(pk_const["constrained_columns"], [])
17181719

1719-
def test_drop_pk_col_readd_pk_col(self):
1720+
@testing.variation(
1721+
"use_inline_pk",
1722+
[
1723+
True,
1724+
(False, exclusions.fails_on(["postgresql", "mysql", "mariadb"])),
1725+
],
1726+
)
1727+
def test_drop_pk_col_readd_pk_col(self, use_inline_pk):
17201728
# drop a column, add it back with primary_key=True, should remain
17211729
with self.op.batch_alter_table("foo") as batch_op:
17221730
batch_op.drop_column("id")
1723-
batch_op.add_column(Column("id", Integer, primary_key=True))
1731+
batch_op.add_column(
1732+
Column("id", Integer, primary_key=True),
1733+
inline_primary_key=bool(use_inline_pk),
1734+
)
17241735

17251736
pk_const = inspect(self.conn).get_pk_constraint("foo")
17261737
eq_(pk_const["constrained_columns"], ["id"])
@@ -2295,9 +2306,6 @@ class BatchRoundTripMySQLTest(BatchRoundTripTest):
22952306
def _datetime_server_default_fixture(self):
22962307
return func.current_timestamp()
22972308

2298-
def test_drop_pk_col_readd_pk_col(self):
2299-
super().test_drop_pk_col_readd_pk_col()
2300-
23012309
@exclusions.fails()
23022310
def test_drop_pk_col_readd_col_also_pk_const(self):
23032311
super().test_drop_pk_col_readd_col_also_pk_const()
@@ -2351,9 +2359,6 @@ def _native_boolean_fixture(self):
23512359
def _datetime_server_default_fixture(self):
23522360
return func.current_timestamp()
23532361

2354-
def test_drop_pk_col_readd_pk_col(self):
2355-
super().test_drop_pk_col_readd_pk_col()
2356-
23572362
@exclusions.fails()
23582363
def test_drop_pk_col_readd_col_also_pk_const(self):
23592364
super().test_drop_pk_col_readd_col_also_pk_const()

tests/test_op.py

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from sqlalchemy.sql.schema import quoted_name
2525

2626
from alembic import op
27+
from alembic import testing
2728
from alembic.operations import MigrateOperation
2829
from alembic.operations import Operations
2930
from alembic.operations import ops
@@ -71,18 +72,53 @@ def test_add_column_schema_hard_quoting(self):
7172
'ALTER TABLE "some.schema".somename ADD COLUMN colname VARCHAR'
7273
)
7374

74-
def test_add_column_primary_key(self):
75+
@testing.variation("use_inline", [True, False, "none"])
76+
def test_add_column_primary_key(self, use_inline):
77+
context = op_fixture("postgresql")
78+
79+
if use_inline.none:
80+
op.add_column(
81+
"somename",
82+
Column("colname", String, primary_key=True),
83+
)
84+
else:
85+
op.add_column(
86+
"somename",
87+
Column("colname", String, primary_key=True),
88+
inline_primary_key=bool(use_inline),
89+
)
90+
91+
if use_inline.use_inline:
92+
context.assert_(
93+
"ALTER TABLE somename ADD COLUMN colname "
94+
"VARCHAR NOT NULL PRIMARY KEY"
95+
)
96+
else:
97+
context.assert_(
98+
"ALTER TABLE somename ADD COLUMN colname " "VARCHAR NOT NULL"
99+
)
100+
101+
def test_add_column_primary_key_not_inline_by_default(self):
75102
context = op_fixture("postgresql")
76103
op.add_column(
77104
"somename",
78105
Column("colname", String, primary_key=True),
79106
)
80107

81108
context.assert_(
82-
"ALTER TABLE somename ADD COLUMN colname "
83-
"VARCHAR NOT NULL PRIMARY KEY"
109+
"ALTER TABLE somename ADD COLUMN colname VARCHAR NOT NULL"
84110
)
85111

112+
def test_add_column_inline_primary_key_no_pk_flag(self):
113+
context = op_fixture("postgresql")
114+
op.add_column(
115+
"somename",
116+
Column("colname", String),
117+
inline_primary_key=True,
118+
)
119+
120+
context.assert_("ALTER TABLE somename ADD COLUMN colname VARCHAR")
121+
86122
def test_rename_table_schema_hard_quoting(self):
87123
context = op_fixture("postgresql")
88124
op.rename_table(

0 commit comments

Comments
 (0)