Skip to content

Commit b6eed88

Browse files
Jesse-Bakkerzzzeek
authored andcommitted
Make if_exists and if_not_exists flags on ddl statements match compiler
Added ``if_exists`` and ``if_not_exists`` parameters for all "Create" / "Drop" constructs including :class:`.CreateSequence`, :class:`.DropSequence`, :class:`.CreateIndex`, :class:`.DropIndex`, etc. allowing generic "IF EXISTS" / "IF NOT EXISTS" phrases to be rendered within DDL. Pull request courtesy Jesse Bakker. Fixes: #7354 Closes: #8492 Pull-request: #8492 Pull-request-sha: d107c6c Change-Id: I367e57b2d9216f5180bcc44e86ca6f3dc794e5ca
1 parent d5905cc commit b6eed88

6 files changed

Lines changed: 95 additions & 42 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
.. change::
2+
:tags: bug, sql
3+
:tickets: 7354
4+
5+
Added ``if_exists`` and ``if_not_exists`` parameters for all "Create" /
6+
"Drop" constructs including :class:`.CreateSequence`,
7+
:class:`.DropSequence`, :class:`.CreateIndex`, :class:`.DropIndex`, etc.
8+
allowing generic "IF EXISTS" / "IF NOT EXISTS" phrases to be rendered
9+
within DDL. Pull request courtesy Jesse Bakker.
10+

lib/sqlalchemy/sql/compiler.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5396,12 +5396,16 @@ def visit_ddl(self, ddl, **kwargs):
53965396
return self.sql_compiler.post_process_text(ddl.statement % context)
53975397

53985398
def visit_create_schema(self, create, **kw):
5399-
schema = self.preparer.format_schema(create.element)
5400-
return "CREATE SCHEMA " + schema
5399+
text = "CREATE SCHEMA "
5400+
if create.if_not_exists:
5401+
text += "IF NOT EXISTS "
5402+
return text + self.preparer.format_schema(create.element)
54015403

54025404
def visit_drop_schema(self, drop, **kw):
5403-
schema = self.preparer.format_schema(drop.element)
5404-
text = "DROP SCHEMA " + schema
5405+
text = "DROP SCHEMA "
5406+
if drop.if_exists:
5407+
text += "IF EXISTS "
5408+
text += self.preparer.format_schema(drop.element)
54055409
if drop.cascade:
54065410
text += " CASCADE"
54075411
return text
@@ -5650,9 +5654,11 @@ def get_identity_options(self, identity_options):
56505654
return " ".join(text)
56515655

56525656
def visit_create_sequence(self, create, prefix=None, **kw):
5653-
text = "CREATE SEQUENCE %s" % self.preparer.format_sequence(
5654-
create.element
5655-
)
5657+
text = "CREATE SEQUENCE "
5658+
if create.if_not_exists:
5659+
text += "IF NOT EXISTS "
5660+
text += self.preparer.format_sequence(create.element)
5661+
56565662
if prefix:
56575663
text += prefix
56585664
if create.element.start is None:
@@ -5663,7 +5669,10 @@ def visit_create_sequence(self, create, prefix=None, **kw):
56635669
return text
56645670

56655671
def visit_drop_sequence(self, drop, **kw):
5666-
return "DROP SEQUENCE %s" % self.preparer.format_sequence(drop.element)
5672+
text = "DROP SEQUENCE "
5673+
if drop.if_exists:
5674+
text += "IF EXISTS "
5675+
return text + self.preparer.format_sequence(drop.element)
56675676

56685677
def visit_drop_constraint(self, drop, **kw):
56695678
constraint = drop.element

lib/sqlalchemy/sql/ddl.py

Lines changed: 36 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
from .schema import Constraint
4141
from .schema import ForeignKeyConstraint
4242
from .schema import SchemaItem
43+
from .schema import Sequence
4344
from .schema import Table
4445
from ..engine.base import _CompiledCacheType
4546
from ..engine.base import Connection
@@ -434,12 +435,8 @@ class _CreateDropBase(ExecutableDDLElement):
434435
def __init__(
435436
self,
436437
element,
437-
if_exists=False,
438-
if_not_exists=False,
439438
):
440439
self.element = self.target = element
441-
self.if_exists = if_exists
442-
self.if_not_exists = if_not_exists
443440
self._ddl_if = getattr(element, "_ddl_if", None)
444441

445442
@property
@@ -457,7 +454,19 @@ def _create_rule_disable(self, compiler):
457454
return False
458455

459456

