Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/sqlalchemy/sql/ddl.py: 52%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

525 statements  

1# sql/ddl.py 

2# Copyright (C) 2009-2025 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: https://www.opensource.org/licenses/mit-license.php 

7# mypy: allow-untyped-defs, allow-untyped-calls 

8 

9""" 

10Provides the hierarchy of DDL-defining schema items as well as routines 

11to invoke them for a create/drop call. 

12 

13""" 

14from __future__ import annotations 

15 

16import contextlib 

17from enum import auto 

18from enum import Flag 

19import typing 

20from typing import Any 

21from typing import Callable 

22from typing import Generic 

23from typing import Iterable 

24from typing import List 

25from typing import Optional 

26from typing import Protocol 

27from typing import Sequence as typing_Sequence 

28from typing import Tuple 

29from typing import TypeVar 

30from typing import Union 

31 

32from . import coercions 

33from . import roles 

34from . import util as sql_util 

35from .base import _generative 

36from .base import DialectKWArgs 

37from .base import Executable 

38from .base import SchemaVisitor 

39from .elements import ClauseElement 

40from .selectable import SelectBase 

41from .selectable import TableClause 

42from .. import exc 

43from .. import util 

44from ..util import topological 

45from ..util.typing import Self 

46 

47 

48if typing.TYPE_CHECKING: 

49 from .compiler import Compiled 

50 from .compiler import DDLCompiler 

51 from .elements import BindParameter 

52 from .schema import Column 

53 from .schema import Constraint 

54 from .schema import ForeignKeyConstraint 

55 from .schema import Index 

56 from .schema import MetaData 

57 from .schema import SchemaItem 

58 from .schema import Sequence as Sequence # noqa: F401 

59 from .schema import Table 

60 from ..engine.base import Connection 

61 from ..engine.interfaces import _CoreSingleExecuteParams 

62 from ..engine.interfaces import CacheStats 

63 from ..engine.interfaces import CompiledCacheType 

64 from ..engine.interfaces import Dialect 

65 from ..engine.interfaces import SchemaTranslateMapType 

66 

67_SI = TypeVar("_SI", bound=Union["SchemaItem", str]) 

68 

69 

70class BaseDDLElement(ClauseElement): 

71 """The root of DDL constructs, including those that are sub-elements 

72 within the "create table" and other processes. 

73 

74 .. versionadded:: 2.0 

75 

76 """ 

77 

78 _hierarchy_supports_caching = False 

79 """disable cache warnings for all _DDLCompiles subclasses. """ 

80 

81 def _compiler(self, dialect, **kw): 

82 """Return a compiler appropriate for this ClauseElement, given a 

83 Dialect.""" 

84 

85 return dialect.ddl_compiler(dialect, self, **kw) 

86 

87 def _compile_w_cache( 

88 self, 

89 dialect: Dialect, 

90 *, 

91 compiled_cache: Optional[CompiledCacheType], 

92 column_keys: List[str], 

93 for_executemany: bool = False, 

94 schema_translate_map: Optional[SchemaTranslateMapType] = None, 

95 **kw: Any, 

96 ) -> tuple[ 

97 Compiled, 

98 typing_Sequence[BindParameter[Any]] | None, 

99 _CoreSingleExecuteParams | None, 

100 CacheStats, 

101 ]: 

102 raise NotImplementedError() 

103 

104 

105class DDLIfCallable(Protocol): 

106 def __call__( 

107 self, 

108 ddl: BaseDDLElement, 

109 target: Union[SchemaItem, str], 

110 bind: Optional[Connection], 

111 tables: Optional[List[Table]] = None, 

112 state: Optional[Any] = None, 

113 *, 

114 dialect: Dialect, 

115 compiler: Optional[DDLCompiler] = ..., 

116 checkfirst: bool, 

117 ) -> bool: ... 

118 

119 

120class DDLIf(typing.NamedTuple): 

121 dialect: Optional[str] 

122 callable_: Optional[DDLIfCallable] 

123 state: Optional[Any] 

124 

125 def _should_execute( 

126 self, 

127 ddl: BaseDDLElement, 

128 target: Union[SchemaItem, str], 

129 bind: Optional[Connection], 

130 compiler: Optional[DDLCompiler] = None, 

131 **kw: Any, 

132 ) -> bool: 

133 if bind is not None: 

134 dialect = bind.dialect 

135 elif compiler is not None: 

136 dialect = compiler.dialect 

137 else: 

138 assert False, "compiler or dialect is required" 

139 

140 if isinstance(self.dialect, str): 

141 if self.dialect != dialect.name: 

142 return False 

143 elif isinstance(self.dialect, (tuple, list, set)): 

144 if dialect.name not in self.dialect: 

145 return False 

146 if self.callable_ is not None and not self.callable_( 

147 ddl, 

148 target, 

149 bind, 

150 state=self.state, 

151 dialect=dialect, 

152 compiler=compiler, 

153 **kw, 

154 ): 

155 return False 

156 

157 return True 

158 

159 

160class ExecutableDDLElement(roles.DDLRole, Executable, BaseDDLElement): 

161 """Base class for standalone executable DDL expression constructs. 

162 

163 This class is the base for the general purpose :class:`.DDL` class, 

164 as well as the various create/drop clause constructs such as 

165 :class:`.CreateTable`, :class:`.DropTable`, :class:`.AddConstraint`, 

166 etc. 

167 

168 .. versionchanged:: 2.0 :class:`.ExecutableDDLElement` is renamed from 

169 :class:`.DDLElement`, which still exists for backwards compatibility. 

170 

171 :class:`.ExecutableDDLElement` integrates closely with SQLAlchemy events, 

172 introduced in :ref:`event_toplevel`. An instance of one is 

173 itself an event receiving callable:: 

174 

175 event.listen( 

176 users, 

177 "after_create", 

178 AddConstraint(constraint).execute_if(dialect="postgresql"), 

179 ) 

180 

181 .. seealso:: 

182 

183 :class:`.DDL` 

184 

185 :class:`.DDLEvents` 

186 

187 :ref:`event_toplevel` 

188 

189 :ref:`schema_ddl_sequences` 

190 

191 """ 

192 

193 _ddl_if: Optional[DDLIf] = None 

194 target: Union[SchemaItem, str, None] = None 

195 

196 def _execute_on_connection( 

197 self, connection, distilled_params, execution_options 

198 ): 

199 return connection._execute_ddl( 

200 self, distilled_params, execution_options 

201 ) 

202 

203 @_generative 

204 def against(self, target: SchemaItem) -> Self: 

205 """Return a copy of this :class:`_schema.ExecutableDDLElement` which 

206 will include the given target. 

207 

208 This essentially applies the given item to the ``.target`` attribute of 

209 the returned :class:`_schema.ExecutableDDLElement` object. This target 

210 is then usable by event handlers and compilation routines in order to 

211 provide services such as tokenization of a DDL string in terms of a 

212 particular :class:`_schema.Table`. 

213 

214 When a :class:`_schema.ExecutableDDLElement` object is established as 

215 an event handler for the :meth:`_events.DDLEvents.before_create` or 

216 :meth:`_events.DDLEvents.after_create` events, and the event then 

217 occurs for a given target such as a :class:`_schema.Constraint` or 

218 :class:`_schema.Table`, that target is established with a copy of the 

219 :class:`_schema.ExecutableDDLElement` object using this method, which 

220 then proceeds to the :meth:`_schema.ExecutableDDLElement.execute` 

221 method in order to invoke the actual DDL instruction. 

222 

223 :param target: a :class:`_schema.SchemaItem` that will be the subject 

224 of a DDL operation. 

225 

226 :return: a copy of this :class:`_schema.ExecutableDDLElement` with the 

227 ``.target`` attribute assigned to the given 

228 :class:`_schema.SchemaItem`. 

229 

230 .. seealso:: 

231 

232 :class:`_schema.DDL` - uses tokenization against the "target" when 

233 processing the DDL string. 

234 

235 """ 

236 self.target = target 

237 return self 

238 

239 @_generative 

240 def execute_if( 

241 self, 

242 dialect: Optional[str] = None, 

243 callable_: Optional[DDLIfCallable] = None, 

244 state: Optional[Any] = None, 

245 ) -> Self: 

