Skip to content

Commit 8fae3e1

Browse files
agriffin-growzzzeek
authored andcommitted
Support if_exists and if_not_exists on create/drop table commands
Added support for :paramref:`.Operations.create_table.if_not_exists` and :paramref:`.Operations.drop_table.if_exists`, adding similar functionality to render IF [NOT] EXISTS for table operations in a similar way as with indexes. Pull request courtesy Aaron Griffin. Fixes: #1520 Closes: #1521 Pull-request: #1521 Pull-request-sha: 469be01 Change-Id: I5dcf44d9e906cdb84c32c4bfb6a1c63cde6324fd
1 parent 9d6e212 commit 8fae3e1

File tree

7 files changed

+101
-12
lines changed

7 files changed

+101
-12
lines changed

alembic/ddl/impl.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -362,11 +362,11 @@ def rename_table(
362362
base.RenameTable(old_table_name, new_table_name, schema=schema)
363363
)
364364

365-
def create_table(self, table: Table) -> None:
365+
def create_table(self, table: Table, **kw: Any) -> None:
366366
table.dispatch.before_create(
367367
table, self.connection, checkfirst=False, _ddl_runner=self
368368
)
369-
self._exec(schema.CreateTable(table))
369+
self._exec(schema.CreateTable(table, **kw))
370370
table.dispatch.after_create(
371371
table, self.connection, checkfirst=False, _ddl_runner=self
372372
)
@@ -385,11 +385,11 @@ def create_table(self, table: Table) -> None:
385385
if comment and with_comment:
386386
self.create_column_comment(column)
387387

388-
def drop_table(self, table: Table) -> None:
388+
def drop_table(self, table: Table, **kw: Any) -> None:
389389
table.dispatch.before_drop(
390390
table, self.connection, checkfirst=False, _ddl_runner=self
391391
)
392-
self._exec(schema.DropTable(table))
392+
self._exec(schema.DropTable(table, **kw))
393393
table.dispatch.after_drop(
394394
table, self.connection, checkfirst=False, _ddl_runner=self
395395
)

alembic/op.pyi