460-
class CreateSchema(_CreateDropBase):
457+
class _CreateBase(_CreateDropBase):
458+
def __init__(self, element, if_not_exists=False):
459+
super().__init__(element)
460+
self.if_not_exists = if_not_exists
461+
462+
463+
class _DropBase(_CreateDropBase):
464+
def __init__(self, element, if_exists=False):
465+
super().__init__(element)
466+
self.if_exists = if_exists
467+
468+
469+
class CreateSchema(_CreateBase):
461470
"""Represent a CREATE SCHEMA statement.
462471
463472
The argument here is the string name of the schema.
@@ -469,19 +478,14 @@ class CreateSchema(_CreateDropBase):
469478
def __init__(
470479
self,
471480
name,
472-
quote=None,
473-
if_exists=False,
474481
if_not_exists=False,
475482
):
476483
"""Create a new :class:`.CreateSchema` construct."""
477484

478-
self.quote = quote
479-
self.element = name
480-
self.if_exists = if_exists
481-
self.if_not_exists = if_not_exists
485+
super().__init__(element=name, if_not_exists=if_not_exists)
482486

483487

484-
class DropSchema(_CreateDropBase):
488+
class DropSchema(_DropBase):
485489
"""Represent a DROP SCHEMA statement.
486490
487491
The argument here is the string name of the schema.
@@ -493,22 +497,16 @@ class DropSchema(_CreateDropBase):
493497
def __init__(
494498
self,
495499
name,
496-
quote=None,
497500
cascade=False,
498501
if_exists=False,
499-
if_not_exists=False,
500502
):
501503
"""Create a new :class:`.DropSchema` construct."""
502504

503-
self.quote = quote
505+
super().__init__(element=name, if_exists=if_exists)
504506
self.cascade = cascade
505-
self.quote = quote
506-
self.element = name
507-
self.if_exists = if_exists
508-
self.if_not_exists = if_not_exists
509507

510508

511-
class CreateTable(_CreateDropBase):
509+
class CreateTable(_CreateBase):
512510
"""Represent a CREATE TABLE statement."""
513511