246 r"""Return a callable that will execute this 

247 :class:`_ddl.ExecutableDDLElement` conditionally within an event 

248 handler. 

249 

250 Used to provide a wrapper for event listening:: 

251 

252 event.listen( 

253 metadata, 

254 "before_create", 

255 DDL("my_ddl").execute_if(dialect="postgresql"), 

256 ) 

257 

258 :param dialect: May be a string or tuple of strings. 

259 If a string, it will be compared to the name of the 

260 executing database dialect:: 

261 

262 DDL("something").execute_if(dialect="postgresql") 

263 

264 If a tuple, specifies multiple dialect names:: 

265 

266 DDL("something").execute_if(dialect=("postgresql", "mysql")) 

267 

268 :param callable\_: A callable, which will be invoked with 

269 three positional arguments as well as optional keyword 

270 arguments: 

271 

272 :ddl: 

273 This DDL element. 

274 

275 :target: 

276 The :class:`_schema.Table` or :class:`_schema.MetaData` 

277 object which is the 

278 target of this event. May be None if the DDL is executed 

279 explicitly. 

280 

281 :bind: 

282 The :class:`_engine.Connection` being used for DDL execution. 

283 May be None if this construct is being created inline within 

284 a table, in which case ``compiler`` will be present. 

285 

286 :tables: 

287 Optional keyword argument - a list of Table objects which are to 

288 be created/ dropped within a MetaData.create_all() or drop_all() 

289 method call. 

290 

291 :dialect: keyword argument, but always present - the 

292 :class:`.Dialect` involved in the operation. 

293 

294 :compiler: keyword argument. Will be ``None`` for an engine 

295 level DDL invocation, but will refer to a :class:`.DDLCompiler` 

296 if this DDL element is being created inline within a table. 

297 

298 :state: 

299 Optional keyword argument - will be the ``state`` argument 

300 passed to this function. 

301 

302 :checkfirst: 

303 Keyword argument, will be True if the 'checkfirst' flag was 

304 set during the call to ``create()``, ``create_all()``, 

305 ``drop()``, ``drop_all()``. 

306 

307 If the callable returns a True value, the DDL statement will be 

308 executed. 

309 

310 :param state: any value which will be passed to the callable\_ 

311 as the ``state`` keyword argument. 

312 

313 .. seealso:: 

314 

315 :meth:`.SchemaItem.ddl_if` 

316 

317 :class:`.DDLEvents` 

318 

319 :ref:`event_toplevel` 

320 

321 """ 

322 self._ddl_if = DDLIf(dialect, callable_, state) 

323 return self 

324 

325 def _should_execute(self, target, bind, **kw): 

326 if self._ddl_if is None: 

327 return True 

328 else: 

329 return self._ddl_if._should_execute(self, target, bind, **kw) 

330 

331 def _invoke_with(self, bind): 

332 if self._should_execute(self.target, bind): 

333 return bind.execute(self) 

334 

335 def __call__(self, target, bind, **kw): 

336 """Execute the DDL as a ddl_listener.""" 

337 

338 self.against(target)._invoke_with(bind) 

339 

340 def _generate(self): 

341 s = self.__class__.__new__(self.__class__) 

342 s.__dict__ = self.__dict__.copy() 

343 return s 

344 

345 

346DDLElement = ExecutableDDLElement 

347""":class:`.DDLElement` is renamed to :class:`.ExecutableDDLElement`.""" 

348 

349 

350class DDL(ExecutableDDLElement): 

351 """A literal DDL statement. 

352 

353 Specifies literal SQL DDL to be executed by the database. DDL objects 

354 function as DDL event listeners, and can be subscribed to those events 

355 listed in :class:`.DDLEvents`, using either :class:`_schema.Table` or 

356 :class:`_schema.MetaData` objects as targets. 

357 Basic templating support allows 

358 a single DDL instance to handle repetitive tasks for multiple tables. 

359 

360 Examples:: 

361 

362 from sqlalchemy import event, DDL 

363 

364 tbl = Table("users", metadata, Column("uid", Integer)) 

365 event.listen(tbl, "before_create", DDL("DROP TRIGGER users_trigger")) 

366 

367 spow = DDL("ALTER TABLE %(table)s SET secretpowers TRUE") 

368 event.listen(tbl, "after_create", spow.execute_if(dialect="somedb")) 

369 

370 drop_spow = DDL("ALTER TABLE users SET secretpowers FALSE") 

371 connection.execute(drop_spow) 

372 

373 When operating on Table events, the following ``statement`` 

374 string substitutions are available: 

375 

376 .. sourcecode:: text 

377 

378 %(table)s - the Table name, with any required quoting applied 

379 %(schema)s - the schema name, with any required quoting applied 

380 %(fullname)s - the Table name including schema, quoted if needed 

381 

382 The DDL's "context", if any, will be combined with the standard 

383 substitutions noted above. Keys present in the context will override 

384 the standard substitutions. 

385 

386 """ 

387 

388 __visit_name__ = "ddl" 

389 

390 def __init__(self, statement, context=None): 

391 """Create a DDL statement. 

392 

393 :param statement: 

394 A string or unicode string to be executed. Statements will be 

395 processed with Python's string formatting operator using 

396 a fixed set of string substitutions, as well as additional 

397 substitutions provided by the optional :paramref:`.DDL.context` 

398 parameter. 

399 

400 A literal '%' in a statement must be escaped as '%%'. 

401 

402 SQL bind parameters are not available in DDL statements. 

403 

404 :param context: 

405 Optional dictionary, defaults to None. These values will be 

406 available for use in string substitutions on the DDL statement. 

407 

408 .. seealso:: 

409 

410 :class:`.DDLEvents` 

411 

412 :ref:`event_toplevel` 

413 

414 """ 

415 

416 if not isinstance(statement, str): 

417 raise exc.ArgumentError( 

418 "Expected a string or unicode SQL statement, got '%r'" 

419 % statement 

420 ) 

421 

422 self.statement = statement 

423 self.context = context or {} 

424 

425 def __repr__(self): 

426 parts = [repr(self.statement)] 

427 if self.context: 

428 parts.append(f"context={self.context}") 

429 

430 return "<%s@%s; %s>" % ( 

431 type(self).__name__, 

432 id(self), 

433 ", ".join(parts), 

434 ) 

435 

436 

437class _CreateDropBase(ExecutableDDLElement, Generic[_SI]): 

438 """Base class for DDL constructs that represent CREATE and DROP or 

439 equivalents. 

440 

441 The common theme of _CreateDropBase is a single 

442 ``element`` attribute which refers to the element 

443 to be created or dropped. 

444 

445 """ 

446 

447 element: _SI 

448 

449 def __init__(self, element: _SI) -> None: 

450 self.element = self.target = element 

451 self._ddl_if = getattr(element, "_ddl_if", None) 

452 

453 @property 

454 def stringify_dialect(self): # type: ignore[override] 

455 assert not isinstance(self.element, str) 

456 return self.element.create_drop_stringify_dialect 

457 

458 def _create_rule_disable(self, compiler): 

459 """Allow disable of _create_rule using a callable. 

460 

461 Pass to _create_rule using 

462 util.portable_instancemethod(self._create_rule_disable) 

463 to retain serializability. 

464 

465 """ 

466 return False 

467 

468 

469class _CreateBase(_CreateDropBase[_SI]): 

470 def __init__(self, element: _SI, if_not_exists: bool = False) -> None: 

471 super().__init__(element) 

472 self.if_not_exists = if_not_exists 

473 

474 

475class TableCreateDDL(_CreateBase["Table"]): 

476 

477 def to_metadata(self, metadata: MetaData, table: Table) -> Self: 

478 raise NotImplementedError() 

479 

480 

481class _DropBase(_CreateDropBase[_SI]): 

482 def __init__(self, element: _SI, if_exists: bool = False) -> None: 

483 super().__init__(element) 

484 self.if_exists = if_exists 

485 

486 

487class TableDropDDL(_DropBase["Table"]): 

488 

489 def to_metadata(self, metadata: MetaData, table: Table) -> Self: 

490 raise NotImplementedError() 

491 

492 

493class CreateSchema(_CreateBase[str]): 

494 """Represent a CREATE SCHEMA statement. 

495 

496 The argument here is the string name of the schema. 

497 

498 """ 

499 

500 __visit_name__ = "create_schema" 

501 

502 stringify_dialect = "default" 

503 

504 def __init__( 

505 self, 

506 name: str, 

507 if_not_exists: bool = False, 

508 ) -> None: 

509 """Create a new :class:`.CreateSchema` construct.""" 

510 

511 super().__init__(element=name, if_not_exists=if_not_exists) 

