1# sql/ddl.py
2# Copyright (C) 2009-2021 the SQLAlchemy authors and contributors
3# <see AUTHORS file>
4#
5# This module is part of SQLAlchemy and is released under
6# the MIT License: http://www.opensource.org/licenses/mit-license.php
7"""
8Provides the hierarchy of DDL-defining schema items as well as routines
9to invoke them for a create/drop call.
10
11"""
12
13from .base import _bind_or_error
14from .base import _generative
15from .base import Executable
16from .base import SchemaVisitor
17from .elements import ClauseElement
18from .. import event
19from .. import exc
20from .. import util
21from ..util import topological
22
23
24class _DDLCompiles(ClauseElement):
25    def _compiler(self, dialect, **kw):
26        """Return a compiler appropriate for this ClauseElement, given a
27        Dialect."""
28
29        return dialect.ddl_compiler(dialect, self, **kw)
30
31
32class DDLElement(Executable, _DDLCompiles):
33    """Base class for DDL expression constructs.
34
35    This class is the base for the general purpose :class:`.DDL` class,
36    as well as the various create/drop clause constructs such as
37    :class:`.CreateTable`, :class:`.DropTable`, :class:`.AddConstraint`,
38    etc.
39
40    :class:`.DDLElement` integrates closely with SQLAlchemy events,
41    introduced in :ref:`event_toplevel`.  An instance of one is
42    itself an event receiving callable::
43
44        event.listen(
45            users,
46            'after_create',
47            AddConstraint(constraint).execute_if(dialect='postgresql')
48        )
49
50    .. seealso::
51
52        :class:`.DDL`
53
54        :class:`.DDLEvents`
55
56        :ref:`event_toplevel`
57
58        :ref:`schema_ddl_sequences`
59
60    """
61
62    _execution_options = Executable._execution_options.union(
63        {"autocommit": True}
64    )
65
66    target = None
67    on = None
68    dialect = None
69    callable_ = None
70
71    def _execute_on_connection(self, connection, multiparams, params):
72        return connection._execute_ddl(self, multiparams, params)
73
74    def execute(self, bind=None, target=None):
75        """Execute this DDL immediately.
76
77        Executes the DDL statement in isolation using the supplied
78        :class:`.Connectable` or
79        :class:`.Connectable` assigned to the ``.bind``
80        property, if not supplied. If the DDL has a conditional ``on``
81        criteria, it will be invoked with None as the event.
82
83        :param bind:
84          Optional, an ``Engine`` or ``Connection``. If not supplied, a valid
85          :class:`.Connectable` must be present in the
86          ``.bind`` property.
87
88        :param target:
89          Optional, defaults to None.  The target :class:`_schema.SchemaItem`
90          for the execute call.  Will be passed to the ``on`` callable if any,
91          and may also provide string expansion data for the statement.
92          See ``execute_at`` for more information.
93
94        """
95
96        if bind is None:
97            bind = _bind_or_error(self)
98
99        if self._should_execute(target, bind):
100            return bind.execute(self.against(target))
101        else:
102            bind.engine.logger.info("DDL execution skipped, criteria not met.")
103
104    @util.deprecated(
105        "0.7",
106        "The :meth:`.DDLElement.execute_at` method is deprecated and will "
107        "be removed in a future release.  Please use the :class:`.DDLEvents` "
108        "listener interface in conjunction with the "
109        ":meth:`.DDLElement.execute_if` method.",
110    )
111    def execute_at(self, event_name, target):
112        """Link execution of this DDL to the DDL lifecycle of a SchemaItem.
113
114        Links this ``DDLElement`` to a ``Table`` or ``MetaData`` instance,
115        executing it when that schema item is created or dropped. The DDL
116        statement will be executed using the same Connection and transactional
117        context as the Table create/drop itself. The ``.bind`` property of
118        this statement is ignored.
119
120        :param event:
121          One of the events defined in the schema item's ``.ddl_events``;
122          e.g. 'before-create', 'after-create', 'before-drop' or 'after-drop'
123
124        :param target:
125          The Table or MetaData instance for which this DDLElement will
126          be associated with.
127
128        A DDLElement instance can be linked to any number of schema items.
129
130        ``execute_at`` builds on the ``append_ddl_listener`` interface of
131        :class:`_schema.MetaData` and :class:`_schema.Table` objects.
132
133        Caveat: Creating or dropping a Table in isolation will also trigger
134        any DDL set to ``execute_at`` that Table's MetaData.  This may change
135        in a future release.
136
137        """
138
139        def call_event(target, connection, **kw):
140            if self._should_execute_deprecated(
141                event_name, target, connection, **kw
142            ):
143                return connection.execute(self.against(target))
144
145        event.listen(target, "" + event_name.replace("-", "_"), call_event)
146
147    @_generative
148    def against(self, target):
149        """Return a copy of this DDL against a specific schema item."""
150
151        self.target = target
152
153    @_generative
154    def execute_if(self, dialect=None, callable_=None, state=None):
155        r"""Return a callable that will execute this
156        DDLElement conditionally.
157
158        Used to provide a wrapper for event listening::
159
160            event.listen(
161                        metadata,
162                        'before_create',
163                        DDL("my_ddl").execute_if(dialect='postgresql')
164                    )
165
166        :param dialect: May be a string, tuple or a callable
167          predicate.  If a string, it will be compared to the name of the
168          executing database dialect::
169
170            DDL('something').execute_if(dialect='postgresql')
171
172          If a tuple, specifies multiple dialect names::
173
174            DDL('something').execute_if(dialect=('postgresql', 'mysql'))
175
176        :param callable\_: A callable, which will be invoked with
177          four positional arguments as well as optional keyword
178          arguments:
179
180            :ddl:
181              This DDL element.
182
183            :target:
184              The :class:`_schema.Table` or :class:`_schema.MetaData`
185              object which is the
186              target of this event. May be None if the DDL is executed
187              explicitly.
188
189            :bind:
190              The :class:`_engine.Connection` being used for DDL execution
191
192            :tables:
193              Optional keyword argument - a list of Table objects which are to
194              be created/ dropped within a MetaData.create_all() or drop_all()
195              method call.
196
197            :state:
198              Optional keyword argument - will be the ``state`` argument
199              passed to this function.
200
201            :checkfirst:
202             Keyword argument, will be True if the 'checkfirst' flag was
203             set during the call to ``create()``, ``create_all()``,
204             ``drop()``, ``drop_all()``.
205
206          If the callable returns a True value, the DDL statement will be
207          executed.
208
209        :param state: any value which will be passed to the callable\_
210          as the ``state`` keyword argument.
211
212        .. seealso::
213
214            :class:`.DDLEvents`
215
216            :ref:`event_toplevel`
217
218        """
219        self.dialect = dialect
220        self.callable_ = callable_
221        self.state = state
222
223    def _should_execute(self, target, bind, **kw):
224        if self.on is not None and not self._should_execute_deprecated(
225            None, target, bind, **kw
226        ):
227            return False
228
229        if isinstance(self.dialect, util.string_types):
230            if self.dialect != bind.engine.name:
231                return False
232        elif isinstance(self.dialect, (tuple, list, set)):
233            if bind.engine.name not in self.dialect:
234                return False
235        if self.callable_ is not None and not self.callable_(
236            self, target, bind, state=self.state, **kw
237        ):
238            return False
239
240        return True
241
242    def _should_execute_deprecated(self, event, target, bind, **kw):
243        if self.on is None:
244            return True
245        elif isinstance(self.on, util.string_types):
246            return self.on == bind.engine.name
247        elif isinstance(self.on, (tuple, list, set)):
248            return bind.engine.name in self.on
249        else:
250            return self.on(self, event, target, bind, **kw)
251
252    def __call__(self, target, bind, **kw):
253        """Execute the DDL as a ddl_listener."""
254
255        if self._should_execute(target, bind, **kw):
256            return bind.execute(self.against(target))
257
258    def _check_ddl_on(self, on):
259        if on is not None and (
260            not isinstance(on, util.string_types + (tuple, list, set))
261            and not util.callable(on)
262        ):
263            raise exc.ArgumentError(
264                "Expected the name of a database dialect, a tuple "
265                "of names, or a callable for "
266                "'on' criteria, got type '%s'." % type(on).__name__
267            )
268
269    def bind(self):
270        if self._bind:
271            return self._bind
272
273    def _set_bind(self, bind):
274        self._bind = bind
275
276    bind = property(bind, _set_bind)
277
278    def _generate(self):
279        s = self.__class__.__new__(self.__class__)
280        s.__dict__ = self.__dict__.copy()
281        return s
282
283
284class DDL(DDLElement):
285    """A literal DDL statement.
286
287    Specifies literal SQL DDL to be executed by the database.  DDL objects
288    function as DDL event listeners, and can be subscribed to those events
289    listed in :class:`.DDLEvents`, using either :class:`_schema.Table` or
290    :class:`_schema.MetaData` objects as targets.
291    Basic templating support allows
292    a single DDL instance to handle repetitive tasks for multiple tables.
293
294    Examples::
295
296      from sqlalchemy import event, DDL
297
298      tbl = Table('users', metadata, Column('uid', Integer))
299      event.listen(tbl, 'before_create', DDL('DROP TRIGGER users_trigger'))
300
301      spow = DDL('ALTER TABLE %(table)s SET secretpowers TRUE')
302      event.listen(tbl, 'after_create', spow.execute_if(dialect='somedb'))
303
304      drop_spow = DDL('ALTER TABLE users SET secretpowers FALSE')
305      connection.execute(drop_spow)
306
307    When operating on Table events, the following ``statement``
308    string substitutions are available::
309
310      %(table)s  - the Table name, with any required quoting applied
311      %(schema)s - the schema name, with any required quoting applied
312      %(fullname)s - the Table name including schema, quoted if needed
313
314    The DDL's "context", if any, will be combined with the standard
315    substitutions noted above.  Keys present in the context will override
316    the standard substitutions.
317
318    """
319
320    __visit_name__ = "ddl"
321
322    @util.deprecated_params(
323        on=(
324            "0.7",
325            "The :paramref:`.DDL.on` parameter is deprecated and will be "
326            "removed in a future release.  Please refer to "
327            ":meth:`.DDLElement.execute_if`.",
328        )
329    )
330    def __init__(self, statement, on=None, context=None, bind=None):
331        """Create a DDL statement.
332
333        :param statement:
334          A string or unicode string to be executed.  Statements will be
335          processed with Python's string formatting operator.  See the
336          ``context`` argument and the ``execute_at`` method.
337
338          A literal '%' in a statement must be escaped as '%%'.
339
340          SQL bind parameters are not available in DDL statements.
341
342        :param on:
343
344          Optional filtering criteria.  May be a string, tuple or a callable
345          predicate.  If a string, it will be compared to the name of the
346          executing database dialect::
347
348            DDL('something', on='postgresql')
349
350          If a tuple, specifies multiple dialect names::
351
352            DDL('something', on=('postgresql', 'mysql'))
353
354          If a callable, it will be invoked with four positional arguments
355          as well as optional keyword arguments:
356
357            :ddl:
358              This DDL element.
359
360            :event:
361              The name of the event that has triggered this DDL, such as
362              'after-create' Will be None if the DDL is executed explicitly.
363
364            :target:
365              The ``Table`` or ``MetaData`` object which is the target of
366              this event. May be None if the DDL is executed explicitly.
367
368            :connection:
369              The ``Connection`` being used for DDL execution
370
371            :tables:
372              Optional keyword argument - a list of Table objects which are to
373              be created/ dropped within a MetaData.create_all() or drop_all()
374              method call.
375
376
377          If the callable returns a true value, the DDL statement will be
378          executed.
379
380        :param context:
381          Optional dictionary, defaults to None.  These values will be
382          available for use in string substitutions on the DDL statement.
383
384        :param bind:
385          Optional. A :class:`.Connectable`, used by
386          default when ``execute()`` is invoked without a bind argument.
387
388
389        .. seealso::
390
391            :class:`.DDLEvents`
392
393            :ref:`event_toplevel`
394
395        """
396
397        if not isinstance(statement, util.string_types):
398            raise exc.ArgumentError(
399                "Expected a string or unicode SQL statement, got '%r'"
400                % statement
401            )
402
403        self.statement = statement
404        self.context = context or {}
405
406        self._check_ddl_on(on)
407        self.on = on
408        self._bind = bind
409
410    def __repr__(self):
411        return "<%s@%s; %s>" % (
412            type(self).__name__,
413            id(self),
414            ", ".join(
415                [repr(self.statement)]
416                + [
417                    "%s=%r" % (key, getattr(self, key))
418                    for key in ("on", "context")
419                    if getattr(self, key)
420                ]
421            ),
422        )
423
424
425class _CreateDropBase(DDLElement):
426    """Base class for DDL constructs that represent CREATE and DROP or
427    equivalents.
428
429    The common theme of _CreateDropBase is a single
430    ``element`` attribute which refers to the element
431    to be created or dropped.
432
433    """
434
435    def __init__(self, element, on=None, bind=None):
436        self.element = element
437        self._check_ddl_on(on)
438        self.on = on
439        self.bind = bind
440
441    def _create_rule_disable(self, compiler):
442        """Allow disable of _create_rule using a callable.
443
444        Pass to _create_rule using
445        util.portable_instancemethod(self._create_rule_disable)
446        to retain serializability.
447
448        """
449        return False
450
451
452class CreateSchema(_CreateDropBase):
453    """Represent a CREATE SCHEMA statement.
454
455    The argument here is the string name of the schema.
456
457    """
458
459    __visit_name__ = "create_schema"
460
461    def __init__(self, name, quote=None, **kw):
462        """Create a new :class:`.CreateSchema` construct."""
463
464        self.quote = quote
465        super(CreateSchema, self).__init__(name, **kw)
466
467
468class DropSchema(_CreateDropBase):
469    """Represent a DROP SCHEMA statement.
470
471    The argument here is the string name of the schema.
472
473    """
474
475    __visit_name__ = "drop_schema"
476
477    def __init__(self, name, quote=None, cascade=False, **kw):
478        """Create a new :class:`.DropSchema` construct."""
479
480        self.quote = quote
481        self.cascade = cascade
482        super(DropSchema, self).__init__(name, **kw)
483
484
485class CreateTable(_CreateDropBase):
486    """Represent a CREATE TABLE statement."""
487
488    __visit_name__ = "create_table"
489
490    def __init__(
491        self, element, on=None, bind=None, include_foreign_key_constraints=None
492    ):
493        """Create a :class:`.CreateTable` construct.
494
495        :param element: a :class:`_schema.Table` that's the subject
496         of the CREATE
497        :param on: See the description for 'on' in :class:`.DDL`.
498        :param bind: See the description for 'bind' in :class:`.DDL`.
499        :param include_foreign_key_constraints: optional sequence of
500         :class:`_schema.ForeignKeyConstraint` objects that will be included
501         inline within the CREATE construct; if omitted, all foreign key
502         constraints that do not specify use_alter=True are included.
503
504         .. versionadded:: 1.0.0
505
506        """
507        super(CreateTable, self).__init__(element, on=on, bind=bind)
508        self.columns = [CreateColumn(column) for column in element.columns]
509        self.include_foreign_key_constraints = include_foreign_key_constraints
510
511
512class _DropView(_CreateDropBase):
513    """Semi-public 'DROP VIEW' construct.
514
515    Used by the test suite for dialect-agnostic drops of views.
516    This object will eventually be part of a public "view" API.
517
518    """
519
520    __visit_name__ = "drop_view"
521
522
523class CreateColumn(_DDLCompiles):
524    """Represent a :class:`_schema.Column`
525    as rendered in a CREATE TABLE statement,
526    via the :class:`.CreateTable` construct.
527
528    This is provided to support custom column DDL within the generation
529    of CREATE TABLE statements, by using the
530    compiler extension documented in :ref:`sqlalchemy.ext.compiler_toplevel`
531    to extend :class:`.CreateColumn`.
532
533    Typical integration is to examine the incoming :class:`_schema.Column`
534    object, and to redirect compilation if a particular flag or condition
535    is found::
536
537        from sqlalchemy import schema
538        from sqlalchemy.ext.compiler import compiles
539
540        @compiles(schema.CreateColumn)
541        def compile(element, compiler, **kw):
542            column = element.element
543
544            if "special" not in column.info:
545                return compiler.visit_create_column(element, **kw)
546
547            text = "%s SPECIAL DIRECTIVE %s" % (
548                    column.name,
549                    compiler.type_compiler.process(column.type)
550                )
551            default = compiler.get_column_default_string(column)
552            if default is not None:
553                text += " DEFAULT " + default
554
555            if not column.nullable:
556                text += " NOT NULL"
557
558            if column.constraints:
559                text += " ".join(
560                            compiler.process(const)
561                            for const in column.constraints)
562            return text
563
564    The above construct can be applied to a :class:`_schema.Table`
565    as follows::
566
567        from sqlalchemy import Table, Metadata, Column, Integer, String
568        from sqlalchemy import schema
569
570        metadata = MetaData()
571
572        table = Table('mytable', MetaData(),
573                Column('x', Integer, info={"special":True}, primary_key=True),
574                Column('y', String(50)),
575                Column('z', String(20), info={"special":True})
576            )
577
578        metadata.create_all(conn)
579
580    Above, the directives we've added to the :attr:`_schema.Column.info`
581    collection
582    will be detected by our custom compilation scheme::
583
584        CREATE TABLE mytable (
585                x SPECIAL DIRECTIVE INTEGER NOT NULL,
586                y VARCHAR(50),
587                z SPECIAL DIRECTIVE VARCHAR(20),
588            PRIMARY KEY (x)
589        )
590
591    The :class:`.CreateColumn` construct can also be used to skip certain
592    columns when producing a ``CREATE TABLE``.  This is accomplished by
593    creating a compilation rule that conditionally returns ``None``.
594    This is essentially how to produce the same effect as using the
595    ``system=True`` argument on :class:`_schema.Column`, which marks a column
596    as an implicitly-present "system" column.
597
598    For example, suppose we wish to produce a :class:`_schema.Table`
599    which skips
600    rendering of the PostgreSQL ``xmin`` column against the PostgreSQL
601    backend, but on other backends does render it, in anticipation of a
602    triggered rule.  A conditional compilation rule could skip this name only
603    on PostgreSQL::
604
605        from sqlalchemy.schema import CreateColumn
606
607        @compiles(CreateColumn, "postgresql")
608        def skip_xmin(element, compiler, **kw):
609            if element.element.name == 'xmin':
610                return None
611            else:
612                return compiler.visit_create_column(element, **kw)
613
614
615        my_table = Table('mytable', metadata,
616                    Column('id', Integer, primary_key=True),
617                    Column('xmin', Integer)
618                )
619
620    Above, a :class:`.CreateTable` construct will generate a ``CREATE TABLE``
621    which only includes the ``id`` column in the string; the ``xmin`` column
622    will be omitted, but only against the PostgreSQL backend.
623
624    """
625
626    __visit_name__ = "create_column"
627
628    def __init__(self, element):
629        self.element = element
630
631
632class DropTable(_CreateDropBase):
633    """Represent a DROP TABLE statement."""
634
635    __visit_name__ = "drop_table"
636
637
638class CreateSequence(_CreateDropBase):
639    """Represent a CREATE SEQUENCE statement."""
640
641    __visit_name__ = "create_sequence"
642
643
644class DropSequence(_CreateDropBase):
645    """Represent a DROP SEQUENCE statement."""
646
647    __visit_name__ = "drop_sequence"
648
649
650class CreateIndex(_CreateDropBase):
651    """Represent a CREATE INDEX statement."""
652
653    __visit_name__ = "create_index"
654
655
656class DropIndex(_CreateDropBase):
657    """Represent a DROP INDEX statement."""
658
659    __visit_name__ = "drop_index"
660
661
662class AddConstraint(_CreateDropBase):
663    """Represent an ALTER TABLE ADD CONSTRAINT statement."""
664
665    __visit_name__ = "add_constraint"
666
667    def __init__(self, element, *args, **kw):
668        super(AddConstraint, self).__init__(element, *args, **kw)
669        element._create_rule = util.portable_instancemethod(
670            self._create_rule_disable
671        )
672
673
674class DropConstraint(_CreateDropBase):
675    """Represent an ALTER TABLE DROP CONSTRAINT statement."""
676
677    __visit_name__ = "drop_constraint"
678
679    def __init__(self, element, cascade=False, **kw):
680        self.cascade = cascade
681        super(DropConstraint, self).__init__(element, **kw)
682        element._create_rule = util.portable_instancemethod(
683            self._create_rule_disable
684        )
685
686
687class SetTableComment(_CreateDropBase):
688    """Represent a COMMENT ON TABLE IS statement."""
689
690    __visit_name__ = "set_table_comment"
691
692
693class DropTableComment(_CreateDropBase):
694    """Represent a COMMENT ON TABLE '' statement.
695
696    Note this varies a lot across database backends.
697
698    """
699
700    __visit_name__ = "drop_table_comment"
701
702
703class SetColumnComment(_CreateDropBase):
704    """Represent a COMMENT ON COLUMN IS statement."""
705
706    __visit_name__ = "set_column_comment"
707
708
709class DropColumnComment(_CreateDropBase):
710    """Represent a COMMENT ON COLUMN IS NULL statement."""
711
712    __visit_name__ = "drop_column_comment"
713
714
715class DDLBase(SchemaVisitor):
716    def __init__(self, connection):
717        self.connection = connection
718
719
720class SchemaGenerator(DDLBase):
721    def __init__(
722        self, dialect, connection, checkfirst=False, tables=None, **kwargs
723    ):
724        super(SchemaGenerator, self).__init__(connection, **kwargs)
725        self.checkfirst = checkfirst
726        self.tables = tables
727        self.preparer = dialect.identifier_preparer
728        self.dialect = dialect
729        self.memo = {}
730
731    def _can_create_table(self, table):
732        self.dialect.validate_identifier(table.name)
733        effective_schema = self.connection.schema_for_object(table)
734        if effective_schema:
735            self.dialect.validate_identifier(effective_schema)
736        return not self.checkfirst or not self.dialect.has_table(
737            self.connection, table.name, schema=effective_schema
738        )
739
740    def _can_create_sequence(self, sequence):
741        effective_schema = self.connection.schema_for_object(sequence)
742
743        return self.dialect.supports_sequences and (
744            (not self.dialect.sequences_optional or not sequence.optional)
745            and (
746                not self.checkfirst
747                or not self.dialect.has_sequence(
748                    self.connection, sequence.name, schema=effective_schema
749                )
750            )
751        )
752
753    def visit_metadata(self, metadata):
754        if self.tables is not None:
755            tables = self.tables
756        else:
757            tables = list(metadata.tables.values())
758
759        collection = sort_tables_and_constraints(
760            [t for t in tables if self._can_create_table(t)]
761        )
762
763        seq_coll = [
764            s
765            for s in metadata._sequences.values()
766            if s.column is None and self._can_create_sequence(s)
767        ]
768
769        event_collection = [t for (t, fks) in collection if t is not None]
770        metadata.dispatch.before_create(
771            metadata,
772            self.connection,
773            tables=event_collection,
774            checkfirst=self.checkfirst,
775            _ddl_runner=self,
776        )
777
778        for seq in seq_coll:
779            self.traverse_single(seq, create_ok=True)
780
781        for table, fkcs in collection:
782            if table is not None:
783                self.traverse_single(
784                    table,
785                    create_ok=True,
786                    include_foreign_key_constraints=fkcs,
787                    _is_metadata_operation=True,
788                )
789            else:
790                for fkc in fkcs:
791                    self.traverse_single(fkc)
792
793        metadata.dispatch.after_create(
794            metadata,
795            self.connection,
796            tables=event_collection,
797            checkfirst=self.checkfirst,
798            _ddl_runner=self,
799        )
800
801    def visit_table(
802        self,
803        table,
804        create_ok=False,
805        include_foreign_key_constraints=None,
806        _is_metadata_operation=False,
807    ):
808        if not create_ok and not self._can_create_table(table):
809            return
810
811        table.dispatch.before_create(
812            table,
813            self.connection,
814            checkfirst=self.checkfirst,
815            _ddl_runner=self,
816            _is_metadata_operation=_is_metadata_operation,
817        )
818
819        for column in table.columns:
820            if column.default is not None:
821                self.traverse_single(column.default)
822
823        if not self.dialect.supports_alter:
824            # e.g., don't omit any foreign key constraints
825            include_foreign_key_constraints = None
826
827        self.connection.execute(
828            # fmt: off
829            CreateTable(
830                table,
831                include_foreign_key_constraints=  # noqa
832                    include_foreign_key_constraints,  # noqa
833            )
834            # fmt: on
835        )
836
837        if hasattr(table, "indexes"):
838            for index in table.indexes:
839                self.traverse_single(index)
840
841        if self.dialect.supports_comments and not self.dialect.inline_comments:
842            if table.comment is not None:
843                self.connection.execute(SetTableComment(table))
844
845            for column in table.columns:
846                if column.comment is not None:
847                    self.connection.execute(SetColumnComment(column))
848
849        table.dispatch.after_create(
850            table,
851            self.connection,
852            checkfirst=self.checkfirst,
853            _ddl_runner=self,
854            _is_metadata_operation=_is_metadata_operation,
855        )
856
857    def visit_foreign_key_constraint(self, constraint):
858        if not self.dialect.supports_alter:
859            return
860        self.connection.execute(AddConstraint(constraint))
861
862    def visit_sequence(self, sequence, create_ok=False):
863        if not create_ok and not self._can_create_sequence(sequence):
864            return
865        self.connection.execute(CreateSequence(sequence))
866
867    def visit_index(self, index):
868        self.connection.execute(CreateIndex(index))
869
870
871class SchemaDropper(DDLBase):
872    def __init__(
873        self, dialect, connection, checkfirst=False, tables=None, **kwargs
874    ):
875        super(SchemaDropper, self).__init__(connection, **kwargs)
876        self.checkfirst = checkfirst
877        self.tables = tables
878        self.preparer = dialect.identifier_preparer
879        self.dialect = dialect
880        self.memo = {}
881
882    def visit_metadata(self, metadata):
883        if self.tables is not None:
884            tables = self.tables
885        else:
886            tables = list(metadata.tables.values())
887
888        try:
889            unsorted_tables = [t for t in tables if self._can_drop_table(t)]
890            collection = list(
891                reversed(
892                    sort_tables_and_constraints(
893                        unsorted_tables,
894                        filter_fn=lambda constraint: False
895                        if not self.dialect.supports_alter
896                        or constraint.name is None
897                        else None,
898                    )
899                )
900            )
901        except exc.CircularDependencyError as err2:
902            if not self.dialect.supports_alter:
903                util.warn(
904                    "Can't sort tables for DROP; an "
905                    "unresolvable foreign key "
906                    "dependency exists between tables: %s; and backend does "
907                    "not support ALTER.  To restore at least a partial sort, "
908                    "apply use_alter=True to ForeignKey and "
909                    "ForeignKeyConstraint "
910                    "objects involved in the cycle to mark these as known "
911                    "cycles that will be ignored."
912                    % (", ".join(sorted([t.fullname for t in err2.cycles])))
913                )
914                collection = [(t, ()) for t in unsorted_tables]
915            else:
916                util.raise_(
917                    exc.CircularDependencyError(
918                        err2.args[0],
919                        err2.cycles,
920                        err2.edges,
921                        msg="Can't sort tables for DROP; an "
922                        "unresolvable foreign key "
923                        "dependency exists between tables: %s.  Please ensure "
924                        "that the ForeignKey and ForeignKeyConstraint objects "
925                        "involved in the cycle have "
926                        "names so that they can be dropped using "
927                        "DROP CONSTRAINT."
928                        % (
929                            ", ".join(
930                                sorted([t.fullname for t in err2.cycles])
931                            )
932                        ),
933                    ),
934                    from_=err2,
935                )
936
937        seq_coll = [
938            s
939            for s in metadata._sequences.values()
940            if self._can_drop_sequence(s)
941        ]
942
943        event_collection = [t for (t, fks) in collection if t is not None]
944
945        metadata.dispatch.before_drop(
946            metadata,
947            self.connection,
948            tables=event_collection,
949            checkfirst=self.checkfirst,
950            _ddl_runner=self,
951        )
952
953        for table, fkcs in collection:
954            if table is not None:
955                self.traverse_single(
956                    table,
957                    drop_ok=True,
958                    _is_metadata_operation=True,
959                    _ignore_sequences=seq_coll,
960                )
961            else:
962                for fkc in fkcs:
963                    self.traverse_single(fkc)
964
965        for seq in seq_coll:
966            self.traverse_single(seq, drop_ok=seq.column is None)
967
968        metadata.dispatch.after_drop(
969            metadata,
970            self.connection,
971            tables=event_collection,
972            checkfirst=self.checkfirst,
973            _ddl_runner=self,
974        )
975
976    def _can_drop_table(self, table):
977        self.dialect.validate_identifier(table.name)
978        effective_schema = self.connection.schema_for_object(table)
979        if effective_schema:
980            self.dialect.validate_identifier(effective_schema)
981        return not self.checkfirst or self.dialect.has_table(
982            self.connection, table.name, schema=effective_schema
983        )
984
985    def _can_drop_sequence(self, sequence):
986        effective_schema = self.connection.schema_for_object(sequence)
987        return self.dialect.supports_sequences and (
988            (not self.dialect.sequences_optional or not sequence.optional)
989            and (
990                not self.checkfirst
991                or self.dialect.has_sequence(
992                    self.connection, sequence.name, schema=effective_schema
993                )
994            )
995        )
996
997    def visit_index(self, index):
998        self.connection.execute(DropIndex(index))
999
1000    def visit_table(
1001        self,
1002        table,
1003        drop_ok=False,
1004        _is_metadata_operation=False,
1005        _ignore_sequences=[],
1006    ):
1007        if not drop_ok and not self._can_drop_table(table):
1008            return
1009
1010        table.dispatch.before_drop(
1011            table,
1012            self.connection,
1013            checkfirst=self.checkfirst,
1014            _ddl_runner=self,
1015            _is_metadata_operation=_is_metadata_operation,
1016        )
1017
1018        self.connection.execute(DropTable(table))
1019
1020        # traverse client side defaults which may refer to server-side
1021        # sequences. noting that some of these client side defaults may also be
1022        # set up as server side defaults (see http://docs.sqlalchemy.org/en/
1023        # latest/core/defaults.html#associating-a-sequence-as-the-server-side-
1024        # default), so have to be dropped after the table is dropped.
1025        for column in table.columns:
1026            if (
1027                column.default is not None
1028                and column.default not in _ignore_sequences
1029            ):
1030                self.traverse_single(column.default)
1031
1032        table.dispatch.after_drop(
1033            table,
1034            self.connection,
1035            checkfirst=self.checkfirst,
1036            _ddl_runner=self,
1037            _is_metadata_operation=_is_metadata_operation,
1038        )
1039
1040    def visit_foreign_key_constraint(self, constraint):
1041        if not self.dialect.supports_alter:
1042            return
1043        self.connection.execute(DropConstraint(constraint))
1044
1045    def visit_sequence(self, sequence, drop_ok=False):
1046
1047        if not drop_ok and not self._can_drop_sequence(sequence):
1048            return
1049        self.connection.execute(DropSequence(sequence))
1050
1051
1052def sort_tables(
1053    tables,
1054    skip_fn=None,
1055    extra_dependencies=None,
1056):
1057    """Sort a collection of :class:`_schema.Table` objects based on
1058    dependency.
1059
1060    This is a dependency-ordered sort which will emit :class:`_schema.Table`
1061    objects such that they will follow their dependent :class:`_schema.Table`
1062    objects.
1063    Tables are dependent on another based on the presence of
1064    :class:`_schema.ForeignKeyConstraint`
1065    objects as well as explicit dependencies
1066    added by :meth:`_schema.Table.add_is_dependent_on`.
1067
1068    .. warning::
1069
1070        The :func:`._schema.sort_tables` function cannot by itself
1071        accommodate automatic resolution of dependency cycles between
1072        tables, which are usually caused by mutually dependent foreign key
1073        constraints. When these cycles are detected, the foreign keys
1074        of these tables are omitted from consideration in the sort.
1075        A warning is emitted when this condition occurs, which will be an
1076        exception raise in a future release.   Tables which are not part
1077        of the cycle will still be returned in dependency order.
1078
1079        To resolve these cycles, the
1080        :paramref:`_schema.ForeignKeyConstraint.use_alter` parameter may be
1081        applied to those constraints which create a cycle.  Alternatively,
1082        the :func:`_schema.sort_tables_and_constraints` function will
1083        automatically return foreign key constraints in a separate
1084        collection when cycles are detected so that they may be applied
1085        to a schema separately.
1086
1087        .. versionchanged:: 1.3.17 - a warning is emitted when
1088           :func:`_schema.sort_tables` cannot perform a proper sort due to
1089           cyclical dependencies.  This will be an exception in a future
1090           release.  Additionally, the sort will continue to return
1091           other tables not involved in the cycle in dependency order
1092           which was not the case previously.
1093
1094    :param tables: a sequence of :class:`_schema.Table` objects.
1095
1096    :param skip_fn: optional callable which will be passed a
1097     :class:`_schema.ForeignKey` object; if it returns True, this
1098     constraint will not be considered as a dependency.  Note this is
1099     **different** from the same parameter in
1100     :func:`.sort_tables_and_constraints`, which is
1101     instead passed the owning :class:`_schema.ForeignKeyConstraint` object.
1102
1103    :param extra_dependencies: a sequence of 2-tuples of tables which will
1104     also be considered as dependent on each other.
1105
1106    .. seealso::
1107
1108        :func:`.sort_tables_and_constraints`
1109
1110        :attr:`_schema.MetaData.sorted_tables` - uses this function to sort
1111
1112
1113    """
1114
1115    if skip_fn is not None:
1116
1117        def _skip_fn(fkc):
1118            for fk in fkc.elements:
1119                if skip_fn(fk):
1120                    return True
1121            else:
1122                return None
1123
1124    else:
1125        _skip_fn = None
1126
1127    return [
1128        t
1129        for (t, fkcs) in sort_tables_and_constraints(
1130            tables,
1131            filter_fn=_skip_fn,
1132            extra_dependencies=extra_dependencies,
1133            _warn_for_cycles=True,
1134        )
1135        if t is not None
1136    ]
1137
1138
1139def sort_tables_and_constraints(
1140    tables, filter_fn=None, extra_dependencies=None, _warn_for_cycles=False
1141):
1142    """Sort a collection of :class:`_schema.Table`  /
1143    :class:`_schema.ForeignKeyConstraint`
1144    objects.
1145
1146    This is a dependency-ordered sort which will emit tuples of
1147    ``(Table, [ForeignKeyConstraint, ...])`` such that each
1148    :class:`_schema.Table` follows its dependent :class:`_schema.Table`
1149    objects.
1150    Remaining :class:`_schema.ForeignKeyConstraint`
1151    objects that are separate due to
1152    dependency rules not satisfied by the sort are emitted afterwards
1153    as ``(None, [ForeignKeyConstraint ...])``.
1154
1155    Tables are dependent on another based on the presence of
1156    :class:`_schema.ForeignKeyConstraint` objects, explicit dependencies
1157    added by :meth:`_schema.Table.add_is_dependent_on`,
1158    as well as dependencies
1159    stated here using the :paramref:`~.sort_tables_and_constraints.skip_fn`
1160    and/or :paramref:`~.sort_tables_and_constraints.extra_dependencies`
1161    parameters.
1162
1163    :param tables: a sequence of :class:`_schema.Table` objects.
1164
1165    :param filter_fn: optional callable which will be passed a
1166     :class:`_schema.ForeignKeyConstraint` object,
1167     and returns a value based on
1168     whether this constraint should definitely be included or excluded as
1169     an inline constraint, or neither.   If it returns False, the constraint
1170     will definitely be included as a dependency that cannot be subject
1171     to ALTER; if True, it will **only** be included as an ALTER result at
1172     the end.   Returning None means the constraint is included in the
1173     table-based result unless it is detected as part of a dependency cycle.
1174
1175    :param extra_dependencies: a sequence of 2-tuples of tables which will
1176     also be considered as dependent on each other.
1177
1178    .. versionadded:: 1.0.0
1179
1180    .. seealso::
1181
1182        :func:`.sort_tables`
1183
1184
1185    """
1186
1187    fixed_dependencies = set()
1188    mutable_dependencies = set()
1189
1190    if extra_dependencies is not None:
1191        fixed_dependencies.update(extra_dependencies)
1192
1193    remaining_fkcs = set()
1194    for table in tables:
1195        for fkc in table.foreign_key_constraints:
1196            if fkc.use_alter is True:
1197                remaining_fkcs.add(fkc)
1198                continue
1199
1200            if filter_fn:
1201                filtered = filter_fn(fkc)
1202
1203                if filtered is True:
1204                    remaining_fkcs.add(fkc)
1205                    continue
1206
1207            dependent_on = fkc.referred_table
1208            if dependent_on is not table:
1209                mutable_dependencies.add((dependent_on, table))
1210
1211        fixed_dependencies.update(
1212            (parent, table) for parent in table._extra_dependencies
1213        )
1214
1215    try:
1216        candidate_sort = list(
1217            topological.sort(
1218                fixed_dependencies.union(mutable_dependencies),
1219                tables,
1220                deterministic_order=True,
1221            )
1222        )
1223    except exc.CircularDependencyError as err:
1224        if _warn_for_cycles:
1225            util.warn(
1226                "Cannot correctly sort tables; there are unresolvable cycles "
1227                'between tables "%s", which is usually caused by mutually '
1228                "dependent foreign key constraints.  Foreign key constraints "
1229                "involving these tables will not be considered; this warning "
1230                "may raise an error in a future release."
1231                % (", ".join(sorted(t.fullname for t in err.cycles)),)
1232            )
1233        for edge in err.edges:
1234            if edge in mutable_dependencies:
1235                table = edge[1]
1236                if table not in err.cycles:
1237                    continue
1238                can_remove = [
1239                    fkc
1240                    for fkc in table.foreign_key_constraints
1241                    if filter_fn is None or filter_fn(fkc) is not False
1242                ]
1243                remaining_fkcs.update(can_remove)
1244                for fkc in can_remove:
1245                    dependent_on = fkc.referred_table
1246                    if dependent_on is not table:
1247                        mutable_dependencies.discard((dependent_on, table))
1248        candidate_sort = list(
1249            topological.sort(
1250                fixed_dependencies.union(mutable_dependencies),
1251                tables,
1252                deterministic_order=True,
1253            )
1254        )
1255
1256    return [
1257        (table, table.foreign_key_constraints.difference(remaining_fkcs))
1258        for table in candidate_sort
1259    ] + [(None, list(remaining_fkcs))]
1260