514512
__visit_name__ = "create_table"
@@ -544,7 +542,7 @@ def __init__(
544542
self.include_foreign_key_constraints = include_foreign_key_constraints
545543

546544

547-
class _DropView(_CreateDropBase):
545+
class _DropView(_DropBase):
548546
"""Semi-public 'DROP VIEW' construct.
549547
550548
Used by the test suite for dialect-agnostic drops of views.
@@ -669,7 +667,7 @@ def __init__(self, element):
669667
self.element = element
670668

671669

672-
class DropTable(_CreateDropBase):
670+
class DropTable(_DropBase):
673671
"""Represent a DROP TABLE statement."""
674672

675673
__visit_name__ = "drop_table"
@@ -689,19 +687,25 @@ def __init__(self, element: Table, if_exists: bool = False):
689687
super().__init__(element, if_exists=if_exists)
690688

691689

692-
class CreateSequence(_CreateDropBase):
690+
class CreateSequence(_CreateBase):
693691
"""Represent a CREATE SEQUENCE statement."""
694692

695693
__visit_name__ = "create_sequence"
696694

695+
def __init__(self, element: Sequence, if_not_exists: bool = False):
696+
super().__init__(element, if_not_exists=if_not_exists)
697+
697698

698-
class DropSequence(_CreateDropBase):
699+
class DropSequence(_DropBase):
699700
"""Represent a DROP SEQUENCE statement."""
700701

701702
__visit_name__ = "drop_sequence"
702703

704+
def __init__(self, element: Sequence, if_exists: bool = False):
705+
super().__init__(element, if_exists=if_exists)
706+
703707

704-
class CreateIndex(_CreateDropBase):
708+
class CreateIndex(_CreateBase):
705709
"""Represent a CREATE INDEX statement."""
706710

707711
__visit_name__ = "create_index"
@@ -711,7 +715,6 @@ def __init__(self, element, if_not_exists=False):
711715
712716
:param element: a :class:`_schema.Index` that's the subject
713717
of the CREATE.
714-
:param on: See the description for 'on' in :class:`.DDL`.
715718
:param if_not_exists: if True, an IF NOT EXISTS operator will be
716719
applied to the construct.
717720
@@ -721,7 +724,7 @@ def __init__(self, element, if_not_exists=False):
721724
super().__init__(element, if_not_exists=if_not_exists)
722725

723726

724-
class DropIndex(_CreateDropBase):
727+
class DropIndex(_DropBase):
725728
"""Represent a DROP INDEX statement."""
726729

727730
__visit_name__ = "drop_index"
@@ -731,7 +734,6 @@ def __init__(self, element, if_exists=False):
731734
732735
:param element: a :class:`_schema.Index` that's the subject
733736
of the DROP.
734-
:param on: See the description for 'on' in :class:`.DDL`.
735737
:param if_exists: if True, an IF EXISTS operator will be applied to the
736738
construct.
737739
@@ -741,26 +743,26 @@ def __init__(self, element, if_exists=False):
741743
super().__init__(element, if_exists=if_exists)
742744

743745

744-
class AddConstraint(_CreateDropBase):
746+
class AddConstraint(_CreateBase):
745747
"""Represent an ALTER TABLE ADD CONSTRAINT statement."""
746748

747749
__visit_name__ = "add_constraint"
748750

749-
def __init__(self, element, *args, **kw):
750-
super().__init__(element, *args, **kw)
751+
def __init__(self, element):
752+
super().__init__(element)
751753
element._create_rule = util.portable_instancemethod(
752754
self._create_rule_disable
753755
)
754756

755757

756-
class DropConstraint(_CreateDropBase):
758+
class DropConstraint(_DropBase):
757759
"""Represent an ALTER TABLE DROP CONSTRAINT statement."""
758760

759761
__visit_name__ = "drop_constraint"
760762

761-
def __init__(self, element, cascade=False, **kw):
763+
def __init__(self, element, cascade=False, if_exists=False, **kw):
762764
self.cascade = cascade
763-
super().__init__(element, **kw)
765+
super().__init__(element, if_exists=if_exists, **kw)
764766
element._create_rule = util.portable_instancemethod(
765767
self._create_rule_disable
766768
)

test/sql/test_constraints.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -765,6 +765,14 @@ def test_create_index_plain(self):
765765
i = Index("xyz", t.c.x)
766766
self.assert_compile(schema.CreateIndex(i), "CREATE INDEX xyz ON t (x)")
767767

768+
def test_create_index_if_not_exists(self):
769+
t = Table("t", MetaData(), Column("x", Integer))
770+
i = Index("xyz", t.c.x)
771+
self.assert_compile(
772+
schema.CreateIndex(i, if_not_exists=True),
773+
"CREATE INDEX IF NOT EXISTS xyz ON t (x)",
774+
)
775+
768776
def test_drop_index_plain_unattached(self):
769777
self.assert_compile(
770778
schema.DropIndex(Index(name="xyz")), "DROP INDEX xyz"
@@ -775,6 +783,12 @@ def test_drop_index_plain(self):
775783
schema.DropIndex(Index(name="xyz")), "DROP INDEX xyz"
776784
)
777785

786+
def test_drop_index_if_exists(self):
787+
self.assert_compile(
788+
schema.DropIndex(Index(name="xyz"), if_exists=True),
789+
"DROP INDEX IF EXISTS xyz",
790+
)
791+
778792
def test_create_index_schema(self):
779793
t = Table("t", MetaData(), Column("x", Integer), schema="foo")
780794
i = Index("xyz", t.c.x)

test/sql/test_metadata.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2620,9 +2620,17 @@ def test_create_drop_schema(self):
26202620
self.assert_compile(
26212621
schema.CreateSchema("sa_schema"), "CREATE SCHEMA sa_schema"
26222622
)
2623+
self.assert_compile(
2624+
schema.CreateSchema("sa_schema", if_not_exists=True),
2625+
"CREATE SCHEMA IF NOT EXISTS sa_schema",
2626+
)
26232627
self.assert_compile(
26242628
schema.DropSchema("sa_schema"), "DROP SCHEMA sa_schema"
26252629
)
2630+
self.assert_compile(
2631+
schema.DropSchema("sa_schema", if_exists=True),
2632+
"DROP SCHEMA IF EXISTS sa_schema",
2633+
)
26262634
self.assert_compile(
26272635
schema.DropSchema("sa_schema", cascade=True),
26282636
"DROP SCHEMA sa_schema CASCADE",

test/sql/test_sequences.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,20 @@ def test_create_drop_ddl(self):
9292
"CREATE SEQUENCE foo_seq START WITH 1 ORDER",
9393
)
9494

95+
self.assert_compile(
96+
CreateSequence(Sequence("foo_seq"), if_not_exists=True),
97+
"CREATE SEQUENCE IF NOT EXISTS foo_seq START WITH 1",
98+
)
99+
95100
self.assert_compile(
96101
DropSequence(Sequence("foo_seq")), "DROP SEQUENCE foo_seq"
97102
)
98103

104+
self.assert_compile(
105+
DropSequence(Sequence("foo_seq"), if_exists=True),
106+
"DROP SEQUENCE IF EXISTS foo_seq",
107+
)
108+
99109

100110
class SequenceExecTest(fixtures.TestBase):
101111
__requires__ = ("sequences",)

0 commit comments

Comments
 (0)