512 

513 

514class DropSchema(_DropBase[str]): 

515 """Represent a DROP SCHEMA statement. 

516 

517 The argument here is the string name of the schema. 

518 

519 """ 

520 

521 __visit_name__ = "drop_schema" 

522 

523 stringify_dialect = "default" 

524 

525 def __init__( 

526 self, 

527 name: str, 

528 cascade: bool = False, 

529 if_exists: bool = False, 

530 ) -> None: 

531 """Create a new :class:`.DropSchema` construct.""" 

532 

533 super().__init__(element=name, if_exists=if_exists) 

534 self.cascade = cascade 

535 

536 

537class CreateTable(TableCreateDDL): 

538 """Represent a CREATE TABLE statement.""" 

539 

540 __visit_name__ = "create_table" 

541 

542 def __init__( 

543 self, 

544 element: Table, 

545 include_foreign_key_constraints: Optional[ 

546 typing_Sequence[ForeignKeyConstraint] 

547 ] = None, 

548 if_not_exists: bool = False, 

549 ) -> None: 

550 """Create a :class:`.CreateTable` construct. 

551 

552 :param element: a :class:`_schema.Table` that's the subject 

553 of the CREATE 

554 :param on: See the description for 'on' in :class:`.DDL`. 

555 :param include_foreign_key_constraints: optional sequence of 

556 :class:`_schema.ForeignKeyConstraint` objects that will be included 

557 inline within the CREATE construct; if omitted, all foreign key 

558 constraints that do not specify use_alter=True are included. 

559 

560 :param if_not_exists: if True, an IF NOT EXISTS operator will be 

561 applied to the construct. 

562 

563 .. versionadded:: 1.4.0b2 

564 

565 """ 

566 super().__init__(element, if_not_exists=if_not_exists) 

567 self.columns = [CreateColumn(column) for column in element.columns] 

568 self.include_foreign_key_constraints = include_foreign_key_constraints 

569 

570 def to_metadata(self, metadata: MetaData, table: Table) -> Self: 

571 return self.__class__(table, if_not_exists=self.if_not_exists) 

572 

573 

574class _TableViaSelect(TableCreateDDL, ExecutableDDLElement): 

575 """Common base class for DDL constructs that generate and render for a 

576 :class:`.Table` given a :class:`.Select` 

577 

578 .. versionadded:: 2.1 

579 

580 """ 

581 

582 table: Table 

583 """:class:`.Table` object representing the table that this 

584 :class:`.CreateTableAs` would generate when executed.""" 

585 

586 def __init__( 

587 self, 

588 selectable: SelectBase, 

589 name: str, 

590 *, 

591 metadata: Optional["MetaData"] = None, 

592 schema: Optional[str] = None, 

593 temporary: bool = False, 

594 if_not_exists: bool = False, 

595 ): 

596 # Coerce selectable to a Select statement 

597 selectable = coercions.expect(roles.DMLSelectRole, selectable) 

598 

599 self.schema = schema 

600 self.selectable = selectable 

601 self.temporary = bool(temporary) 

602 self.if_not_exists = bool(if_not_exists) 

603 self.metadata = metadata 

604 self.table_name = name 

605 self._gen_table() 

606 

607 @property 

608 def element(self): # type: ignore 

609 return self.table 

610 

611 def to_metadata(self, metadata: MetaData, table: Table) -> Self: 

612 new = self.__class__.__new__(self.__class__) 

613 new.__dict__.update(self.__dict__) 

614 new.metadata = metadata 

615 new.table = table 

616 return new 

617 

618 @util.preload_module("sqlalchemy.sql.schema") 

619 def _gen_table(self) -> None: 

620 MetaData = util.preloaded.sql_schema.MetaData 

621 Column = util.preloaded.sql_schema.Column 

622 Table = util.preloaded.sql_schema.Table 

623 MetaData = util.preloaded.sql_schema.MetaData 

624 

625 column_name_type_pairs = ( 

626 (name, col_element.type) 

627 for _, name, _, col_element, _ in ( 

628 self.selectable._generate_columns_plus_names( 

629 anon_for_dupe_key=False 

630 ) 

631 ) 

632 ) 

633 

634 if self.metadata is None: 

635 self.metadata = metadata = MetaData() 

636 else: 

637 metadata = self.metadata 

638 

639 self.table = Table( 

640 self.table_name, 

641 metadata, 

642 *(Column(name, typ) for name, typ in column_name_type_pairs), 

643 schema=self.schema, 

644 _creator_ddl=self, 

645 ) 

646 

647 

648class CreateTableAs(DialectKWArgs, _TableViaSelect): 

649 """Represent a CREATE TABLE ... AS statement. 

650 

651 This creates a new table directly from the output of a SELECT, including 

652 its schema and its initial set of data. Unlike a view, the 

653 new table is fixed and does not synchronize further with the originating 

654 SELECT statement. 

655 

656 The example below illustrates basic use of :class:`.CreateTableAs`; given a 

657 :class:`.Select` and optional :class:`.MetaData`, the 

658 :class:`.CreateTableAs` may be invoked directly via 

659 :meth:`.Connection.execute` or indirectly via :meth:`.MetaData.create_all`; 

660 the :attr:`.CreateTableAs.table` attribute provides a :class:`.Table` 

661 object with which to generate new queries:: 

662 

663 from sqlalchemy import CreateTableAs 

664 from sqlalchemy import select 

665 

666 # instantiate CreateTableAs given a select() and optional MetaData 

667 cas = CreateTableAs( 

668 select(users.c.id, users.c.name).where(users.c.status == "active"), 

669 "active_users", 

670 metadata=some_metadata, 

671 ) 

672 

673 # a Table object is available immediately via the .table attribute 

674 new_statement = select(cas.table) 

675 

676 # to emit CREATE TABLE AS, either invoke CreateTableAs directly... 

677 with engine.begin() as conn: 

678 conn.execute(cas) 

679 

680 # or alternatively, invoke metadata.create_all() 

681 some_metdata.create_all(engine) 

682 

683 # drop is performed in the usual way, via drop_all 

684 # or table.drop() 

685 some_metdata.drop_all(engine) 

686 

687 For detailed background on :class:`.CreateTableAs` see 

688 :ref:`metadata_create_table_as`. 

689 

690 .. versionadded:: 2.1 

691 

692 :param selectable: :class:`_sql.Select` 

693 The SELECT statement providing the columns and rows. 

694 

695 :param table_name: table name as a string. Combine with the optional 

696 :paramref:`.CreateTableAs.schema` parameter to indicate a 

697 schema-qualified table name. 

698 

699 :param metadata: :class:`_schema.MetaData`, optional 

700 If provided, the :class:`_schema.Table` object available via the 

701 :attr:`.table` attribute will be associated with this 

702 :class:`.MetaData`. Otherwise, a new, empty :class:`.MetaData` 

703 is created. 

704 

705 :param schema: str, optional schema or owner name. 

706 

707 :param temporary: bool, default False. 

708 If True, render ``TEMPORARY`` 

709 

710 :param if_not_exists: bool, default False. 

711 If True, render ``IF NOT EXISTS`` 

712 

713 .. seealso:: 

714 

715 :ref:`metadata_create_table_as` - in :ref:`metadata_toplevel` 

716 

717 :meth:`_sql.SelectBase.into` - convenience method to create a 

718 :class:`_schema.CreateTableAs` from a SELECT statement 

719 

720 :class:`.CreateView` 

721 

722 

723 """ 

724 

725 __visit_name__ = "create_table_as" 

726 inherit_cache = False 

727 

728 table: Table 

729 """:class:`.Table` object representing the table that this 

730 :class:`.CreateTableAs` would generate when executed.""" 

731 

732 def __init__( 

733 self, 

734 selectable: SelectBase, 

735 table_name: str, 

736 *, 

737 metadata: Optional["MetaData"] = None, 

738 schema: Optional[str] = None, 

739 temporary: bool = False, 

740 if_not_exists: bool = False, 

741 **dialect_kwargs: Any, 

742 ): 

743 self._validate_dialect_kwargs(dialect_kwargs) 

744 super().__init__( 

745 selectable=selectable, 

746 name=table_name, 

747 metadata=metadata, 

748 schema=schema, 

749 temporary=temporary, 

750 if_not_exists=if_not_exists, 

751 ) 

752 

753 

754class CreateView(DialectKWArgs, _TableViaSelect): 