+19-2
Original file line numberDiff line numberDiff line change
@@ -747,7 +747,12 @@ def create_primary_key(
747747
748748
"""
749749

750-
def create_table(table_name: str, *columns: SchemaItem, **kw: Any) -> Table:
750+
def create_table(
751+
table_name: str,
752+
*columns: SchemaItem,
753+
if_not_exists: Optional[bool] = None,
754+
**kw: Any,
755+
) -> Table:
751756
r"""Issue a "create table" instruction using the current migration
752757
context.
753758
@@ -818,6 +823,10 @@ def create_table(table_name: str, *columns: SchemaItem, **kw: Any) -> Table:
818823
quoting of the schema outside of the default behavior, use
819824
the SQLAlchemy construct
820825
:class:`~sqlalchemy.sql.elements.quoted_name`.
826+
:param if_not_exists: If True, adds IF NOT EXISTS operator when
827+
creating the new table.
828+
829+
.. versionadded:: 1.13.3
821830
:param \**kw: Other keyword arguments are passed to the underlying
822831
:class:`sqlalchemy.schema.Table` object created for the command.
823832
@@ -998,7 +1007,11 @@ def drop_index(
9981007
"""
9991008

10001009
def drop_table(
1001-
table_name: str, *, schema: Optional[str] = None, **kw: Any
1010+
table_name: str,
1011+
*,
1012+
schema: Optional[str] = None,
1013+
if_exists: Optional[bool] = None,
1014+
**kw: Any,
10021015
) -> None:
10031016
r"""Issue a "drop table" instruction using the current
10041017
migration context.
@@ -1013,6 +1026,10 @@ def drop_table(
10131026
quoting of the schema outside of the default behavior, use
10141027
the SQLAlchemy construct
10151028
:class:`~sqlalchemy.sql.elements.quoted_name`.
1029+
:param if_exists: If True, adds IF EXISTS operator when
1030+
dropping the table.
1031+
1032+
.. versionadded:: 1.13.3
10161033
:param \**kw: Other keyword arguments are passed to the underlying
10171034
:class:`sqlalchemy.schema.Table` object created for the command.
10181035

alembic/operations/base.py

+19-2
Original file line numberDiff line numberDiff line change
@@ -1175,7 +1175,11 @@ def create_primary_key(
11751175
...
11761176

11771177
def create_table(
1178-
self, table_name: str, *columns: SchemaItem, **kw: Any
1178+
self,
1179+
table_name: str,
1180+
*columns: SchemaItem,
1181+
if_not_exists: Optional[bool] = None,
1182+
**kw: Any,
11791183
) -> Table:
11801184
r"""Issue a "create table" instruction using the current migration
11811185
context.
@@ -1247,6 +1251,10 @@ def create_table(
12471251
quoting of the schema outside of the default behavior, use
12481252
the SQLAlchemy construct
12491253
:class:`~sqlalchemy.sql.elements.quoted_name`.
1254+
:param if_not_exists: If True, adds IF NOT EXISTS operator when
1255+
creating the new table.
1256+
1257+
.. versionadded:: 1.13.3
12501258
:param \**kw: Other keyword arguments are passed to the underlying
12511259
:class:`sqlalchemy.schema.Table` object created for the command.
12521260
@@ -1438,7 +1446,12 @@ def drop_index(
14381446
...
14391447

14401448
def drop_table(
1441-
self, table_name: str, *, schema: Optional[str] = None, **kw: Any
1449+
self,
1450+
table_name: str,
1451+
*,
1452+
schema: Optional[str] = None,
1453+
if_exists: Optional[bool] = None,
1454+
**kw: Any,
14421455
) -> None:
14431456
r"""Issue a "drop table" instruction using the current
14441457
migration context.
@@ -1453,6 +1466,10 @@ def drop_table(
14531466
quoting of the schema outside of the default behavior, use
14541467
the SQLAlchemy construct
14551468
:class:`~sqlalchemy.sql.elements.quoted_name`.
1469+
:param if_exists: If True, adds IF EXISTS operator when
1470+
dropping the table.
1471+
1472+
.. versionadded:: 1.13.3
14561473
:param \**kw: Other keyword arguments are passed to the underlying
14571474
:class:`sqlalchemy.schema.Table` object created for the command.
14581475

alembic/operations/ops.py

+16-2
Original file line numberDiff line numberDiff line change
@@ -1159,13 +1159,15 @@ def __init__(
11591159
columns: Sequence[SchemaItem],
11601160
*,
11611161
schema: Optional[str] = None,
1162+
if_not_exists: Optional[bool] = None,
11621163
_namespace_metadata: Optional[MetaData] = None,
11631164
_constraints_included: bool = False,
11641165
**kw: Any,
11651166
) -> None:
11661167
self.table_name = table_name
11671168
self.columns = columns
11681169
self.schema = schema
1170+
self.if_not_exists = if_not_exists
11691171
self.info = kw.pop("info", {})
11701172
self.comment = kw.pop("comment", None)
11711173
self.prefixes = kw.pop("prefixes", None)
@@ -1228,6 +1230,7 @@ def create_table(
12281230
operations: Operations,
12291231
table_name: str,
12301232
*columns: SchemaItem,
1233+
if_not_exists: Optional[bool] = None,
12311234
**kw: Any,
12321235
) -> Table:
12331236
r"""Issue a "create table" instruction using the current migration
@@ -1300,14 +1303,18 @@ def create_table(
13001303
quoting of the schema outside of the default behavior, use
13011304
the SQLAlchemy construct
13021305
:class:`~sqlalchemy.sql.elements.quoted_name`.
1306+
:param if_not_exists: If True, adds IF NOT EXISTS operator when
1307+
creating the new table.
1308+
1309+
.. versionadded:: 1.13.3
13031310
:param \**kw: Other keyword arguments are passed to the underlying
13041311
:class:`sqlalchemy.schema.Table` object created for the command.
13051312
13061313
:return: the :class:`~sqlalchemy.schema.Table` object corresponding
13071314
to the parameters given.
13081315
13091316
"""
1310-
op = cls(table_name, columns, **kw)
1317+
op = cls(table_name, columns, if_not_exists=if_not_exists, **kw)
13111318
return operations.invoke(op)
13121319

13131320

@@ -1320,11 +1327,13 @@ def __init__(
13201327
table_name: str,
13211328
*,
13221329
schema: Optional[str] = None,
1330+
if_exists: Optional[bool] = None,
13231331
table_kw: Optional[MutableMapping[Any, Any]] = None,
13241332
_reverse: Optional[CreateTableOp] = None,
13251333
) -> None:
13261334
self.table_name = table_name
13271335
self.schema = schema
1336+
self.if_exists = if_exists
13281337
self.table_kw = table_kw or {}
13291338
self.comment = self.table_kw.pop("comment", None)
13301339
self.info = self.table_kw.pop("info", None)
@@ -1385,6 +1394,7 @@ def drop_table(
13851394
table_name: str,
13861395
*,
13871396
schema: Optional[str] = None,
1397+
if_exists: Optional[bool] = None,
13881398
**kw: Any,
13891399
) -> None:
13901400
r"""Issue a "drop table" instruction using the current
@@ -1400,11 +1410,15 @@ def drop_table(
14001410
quoting of the schema outside of the default behavior, use
14011411
the SQLAlchemy construct
14021412
:class:`~sqlalchemy.sql.elements.quoted_name`.
1413+
:param if_exists: If True, adds IF EXISTS operator when
1414+
dropping the table.
1415+
1416+
.. versionadded:: 1.13.3
14031417
:param \**kw: Other keyword arguments are passed to the underlying
14041418
:class:`sqlalchemy.schema.Table` object created for the command.
14051419
14061420
"""
1407-
op = cls(table_name, schema=schema, table_kw=kw)
1421+
op = cls(table_name, schema=schema, if_exists=if_exists, table_kw=kw)
14081422
operations.invoke(op)
14091423

14101424

alembic/operations/toimpl.py

+14-2
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,14 @@ def _count_constraint(constraint):
7979

8080
@Operations.implementation_for(ops.DropTableOp)
8181
def drop_table(operations: "Operations", operation: "ops.DropTableOp") -> None:
82+
kw = {}
83+
if operation.if_exists is not None:
84+
if not sqla_14:
85+
raise NotImplementedError("SQLAlchemy 1.4+ required")
86+
87+
kw["if_exists"] = operation.if_exists
8288
operations.impl.drop_table(
83-
operation.to_table(operations.migration_context)
89+
operation.to_table(operations.migration_context), **kw
8490
)
8591

8692

@@ -127,8 +133,14 @@ def drop_index(operations: "Operations", operation: "ops.DropIndexOp") -> None:
127133
def create_table(
128134
operations: "Operations", operation: "ops.CreateTableOp"
129135
) -> "Table":
136+
kw = {}
137+
if operation.if_not_exists is not None:
138+
if not sqla_14:
139+
raise NotImplementedError("SQLAlchemy 1.4+ required")
140+
141+
kw["if_not_exists"] = operation.if_not_exists
130142
table = operation.to_table(operations.migration_context)
131-
operations.impl.create_table(table)
143+
operations.impl.create_table(table, **kw)
132144
return table
133145

134146

docs/build/unreleased/1520.rst

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.. change::
2+
:tags: usecase, operations
3+
:tickets: 1520
4+
5+
Added support for :paramref:`.Operations.create_table.if_not_exists` and
6+
:paramref:`.Operations.drop_table.if_exists`, adding similar functionality
7+
to render IF [NOT] EXISTS for table operations in a similar way as with
8+
indexes. Pull request courtesy Aaron Griffin.
9+

tests/test_op.py

+20
Original file line numberDiff line numberDiff line change
@@ -907,6 +907,12 @@ def test_drop_table_schema(self):
907907
op.drop_table("tb_test", schema="foo")
908908
context.assert_("DROP TABLE foo.tb_test")
909909

910+
@config.requirements.sqlalchemy_14
911+
def test_drop_table_if_exists(self):
912+
context = op_fixture()
913+
op.drop_table("tb_test", if_exists=True)
914+
context.assert_("DROP TABLE IF EXISTS tb_test")
915+
910916
def test_create_table_selfref(self):
911917
context = op_fixture()
912918
op.create_table(
@@ -1079,6 +1085,20 @@ def test_create_table_two_fk(self):
10791085
"FOREIGN KEY(foo_bar) REFERENCES foo (bar))"
10801086
)
10811087

1088+
@config.requirements.sqlalchemy_14
1089+
def test_create_table_if_not_exists(self):
1090+
context = op_fixture()
1091+
op.create_table(
1092+
"some_table",
1093+
Column("id", Integer, primary_key=True),
1094+
if_not_exists=True,
1095+
)
1096+
context.assert_(
1097+
"CREATE TABLE IF NOT EXISTS some_table ("
1098+
"id INTEGER NOT NULL, "
1099+
"PRIMARY KEY (id))"
1100+
)
1101+
10821102
def test_execute_delete(self):
10831103
context = op_fixture()
10841104

0 commit comments

Comments
 (0)