755 """Represent a CREATE VIEW statement. 

756 

757 This creates a new view based on a particular SELECT statement. The schema 

758 of the view is based on the columns of the SELECT statement, and the data 

759 present in the view is derived from the rows represented by the 

760 SELECT. A non-materialized view will evaluate the SELECT statement 

761 dynamicaly as it is queried, whereas a materialized view represents a 

762 snapshot of the SELECT statement at a particular point in time and 

763 typically needs to be refreshed manually using database-specific commands. 

764 

765 The example below illustrates basic use of :class:`.CreateView`; given a 

766 :class:`.Select` and optional :class:`.MetaData`, the 

767 :class:`.CreateView` may be invoked directly via 

768 :meth:`.Connection.execute` or indirectly via :meth:`.MetaData.create_all`; 

769 the :attr:`.CreateView.table` attribute provides a :class:`.Table` 

770 object with which to generate new queries:: 

771 

772 

773 from sqlalchemy import select 

774 from sqlalchemy.sql.ddl import CreateView 

775 

776 # instantiate CreateView given a select() and optional MetaData 

777 create_view = CreateView( 

778 select(users.c.id, users.c.name).where(users.c.status == "active"), 

779 "active_users_view", 

780 metadata=some_metadata, 

781 ) 

782 

783 # a Table object is available immediately via the .table attribute 

784 new_statement = select(create_view.table) 

785 

786 # to emit CREATE VIEW, either invoke CreateView directly... 

787 with engine.begin() as conn: 

788 conn.execute(create_view) 

789 

790 # or alternatively, invoke metadata.create_all() 

791 some_metdata.create_all(engine) 

792 

793 # drop is performed in the usual way, via drop_all 

794 # or table.drop() (will emit DROP VIEW) 

795 some_metdata.drop_all(engine) 

796 

797 For detailed background on :class:`.CreateView` see 

798 :ref:`metadata_create_view`. 

799 

800 .. versionadded:: 2.1 

801 

802 :param selectable: :class:`_sql.Select` 

803 The SELECT statement defining the view. 

804 

805 :param view_name: table name as a string. Combine with the optional 

806 :paramref:`.CreateView.schema` parameter to indicate a 

807 schema-qualified table name. 

808 

809 :param metadata: :class:`_schema.MetaData`, optional 

810 If provided, the :class:`_schema.Table` object available via the 

811 :attr:`.table` attribute will be associated with this 

812 :class:`.MetaData`. Otherwise, a new, empty :class:`.MetaData` 

813 is created. 

814 

815 :param schema: str, optional schema or owner name. 

816 

817 :param temporary: bool, default False. 

818 If True, render ``TEMPORARY`` 

819 

820 :param or_replace: bool, default False. 

821 If True, render ``OR REPLACE`` to replace an existing view if it 

822 exists. Supported by PostgreSQL, MySQL, MariaDB, and Oracle. 

823 Not supported by SQLite or SQL Server. 

824 

825 .. versionadded:: 2.1 

826 

827 :param materialized: bool, default False. 

828 If True, render ``MATERIALIZED`` to create a materialized view. 

829 Materialized views store the query results physically and can be 

830 refreshed periodically. Not supported by all database backends. 

831 

832 .. versionadded:: 2.1 

833 

834 :param dialect_kw: Additional keyword arguments are dialect-specific and 

835 are passed as keyword arguments to the dialect's compiler. 

836 

837 .. note:: 

838 

839 For SQLite, the ``sqlite_if_not_exists`` boolean parameter 

840 is supported to render ``CREATE VIEW IF NOT EXISTS``. 

841 

842 .. versionadded:: 2.1 

843 

844 .. seealso:: 

845 

846 :ref:`metadata_create_view` - in :ref:`metadata_toplevel` 

847 

848 :class:`.CreateTableAs` - for creating a table from a SELECT statement 

849 

850 """ 

851 

852 __visit_name__ = "create_view" 

853 

854 inherit_cache = False 

855 

856 table: Table 

857 """:class:`.Table` object representing the view that this 

858 :class:`.CreateView` would generate when executed.""" 

859 

860 materialized: bool 

861 """Boolean flag indicating if this is a materialized view.""" 

862 

863 or_replace: bool 

864 """Boolean flag indicating if OR REPLACE should be used.""" 

865 

866 def __init__( 

867 self, 

868 selectable: SelectBase, 

869 view_name: str, 

870 *, 

871 metadata: Optional["MetaData"] = None, 

872 schema: Optional[str] = None, 

873 temporary: bool = False, 

874 or_replace: bool = False, 

875 materialized: bool = False, 

876 **dialect_kwargs: Any, 

877 ): 

878 self._validate_dialect_kwargs(dialect_kwargs) 

879 super().__init__( 

880 selectable=selectable, 

881 name=view_name, 

882 metadata=metadata, 

883 schema=schema, 

884 temporary=temporary, 

885 if_not_exists=False, 

886 ) 

887 self.materialized = materialized 

888 self.or_replace = or_replace 

889 self.table._dropper_ddl = DropView( 

890 self.table, materialized=materialized 

891 ) 

892 

893 

894class DropView(TableDropDDL): 

895 """'DROP VIEW' construct. 

896 

897 .. versionadded:: 2.1 the :class:`.DropView` construct became public 

898 and was renamed from ``_DropView``. 

899 

900 """ 

901 

902 __visit_name__ = "drop_view" 

903 

904 materialized: bool 

905 """Boolean flag indicating if this is a materialized view.""" 

906 

907 def __init__( 

908 self, 

909 element: Table, 

910 *, 

911 if_exists: bool = False, 

912 materialized: bool = False, 

913 ) -> None: 

914 super().__init__(element, if_exists=if_exists) 

915 self.materialized = materialized 

916 

917 def to_metadata(self, metadata: MetaData, table: Table) -> Self: 

918 new = self.__class__.__new__(self.__class__) 

919 new.__dict__.update(self.__dict__) 

920 new.element = table 

921 return new 

922 

923 

924class CreateConstraint(BaseDDLElement): 

925 element: Constraint 

926 

927 def __init__(self, element: Constraint) -> None: 

928 self.element = element 

929 

930 

931class CreateColumn(BaseDDLElement): 

932 """Represent a :class:`_schema.Column` 

933 as rendered in a CREATE TABLE statement, 

934 via the :class:`.CreateTable` construct. 

935 

936 This is provided to support custom column DDL within the generation 

937 of CREATE TABLE statements, by using the 

938 compiler extension documented in :ref:`sqlalchemy.ext.compiler_toplevel` 

939 to extend :class:`.CreateColumn`. 

940 

941 Typical integration is to examine the incoming :class:`_schema.Column` 

942 object, and to redirect compilation if a particular flag or condition 

943 is found:: 

944 

945 from sqlalchemy import schema 

946 from sqlalchemy.ext.compiler import compiles 

947 

948 

949 @compiles(schema.CreateColumn) 

950 def compile(element, compiler, **kw): 

951 column = element.element 

952 

953 if "special" not in column.info: 

954 return compiler.visit_create_column(element, **kw) 

955 

956 text = "%s SPECIAL DIRECTIVE %s" % ( 

957 column.name, 

958 compiler.type_compiler.process(column.type), 

959 ) 

960 default = compiler.get_column_default_string(column) 

961 if default is not None: 

962 text += " DEFAULT " + default 

963 

964 if not column.nullable: 

965 text += " NOT NULL" 

966 

967 if column.constraints: 

968 text += " ".join( 

969 compiler.process(const) for const in column.constraints 

970 ) 

971 return text 

972 

973 The above construct can be applied to a :class:`_schema.Table` 

974 as follows:: 

975 

976 from sqlalchemy import Table, Metadata, Column, Integer, String 

977 from sqlalchemy import schema 

978 

979 metadata = MetaData() 

980 

981 table = Table( 

982 "mytable", 

983 MetaData(), 

984 Column("x", Integer, info={"special": True}, primary_key=True), 

985 Column("y", String(50)), 

986 Column("z", String(20), info={"special": True}), 

987 ) 

988 

989 metadata.create_all(conn) 

990 

991 Above, the directives we've added to the :attr:`_schema.Column.info` 

992 collection 

993 will be detected by our custom compilation scheme: 

994 

995 .. sourcecode:: sql 

996 

997 CREATE TABLE mytable ( 

998 x SPECIAL DIRECTIVE INTEGER NOT NULL, 

999 y VARCHAR(50), 

1000 z SPECIAL DIRECTIVE VARCHAR(20), 

1001 PRIMARY KEY (x) 

1002 ) 

1003 

1004 The :class:`.CreateColumn` construct can also be used to skip certain 

1005 columns when producing a ``CREATE TABLE``. This is accomplished by 

1006 creating a compilation rule that conditionally returns ``None``. 

1007 This is essentially how to produce the same effect as using the 

1008 ``system=True`` argument on :class:`_schema.Column`, which marks a column 

1009 as an implicitly-present "system" column. 

1010 

1011 For example, suppose we wish to produce a :class:`_schema.Table` 

1012 which skips 

1013 rendering of the PostgreSQL ``xmin`` column against the PostgreSQL 

1014 backend, but on other backends does render it, in anticipation of a 

1015 triggered rule. A conditional compilation rule could skip this name only 

1016 on PostgreSQL:: 

1017 

1018 from sqlalchemy.schema import CreateColumn 

1019 

1020 

1021 @compiles(CreateColumn, "postgresql") 

1022 def skip_xmin(element, compiler, **kw): 

1023 if element.element.name == "xmin": 

1024 return None 

1025 else: 

1026 return compiler.visit_create_column(element, **kw) 

1027 

1028 

1029 my_table = Table( 

1030 "mytable", 

1031 metadata, 

1032 Column("id", Integer, primary_key=True), 

1033 Column("xmin", Integer), 

1034 ) 

1035 

1036 Above, a :class:`.CreateTable` construct will generate a ``CREATE TABLE`` 

1037 which only includes the ``id`` column in the string; the ``xmin`` column 

1038 will be omitted, but only against the PostgreSQL backend. 

1039 

1040 """ 

1041 

1042 __visit_name__ = "create_column" 

1043 

1044 element: Column[Any] 

1045 

1046 def __init__(self, element: Column[Any]) -> None: 

1047 self.element = element 

1048 

1049 

1050class DropTable(TableDropDDL): 

1051 """Represent a DROP TABLE statement.""" 

1052 

1053 __visit_name__ = "drop_table" 

1054 

1055 def __init__(self, element: Table, if_exists: bool = False) -> None: 

1056 """Create a :class:`.DropTable` construct. 

1057 

1058 :param element: a :class:`_schema.Table` that's the subject 

1059 of the DROP. 

1060 :param on: See the description for 'on' in :class:`.DDL`. 

1061 :param if_exists: if True, an IF EXISTS operator will be applied to the 

1062 construct. 

1063 

1064 .. versionadded:: 1.4.0b2 

1065 

1066 """ 

1067 super().__init__(element, if_exists=if_exists) 

1068 

1069 def to_metadata(self, metadata: MetaData, table: Table) -> Self: 

1070 return self.__class__(table, if_exists=self.if_exists) 

1071 

1072 

1073class CreateSequence(_CreateBase["Sequence"]): 

1074 """Represent a CREATE SEQUENCE statement.""" 

1075 

1076 __visit_name__ = "create_sequence" 

1077 

1078 

1079class DropSequence(_DropBase["Sequence"]): 

1080 """Represent a DROP SEQUENCE statement.""" 

1081 

1082 __visit_name__ = "drop_sequence" 

1083 

1084 

1085class CreateIndex(_CreateBase["Index"]): 

1086 """Represent a CREATE INDEX statement.""" 

1087 

1088 __visit_name__ = "create_index" 

1089 

1090 def __init__(self, element: Index, if_not_exists: bool = False) -> None: 

1091 """Create a :class:`.Createindex` construct. 

1092 

1093 :param element: a :class:`_schema.Index` that's the subject 

1094 of the CREATE. 

1095 :param if_not_exists: if True, an IF NOT EXISTS operator will be 

1096 applied to the construct. 

1097 

1098 .. versionadded:: 1.4.0b2 

1099 

1100 """ 

1101 super().__init__(element, if_not_exists=if_not_exists) 

1102 

1103 

1104class DropIndex(_DropBase["Index"]): 

1105 """Represent a DROP INDEX statement.""" 

1106 

1107 __visit_name__ = "drop_index" 

1108 

1109 def __init__(self, element: Index, if_exists: bool = False) -> None: 

1110 """Create a :class:`.DropIndex` construct. 

1111 

1112 :param element: a :class:`_schema.Index` that's the subject 

1113 of the DROP. 

1114 :param if_exists: if True, an IF EXISTS operator will be applied to the 

1115 construct. 

1116 

1117 .. versionadded:: 1.4.0b2 

1118 

1119 """ 

1120 super().__init__(element, if_exists=if_exists) 

1121 

1122 

1123class AddConstraint(_CreateBase["Constraint"]): 

1124 """Represent an ALTER TABLE ADD CONSTRAINT statement.""" 

1125 

1126 __visit_name__ = "add_constraint" 

1127 

1128 def __init__( 

1129 self, 

1130 element: Constraint, 

1131 *, 

1132 isolate_from_table: bool = True, 

1133 ) -> None: 

1134 """Construct a new :class:`.AddConstraint` construct. 

1135 

1136 :param element: a :class:`.Constraint` object 

1137 

1138 :param isolate_from_table: optional boolean, defaults to True. Has 

1139 the effect of the incoming constraint being isolated from being 

1140 included in a CREATE TABLE sequence when associated with a 

1141 :class:`.Table`. 

1142 

1143 .. versionadded:: 2.0.39 - added 

1144 :paramref:`.AddConstraint.isolate_from_table`, defaulting 

1145 to True. Previously, the behavior of this parameter was implicitly 

1146 turned on in all cases. 

1147 

1148 """ 

1149 super().__init__(element) 

1150 

1151 if isolate_from_table: 

1152 element._create_rule = self._create_rule_disable 

1153 

1154 

1155class DropConstraint(_DropBase["Constraint"]): 

1156 """Represent an ALTER TABLE DROP CONSTRAINT statement.""" 

1157 

1158 __visit_name__ = "drop_constraint" 

1159 

1160 def __init__( 

1161 self, 

1162 element: Constraint, 

1163 *, 

1164 cascade: bool = False, 

1165 if_exists: bool = False, 

1166 isolate_from_table: bool = True, 

1167 **kw: Any, 

1168 ) -> None: 

1169 """Construct a new :class:`.DropConstraint` construct. 

1170 

1171 :param element: a :class:`.Constraint` object 

1172 :param cascade: optional boolean, indicates backend-specific 

1173 "CASCADE CONSTRAINT" directive should be rendered if available 

1174 :param if_exists: optional boolean, indicates backend-specific 

1175 "IF EXISTS" directive should be rendered if available 

1176 :param isolate_from_table: optional boolean, defaults to True. Has 

1177 the effect of the incoming constraint being isolated from being 

1178 included in a CREATE TABLE sequence when associated with a 

1179 :class:`.Table`. 

1180 

1181 .. versionadded:: 2.0.39 - added 

1182 :paramref:`.DropConstraint.isolate_from_table`, defaulting 

1183 to True. Previously, the behavior of this parameter was implicitly 

1184 turned on in all cases. 

1185 

1186 """ 

1187 self.cascade = cascade 

1188 super().__init__(element, if_exists=if_exists, **kw) 

1189 

1190 if isolate_from_table: 

1191 element._create_rule = self._create_rule_disable 

1192 

1193 

1194class SetTableComment(_CreateDropBase["Table"]): 

1195 """Represent a COMMENT ON TABLE IS statement.""" 

1196 

1197 __visit_name__ = "set_table_comment" 

1198 

1199 

1200class DropTableComment(_CreateDropBase["Table"]): 

1201 """Represent a COMMENT ON TABLE '' statement. 

1202 

1203 Note this varies a lot across database backends. 

1204 

1205 """ 

1206 

1207 __visit_name__ = "drop_table_comment" 

1208 

1209 

1210class SetColumnComment(_CreateDropBase["Column[Any]"]): 

1211 """Represent a COMMENT ON COLUMN IS statement.""" 

1212 

1213 __visit_name__ = "set_column_comment" 

1214 

1215 

1216class DropColumnComment(_CreateDropBase["Column[Any]"]): 

1217 """Represent a COMMENT ON COLUMN IS NULL statement.""" 

1218 

1219 __visit_name__ = "drop_column_comment" 

1220 

1221 

1222class SetConstraintComment(_CreateDropBase["Constraint"]): 

1223 """Represent a COMMENT ON CONSTRAINT IS statement.""" 

1224 

1225 __visit_name__ = "set_constraint_comment" 

1226 

1227 

1228class DropConstraintComment(_CreateDropBase["Constraint"]): 

1229 """Represent a COMMENT ON CONSTRAINT IS NULL statement.""" 

1230 

1231 __visit_name__ = "drop_constraint_comment" 

1232 

1233 

1234class InvokeDDLBase(SchemaVisitor): 

1235 def __init__(self, connection, **kw): 

1236 self.connection = connection 

1237 assert not kw, f"Unexpected keywords: {kw.keys()}" 

1238 

1239 @contextlib.contextmanager 

1240 def with_ddl_events(self, target, **kw): 

1241 """helper context manager that will apply appropriate DDL events 

1242 to a CREATE or DROP operation.""" 

1243 

1244 raise NotImplementedError() 

1245 

1246 

1247class InvokeCreateDDLBase(InvokeDDLBase): 

1248 @contextlib.contextmanager 

1249 def with_ddl_events(self, target, **kw): 

1250 """helper context manager that will apply appropriate DDL events 

1251 to a CREATE or DROP operation.""" 

1252 

1253 target.dispatch.before_create( 

1254 target, self.connection, _ddl_runner=self, **kw 

1255 ) 

1256 yield 

1257 target.dispatch.after_create( 

1258 target, self.connection, _ddl_runner=self, **kw 

1259 ) 

1260 

1261 

1262class InvokeDropDDLBase(InvokeDDLBase): 

1263 @contextlib.contextmanager 

1264 def with_ddl_events(self, target, **kw): 

1265 """helper context manager that will apply appropriate DDL events 

1266 to a CREATE or DROP operation.""" 

1267 

1268 target.dispatch.before_drop( 

1269 target, self.connection, _ddl_runner=self, **kw 

1270 ) 

1271 yield 

1272 target.dispatch.after_drop( 

1273 target, self.connection, _ddl_runner=self, **kw 

1274 ) 

1275 

1276 

1277class CheckFirst(Flag): 

1278 """Enumeration for the :paramref:`.MetaData.create_all.checkfirst` 

1279 parameter passed to methods like :meth:`.MetaData.create_all`, 

1280 :meth:`.MetaData.drop_all`, :meth:`.Table.create`, :meth:`.Table.drop` and 

1281 others. 

1282 

1283 This enumeration indicates what kinds of objects should be "checked" 

1284 with a separate query before emitting CREATE or DROP for that object. 

1285 

1286 Can use ``CheckFirst(bool_value)`` to convert from a boolean value. 

1287 

1288 .. versionadded:: 2.1 

1289 

1290 """ 

1291 

1292 NONE = 0 # equivalent to False 

1293 """No items should be checked""" 

1294 

1295 # avoid 1 so that bool True doesn't match by value 

1296 TABLES = 2 

1297 """Check for tables""" 

1298 

1299 VIEWS = auto() 

1300 """Check for views""" 

1301 

1302 INDEXES = auto() 

1303 """Check for indexes""" 

1304 

1305 SEQUENCES = auto() 

1306 """Check for sequences""" 

1307 

1308 TYPES = auto() 

1309 """Check for custom datatypes that are created server-side 

1310 

1311 This is currently used by PostgreSQL. 

1312 

1313 """ 

1314 

1315 ALL = TABLES | VIEWS | INDEXES | SEQUENCES | TYPES # equivalent to True 

1316 

1317 @classmethod 

1318 def _missing_(cls, value: object) -> Any: 

1319 if isinstance(value, bool): 

1320 return cls.ALL if value else cls.NONE 

1321 return super()._missing_(value) 

1322 

1323 

1324class SchemaGenerator(InvokeCreateDDLBase): 

1325 def __init__( 

1326 self, 

1327 dialect, 

1328 connection, 

1329 checkfirst=CheckFirst.NONE, 

1330 tables=None, 

1331 **kwargs, 

1332 ): 

1333 super().__init__(connection, **kwargs) 

1334 self.checkfirst = CheckFirst(checkfirst) 

1335 self.tables = tables 

1336 self.preparer = dialect.identifier_preparer 

1337 self.dialect = dialect 

1338 self.memo = {} 

1339 

1340 def _can_create_table(self, table): 

1341 self.dialect.validate_identifier(table.name) 

1342 effective_schema = self.connection.schema_for_object(table) 

1343 if effective_schema: 

1344 self.dialect.validate_identifier(effective_schema) 

1345 

1346 bool_to_check = ( 

1347 CheckFirst.TABLES if not table.is_view else CheckFirst.VIEWS 

1348 ) 

1349 return ( 

1350 not self.checkfirst & bool_to_check 

1351 or not self.dialect.has_table( 

1352 self.connection, table.name, schema=effective_schema 

1353 ) 

1354 ) 

1355 

1356 def _can_create_index(self, index): 

1357 effective_schema = self.connection.schema_for_object(index.table) 

1358 if effective_schema: 

1359 self.dialect.validate_identifier(effective_schema) 

1360 return ( 

1361 not self.checkfirst & CheckFirst.INDEXES 

1362 or not self.dialect.has_index( 

1363 self.connection, 

1364 index.table.name, 

1365 index.name, 

1366 schema=effective_schema, 

1367 ) 

1368 ) 

1369 

1370 def _can_create_sequence(self, sequence): 

1371 effective_schema = self.connection.schema_for_object(sequence) 

1372 

1373 return self.dialect.supports_sequences and ( 

1374 (not self.dialect.sequences_optional or not sequence.optional) 

1375 and ( 

1376 not self.checkfirst & CheckFirst.SEQUENCES 

1377 or not self.dialect.has_sequence( 

1378 self.connection, sequence.name, schema=effective_schema 

1379 ) 

1380 ) 

1381 ) 

1382 

1383 def visit_metadata(self, metadata): 

1384 if self.tables is not None: 

1385 tables = self.tables 

1386 else: 

1387 tables = list(metadata.tables.values()) 

1388 

1389 collection = sort_tables_and_constraints( 

1390 [t for t in tables if self._can_create_table(t)] 

1391 ) 

1392 

1393 seq_coll = [ 

1394 s 

1395 for s in metadata._sequences.values() 

1396 if s.column is None and self._can_create_sequence(s) 

1397 ] 

1398 

1399 event_collection = [t for (t, fks) in collection if t is not None] 

1400 

1401 with self.with_ddl_events( 

1402 metadata, 

1403 tables=event_collection, 

1404 checkfirst=self.checkfirst, 

1405 ): 

1406 for seq in seq_coll: 

1407 self.traverse_single(seq, create_ok=True) 

1408 

1409 for table, fkcs in collection: 

1410 if table is not None: 

1411 self.traverse_single( 

1412 table, 

1413 create_ok=True, 

1414 include_foreign_key_constraints=fkcs, 

1415 _is_metadata_operation=True, 

1416 ) 

1417 else: 

1418 for fkc in fkcs: 

1419 self.traverse_single(fkc) 

1420 

1421 def visit_table( 

1422 self, 

1423 table, 

1424 create_ok=False, 

1425 include_foreign_key_constraints=None, 

1426 _is_metadata_operation=False, 

1427 ): 

1428 if not create_ok and not self._can_create_table(table): 

1429 return 

1430 

1431 with self.with_ddl_events( 

1432 table, 

1433 checkfirst=self.checkfirst, 

1434 _is_metadata_operation=_is_metadata_operation, 

1435 ): 

1436 for column in table.columns: 

1437 if column.default is not None: 

1438 self.traverse_single(column.default) 

1439 

1440 if not self.dialect.supports_alter: 

1441 # e.g., don't omit any foreign key constraints 

1442 include_foreign_key_constraints = None 

1443 

1444 if table._creator_ddl is not None: 

1445 table_create_ddl = table._creator_ddl 

1446 else: 

1447 table_create_ddl = CreateTable( 

1448 table, 

1449 include_foreign_key_constraints=( 

1450 include_foreign_key_constraints 

1451 ), 

1452 ) 

1453 

1454 table_create_ddl._invoke_with(self.connection) 

1455 

1456 if hasattr(table, "indexes"): 

1457 for index in table.indexes: 

1458 self.traverse_single(index, create_ok=True) 

1459 

1460 if ( 

1461 self.dialect.supports_comments 

1462 and not self.dialect.inline_comments 

1463 ): 

1464 if table.comment is not None: 

1465 SetTableComment(table)._invoke_with(self.connection) 

1466 

1467 for column in table.columns: 

1468 if column.comment is not None: 

1469 SetColumnComment(column)._invoke_with(self.connection) 

1470 

1471 if self.dialect.supports_constraint_comments: 

1472 for constraint in table.constraints: 

1473 if constraint.comment is not None: 

1474 self.connection.execute( 

1475 SetConstraintComment(constraint) 

1476 ) 

1477 

1478 def visit_foreign_key_constraint(self, constraint): 

1479 if not self.dialect.supports_alter: 

1480 return 

1481 

1482 with self.with_ddl_events(constraint): 

1483 AddConstraint(constraint)._invoke_with(self.connection) 

1484 

1485 def visit_sequence(self, sequence, create_ok=False): 

1486 if not create_ok and not self._can_create_sequence(sequence): 

1487 return 

1488 with self.with_ddl_events(sequence): 

1489 CreateSequence(sequence)._invoke_with(self.connection) 

1490 

1491 def visit_index(self, index, create_ok=False): 

1492 if not create_ok and not self._can_create_index(index): 

1493 return 

1494 with self.with_ddl_events(index): 

1495 CreateIndex(index)._invoke_with(self.connection) 

1496 

1497 

1498class SchemaDropper(InvokeDropDDLBase): 

1499 def __init__( 

1500 self, 

1501 dialect, 

1502 connection, 

1503 checkfirst=CheckFirst.NONE, 

1504 tables=None, 

1505 **kwargs, 

1506 ): 

1507 super().__init__(connection, **kwargs) 

1508 self.checkfirst = CheckFirst(checkfirst) 

1509 self.tables = tables 

1510 self.preparer = dialect.identifier_preparer 

1511 self.dialect = dialect 

1512 self.memo = {} 

1513 

1514 def visit_metadata(self, metadata): 

1515 if self.tables is not None: 

1516 tables = self.tables 

1517 else: 

1518 tables = list(metadata.tables.values()) 

1519 

1520 try: 

1521 unsorted_tables = [t for t in tables if self._can_drop_table(t)] 

1522 collection = list( 

1523 reversed( 

1524 sort_tables_and_constraints( 

1525 unsorted_tables, 

1526 filter_fn=lambda constraint: ( 

1527 False 

1528 if not self.dialect.supports_alter 

1529 or constraint.name is None 

1530 else None 

1531 ), 

1532 ) 

1533 ) 

1534 ) 

1535 except exc.CircularDependencyError as err2: 

1536 if not self.dialect.supports_alter: 

1537 util.warn( 

1538 "Can't sort tables for DROP; an " 

1539 "unresolvable foreign key " 

1540 "dependency exists between tables: %s; and backend does " 

1541 "not support ALTER. To restore at least a partial sort, " 

1542 "apply use_alter=True to ForeignKey and " 

1543 "ForeignKeyConstraint " 

1544 "objects involved in the cycle to mark these as known " 

1545 "cycles that will be ignored." 

1546 % (", ".join(sorted([t.fullname for t in err2.cycles]))) 

1547 ) 

1548 collection = [(t, ()) for t in unsorted_tables] 

1549 else: 

1550 raise exc.CircularDependencyError( 

1551 err2.args[0], 

1552 err2.cycles, 

1553 err2.edges, 

1554 msg="Can't sort tables for DROP; an " 

1555 "unresolvable foreign key " 

1556 "dependency exists between tables: %s. Please ensure " 

1557 "that the ForeignKey and ForeignKeyConstraint objects " 

1558 "involved in the cycle have " 

1559 "names so that they can be dropped using " 

1560 "DROP CONSTRAINT." 

1561 % (", ".join(sorted([t.fullname for t in err2.cycles]))), 

1562 ) from err2 

1563 

1564 seq_coll = [ 

1565 s 

1566 for s in metadata._sequences.values() 

1567 if self._can_drop_sequence(s) 

1568 ] 

1569 

1570 event_collection = [t for (t, fks) in collection if t is not None] 

1571 

1572 with self.with_ddl_events( 

1573 metadata, 

1574 tables=event_collection, 

1575 checkfirst=self.checkfirst, 

1576 ): 

1577 for table, fkcs in collection: 

1578 if table is not None: 

1579 self.traverse_single( 

1580 table, 

1581 drop_ok=True, 

1582 _is_metadata_operation=True, 

1583 _ignore_sequences=seq_coll, 

1584 ) 

1585 else: 

1586 for fkc in fkcs: 

1587 self.traverse_single(fkc) 

1588 

1589 for seq in seq_coll: 

1590 self.traverse_single(seq, drop_ok=seq.column is None) 

1591 

1592 def _can_drop_table(self, table): 

1593 self.dialect.validate_identifier(table.name) 

1594 effective_schema = self.connection.schema_for_object(table) 

1595 if effective_schema: 

1596 self.dialect.validate_identifier(effective_schema) 

1597 bool_to_check = ( 

1598 CheckFirst.TABLES if not table.is_view else CheckFirst.VIEWS 

1599 ) 

1600 

1601 return not self.checkfirst & bool_to_check or self.dialect.has_table( 

1602 self.connection, table.name, schema=effective_schema 

1603 ) 

1604 

1605 def _can_drop_index(self, index): 

1606 effective_schema = self.connection.schema_for_object(index.table) 

1607 if effective_schema: 

1608 self.dialect.validate_identifier(effective_schema) 

1609 return ( 

1610 not self.checkfirst & CheckFirst.INDEXES 

1611 or self.dialect.has_index( 

1612 self.connection, 

1613 index.table.name, 

1614 index.name, 

1615 schema=effective_schema, 

1616 ) 

1617 ) 

1618 

1619 def _can_drop_sequence(self, sequence): 

1620 effective_schema = self.connection.schema_for_object(sequence) 

1621 return self.dialect.supports_sequences and ( 

1622 (not self.dialect.sequences_optional or not sequence.optional) 

1623 and ( 

1624 not self.checkfirst & CheckFirst.SEQUENCES 

1625 or self.dialect.has_sequence( 

1626 self.connection, sequence.name, schema=effective_schema 

1627 ) 

1628 ) 

1629 ) 

1630 

1631 def visit_index(self, index, drop_ok=False): 

1632 if not drop_ok and not self._can_drop_index(index): 

1633 return 

1634 

1635 with self.with_ddl_events(index): 

1636 DropIndex(index)(index, self.connection) 

1637 

1638 def visit_table( 

1639 self, 

1640 table, 

1641 drop_ok=False, 

1642 _is_metadata_operation=False, 

1643 _ignore_sequences=(), 

1644 ): 

1645 if not drop_ok and not self._can_drop_table(table): 

1646 return 

1647 

1648 with self.with_ddl_events( 

1649 table, 

1650 checkfirst=self.checkfirst, 

1651 _is_metadata_operation=_is_metadata_operation, 

1652 ): 

1653 if table._dropper_ddl is not None: 

1654 table_dropper_ddl = table._dropper_ddl 

1655 else: 

1656 table_dropper_ddl = DropTable(table) 

1657 table_dropper_ddl._invoke_with(self.connection) 

1658 

1659 # traverse client side defaults which may refer to server-side 

1660 # sequences. noting that some of these client side defaults may 

1661 # also be set up as server side defaults 

1662 # (see https://docs.sqlalchemy.org/en/ 

1663 # latest/core/defaults.html 

1664 # #associating-a-sequence-as-the-server-side- 

1665 # default), so have to be dropped after the table is dropped. 

1666 for column in table.columns: 

1667 if ( 

1668 column.default is not None 

1669 and column.default not in _ignore_sequences 

1670 ): 

1671 self.traverse_single(column.default) 

1672 

1673 def visit_foreign_key_constraint(self, constraint): 

1674 if not self.dialect.supports_alter: 

1675 return 

1676 with self.with_ddl_events(constraint): 

1677 DropConstraint(constraint)._invoke_with(self.connection) 

1678 

1679 def visit_sequence(self, sequence, drop_ok=False): 

1680 if not drop_ok and not self._can_drop_sequence(sequence): 

1681 return 

1682 with self.with_ddl_events(sequence): 

1683 DropSequence(sequence)._invoke_with(self.connection) 

1684 

1685 

1686def sort_tables( 

1687 tables: Iterable[TableClause], 

1688 skip_fn: Optional[Callable[[ForeignKeyConstraint], bool]] = None, 

1689 extra_dependencies: Optional[ 

1690 typing_Sequence[Tuple[TableClause, TableClause]] 

1691 ] = None, 

1692) -> List[Table]: 

1693 """Sort a collection of :class:`_schema.Table` objects based on 

1694 dependency. 

1695 

1696 This is a dependency-ordered sort which will emit :class:`_schema.Table` 

1697 objects such that they will follow their dependent :class:`_schema.Table` 

1698 objects. 

1699 Tables are dependent on another based on the presence of 

1700 :class:`_schema.ForeignKeyConstraint` 

1701 objects as well as explicit dependencies 

1702 added by :meth:`_schema.Table.add_is_dependent_on`. 

1703 

1704 .. warning:: 

1705 

1706 The :func:`._schema.sort_tables` function cannot by itself 

1707 accommodate automatic resolution of dependency cycles between 

1708 tables, which are usually caused by mutually dependent foreign key 

1709 constraints. When these cycles are detected, the foreign keys 

1710 of these tables are omitted from consideration in the sort. 

1711 A warning is emitted when this condition occurs, which will be an 

1712 exception raise in a future release. Tables which are not part 

1713 of the cycle will still be returned in dependency order. 

1714 

1715 To resolve these cycles, the 

1716 :paramref:`_schema.ForeignKeyConstraint.use_alter` parameter may be 

1717 applied to those constraints which create a cycle. Alternatively, 

1718 the :func:`_schema.sort_tables_and_constraints` function will 

1719 automatically return foreign key constraints in a separate 

1720 collection when cycles are detected so that they may be applied 

1721 to a schema separately. 

1722 

1723 :param tables: a sequence of :class:`_schema.Table` objects. 

1724 

1725 :param skip_fn: optional callable which will be passed a 

1726 :class:`_schema.ForeignKeyConstraint` object; if it returns True, this 

1727 constraint will not be considered as a dependency. Note this is 

1728 **different** from the same parameter in 

1729 :func:`.sort_tables_and_constraints`, which is 

1730 instead passed the owning :class:`_schema.ForeignKeyConstraint` object. 

1731 

1732 :param extra_dependencies: a sequence of 2-tuples of tables which will 

1733 also be considered as dependent on each other. 

1734 

1735 .. seealso:: 

1736 

1737 :func:`.sort_tables_and_constraints` 

1738 

1739 :attr:`_schema.MetaData.sorted_tables` - uses this function to sort 

1740 

1741 

1742 """ 

1743 

1744 if skip_fn is not None: 

1745 fixed_skip_fn = skip_fn 

1746 

1747 def _skip_fn(fkc): 

1748 for fk in fkc.elements: 

1749 if fixed_skip_fn(fk): 

1750 return True 

1751 else: 

1752 return None 

1753 

1754 else: 

1755 _skip_fn = None # type: ignore 

1756 

1757 return [ 

1758 t 

1759 for (t, fkcs) in sort_tables_and_constraints( 

1760 tables, 

1761 filter_fn=_skip_fn, 

1762 extra_dependencies=extra_dependencies, 

1763 _warn_for_cycles=True, 

1764 ) 

1765 if t is not None 

1766 ] 

1767 

1768 

1769@util.preload_module("sqlalchemy.sql.schema") 

1770def sort_tables_and_constraints( 

1771 tables, filter_fn=None, extra_dependencies=None, _warn_for_cycles=False 

1772): 

1773 """Sort a collection of :class:`_schema.Table` / 

1774 :class:`_schema.ForeignKeyConstraint` 

1775 objects. 

1776 

1777 This is a dependency-ordered sort which will emit tuples of 

1778 ``(Table, [ForeignKeyConstraint, ...])`` such that each 

1779 :class:`_schema.Table` follows its dependent :class:`_schema.Table` 

1780 objects. 

1781 Remaining :class:`_schema.ForeignKeyConstraint` 

1782 objects that are separate due to 

1783 dependency rules not satisfied by the sort are emitted afterwards 

1784 as ``(None, [ForeignKeyConstraint ...])``. 

1785 

1786 Tables are dependent on another based on the presence of 

1787 :class:`_schema.ForeignKeyConstraint` objects, explicit dependencies 

1788 added by :meth:`_schema.Table.add_is_dependent_on`, 

1789 as well as dependencies 

1790 stated here using the :paramref:`~.sort_tables_and_constraints.skip_fn` 

1791 and/or :paramref:`~.sort_tables_and_constraints.extra_dependencies` 

1792 parameters. 

1793 

1794 :param tables: a sequence of :class:`_schema.Table` objects. 

1795 

1796 :param filter_fn: optional callable which will be passed a 

1797 :class:`_schema.ForeignKeyConstraint` object, 

1798 and returns a value based on 

1799 whether this constraint should definitely be included or excluded as 

1800 an inline constraint, or neither. If it returns False, the constraint 

1801 will definitely be included as a dependency that cannot be subject 

1802 to ALTER; if True, it will **only** be included as an ALTER result at 

1803 the end. Returning None means the constraint is included in the 

1804 table-based result unless it is detected as part of a dependency cycle. 

1805 

1806 :param extra_dependencies: a sequence of 2-tuples of tables which will 

1807 also be considered as dependent on each other. 

1808 

1809 .. seealso:: 

1810 

1811 :func:`.sort_tables` 

1812 

1813 

1814 """ 

1815 Table = util.preloaded.sql_schema.Table 

1816 

1817 fixed_dependencies = set() 

1818 mutable_dependencies = set() 

1819 

1820 if extra_dependencies is not None: 

1821 fixed_dependencies.update(extra_dependencies) 

1822 

1823 remaining_fkcs = set() 

1824 for table in tables: 

1825 for fkc in table.foreign_key_constraints: 

1826 if fkc.use_alter is True: 

1827 remaining_fkcs.add(fkc) 

1828 continue 

1829 

1830 if filter_fn: 

1831 filtered = filter_fn(fkc) 

1832 

1833 if filtered is True: 

1834 remaining_fkcs.add(fkc) 

1835 continue 

1836 

1837 dependent_on = fkc.referred_table 

1838 if dependent_on is not table: 

1839 mutable_dependencies.add((dependent_on, table)) 

1840 

1841 if isinstance(table._creator_ddl, _TableViaSelect): 

1842 selectable = table._creator_ddl.selectable 

1843 for selected_table in sql_util.find_tables( 

1844 selectable, 

1845 check_columns=True, 

1846 include_aliases=True, 

1847 include_joins=True, 

1848 include_selects=True, 

1849 include_crud=True, 

1850 ): 

1851 if ( 

1852 isinstance(selected_table, Table) 

1853 and selected_table.metadata is table.metadata 

1854 ): 

1855 fixed_dependencies.add((selected_table, table)) 

1856 

1857 fixed_dependencies.update( 

1858 (parent, table) for parent in table._extra_dependencies 

1859 ) 

1860 

1861 try: 

1862 candidate_sort = list( 

1863 topological.sort( 

1864 fixed_dependencies.union(mutable_dependencies), 

1865 tables, 

1866 ) 

1867 ) 

1868 except exc.CircularDependencyError as err: 

1869 if _warn_for_cycles: 

1870 util.warn( 

1871 "Cannot correctly sort tables; there are unresolvable cycles " 

1872 'between tables "%s", which is usually caused by mutually ' 

1873 "dependent foreign key constraints. Foreign key constraints " 

1874 "involving these tables will not be considered; this warning " 

1875 "may raise an error in a future release." 

1876 % (", ".join(sorted(t.fullname for t in err.cycles)),) 

1877 ) 

1878 for edge in err.edges: 

1879 if edge in mutable_dependencies: 

1880 table = edge[1] 

1881 if table not in err.cycles: 

1882 continue 

1883 can_remove = [ 

1884 fkc 

1885 for fkc in table.foreign_key_constraints 

1886 if filter_fn is None or filter_fn(fkc) is not False 

1887 ] 

1888 remaining_fkcs.update(can_remove) 

1889 for fkc in can_remove: 

1890 dependent_on = fkc.referred_table 

1891 if dependent_on is not table: 

1892 mutable_dependencies.discard((dependent_on, table)) 

1893 candidate_sort = list( 

1894 topological.sort( 

1895 fixed_dependencies.union(mutable_dependencies), 

1896 tables, 

1897 ) 

1898 ) 

1899 

1900 return [ 

1901 (table, table.foreign_key_constraints.difference(remaining_fkcs)) 

1902 for table in candidate_sort 

1903 ] + [(None, list(remaining_fkcs))]