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

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

1005 statements  

1# sql/base.py 

2# Copyright (C) 2005-2026 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"""Foundational utilities common to many sql modules.""" 

10 

11from __future__ import annotations 

12 

13import collections 

14from enum import Enum 

15import itertools 

16from itertools import zip_longest 

17import operator 

18import re 

19from typing import Any 

20from typing import Callable 

21from typing import cast 

22from typing import Collection 

23from typing import Dict 

24from typing import Final 

25from typing import FrozenSet 

26from typing import Generator 

27from typing import Generic 

28from typing import Iterable 

29from typing import Iterator 

30from typing import List 

31from typing import Mapping 

32from typing import MutableMapping 

33from typing import NamedTuple 

34from typing import NoReturn 

35from typing import Optional 

36from typing import overload 

37from typing import Protocol 

38from typing import Sequence 

39from typing import Set 

40from typing import Tuple 

41from typing import Type 

42from typing import TYPE_CHECKING 

43from typing import TypeGuard 

44from typing import TypeVar 

45from typing import Union 

46 

47from . import roles 

48from . import visitors 

49from .cache_key import HasCacheKey # noqa 

50from .cache_key import MemoizedHasCacheKey # noqa 

51from .traversals import HasCopyInternals # noqa 

52from .visitors import ClauseVisitor 

53from .visitors import ExtendedInternalTraversal 

54from .visitors import ExternallyTraversible 

55from .visitors import InternalTraversal 

56from .. import event 

57from .. import exc 

58from .. import util 

59from ..util import EMPTY_DICT 

60from ..util import HasMemoized as HasMemoized 

61from ..util import hybridmethod 

62from ..util import warn_deprecated 

63from ..util.typing import Self 

64from ..util.typing import TypeVarTuple 

65from ..util.typing import Unpack 

66 

67if TYPE_CHECKING: 

68 from . import coercions 

69 from . import elements 

70 from . import type_api 

71 from ._orm_types import DMLStrategyArgument 

72 from ._orm_types import SynchronizeSessionArgument 

73 from ._typing import _CLE 

74 from .cache_key import CacheKey 

75 from .compiler import SQLCompiler 

76 from .dml import Delete 

77 from .dml import Insert 

78 from .dml import Update 

79 from .elements import BindParameter 

80 from .elements import ClauseElement 

81 from .elements import ClauseList 

82 from .elements import ColumnClause # noqa 

83 from .elements import ColumnElement 

84 from .elements import NamedColumn 

85 from .elements import SQLCoreOperations 

86 from .elements import TextClause 

87 from .schema import Column 

88 from .schema import DefaultGenerator 

89 from .selectable import _JoinTargetElement 

90 from .selectable import _SelectIterable 

91 from .selectable import FromClause 

92 from .selectable import Select 

93 from .visitors import anon_map 

94 from ..engine import Connection 

95 from ..engine import CursorResult 

96 from ..engine.interfaces import _CoreMultiExecuteParams 

97 from ..engine.interfaces import _CoreSingleExecuteParams 

98 from ..engine.interfaces import _ExecuteOptions 

99 from ..engine.interfaces import _ImmutableExecuteOptions 

100 from ..engine.interfaces import CacheStats 

101 from ..engine.interfaces import Compiled 

102 from ..engine.interfaces import CompiledCacheType 

103 from ..engine.interfaces import CoreExecuteOptionsParameter 

104 from ..engine.interfaces import Dialect 

105 from ..engine.interfaces import IsolationLevel 

106 from ..engine.interfaces import SchemaTranslateMapType 

107 from ..event import dispatcher 

108 

109if not TYPE_CHECKING: 

110 coercions = None # noqa 

111 elements = None # noqa 

112 type_api = None # noqa 

113 

114 

115_Ts = TypeVarTuple("_Ts") 

116 

117 

118class _NoArg(Enum): 

119 NO_ARG = 0 

120 

121 def __repr__(self): 

122 return f"_NoArg.{self.name}" 

123 

124 

125NO_ARG: Final = _NoArg.NO_ARG 

126 

127 

128class _NoneName(Enum): 

129 NONE_NAME = 0 

130 """indicate a 'deferred' name that was ultimately the value None.""" 

131 

132 

133_NONE_NAME: Final = _NoneName.NONE_NAME 

134 

135_T = TypeVar("_T", bound=Any) 

136 

137_Fn = TypeVar("_Fn", bound=Callable[..., Any]) 

138 

139_AmbiguousTableNameMap = MutableMapping[str, str] 

140 

141 

142class _DefaultDescriptionTuple(NamedTuple): 

143 arg: Any 

144 is_scalar: Optional[bool] 

145 is_callable: Optional[bool] 

146 is_sentinel: Optional[bool] 

147 

148 @classmethod 

149 def _from_column_default( 

150 cls, default: Optional[DefaultGenerator] 

151 ) -> _DefaultDescriptionTuple: 

152 return ( 

153 _DefaultDescriptionTuple( 

154 default.arg, # type: ignore[attr-defined] 

155 default.is_scalar, 

156 default.is_callable, 

157 default.is_sentinel, 

158 ) 

159 if default 

160 and ( 

161 default.has_arg 

162 or (not default.for_update and default.is_sentinel) 

163 ) 

164 else _DefaultDescriptionTuple(None, None, None, None) 

165 ) 

166 

167 

168_never_select_column: operator.attrgetter[Any] = operator.attrgetter( 

169 "_omit_from_statements" 

170) 

171 

172 

173class _EntityNamespace(Protocol): 

174 def __getattr__(self, key: str) -> SQLCoreOperations[Any]: ... 

175 

176 

177class _HasEntityNamespace(Protocol): 

178 @util.ro_non_memoized_property 

179 def entity_namespace(self) -> _EntityNamespace: ... 

180 

181 

182def _is_has_entity_namespace(element: Any) -> TypeGuard[_HasEntityNamespace]: 

183 return hasattr(element, "entity_namespace") 

184 

185 

186# Remove when https://github.com/python/mypy/issues/14640 will be fixed 

187_Self = TypeVar("_Self", bound=Any) 

188 

189 

190class Immutable: 

191 """mark a ClauseElement as 'immutable' when expressions are cloned. 

192 

193 "immutable" objects refers to the "mutability" of an object in the 

194 context of SQL DQL and DML generation. Such as, in DQL, one can 

195 compose a SELECT or subquery of varied forms, but one cannot modify 

196 the structure of a specific table or column within DQL. 

197 :class:`.Immutable` is mostly intended to follow this concept, and as 

198 such the primary "immutable" objects are :class:`.ColumnClause`, 

199 :class:`.Column`, :class:`.TableClause`, :class:`.Table`. 

200 

201 """ 

202 

203 __slots__ = () 

204 

205 _is_immutable: bool = True 

206 

207 def unique_params(self, *optionaldict: Any, **kwargs: Any) -> NoReturn: 

208 raise NotImplementedError("Immutable objects do not support copying") 

209 

210 def params(self, *optionaldict: Any, **kwargs: Any) -> NoReturn: 

211 raise NotImplementedError("Immutable objects do not support copying") 

212 

213 def _clone(self: _Self, **kw: Any) -> _Self: 

214 return self 

215 

216 def _copy_internals( 

217 self, *, omit_attrs: Iterable[str] = (), **kw: Any 

218 ) -> None: 

219 pass 

220 

221 

222class SingletonConstant(Immutable): 

223 """Represent SQL constants like NULL, TRUE, FALSE""" 

224 

225 _is_singleton_constant: bool = True 

226 

227 _singleton: SingletonConstant 

228 

229 def __new__(cls: _T, *arg: Any, **kw: Any) -> _T: 

230 return cast(_T, cls._singleton) 

231 

232 @util.non_memoized_property 

233 def proxy_set(self) -> FrozenSet[ColumnElement[Any]]: 

234 raise NotImplementedError() 

235 

236 @classmethod 

237 def _create_singleton(cls) -> None: 

238 obj = object.__new__(cls) 

239 obj.__init__() # type: ignore[misc] 

240 

241 # for a long time this was an empty frozenset, meaning 

242 # a SingletonConstant would never be a "corresponding column" in 

243 # a statement. This referred to #6259. However, in #7154 we see 

244 # that we do in fact need "correspondence" to work when matching cols 

245 # in result sets, so the non-correspondence was moved to a more 

246 # specific level when we are actually adapting expressions for SQL 

247 # render only. 

248 obj.proxy_set = frozenset([obj]) 

249 cls._singleton = obj 

250 

251 

252def _from_objects( 

253 *elements: Union[ 

254 ColumnElement[Any], FromClause, TextClause, _JoinTargetElement 

255 ] 

256) -> Iterator[FromClause]: 

257 return itertools.chain.from_iterable( 

258 [element._from_objects for element in elements] 

259 ) 

260 

261 

262def _select_iterables( 

263 elements: Iterable[roles.ColumnsClauseRole], 

264) -> _SelectIterable: 

265 """expand tables into individual columns in the 

266 given list of column expressions. 

267 

268 """ 

269 return itertools.chain.from_iterable( 

270 [c._select_iterable for c in elements] 

271 ) 

272 

273 

274_SelfGenerativeType = TypeVar("_SelfGenerativeType", bound="_GenerativeType") 

275 

276 

277class _GenerativeType(Protocol): 

278 def _generate(self) -> Self: ... 

279 

280 

281def _generative(fn: _Fn) -> _Fn: 

282 """non-caching _generative() decorator. 

283 

284 This is basically the legacy decorator that copies the object and 

285 runs a method on the new copy. 

286 

287 """ 

288 

289 @util.decorator 

290 def _generative( 

291 fn: _Fn, self: _SelfGenerativeType, *args: Any, **kw: Any 

292 ) -> _SelfGenerativeType: 

293 """Mark a method as generative.""" 

294 

295 self = self._generate() 

296 x = fn(self, *args, **kw) 

297 assert x is self, "generative methods must return self" 

298 return self 

299 

300 decorated = _generative(fn) 

301 decorated.non_generative = fn # type: ignore[attr-defined] 

302 return decorated 

303 

304 

305def _exclusive_against(*names: str, **kw: Any) -> Callable[[_Fn], _Fn]: 

306 msgs: Dict[str, str] = kw.pop("msgs", {}) 

307 

308 defaults: Dict[str, str] = kw.pop("defaults", {}) 

309 

310 getters: List[Tuple[str, operator.attrgetter[Any], Optional[str]]] = [ 

311 (name, operator.attrgetter(name), defaults.get(name, None)) 

312 for name in names 

313 ] 

314 

315 @util.decorator 

316 def check(fn: _Fn, *args: Any, **kw: Any) -> Any: 

317 # make pylance happy by not including "self" in the argument 

318 # list 

319 self = args[0] 

320 args = args[1:] 

321 for name, getter, default_ in getters: 

322 if getter(self) is not default_: 

323 msg = msgs.get( 

324 name, 

325 "Method %s() has already been invoked on this %s construct" 

326 % (fn.__name__, self.__class__), 

327 ) 

328 raise exc.InvalidRequestError(msg) 

329 return fn(self, *args, **kw) 

330 

331 return check 

332 

333 

334def _clone(element, **kw): 

335 return element._clone(**kw) 

336 

337 

338def _expand_cloned( 

339 elements: Iterable[_CLE], 

340) -> Iterable[_CLE]: 

341 """expand the given set of ClauseElements to be the set of all 'cloned' 

342 predecessors. 

343 

344 """ 

345 # TODO: cython candidate 

346 return itertools.chain(*[x._cloned_set for x in elements]) 

347 

348 

349def _de_clone( 

350 elements: Iterable[_CLE], 

351) -> Iterable[_CLE]: 

352 for x in elements: 

353 while x._is_clone_of is not None: 

354 x = x._is_clone_of 

355 yield x 

356 

357 

358def _cloned_intersection(a: Iterable[_CLE], b: Iterable[_CLE]) -> Set[_CLE]: 

359 """return the intersection of sets a and b, counting 

360 any overlap between 'cloned' predecessors. 

361 

362 The returned set is in terms of the entities present within 'a'. 

363 

364 """ 

365 all_overlap: Set[_CLE] = set(_expand_cloned(a)).intersection( 

366 _expand_cloned(b) 

367 ) 

368 return {elem for elem in a if all_overlap.intersection(elem._cloned_set)} 

369 

370 

371def _cloned_difference(a: Iterable[_CLE], b: Iterable[_CLE]) -> Set[_CLE]: 

372 all_overlap: Set[_CLE] = set(_expand_cloned(a)).intersection( 

373 _expand_cloned(b) 

374 ) 

375 return { 

376 elem for elem in a if not all_overlap.intersection(elem._cloned_set) 

377 } 

378 

379 

380class DialectKWArgConst(Enum): 

381 """Constants for dialect argument defaults in 

382 :attr:`.DefaultDialect.construct_arguments`. 

383 

384 """ 

385 

386 REFLECTED_ONLY = 1 

387 """Mark a dialect argument as database state reported by reflection. 

388 

389 Applies to any construct that takes part in 

390 :attr:`.DefaultDialect.construct_arguments`, such as :class:`.Table`, 

391 :class:`.Column`, :class:`.Index` or :class:`.CheckConstraint`. The 

392 value is kept in the construct's read-only 

393 :attr:`.DialectKWArgs.reflect_only_elements` mapping, separate from the 

394 options used to generate DDL. 

395 

396 .. seealso:: 

397 

398 :attr:`.DialectKWArgs.reflect_only_elements` 

399 

400 .. versionadded:: 2.1 

401 

402 """ 

403 

404 

405class _DialectArgView(MutableMapping[str, Any]): 

406 """A dictionary view of dialect-level arguments in the form 

407 <dialectname>_<argument_name>. 

408 

409 """ 

410 

411 __slots__ = ("obj",) 

412 

413 def __init__(self, obj: DialectKWArgs) -> None: 

414 self.obj = obj 

415 

416 def _key(self, key: str) -> Tuple[str, str]: 

417 try: 

418 dialect, value_key = key.split("_", 1) 

419 except ValueError as err: 

420 raise KeyError(key) from err 

421 else: 

422 return dialect, value_key 

423 

424 def __getitem__(self, key: str) -> Any: 

425 dialect, value_key = self._key(key) 

426 

427 try: 

428 opt = self.obj.dialect_options[dialect] 

429 except exc.NoSuchModuleError as err: 

430 raise KeyError(key) from err 

431 else: 

432 return opt[value_key] 

433 

434 def __setitem__(self, key: str, value: Any) -> None: 

435 try: 

436 dialect, value_key = self._key(key) 

437 except KeyError as err: 

438 raise exc.ArgumentError( 

439 "Keys must be of the form <dialectname>_<argname>" 

440 ) from err 

441 else: 

442 self.obj.dialect_options[dialect][value_key] = value 

443 

444 def __delitem__(self, key: str) -> None: 

445 dialect, value_key = self._key(key) 

446 del self.obj.dialect_options[dialect][value_key] 

447 

448 def __len__(self) -> int: 

449 return sum( 

450 len(args._non_defaults) 

451 for args in self.obj.dialect_options.values() 

452 ) 

453 

454 def __iter__(self) -> Generator[str, None, None]: 

455 return ( 

456 "%s_%s" % (dialect_name, value_name) 

457 for dialect_name in self.obj.dialect_options 

458 for value_name in self.obj.dialect_options[ 

459 dialect_name 

460 ]._non_defaults 

461 ) 

462 

463 

464class _ReflectOnlyView(Mapping[str, Mapping[str, Any]]): 

465 """A read-only dictionary view of reflection-only dialect-level 

466 arguments, keyed to <dialectname>, then <argument_name>. 

467 

468 A dialect name that has no reflection-only arguments present returns 

469 an empty mapping. 

470 

471 """ 

472 

473 __slots__ = ("obj",) 

474 

475 def __init__(self, obj: DialectKWArgs) -> None: 

476 self.obj = obj 

477 

478 def __getitem__(self, key: str) -> Mapping[str, Any]: 

479 # check membership first so that the lookup does not attempt 

480 # to load a dialect 

481 if key in self.obj.dialect_options: 

482 return self.obj.dialect_options[key]._reflect_only_elements 

483 else: 

484 return EMPTY_DICT 

485 

486 def __contains__(self, key: object) -> bool: 

487 return bool( 

488 isinstance(key, str) 

489 and key in self.obj.dialect_options 

490 and self.obj.dialect_options[key]._reflect_only_elements 

491 ) 

492 

493 def __len__(self) -> int: 

494 return sum(1 for _ in self) 

495 

496 def __iter__(self) -> Generator[str, None, None]: 

497 return ( 

498 dialect_name 

499 for dialect_name, args in self.obj.dialect_options.items() 

500 if args._reflect_only_elements 

501 ) 

502 

503 

504class _DialectArgDict(MutableMapping[str, Any]): 

505 """A dictionary view of dialect-level arguments for a specific 

506 dialect. 

507 

508 Maintains a separate collection of user-specified arguments 

509 and dialect-specified default arguments. 

510 

511 """ 

512 

513 def __init__(self) -> None: 

514 self._non_defaults: Dict[str, Any] = {} 

515 self._defaults: Dict[str, Any] = {} 

516 self._reflection_only_keys: FrozenSet[str] = util.EMPTY_SET 

517 self._reflect_only_elements: util.immutabledict[str, Any] = ( 

518 util.EMPTY_DICT 

519 ) 

520 

521 def __len__(self) -> int: 

522 return len(set(self._non_defaults).union(self._defaults)) 

523 

524 def __iter__(self) -> Iterator[str]: 

525 return iter(set(self._non_defaults).union(self._defaults)) 

526 

527 def __getitem__(self, key: str) -> Any: 

528 if key in self._non_defaults: 

529 return self._non_defaults[key] 

530 else: 

531 return self._defaults[key] 

532 

533 def __setitem__(self, key: str, value: Any) -> None: 

534 if key in self._reflection_only_keys: 

535 self._reflect_only_elements = self._reflect_only_elements.union( 

536 {key: value} 

537 ) 

538 else: 

539 self._non_defaults[key] = value 

540 

541 def __delitem__(self, key: str) -> None: 

542 del self._non_defaults[key] 

543 

544 

545@util.preload_module("sqlalchemy.dialects") 

546def _kw_reg_for_dialect(dialect_name: str) -> Optional[Dict[Any, Any]]: 

547 dialect_cls = util.preloaded.dialects.registry.load(dialect_name) 

548 if dialect_cls.construct_arguments is None: 

549 return None 

550 return dict(dialect_cls.construct_arguments) 

551 

552 

553class DialectKWArgs: 

554 """Establish the ability for a class to have dialect-specific arguments 

555 with defaults and constructor validation. 

556 

557 The :class:`.DialectKWArgs` interacts with the 

558 :attr:`.DefaultDialect.construct_arguments` present on a dialect. 

559 

560 .. seealso:: 

561 

562 :attr:`.DefaultDialect.construct_arguments` 

563 

564 """ 

565 

566 __slots__ = () 

567 

568 _dialect_kwargs_traverse_internals: List[Tuple[str, Any]] = [ 

569 ("dialect_options", InternalTraversal.dp_dialect_options) 

570 ] 

571 

572 def get_dialect_option( 

573 self, 

574 dialect: Dialect, 

575 argument_name: str, 

576 *, 

577 else_: Any = None, 

578 deprecated_fallback: Optional[str] = None, 

579 ) -> Any: 

580 r"""Return the value of a dialect-specific option, or *else_* if 

581 this dialect does not register the given argument. 

582 

583 This is useful for DDL compilers that may be inherited by 

584 third-party dialects whose ``construct_arguments`` do not 

585 include the same set of keys as the parent dialect. 

586 

587 :param dialect: The dialect for which to retrieve the option. 

588 :param argument_name: The name of the argument to retrieve. 

589 :param else\_: The value to return if the argument is not present. 

590 :param deprecated_fallback: Optional dialect name to fall back to 

591 if the argument is not present for the current dialect. If the 

592 argument is present for the fallback dialect but not the current 

593 dialect, a deprecation warning will be emitted. 

594 

595 """ 

596 

597 registry = DialectKWArgs._kw_registry[dialect.name] 

598 if registry is None: 

599 return else_ 

600 

601 if argument_name in registry.get(self.__class__, {}): 

602 if ( 

603 argument_name 

604 in self.dialect_options[dialect.name]._reflection_only_keys 

605 ): 

606 return else_ 

607 if ( 

608 deprecated_fallback is None 

609 or dialect.name == deprecated_fallback 

610 ): 

611 return self.dialect_options[dialect.name][argument_name] 

612 

613 # deprecated_fallback is present; need to look in two places 

614 

615 # Current dialect has this option registered. 

616 # Check if user explicitly set it. 

617 if ( 

618 dialect.name in self.dialect_options 

619 and argument_name 

620 in self.dialect_options[dialect.name]._non_defaults 

621 ): 

622 # User explicitly set this dialect's option - use it 

623 return self.dialect_options[dialect.name][argument_name] 

624 

625 # User didn't set current dialect's option. 

626 # Check for deprecated fallback. 

627 elif ( 

628 deprecated_fallback in self.dialect_options 

629 and argument_name 

630 in self.dialect_options[deprecated_fallback]._non_defaults 

631 ): 

632 # User set fallback option but not current dialect's option 

633 warn_deprecated( 

634 f"Using '{deprecated_fallback}_{argument_name}' " 

635 f"with the '{dialect.name}' dialect is deprecated; " 

636 f"please additionally specify " 

637 f"'{dialect.name}_{argument_name}'.", 

638 version="2.1", 

639 ) 

640 return self.dialect_options[deprecated_fallback][argument_name] 

641 

642 # Return default value 

643 return self.dialect_options[dialect.name][argument_name] 

644 else: 

645 # Current dialect doesn't have the option registered at all. 

646 # Don't warn - if a third-party dialect doesn't support an 

647 # option, that's their choice, not a deprecation case. 

648 return else_ 

649 

650 @classmethod 

651 def argument_for( 

652 cls, dialect_name: str, argument_name: str, default: Any 

653 ) -> None: 

654 """Add a new kind of dialect-specific keyword argument for this class. 

655 

656 E.g.:: 

657 

658 Index.argument_for("mydialect", "length", None) 

659 

660 some_index = Index("a", "b", mydialect_length=5) 

661 

662 The :meth:`.DialectKWArgs.argument_for` method is a per-argument 

663 way adding extra arguments to the 

664 :attr:`.DefaultDialect.construct_arguments` dictionary. This 

665 dictionary provides a list of argument names accepted by various 

666 schema-level constructs on behalf of a dialect. 

667 

668 New dialects should typically specify this dictionary all at once as a 

669 data member of the dialect class. The use case for ad-hoc addition of 

670 argument names is typically for end-user code that is also using 

671 a custom compilation scheme which consumes the additional arguments. 

672 

673 :param dialect_name: name of a dialect. The dialect must be 

674 locatable, else a :class:`.NoSuchModuleError` is raised. The 

675 dialect must also include an existing 

676 :attr:`.DefaultDialect.construct_arguments` collection, indicating 

677 that it participates in the keyword-argument validation and default 

678 system, else :class:`.ArgumentError` is raised. If the dialect does 

679 not include this collection, then any keyword argument can be 

680 specified on behalf of this dialect already. All dialects packaged 

681 within SQLAlchemy include this collection, however for third party 

682 dialects, support may vary. 

683 

684 :param argument_name: name of the parameter. 

685 

686 :param default: default value of the parameter. 

687 

688 """ 

689 

690 construct_arg_dictionary: Optional[Dict[Any, Any]] = ( 

691 DialectKWArgs._kw_registry[dialect_name] 

692 ) 

693 if construct_arg_dictionary is None: 

694 raise exc.ArgumentError( 

695 "Dialect '%s' does have keyword-argument " 

696 "validation and defaults enabled configured" % dialect_name 

697 ) 

698 if cls not in construct_arg_dictionary: 

699 construct_arg_dictionary[cls] = {} 

700 construct_arg_dictionary[cls][argument_name] = default 

701 

702 @property 

703 def dialect_kwargs(self) -> _DialectArgView: 

704 """A collection of keyword arguments specified as dialect-specific 

705 options to this construct. 

706 

707 The arguments are present here in their original ``<dialect>_<kwarg>`` 

708 format. Only arguments that were actually passed are included; 

709 unlike the :attr:`.DialectKWArgs.dialect_options` collection, which 

710 contains all options known by this dialect including defaults. 

711 

712 The collection is also writable; keys are accepted of the 

713 form ``<dialect>_<kwarg>`` where the value will be assembled 

714 into the list of options. 

715 

716 .. seealso:: 

717 

718 :attr:`.DialectKWArgs.dialect_options` - nested dictionary form 

719 

720 """ 

721 return _DialectArgView(self) 

722 

723 @property 

724 def kwargs(self) -> _DialectArgView: 

725 """A synonym for :attr:`.DialectKWArgs.dialect_kwargs`.""" 

726 return self.dialect_kwargs 

727 

728 @property 

729 def reflect_only_elements(self) -> Mapping[str, Mapping[str, Any]]: 

730 """A read-only collection of dialect-specific database state 

731 reported by reflection, separate from DDL options. 

732 

733 Holds values for arguments whose 

734 :attr:`.DefaultDialect.construct_arguments` default is 

735 :attr:`.DialectKWArgConst.REFLECTED_ONLY`. Like 

736 :attr:`.DialectKWArgs.dialect_options`, this is a two-level nested 

737 collection keyed to ``<dialect_name>`` and ``<argument_name>``; a 

738 dialect name with no values present returns an empty mapping, for 

739 example:: 

740 

741 invalid = my_index.reflect_only_elements["postgresql"].get("invalid") 

742 

743 These values are not included in 

744 :attr:`.DialectKWArgs.dialect_options` or 

745 :attr:`.DialectKWArgs.dialect_kwargs` and take no part in DDL 

746 compilation. They are carried along to copies of the object, such 

747 as those produced by :meth:`.Table.to_metadata`. 

748 

749 .. versionadded:: 2.1 

750 

751 .. seealso:: 

752 

753 :attr:`.DialectKWArgConst.REFLECTED_ONLY` 

754 

755 """ # noqa: E501 

756 return _ReflectOnlyView(self) 

757 

758 @property 

759 def _reflect_only_kwargs(self) -> Dict[str, Any]: 

760 """The contents of :attr:`.DialectKWArgs.reflect_only_elements` in 

761 flat ``<dialect>_<argument>`` form, suitable to be passed to the 

762 constructor of a copy of this object.""" 

763 

764 return { 

765 f"{dialect_name}_{key}": value 

766 for dialect_name, elements in self.reflect_only_elements.items() 

767 for key, value in elements.items() 

768 } 

769 

770 def _copy_reflect_only_elements(self, other: DialectKWArgs) -> None: 

771 """Copy the contents of :attr:`.DialectKWArgs.reflect_only_elements` 

772 onto ``other``, a copy of this object.""" 

773 

774 if "dialect_options" not in self.__dict__: 

775 return 

776 for dialect_name, args in self.dialect_options.items(): 

777 if args._reflect_only_elements: 

778 other.dialect_options[dialect_name]._reflect_only_elements = ( 

779 args._reflect_only_elements 

780 ) 

781 

782 _kw_registry: util.PopulateDict[str, Optional[Dict[Any, Any]]] = ( 

783 util.PopulateDict(_kw_reg_for_dialect) 

784 ) 

785 

786 @classmethod 

787 def _kw_reg_for_dialect_cls(cls, dialect_name: str) -> _DialectArgDict: 

788 construct_arg_dictionary = DialectKWArgs._kw_registry[dialect_name] 

789 d = _DialectArgDict() 

790 

791 if construct_arg_dictionary is None: 

792 d._defaults.update({"*": None}) 

793 else: 

794 for cls in reversed(cls.__mro__): 

795 if cls in construct_arg_dictionary: 

796 d._defaults.update(construct_arg_dictionary[cls]) 

797 d._reflection_only_keys = frozenset( 

798 key 

799 for key, value in d._defaults.items() 

800 if value is DialectKWArgConst.REFLECTED_ONLY 

801 ) 

802 for key in d._reflection_only_keys: 

803 del d._defaults[key] 

804 return d 

805 

806 @util.memoized_property 

807 def dialect_options(self) -> util.PopulateDict[str, _DialectArgDict]: 

808 """A collection of keyword arguments specified as dialect-specific 

809 options to this construct. 

810 

811 This is a two-level nested registry, keyed to ``<dialect_name>`` 

812 and ``<argument_name>``. For example, the ``postgresql_where`` 

813 argument would be locatable as:: 

814 

815 arg = my_object.dialect_options["postgresql"]["where"] 

816 

817 .. versionadded:: 0.9.2 

818 

819 Arguments a dialect declares as 

820 :attr:`.DialectKWArgConst.REFLECTED_ONLY` are not included in this 

821 collection; they are available from 

822 :attr:`.DialectKWArgs.reflect_only_elements`. 

823 

824 .. seealso:: 

825 

826 :attr:`.DialectKWArgs.dialect_kwargs` - flat dictionary form 

827 

828 :attr:`.DialectKWArgs.reflect_only_elements` - database state 

829 reported by reflection 

830 

831 """ 

832 

833 return util.PopulateDict(self._kw_reg_for_dialect_cls) 

834 

835 def _validate_dialect_kwargs(self, kwargs: Dict[str, Any]) -> None: 

836 # validate remaining kwargs that they all specify DB prefixes 

837 

838 if not kwargs: 

839 return 

840 

841 for k in kwargs: 

842 m = re.match("^(.+?)_(.+)$", k) 

843 if not m: 

844 raise TypeError( 

845 "Additional arguments should be " 

846 "named <dialectname>_<argument>, got '%s'" % k 

847 ) 

848 dialect_name, arg_name = m.group(1, 2) 

849 

850 try: 

851 construct_arg_dictionary = self.dialect_options[dialect_name] 

852 except exc.NoSuchModuleError: 

853 util.warn( 

854 "Can't validate argument %r; can't " 

855 "locate any SQLAlchemy dialect named %r" 

856 % (k, dialect_name) 

857 ) 

858 self.dialect_options[dialect_name] = d = _DialectArgDict() 

859 d._defaults.update({"*": None}) 

860 d._non_defaults[arg_name] = kwargs[k] 

861 else: 

862 if ( 

863 "*" not in construct_arg_dictionary 

864 and arg_name not in construct_arg_dictionary 

865 and arg_name 

866 not in construct_arg_dictionary._reflection_only_keys 

867 ): 

868 raise exc.ArgumentError( 

869 "Argument %r is not accepted by " 

870 "dialect %r on behalf of %r" 

871 % (k, dialect_name, self.__class__) 

872 ) 

873 else: 

874 construct_arg_dictionary[arg_name] = kwargs[k] 

875 

876 

877class CompileState: 

878 """Produces additional object state necessary for a statement to be 

879 compiled. 

880 

881 the :class:`.CompileState` class is at the base of classes that assemble 

882 state for a particular statement object that is then used by the 

883 compiler. This process is essentially an extension of the process that 

884 the SQLCompiler.visit_XYZ() method takes, however there is an emphasis 

885 on converting raw user intent into more organized structures rather than 

886 producing string output. The top-level :class:`.CompileState` for the 

887 statement being executed is also accessible when the execution context 

888 works with invoking the statement and collecting results. 

889 

890 The production of :class:`.CompileState` is specific to the compiler, such 

891 as within the :meth:`.SQLCompiler.visit_insert`, 

892 :meth:`.SQLCompiler.visit_select` etc. methods. These methods are also 

893 responsible for associating the :class:`.CompileState` with the 

894 :class:`.SQLCompiler` itself, if the statement is the "toplevel" statement, 

895 i.e. the outermost SQL statement that's actually being executed. 

896 There can be other :class:`.CompileState` objects that are not the 

897 toplevel, such as when a SELECT subquery or CTE-nested 

898 INSERT/UPDATE/DELETE is generated. 

899 

900 .. versionadded:: 1.4 

901 

902 """ 

903 

904 __slots__ = ("statement", "_ambiguous_table_name_map") 

905 

906 plugins: Dict[Tuple[str, str], Type[CompileState]] = {} 

907 

908 _ambiguous_table_name_map: Optional[_AmbiguousTableNameMap] 

909 

910 @classmethod 

911 def create_for_statement( 

912 cls, statement: Executable, compiler: SQLCompiler, **kw: Any 

913 ) -> CompileState: 

914 # factory construction. 

915 

916 if statement._propagate_attrs: 

917 plugin_name = statement._propagate_attrs.get( 

918 "compile_state_plugin", "default" 

919 ) 

920 klass = cls.plugins.get( 

921 (plugin_name, statement._effective_plugin_target), None 

922 ) 

923 if klass is None: 

924 klass = cls.plugins[ 

925 ("default", statement._effective_plugin_target) 

926 ] 

927 

928 else: 

929 klass = cls.plugins[ 

930 ("default", statement._effective_plugin_target) 

931 ] 

932 

933 if klass is cls: 

934 return cls(statement, compiler, **kw) 

935 else: 

936 return klass.create_for_statement(statement, compiler, **kw) 

937 

938 def __init__(self, statement, compiler, **kw): 

939 self.statement = statement 

940 

941 @classmethod 

942 def get_plugin_class( 

943 cls, statement: Executable 

944 ) -> Optional[Type[CompileState]]: 

945 plugin_name = statement._propagate_attrs.get( 

946 "compile_state_plugin", None 

947 ) 

948 

949 if plugin_name: 

950 key = (plugin_name, statement._effective_plugin_target) 

951 if key in cls.plugins: 

952 return cls.plugins[key] 

953 

954 # there's no case where we call upon get_plugin_class() and want 

955 # to get None back, there should always be a default. return that 

956 # if there was no plugin-specific class (e.g. Insert with "orm" 

957 # plugin) 

958 try: 

959 return cls.plugins[("default", statement._effective_plugin_target)] 

960 except KeyError: 

961 return None 

962 

963 @classmethod 

964 def _get_plugin_class_for_plugin( 

965 cls, statement: Executable, plugin_name: str 

966 ) -> Optional[Type[CompileState]]: 

967 try: 

968 return cls.plugins[ 

969 (plugin_name, statement._effective_plugin_target) 

970 ] 

971 except KeyError: 

972 return None 

973 

974 @classmethod 

975 def plugin_for( 

976 cls, plugin_name: str, visit_name: str 

977 ) -> Callable[[_Fn], _Fn]: 

978 def decorate(cls_to_decorate): 

979 cls.plugins[(plugin_name, visit_name)] = cls_to_decorate 

980 return cls_to_decorate 

981 

982 return decorate 

983 

984 

985class Generative(HasMemoized): 

986 """Provide a method-chaining pattern in conjunction with the 

987 @_generative decorator.""" 

988 

989 def _generate(self) -> Self: 

990 skip = self._memoized_keys 

991 cls = self.__class__ 

992 s = cls.__new__(cls) 

993 if skip: 

994 # ensure this iteration remains atomic 

995 s.__dict__ = { 

996 k: v for k, v in self.__dict__.copy().items() if k not in skip 

997 } 

998 else: 

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

1000 return s 

1001 

1002 

1003class InPlaceGenerative(HasMemoized): 

1004 """Provide a method-chaining pattern in conjunction with the 

1005 @_generative decorator that mutates in place.""" 

1006 

1007 __slots__ = () 

1008 

1009 def _generate(self) -> Self: 

1010 skip = self._memoized_keys 

1011 # note __dict__ needs to be in __slots__ if this is used 

1012 for k in skip: 

1013 self.__dict__.pop(k, None) 

1014 return self 

1015 

1016 

1017class HasCompileState(Generative): 

1018 """A class that has a :class:`.CompileState` associated with it.""" 

1019 

1020 _compile_state_plugin: Optional[Type[CompileState]] = None 

1021 

1022 _attributes: util.immutabledict[str, Any] = util.EMPTY_DICT 

1023 

1024 _compile_state_factory = CompileState.create_for_statement 

1025 

1026 

1027class _MetaOptions(type): 

1028 """metaclass for the Options class. 

1029 

1030 This metaclass is actually necessary despite the availability of the 

1031 ``__init_subclass__()`` hook as this type also provides custom class-level 

1032 behavior for the ``__add__()`` method. 

1033 

1034 """ 

1035 

1036 _cache_attrs: Tuple[str, ...] 

1037 

1038 def __add__(self, other): 

1039 o1 = self() 

1040 

1041 if set(other).difference(self._cache_attrs): 

1042 raise TypeError( 

1043 "dictionary contains attributes not covered by " 

1044 "Options class %s: %r" 

1045 % (self, set(other).difference(self._cache_attrs)) 

1046 ) 

1047 

1048 o1.__dict__.update(other) 

1049 return o1 

1050 

1051 if TYPE_CHECKING: 

1052 

1053 def __getattr__(self, key: str) -> Any: ... 

1054 

1055 def __setattr__(self, key: str, value: Any) -> None: ... 

1056 

1057 def __delattr__(self, key: str) -> None: ... 

1058 

1059 

1060class Options(metaclass=_MetaOptions): 

1061 """A cacheable option dictionary with defaults.""" 

1062 

1063 __slots__ = () 

1064 

1065 _cache_attrs: Tuple[str, ...] 

1066 

1067 def __init_subclass__(cls) -> None: 

1068 dict_ = cls.__dict__ 

1069 cls._cache_attrs = tuple( 

1070 sorted( 

1071 d 

1072 for d in dict_ 

1073 if not d.startswith("__") 

1074 and d not in ("_cache_key_traversal",) 

1075 ) 

1076 ) 

1077 super().__init_subclass__() 

1078 

1079 def __init__(self, **kw: Any) -> None: 

1080 self.__dict__.update(kw) 

1081 

1082 def __add__(self, other): 

1083 o1 = self.__class__.__new__(self.__class__) 

1084 o1.__dict__.update(self.__dict__) 

1085 

1086 if set(other).difference(self._cache_attrs): 

1087 raise TypeError( 

1088 "dictionary contains attributes not covered by " 

1089 "Options class %s: %r" 

1090 % (self, set(other).difference(self._cache_attrs)) 

1091 ) 

1092 

1093 o1.__dict__.update(other) 

1094 return o1 

1095 

1096 def __eq__(self, other): 

1097 # TODO: very inefficient. This is used only in test suites 

1098 # right now. 

1099 for a, b in zip_longest(self._cache_attrs, other._cache_attrs): 

1100 if getattr(self, a) != getattr(other, b): 

1101 return False 

1102 return True 

1103 

1104 def __repr__(self) -> str: 

1105 # TODO: fairly inefficient, used only in debugging right now. 

1106 

1107 return "%s(%s)" % ( 

1108 self.__class__.__name__, 

1109 ", ".join( 

1110 "%s=%r" % (k, self.__dict__[k]) 

1111 for k in self._cache_attrs 

1112 if k in self.__dict__ 

1113 ), 

1114 ) 

1115 

1116 @classmethod 

1117 def isinstance(cls, klass: Type[Any]) -> bool: 

1118 return issubclass(cls, klass) 

1119 

1120 @hybridmethod 

1121 def add_to_element(self, name: str, value: str) -> Any: 

1122 return self + {name: getattr(self, name) + value} 

1123 

1124 @hybridmethod 

1125 def _state_dict_inst(self) -> Mapping[str, Any]: 

1126 return self.__dict__ 

1127 

1128 _state_dict_const: util.immutabledict[str, Any] = util.EMPTY_DICT 

1129 

1130 @_state_dict_inst.classlevel 

1131 def _state_dict(cls) -> Mapping[str, Any]: 

1132 return cls._state_dict_const 

1133 

1134 @classmethod 

1135 def safe_merge(cls, other: "Options") -> Any: 

1136 d = other._state_dict() 

1137 

1138 # only support a merge with another object of our class 

1139 # and which does not have attrs that we don't. otherwise 

1140 # we risk having state that might not be part of our cache 

1141 # key strategy 

1142 

1143 if ( 

1144 cls is not other.__class__ 

1145 and other._cache_attrs 

1146 and set(other._cache_attrs).difference(cls._cache_attrs) 

1147 ): 

1148 raise TypeError( 

1149 "other element %r is not empty, is not of type %s, " 

1150 "and contains attributes not covered here %r" 

1151 % ( 

1152 other, 

1153 cls, 

1154 set(other._cache_attrs).difference(cls._cache_attrs), 

1155 ) 

1156 ) 

1157 return cls + d 

1158 

1159 @classmethod 

1160 def from_execution_options( 

1161 cls, 

1162 key: str, 

1163 attrs: set[str], 

1164 exec_options: Mapping[str, Any], 

1165 statement_exec_options: Mapping[str, Any], 

1166 ) -> Tuple["Options", Mapping[str, Any]]: 

1167 """process Options argument in terms of execution options. 

1168 

1169 

1170 e.g.:: 

1171 

1172 ( 

1173 load_options, 

1174 execution_options, 

1175 ) = QueryContext.default_load_options.from_execution_options( 

1176 "_sa_orm_load_options", 

1177 {"populate_existing", "autoflush", "yield_per"}, 

1178 execution_options, 

1179 statement._execution_options, 

1180 ) 

1181 

1182 get back the Options and refresh "_sa_orm_load_options" in the 

1183 exec options dict w/ the Options as well 

1184 

1185 """ 

1186 

1187 # common case is that no options we are looking for are 

1188 # in either dictionary, so cancel for that first 

1189 check_argnames = attrs.intersection( 

1190 set(exec_options).union(statement_exec_options) 

1191 ) 

1192 

1193 existing_options = exec_options.get(key, cls) 

1194 

1195 if check_argnames: 

1196 result = {} 

1197 for argname in check_argnames: 

1198 local = "_" + argname 

1199 if argname in exec_options: 

1200 result[local] = exec_options[argname] 

1201 elif argname in statement_exec_options: 

1202 result[local] = statement_exec_options[argname] 

1203 

1204 new_options = existing_options + result 

1205 exec_options = util.EMPTY_DICT.merge_with( 

1206 exec_options, {key: new_options} 

1207 ) 

1208 return new_options, exec_options 

1209 

1210 else: 

1211 return existing_options, exec_options 

1212 

1213 if TYPE_CHECKING: 

1214 

1215 def __getattr__(self, key: str) -> Any: ... 

1216 

1217 def __setattr__(self, key: str, value: Any) -> None: ... 

1218 

1219 def __delattr__(self, key: str) -> None: ... 

1220 

1221 

1222class CacheableOptions(Options, HasCacheKey): 

1223 __slots__ = () 

1224 

1225 @hybridmethod 

1226 def _gen_cache_key_inst( 

1227 self, anon_map: Any, bindparams: List[BindParameter[Any]] 

1228 ) -> Optional[Tuple[Any]]: 

1229 # _gen_cache_key is a compiled function in _cache_key_cy; its 

1230 # cython directives make mypy see it as untyped 

1231 return HasCacheKey._gen_cache_key( # type: ignore[no-any-return] # noqa: E501 

1232 self, anon_map, bindparams 

1233 ) 

1234 

1235 @_gen_cache_key_inst.classlevel 

1236 def _gen_cache_key( 

1237 cls, anon_map: "anon_map", bindparams: List[BindParameter[Any]] 

1238 ) -> Tuple[CacheableOptions, Any]: 

1239 return (cls, ()) 

1240 

1241 @hybridmethod 

1242 def _generate_cache_key(self) -> Optional[CacheKey]: 

1243 return HasCacheKey._generate_cache_key(self) 

1244 

1245 

1246class ExecutableOption(HasCopyInternals): 

1247 __slots__ = () 

1248 

1249 _annotations: _ImmutableExecuteOptions = util.EMPTY_DICT 

1250 

1251 __visit_name__: str = "executable_option" 

1252 

1253 _is_has_cache_key: bool = False 

1254 

1255 _is_core: bool = True 

1256 

1257 def _clone(self, **kw): 

1258 """Create a shallow copy of this ExecutableOption.""" 

1259 c = self.__class__.__new__(self.__class__) 

1260 c.__dict__ = dict(self.__dict__) # type: ignore[misc] 

1261 return c 

1262 

1263 

1264_L = TypeVar("_L", bound=str) 

1265 

1266 

1267class HasSyntaxExtensions(Generic[_L]): 

1268 

1269 _position_map: Mapping[_L, str] 

1270 

1271 @_generative 

1272 def ext(self, extension: SyntaxExtension) -> Self: 

1273 """Applies a SQL syntax extension to this statement. 

1274 

1275 SQL syntax extensions are :class:`.ClauseElement` objects that define 

1276 some vendor-specific syntactical construct that take place in specific 

1277 parts of a SQL statement. Examples include vendor extensions like 

1278 PostgreSQL / SQLite's "ON DUPLICATE KEY UPDATE", PostgreSQL's 

1279 "DISTINCT ON", and MySQL's "LIMIT" that can be applied to UPDATE 

1280 and DELETE statements. 

1281 

1282 .. seealso:: 

1283 

1284 :ref:`examples_syntax_extensions` 

1285 

1286 :func:`_mysql.limit` - DML LIMIT for MySQL 

1287 

1288 :func:`_postgresql.distinct_on` - DISTINCT ON for PostgreSQL 

1289 

1290 .. versionadded:: 2.1 

1291 

1292 """ 

1293 extension = coercions.expect( 

1294 roles.SyntaxExtensionRole, extension, apply_propagate_attrs=self 

1295 ) 

1296 self._apply_syntax_extension_to_self(extension) 

1297 return self 

1298 

1299 @util.preload_module("sqlalchemy.sql.elements") 

1300 def apply_syntax_extension_point( 

1301 self, 

1302 apply_fn: Callable[[Sequence[ClauseElement]], Sequence[ClauseElement]], 

1303 position: _L, 

1304 ) -> None: 

1305 """Apply a :class:`.SyntaxExtension` to a known extension point. 

1306 

1307 Should be used only internally by :class:`.SyntaxExtension`. 

1308 

1309 E.g.:: 

1310 

1311 class Qualify(SyntaxExtension, ClauseElement): 

1312 

1313 # ... 

1314 

1315 def apply_to_select(self, select_stmt: Select) -> None: 

1316 # append self to existing 

1317 select_stmt.apply_extension_point( 

1318 lambda existing: [*existing, self], "post_criteria" 

1319 ) 

1320 

1321 

1322 class ReplaceExt(SyntaxExtension, ClauseElement): 

1323 

1324 # ... 

1325 

1326 def apply_to_select(self, select_stmt: Select) -> None: 

1327 # replace any existing elements regardless of type 

1328 select_stmt.apply_extension_point( 

1329 lambda existing: [self], "post_criteria" 

1330 ) 

1331 

1332 

1333 class ReplaceOfTypeExt(SyntaxExtension, ClauseElement): 

1334 

1335 # ... 

1336 

1337 def apply_to_select(self, select_stmt: Select) -> None: 

1338 # replace any existing elements of the same type 

1339 select_stmt.apply_extension_point( 

1340 self.append_replacing_same_type, "post_criteria" 

1341 ) 

1342 

1343 :param apply_fn: callable function that will receive a sequence of 

1344 :class:`.ClauseElement` that is already populating the extension 

1345 point (the sequence is empty if there isn't one), and should return 

1346 a new sequence of :class:`.ClauseElement` that will newly populate 

1347 that point. The function typically can choose to concatenate the 

1348 existing values with the new one, or to replace the values that are 

1349 there with a new one by returning a list of a single element, or 

1350 to perform more complex operations like removing only the same 

1351 type element from the input list of merging already existing elements 

1352 of the same type. Some examples are shown in the examples above 

1353 :param position: string name of the position to apply to. This 

1354 varies per statement type. IDEs should show the possible values 

1355 for each statement type as it's typed with a ``typing.Literal`` per 

1356 statement. 

1357 

1358 .. seealso:: 

1359 

1360 :ref:`examples_syntax_extensions` 

1361 

1362 :meth:`.ext` 

1363 

1364 

1365 """ # noqa: E501 

1366 

1367 try: 

1368 attrname = self._position_map[position] 

1369 except KeyError as ke: 

1370 raise ValueError( 

1371 f"Unknown position {position!r} for {self.__class__} " 

1372 f"construct; known positions: " 

1373 f"{', '.join(repr(k) for k in self._position_map)}" 

1374 ) from ke 

1375 else: 

1376 ElementList = util.preloaded.sql_elements.ElementList 

1377 existing: Optional[ClauseElement] = getattr(self, attrname, None) 

1378 if existing is None: 

1379 input_seq: Tuple[ClauseElement, ...] = () 

1380 elif isinstance(existing, ElementList): 

1381 input_seq = existing.clauses 

1382 else: 

1383 input_seq = (existing,) 

1384 

1385 new_seq = apply_fn(input_seq) 

1386 assert new_seq, "cannot return empty sequence" 

1387 new = new_seq[0] if len(new_seq) == 1 else ElementList(new_seq) 

1388 setattr(self, attrname, new) 

1389 

1390 def _apply_syntax_extension_to_self( 

1391 self, extension: SyntaxExtension 

1392 ) -> None: 

1393 raise NotImplementedError() 

1394 

1395 def _get_syntax_extensions_as_dict(self) -> Mapping[_L, SyntaxExtension]: 

1396 res: Dict[_L, SyntaxExtension] = {} 

1397 for name, attr in self._position_map.items(): 

1398 value = getattr(self, attr) 

1399 if value is not None: 

1400 res[name] = value 

1401 return res 

1402 

1403 def _set_syntax_extensions(self, **extensions: SyntaxExtension) -> None: 

1404 for name, value in extensions.items(): 

1405 setattr(self, self._position_map[name], value) # type: ignore[index] # noqa: E501 

1406 

1407 

1408class SyntaxExtension(roles.SyntaxExtensionRole): 

1409 """Defines a unit that when also extending from :class:`.ClauseElement` 

1410 can be applied to SQLAlchemy statements :class:`.Select`, 

1411 :class:`_sql.Insert`, :class:`.Update` and :class:`.Delete` making use of 

1412 pre-established SQL insertion points within these constructs. 

1413 

1414 .. versionadded:: 2.1 

1415 

1416 .. seealso:: 

1417 

1418 :ref:`examples_syntax_extensions` 

1419 

1420 """ 

1421 

1422 def append_replacing_same_type( 

1423 self, existing: Sequence[ClauseElement] 

1424 ) -> Sequence[ClauseElement]: 

1425 """Utility function that can be used as 

1426 :paramref:`_sql.Select.apply_syntax_extension_point.apply_fn` 

1427 to remove any other element of the same type in existing and appending 

1428 ``self`` to the list. 

1429 

1430 This is equivalent to:: 

1431 

1432 stmt.apply_syntax_extension_point( 

1433 lambda existing: [ 

1434 *(e for e in existing if not isinstance(e, ReplaceOfTypeExt)), 

1435 self, 

1436 ], 

1437 "post_criteria", 

1438 ) 

1439 

1440 .. seealso:: 

1441 

1442 :ref:`examples_syntax_extensions` 

1443 

1444 :meth:`_sql.Select.apply_syntax_extension_point` and equivalents 

1445 in :class:`_dml.Insert`, :class:`_dml.Delete`, :class:`_dml.Update` 

1446 

1447 """ # noqa: E501 

1448 cls = type(self) 

1449 return [*(e for e in existing if not isinstance(e, cls)), self] # type: ignore[list-item] # noqa: E501 

1450 

1451 def apply_to_select(self, select_stmt: Select[Unpack[_Ts]]) -> None: 

1452 """Apply this :class:`.SyntaxExtension` to a :class:`.Select`""" 

1453 raise NotImplementedError( 

1454 f"Extension {type(self).__name__} cannot be applied to select" 

1455 ) 

1456 

1457 def apply_to_update(self, update_stmt: Update) -> None: 

1458 """Apply this :class:`.SyntaxExtension` to an :class:`.Update`""" 

1459 raise NotImplementedError( 

1460 f"Extension {type(self).__name__} cannot be applied to update" 

1461 ) 

1462 

1463 def apply_to_delete(self, delete_stmt: Delete) -> None: 

1464 """Apply this :class:`.SyntaxExtension` to a :class:`.Delete`""" 

1465 raise NotImplementedError( 

1466 f"Extension {type(self).__name__} cannot be applied to delete" 

1467 ) 

1468 

1469 def apply_to_insert(self, insert_stmt: Insert) -> None: 

1470 """Apply this :class:`.SyntaxExtension` to an :class:`_sql.Insert`""" 

1471 raise NotImplementedError( 

1472 f"Extension {type(self).__name__} cannot be applied to insert" 

1473 ) 

1474 

1475 

1476class Executable(roles.StatementRole): 

1477 """Mark a :class:`_expression.ClauseElement` as supporting execution. 

1478 

1479 :class:`.Executable` is a superclass for all "statement" types 

1480 of objects, including :func:`select`, :func:`delete`, :func:`update`, 

1481 :func:`insert`, :func:`text`. 

1482 

1483 """ 

1484 

1485 supports_execution: bool = True 

1486 _execution_options: _ImmutableExecuteOptions = util.EMPTY_DICT 

1487 _is_default_generator: bool = False 

1488 _with_options: Tuple[ExecutableOption, ...] = () 

1489 _compile_state_funcs: Tuple[ 

1490 Tuple[Callable[[CompileState], None], Any], ... 

1491 ] = () 

1492 _compile_options: Optional[Union[Type[CacheableOptions], CacheableOptions]] 

1493 

1494 _executable_traverse_internals = [ 

1495 ("_with_options", InternalTraversal.dp_executable_options), 

1496 ( 

1497 "_compile_state_funcs", 

1498 ExtendedInternalTraversal.dp_compile_state_funcs, 

1499 ), 

1500 ("_propagate_attrs", ExtendedInternalTraversal.dp_propagate_attrs), 

1501 ] 

1502 

1503 is_select: bool = False 

1504 is_from_statement: bool = False 

1505 is_update: bool = False 

1506 is_insert: bool = False 

1507 is_text: bool = False 

1508 is_delete: bool = False 

1509 is_dml: bool = False 

1510 

1511 if TYPE_CHECKING: 

1512 __visit_name__: str 

1513 

1514 def _compile_w_cache( 

1515 self, 

1516 dialect: Dialect, 

1517 *, 

1518 compiled_cache: Optional[CompiledCacheType], 

1519 column_keys: List[str], 

1520 for_executemany: bool = False, 

1521 schema_translate_map: Optional[SchemaTranslateMapType] = None, 

1522 **kw: Any, 

1523 ) -> tuple[ 

1524 Compiled, 

1525 Sequence[BindParameter[Any]] | None, 

1526 _CoreSingleExecuteParams | None, 

1527 CacheStats, 

1528 ]: ... 

1529 

1530 def _execute_on_connection( 

1531 self, 

1532 connection: Connection, 

1533 distilled_params: _CoreMultiExecuteParams, 

1534 execution_options: CoreExecuteOptionsParameter, 

1535 ) -> CursorResult[Any]: ... 

1536 

1537 def _execute_on_scalar( 

1538 self, 

1539 connection: Connection, 

1540 distilled_params: _CoreMultiExecuteParams, 

1541 execution_options: CoreExecuteOptionsParameter, 

1542 ) -> Any: ... 

1543 

1544 @util.ro_non_memoized_property 

1545 def _all_selected_columns(self) -> _SelectIterable: 

1546 raise NotImplementedError() 

1547 

1548 @property 

1549 def _effective_plugin_target(self) -> str: 

1550 return self.__visit_name__ 

1551 

1552 @_generative 

1553 def options(self, *options: ExecutableOption) -> Self: 

1554 """Apply options to this statement. 

1555 

1556 In the general sense, options are any kind of Python object 

1557 that can be interpreted by systems that consume the statement outside 

1558 of the regular SQL compiler chain. Specifically, these options are 

1559 the ORM level options that apply "eager load" and other loading 

1560 behaviors to an ORM query. 

1561 

1562 For background on specific kinds of options for specific kinds of 

1563 statements, refer to the documentation for those option objects. 

1564 

1565 .. versionchanged:: 1.4 - added :meth:`.Executable.options` to 

1566 Core statement objects towards the goal of allowing unified 

1567 Core / ORM querying capabilities. 

1568 

1569 .. seealso:: 

1570 

1571 :ref:`loading_columns` - refers to options specific to the usage 

1572 of ORM queries 

1573 

1574 :ref:`relationship_loader_options` - refers to options specific 

1575 to the usage of ORM queries 

1576 

1577 """ 

1578 self._with_options += tuple( 

1579 coercions.expect(roles.ExecutableOptionRole, opt) 

1580 for opt in options 

1581 ) 

1582 return self 

1583 

1584 @_generative 

1585 def _set_compile_options(self, compile_options: CacheableOptions) -> Self: 

1586 """Assign the compile options to a new value. 

1587 

1588 :param compile_options: appropriate CacheableOptions structure 

1589 

1590 """ 

1591 

1592 self._compile_options = compile_options 

1593 return self 

1594 

1595 @_generative 

1596 def _update_compile_options(self, options: CacheableOptions) -> Self: 

1597 """update the _compile_options with new keys.""" 

1598 

1599 assert self._compile_options is not None 

1600 self._compile_options += options 

1601 return self 

1602 

1603 @_generative 

1604 def _add_compile_state_func( 

1605 self, 

1606 callable_: Callable[[CompileState], None], 

1607 cache_args: Any, 

1608 ) -> Self: 

1609 """Add a compile state function to this statement. 

1610 

1611 When using the ORM only, these are callable functions that will 

1612 be given the CompileState object upon compilation. 

1613 

1614 A second argument cache_args is required, which will be combined with 

1615 the ``__code__`` identity of the function itself in order to produce a 

1616 cache key. 

1617 

1618 """ 

1619 self._compile_state_funcs += ((callable_, cache_args),) 

1620 return self 

1621 

1622 @overload 

1623 def execution_options( 

1624 self, 

1625 *, 

1626 compiled_cache: Optional[CompiledCacheType] = ..., 

1627 logging_token: str = ..., 

1628 isolation_level: IsolationLevel = ..., 

1629 no_parameters: bool = False, 

1630 stream_results: bool = False, 

1631 max_row_buffer: int = ..., 

1632 yield_per: int = ..., 

1633 driver_column_names: bool = ..., 

1634 insertmanyvalues_page_size: int = ..., 

1635 schema_translate_map: Optional[SchemaTranslateMapType] = ..., 

1636 populate_existing: bool = False, 

1637 autoflush: bool = False, 

1638 synchronize_session: SynchronizeSessionArgument = ..., 

1639 dml_strategy: DMLStrategyArgument = ..., 

1640 render_nulls: bool = ..., 

1641 is_delete_using: bool = ..., 

1642 is_update_from: bool = ..., 

1643 preserve_rowcount: bool = False, 

1644 **opt: Any, 

1645 ) -> Self: ... 

1646 

1647 @overload 

1648 def execution_options(self, **opt: Any) -> Self: ... 

1649 

1650 @_generative 

1651 def execution_options(self, **kw: Any) -> Self: 

1652 """Set non-SQL options for the statement which take effect during 

1653 execution. 

1654 

1655 Execution options can be set at many scopes, including per-statement, 

1656 per-connection, or per execution, using methods such as 

1657 :meth:`_engine.Connection.execution_options` and parameters which 

1658 accept a dictionary of options such as 

1659 :paramref:`_engine.Connection.execute.execution_options` and 

1660 :paramref:`_orm.Session.execute.execution_options`. 

1661 

1662 The primary characteristic of an execution option, as opposed to 

1663 other kinds of options such as ORM loader options, is that 

1664 **execution options never affect the compiled SQL of a query, only 

1665 things that affect how the SQL statement itself is invoked or how 

1666 results are fetched**. That is, execution options are not part of 

1667 what's accommodated by SQL compilation nor are they considered part of 

1668 the cached state of a statement. 

1669 

1670 The :meth:`_sql.Executable.execution_options` method is 

1671 :term:`generative`, as 

1672 is the case for the method as applied to the :class:`_engine.Engine` 

1673 and :class:`_orm.Query` objects, which means when the method is called, 

1674 a copy of the object is returned, which applies the given parameters to 

1675 that new copy, but leaves the original unchanged:: 

1676 

1677 statement = select(table.c.x, table.c.y) 

1678 new_statement = statement.execution_options(my_option=True) 

1679 

1680 An exception to this behavior is the :class:`_engine.Connection` 

1681 object, where the :meth:`_engine.Connection.execution_options` method 

1682 is explicitly **not** generative. 

1683 

1684 The kinds of options that may be passed to 

1685 :meth:`_sql.Executable.execution_options` and other related methods and 

1686 parameter dictionaries include parameters that are explicitly consumed 

1687 by SQLAlchemy Core or ORM, as well as arbitrary keyword arguments not 

1688 defined by SQLAlchemy, which means the methods and/or parameter 

1689 dictionaries may be used for user-defined parameters that interact with 

1690 custom code, which may access the parameters using methods such as 

1691 :meth:`_sql.Executable.get_execution_options` and 

1692 :meth:`_engine.Connection.get_execution_options`, or within selected 

1693 event hooks using a dedicated ``execution_options`` event parameter 

1694 such as 

1695 :paramref:`_events.ConnectionEvents.before_execute.execution_options` 

1696 or :attr:`_orm.ORMExecuteState.execution_options`, e.g.:: 

1697 

1698 from sqlalchemy import event 

1699 

1700 

1701 @event.listens_for(some_engine, "before_execute") 

1702 def _process_opt(conn, statement, multiparams, params, execution_options): 

1703 "run a SQL function before invoking a statement" 

1704 

1705 if execution_options.get("do_special_thing", False): 

1706 conn.exec_driver_sql("run_special_function()") 

1707 

1708 Within the scope of options that are explicitly recognized by 

1709 SQLAlchemy, most apply to specific classes of objects and not others. 

1710 The most common execution options include: 

1711 

1712 * :paramref:`_engine.Connection.execution_options.isolation_level` - 

1713 sets the isolation level for a connection or a class of connections 

1714 via an :class:`_engine.Engine`. This option is accepted only 

1715 by :class:`_engine.Connection` or :class:`_engine.Engine`. 

1716 

1717 * :paramref:`_engine.Connection.execution_options.stream_results` - 

1718 indicates results should be fetched using a server side cursor; 

1719 this option is accepted by :class:`_engine.Connection`, by the 

1720 :paramref:`_engine.Connection.execute.execution_options` parameter 

1721 on :meth:`_engine.Connection.execute`, and additionally by 

1722 :meth:`_sql.Executable.execution_options` on a SQL statement object, 

1723 as well as by ORM constructs like :meth:`_orm.Session.execute`. 

1724 

1725 * :paramref:`_engine.Connection.execution_options.compiled_cache` - 

1726 indicates a dictionary that will serve as the 

1727 :ref:`SQL compilation cache <sql_caching>` 

1728 for a :class:`_engine.Connection` or :class:`_engine.Engine`, as 

1729 well as for ORM methods like :meth:`_orm.Session.execute`. 

1730 Can be passed as ``None`` to disable caching for statements. 

1731 This option is not accepted by 

1732 :meth:`_sql.Executable.execution_options` as it is inadvisable to 

1733 carry along a compilation cache within a statement object. 

1734 

1735 * :paramref:`_engine.Connection.execution_options.schema_translate_map` 

1736 - a mapping of schema names used by the 

1737 :ref:`Schema Translate Map <schema_translating>` feature, accepted 

1738 by :class:`_engine.Connection`, :class:`_engine.Engine`, 

1739 :class:`_sql.Executable`, as well as by ORM constructs 

1740 like :meth:`_orm.Session.execute`. 

1741 

1742 .. seealso:: 

1743 

1744 :meth:`_engine.Connection.execution_options` 

1745 

1746 :paramref:`_engine.Connection.execute.execution_options` 

1747 

1748 :paramref:`_orm.Session.execute.execution_options` 

1749 

1750 :ref:`orm_queryguide_execution_options` - documentation on all 

1751 ORM-specific execution options 

1752 

1753 """ # noqa: E501 

1754 if "isolation_level" in kw: 

1755 raise exc.ArgumentError( 

1756 "'isolation_level' execution option may only be specified " 

1757 "on Connection.execution_options(), or " 

1758 "per-engine using the isolation_level " 

1759 "argument to create_engine()." 

1760 ) 

1761 if "compiled_cache" in kw: 

1762 raise exc.ArgumentError( 

1763 "'compiled_cache' execution option may only be specified " 

1764 "on Connection.execution_options(), not per statement." 

1765 ) 

1766 self._execution_options = self._execution_options.union(kw) 

1767 return self 

1768 

1769 def get_execution_options(self) -> _ExecuteOptions: 

1770 """Get the non-SQL options which will take effect during execution. 

1771 

1772 .. seealso:: 

1773 

1774 :meth:`.Executable.execution_options` 

1775 """ 

1776 return self._execution_options 

1777 

1778 

1779class ExecutableStatement(Executable): 

1780 """Executable subclass that implements a lightweight version of ``params`` 

1781 that avoids a full cloned traverse. 

1782 

1783 .. versionadded:: 2.1 

1784 

1785 """ 

1786 

1787 _params: util.immutabledict[str, Any] = EMPTY_DICT 

1788 

1789 _executable_traverse_internals = ( 

1790 Executable._executable_traverse_internals 

1791 + [("_params", InternalTraversal.dp_params)] 

1792 ) 

1793 

1794 @_generative 

1795 def params( 

1796 self, 

1797 __optionaldict: _CoreSingleExecuteParams | None = None, 

1798 /, 

1799 **kwargs: Any, 

1800 ) -> Self: 

1801 """Return a copy with the provided bindparam values. 

1802 

1803 Returns a copy of this Executable with bindparam values set 

1804 to the given dictionary:: 

1805 

1806 >>> clause = column("x") + bindparam("foo") 

1807 >>> print(clause.compile().params) 

1808 {'foo': None} 

1809 >>> print(clause.params({"foo": 7}).compile().params) 

1810 {'foo': 7} 

1811 

1812 """ 

1813 if __optionaldict: 

1814 kwargs.update(__optionaldict) 

1815 self._params = ( 

1816 util.immutabledict(kwargs) 

1817 if not self._params 

1818 else self._params | kwargs 

1819 ) 

1820 return self 

1821 

1822 

1823class SchemaEventTarget(event.EventTarget): 

1824 """Base class for elements that are the targets of :class:`.DDLEvents` 

1825 events. 

1826 

1827 This includes :class:`.SchemaItem` as well as :class:`.SchemaType`. 

1828 

1829 """ 

1830 

1831 dispatch: dispatcher[SchemaEventTarget] 

1832 

1833 def _set_parent(self, parent: SchemaEventTarget, **kw: Any) -> None: 

1834 """Associate with this SchemaEvent's parent object.""" 

1835 

1836 def _set_parent_with_dispatch( 

1837 self, parent: SchemaEventTarget, **kw: Any 

1838 ) -> None: 

1839 self.dispatch.before_parent_attach(self, parent) 

1840 self._set_parent(parent, **kw) 

1841 self.dispatch.after_parent_attach(self, parent) 

1842 

1843 

1844class SchemaVisitable(SchemaEventTarget, visitors.Visitable): 

1845 """Base class for elements that are targets of a :class:`.SchemaVisitor`. 

1846 

1847 .. versionadded:: 2.0.41 

1848 

1849 """ 

1850 

1851 

1852class SchemaVisitor(ClauseVisitor): 

1853 """Define the visiting for ``SchemaItem`` and more 

1854 generally ``SchemaVisitable`` objects. 

1855 

1856 """ 

1857 

1858 __traverse_options__: Dict[str, Any] = {"schema_visitor": True} 

1859 

1860 

1861class _SentinelDefaultCharacterization(Enum): 

1862 NONE = "none" 

1863 UNKNOWN = "unknown" 

1864 CLIENTSIDE = "clientside" 

1865 SENTINEL_DEFAULT = "sentinel_default" 

1866 SERVERSIDE = "serverside" 

1867 IDENTITY = "identity" 

1868 SEQUENCE = "sequence" 

1869 MONOTONIC_FUNCTION = "monotonic" 

1870 

1871 

1872class _SentinelColumnCharacterization(NamedTuple): 

1873 columns: Optional[Sequence[Column[Any]]] = None 

1874 is_explicit: bool = False 

1875 is_autoinc: bool = False 

1876 default_characterization: _SentinelDefaultCharacterization = ( 

1877 _SentinelDefaultCharacterization.NONE 

1878 ) 

1879 

1880 

1881_COLKEY = TypeVar("_COLKEY", Union[None, str], str) 

1882 

1883_COL_co = TypeVar("_COL_co", bound="ColumnElement[Any]", covariant=True) 

1884_COL = TypeVar("_COL", bound="ColumnElement[Any]") 

1885 

1886 

1887class _ColumnMetrics(Generic[_COL_co]): 

1888 __slots__ = ("column",) 

1889 

1890 column: _COL_co 

1891 

1892 def __init__( 

1893 self, collection: ColumnCollection[Any, _COL_co], col: _COL_co 

1894 ) -> None: 

1895 self.column = col 

1896 

1897 # proxy_index being non-empty means it was initialized. 

1898 # so we need to update it 

1899 pi = collection._proxy_index 

1900 if pi: 

1901 for eps_col in col._expanded_proxy_set: 

1902 pi[eps_col].add(self) 

1903 

1904 def get_expanded_proxy_set(self) -> FrozenSet[ColumnElement[Any]]: 

1905 return self.column._expanded_proxy_set 

1906 

1907 def dispose(self, collection: ColumnCollection[_COLKEY, _COL_co]) -> None: 

1908 pi = collection._proxy_index 

1909 if not pi: 

1910 return 

1911 for col in self.column._expanded_proxy_set: 

1912 colset = pi.get(col, None) 

1913 if colset: 

1914 colset.discard(self) 

1915 if colset is not None and not colset: 

1916 del pi[col] 

1917 

1918 def embedded( 

1919 self, 

1920 target_set: Union[ 

1921 Set[ColumnElement[Any]], FrozenSet[ColumnElement[Any]] 

1922 ], 

1923 ) -> bool: 

1924 expanded_proxy_set = self.column._expanded_proxy_set 

1925 for t in target_set.difference(expanded_proxy_set): 

1926 if not expanded_proxy_set.intersection(_expand_cloned([t])): 

1927 return False 

1928 return True 

1929 

1930 

1931class ColumnCollection(Generic[_COLKEY, _COL_co]): 

1932 """Base class for collection of :class:`_expression.ColumnElement` 

1933 instances, typically for :class:`_sql.FromClause` objects. 

1934 

1935 The :class:`_sql.ColumnCollection` object is most commonly available 

1936 as the :attr:`_schema.Table.c` or :attr:`_schema.Table.columns` collection 

1937 on the :class:`_schema.Table` object, introduced at 

1938 :ref:`metadata_tables_and_columns`. 

1939 

1940 The :class:`_expression.ColumnCollection` has both mapping- and sequence- 

1941 like behaviors. A :class:`_expression.ColumnCollection` usually stores 

1942 :class:`_schema.Column` objects, which are then accessible both via mapping 

1943 style access as well as attribute access style. 

1944 

1945 To access :class:`_schema.Column` objects using ordinary attribute-style 

1946 access, specify the name like any other object attribute, such as below 

1947 a column named ``employee_name`` is accessed:: 

1948 

1949 >>> employee_table.c.employee_name 

1950 

1951 To access columns that have names with special characters or spaces, 

1952 index-style access is used, such as below which illustrates a column named 

1953 ``employee ' payment`` is accessed:: 

1954 

1955 >>> employee_table.c["employee ' payment"] 

1956 

1957 As the :class:`_sql.ColumnCollection` object provides a Python dictionary 

1958 interface, common dictionary method names like 

1959 :meth:`_sql.ColumnCollection.keys`, :meth:`_sql.ColumnCollection.values`, 

1960 and :meth:`_sql.ColumnCollection.items` are available, which means that 

1961 database columns that are keyed under these names also need to use indexed 

1962 access:: 

1963 

1964 >>> employee_table.c["values"] 

1965 

1966 

1967 The name for which a :class:`_schema.Column` would be present is normally 

1968 that of the :paramref:`_schema.Column.key` parameter. In some contexts, 

1969 such as a :class:`_sql.Select` object that uses a label style set 

1970 using the :meth:`_sql.Select.set_label_style` method, a column of a certain 

1971 key may instead be represented under a particular label name such 

1972 as ``tablename_columnname``:: 

1973 

1974 >>> from sqlalchemy import select, column, table 

1975 >>> from sqlalchemy import LABEL_STYLE_TABLENAME_PLUS_COL 

1976 >>> t = table("t", column("c")) 

1977 >>> stmt = select(t).set_label_style(LABEL_STYLE_TABLENAME_PLUS_COL) 

1978 >>> subq = stmt.subquery() 

1979 >>> subq.c.t_c 

1980 <sqlalchemy.sql.elements.ColumnClause at 0x7f59dcf04fa0; t_c> 

1981 

1982 :class:`.ColumnCollection` also indexes the columns in order and allows 

1983 them to be accessible by their integer position:: 

1984 

1985 >>> cc[0] 

1986 Column('x', Integer(), table=None) 

1987 >>> cc[1] 

1988 Column('y', Integer(), table=None) 

1989 

1990 .. versionadded:: 1.4 :class:`_expression.ColumnCollection` 

1991 allows integer-based 

1992 index access to the collection. 

1993 

1994 Iterating the collection yields the column expressions in order:: 

1995 

1996 >>> list(cc) 

1997 [Column('x', Integer(), table=None), 

1998 Column('y', Integer(), table=None)] 

1999 

2000 The :class:`_expression.ColumnCollection` base class is read-only. 

2001 For mutation operations, the :class:`.WriteableColumnCollection` subclass 

2002 provides methods such as :meth:`.WriteableColumnCollection.add`. 

2003 A special subclass :class:`.DedupeColumnCollection` exists which 

2004 maintains SQLAlchemy's older behavior of not allowing duplicates; this 

2005 collection is used for schema level objects like :class:`_schema.Table` 

2006 and :class:`.PrimaryKeyConstraint` where this deduping is helpful. 

2007 The :class:`.DedupeColumnCollection` class also has additional mutation 

2008 methods as the schema constructs have more use cases that require removal 

2009 and replacement of columns. 

2010 

2011 .. versionchanged:: 1.4 :class:`_expression.ColumnCollection` 

2012 now stores duplicate 

2013 column keys as well as the same column in multiple positions. The 

2014 :class:`.DedupeColumnCollection` class is added to maintain the 

2015 former behavior in those cases where deduplication as well as 

2016 additional replace/remove operations are needed. 

2017 

2018 .. versionchanged:: 2.1 :class:`_expression.ColumnCollection` is now 

2019 a read-only base class. Mutation operations are available through 

2020 :class:`.WriteableColumnCollection` and :class:`.DedupeColumnCollection` 

2021 subclasses. 

2022 

2023 

2024 """ 

2025 

2026 __slots__ = ("_collection", "_index", "_colset", "_proxy_index") 

2027 

2028 _collection: List[Tuple[_COLKEY, _COL_co, _ColumnMetrics[_COL_co]]] 

2029 _index: Dict[Union[None, str, int], Tuple[_COLKEY, _COL_co]] 

2030 _colset: Set[_COL_co] 

2031 _proxy_index: Dict[ColumnElement[Any], Set[_ColumnMetrics[_COL_co]]] 

2032 

2033 def __init__(self) -> None: 

2034 raise TypeError( 

2035 "ColumnCollection is an abstract base class and cannot be " 

2036 "instantiated directly. Use WriteableColumnCollection or " 

2037 "DedupeColumnCollection instead." 

2038 ) 

2039 

2040 @util.preload_module("sqlalchemy.sql.elements") 

2041 def __clause_element__(self) -> ClauseList: 

2042 elements = util.preloaded.sql_elements 

2043 

2044 return elements.ClauseList( 

2045 _literal_as_text_role=roles.ColumnsClauseRole, 

2046 group=False, 

2047 *self._all_columns, 

2048 ) 

2049 

2050 @property 

2051 def _all_columns(self) -> List[_COL_co]: 

2052 return [col for (_, col, _) in self._collection] 

2053 

2054 def keys(self) -> List[_COLKEY]: 

2055 """Return a sequence of string key names for all columns in this 

2056 collection.""" 

2057 return [k for (k, _, _) in self._collection] 

2058 

2059 def values(self) -> List[_COL_co]: 

2060 """Return a sequence of :class:`_sql.ColumnClause` or 

2061 :class:`_schema.Column` objects for all columns in this 

2062 collection.""" 

2063 return [col for (_, col, _) in self._collection] 

2064 

2065 def items(self) -> List[Tuple[_COLKEY, _COL_co]]: 

2066 """Return a sequence of (key, column) tuples for all columns in this 

2067 collection each consisting of a string key name and a 

2068 :class:`_sql.ColumnClause` or 

2069 :class:`_schema.Column` object. 

2070 """ 

2071 

2072 return [(k, col) for (k, col, _) in self._collection] 

2073 

2074 def __bool__(self) -> bool: 

2075 return bool(self._collection) 

2076 

2077 def __len__(self) -> int: 

2078 return len(self._collection) 

2079 

2080 def __iter__(self) -> Iterator[_COL_co]: 

2081 # turn to a list first to maintain over a course of changes 

2082 return iter([col for _, col, _ in self._collection]) 

2083 

2084 @overload 

2085 def __getitem__(self, key: Union[str, int]) -> _COL_co: ... 

2086 

2087 @overload 

2088 def __getitem__( 

2089 self, key: Union[Tuple[Union[str, int], ...], slice] 

2090 ) -> ReadOnlyColumnCollection[_COLKEY, _COL_co]: ... 

2091 

2092 def __getitem__( 

2093 self, key: Union[str, int, slice, Tuple[Union[str, int], ...]] 

2094 ) -> Union[ReadOnlyColumnCollection[_COLKEY, _COL_co], _COL_co]: 

2095 try: 

2096 if isinstance(key, (tuple, slice)): 

2097 if isinstance(key, slice): 

2098 cols = ( 

2099 (sub_key, col) 

2100 for (sub_key, col, _) in self._collection[key] 

2101 ) 

2102 else: 

2103 cols = (self._index[sub_key] for sub_key in key) 

2104 

2105 return WriteableColumnCollection(cols).as_readonly() 

2106 else: 

2107 return self._index[key][1] 

2108 except KeyError as err: 

2109 if isinstance(err.args[0], int): 

2110 raise IndexError(err.args[0]) from err 

2111 else: 

2112 raise 

2113 

2114 def __getattr__(self, key: str) -> _COL_co: 

2115 try: 

2116 return self._index[key][1] 

2117 except KeyError as err: 

2118 raise AttributeError(key) from err 

2119 

2120 def __contains__(self, key: str) -> bool: 

2121 if key not in self._index: 

2122 if not isinstance(key, str): 

2123 raise exc.ArgumentError( 

2124 "__contains__ requires a string argument" 

2125 ) 

2126 return False 

2127 else: 

2128 return True 

2129 

2130 def compare(self, other: ColumnCollection[_COLKEY, _COL_co]) -> bool: 

2131 """Compare this :class:`_expression.ColumnCollection` to another 

2132 based on the names of the keys""" 

2133 

2134 for l, r in zip_longest(self, other): 

2135 if l is not r: 

2136 return False 

2137 else: 

2138 return True 

2139 

2140 def __eq__(self, other: Any) -> bool: 

2141 return self.compare(other) 

2142 

2143 @overload 

2144 def get(self, key: str, default: None = None) -> Optional[_COL_co]: ... 

2145 

2146 @overload 

2147 def get(self, key: str, default: _COL) -> Union[_COL_co, _COL]: ... 

2148 

2149 def get( 

2150 self, key: str, default: Optional[_COL] = None 

2151 ) -> Optional[Union[_COL_co, _COL]]: 

2152 """Get a :class:`_sql.ColumnClause` or :class:`_schema.Column` object 

2153 based on a string key name from this 

2154 :class:`_expression.ColumnCollection`.""" 

2155 

2156 if key in self._index: 

2157 return self._index[key][1] 

2158 else: 

2159 return default 

2160 

2161 def __str__(self) -> str: 

2162 return "%s(%s)" % ( 

2163 self.__class__.__name__, 

2164 ", ".join(str(c) for c in self), 

2165 ) 

2166 

2167 # https://github.com/python/mypy/issues/4266 

2168 __hash__: Optional[int] = None # type: ignore[assignment] 

2169 

2170 def contains_column(self, col: ColumnElement[Any]) -> bool: 

2171 """Checks if a column object exists in this collection""" 

2172 if col not in self._colset: 

2173 if isinstance(col, str): 

2174 raise exc.ArgumentError( 

2175 "contains_column cannot be used with string arguments. " 

2176 "Use ``col_name in table.c`` instead." 

2177 ) 

2178 return False 

2179 else: 

2180 return True 

2181 

2182 def _as_readonly(self) -> ReadOnlyColumnCollection[_COLKEY, _COL_co]: 

2183 raise NotImplementedError() 

2184 

2185 def corresponding_column( 

2186 self, column: _COL, require_embedded: bool = False 

2187 ) -> Optional[Union[_COL, _COL_co]]: 

2188 """Given a :class:`_expression.ColumnElement`, return the exported 

2189 :class:`_expression.ColumnElement` object from this 

2190 :class:`_expression.ColumnCollection` 

2191 which corresponds to that original :class:`_expression.ColumnElement` 

2192 via a common 

2193 ancestor column. 

2194 

2195 :param column: the target :class:`_expression.ColumnElement` 

2196 to be matched. 

2197 

2198 :param require_embedded: only return corresponding columns for 

2199 the given :class:`_expression.ColumnElement`, if the given 

2200 :class:`_expression.ColumnElement` 

2201 is actually present within a sub-element 

2202 of this :class:`_expression.Selectable`. 

2203 Normally the column will match if 

2204 it merely shares a common ancestor with one of the exported 

2205 columns of this :class:`_expression.Selectable`. 

2206 

2207 .. seealso:: 

2208 

2209 :meth:`_expression.Selectable.corresponding_column` 

2210 - invokes this method 

2211 against the collection returned by 

2212 :attr:`_expression.Selectable.exported_columns`. 

2213 

2214 .. versionchanged:: 1.4 the implementation for ``corresponding_column`` 

2215 was moved onto the :class:`_expression.ColumnCollection` itself. 

2216 

2217 """ 

2218 raise NotImplementedError() 

2219 

2220 

2221class WriteableColumnCollection(ColumnCollection[_COLKEY, _COL_co]): 

2222 """A :class:`_sql.ColumnCollection` that allows mutation operations. 

2223 

2224 This is the writable form of :class:`_sql.ColumnCollection` that 

2225 implements methods such as :meth:`.add`, :meth:`.remove`, :meth:`.update`, 

2226 and :meth:`.clear`. 

2227 

2228 This class is used internally for building column collections during 

2229 construction of SQL constructs. For schema-level objects that require 

2230 deduplication behavior, use :class:`.DedupeColumnCollection`. 

2231 

2232 .. versionadded:: 2.1 

2233 

2234 """ 

2235 

2236 __slots__ = () 

2237 

2238 def __init__( 

2239 self, columns: Optional[Iterable[Tuple[_COLKEY, _COL_co]]] = None 

2240 ): 

2241 object.__setattr__(self, "_colset", set()) 

2242 object.__setattr__(self, "_index", {}) 

2243 object.__setattr__( 

2244 self, "_proxy_index", collections.defaultdict(util.OrderedSet) 

2245 ) 

2246 object.__setattr__(self, "_collection", []) 

2247 if columns: 

2248 self._initial_populate(columns) 

2249 

2250 def _initial_populate( 

2251 self, iter_: Iterable[Tuple[_COLKEY, _COL_co]] 

2252 ) -> None: 

2253 self._populate_separate_keys(iter_) 

2254 

2255 def _populate_separate_keys( 

2256 self, iter_: Iterable[Tuple[_COLKEY, _COL_co]] 

2257 ) -> None: 

2258 """populate from an iterator of (key, column)""" 

2259 

2260 self._collection[:] = collection = [ 

2261 (k, c, _ColumnMetrics(self, c)) for k, c in iter_ 

2262 ] 

2263 self._colset.update(c._deannotate() for _, c, _ in collection) 

2264 self._index.update( 

2265 {idx: (k, c) for idx, (k, c, _) in enumerate(collection)} 

2266 ) 

2267 self._index.update({k: (k, col) for k, col, _ in reversed(collection)}) 

2268 

2269 def __getstate__(self) -> Dict[str, Any]: 

2270 return { 

2271 "_collection": [(k, c) for k, c, _ in self._collection], 

2272 "_index": self._index, 

2273 } 

2274 

2275 def __setstate__(self, state: Dict[str, Any]) -> None: 

2276 object.__setattr__(self, "_index", state["_index"]) 

2277 object.__setattr__( 

2278 self, "_proxy_index", collections.defaultdict(util.OrderedSet) 

2279 ) 

2280 object.__setattr__( 

2281 self, 

2282 "_collection", 

2283 [ 

2284 (k, c, _ColumnMetrics(self, c)) 

2285 for (k, c) in state["_collection"] 

2286 ], 

2287 ) 

2288 object.__setattr__( 

2289 self, "_colset", {col for k, col, _ in self._collection} 

2290 ) 

2291 

2292 def add( 

2293 self, 

2294 column: ColumnElement[Any], 

2295 key: Optional[_COLKEY] = None, 

2296 ) -> None: 

2297 """Add a column to this :class:`_sql.WriteableColumnCollection`. 

2298 

2299 .. note:: 

2300 

2301 This method is **not normally used by user-facing code**, as the 

2302 :class:`_sql.WriteableColumnCollection` is usually part of an 

2303 existing object such as a :class:`_schema.Table`. To add a 

2304 :class:`_schema.Column` to an existing :class:`_schema.Table` 

2305 object, use the :meth:`_schema.Table.append_column` method. 

2306 

2307 """ 

2308 colkey: _COLKEY 

2309 

2310 if key is None: 

2311 colkey = column.key # type: ignore[assignment] 

2312 else: 

2313 colkey = key 

2314 

2315 l = len(self._collection) 

2316 

2317 # don't really know how this part is supposed to work w/ the 

2318 # covariant thing 

2319 

2320 _column = cast(_COL_co, column) 

2321 

2322 self._collection.append( 

2323 (colkey, _column, _ColumnMetrics(self, _column)) 

2324 ) 

2325 self._colset.add(_column._deannotate()) 

2326 

2327 self._index[l] = (colkey, _column) 

2328 if colkey not in self._index: 

2329 self._index[colkey] = (colkey, _column) 

2330 

2331 def _as_readonly(self) -> ReadOnlyColumnCollection[_COLKEY, _COL_co]: 

2332 return ReadOnlyColumnCollection(self) 

2333 

2334 def as_readonly(self) -> ReadOnlyColumnCollection[_COLKEY, _COL_co]: 

2335 """Return a "read only" form of this 

2336 :class:`_sql.WriteableColumnCollection`.""" 

2337 

2338 return self._as_readonly() 

2339 

2340 def _init_proxy_index(self) -> None: 

2341 """populate the "proxy index", if empty. 

2342 

2343 proxy index is added in 2.0 to provide more efficient operation 

2344 for the corresponding_column() method. 

2345 

2346 For reasons of both time to construct new .c collections as well as 

2347 memory conservation for large numbers of large .c collections, the 

2348 proxy_index is only filled if corresponding_column() is called. once 

2349 filled it stays that way, and new _ColumnMetrics objects created after 

2350 that point will populate it with new data. Note this case would be 

2351 unusual, if not nonexistent, as it means a .c collection is being 

2352 mutated after corresponding_column() were used, however it is tested in 

2353 test/base/test_utils.py. 

2354 

2355 """ 

2356 pi = self._proxy_index 

2357 if pi: 

2358 return 

2359 

2360 for _, _, metrics in self._collection: 

2361 eps = metrics.column._expanded_proxy_set 

2362 

2363 for eps_col in eps: 

2364 pi[eps_col].add(metrics) 

2365 

2366 def corresponding_column( 

2367 self, column: _COL, require_embedded: bool = False 

2368 ) -> Optional[Union[_COL, _COL_co]]: 

2369 """Given a :class:`_expression.ColumnElement`, return the exported 

2370 :class:`_expression.ColumnElement` object from this 

2371 :class:`_expression.ColumnCollection` 

2372 which corresponds to that original :class:`_expression.ColumnElement` 

2373 via a common 

2374 ancestor column. 

2375 

2376 See :meth:`.ColumnCollection.corresponding_column` for parameter 

2377 information. 

2378 

2379 """ 

2380 # TODO: cython candidate 

2381 

2382 # don't dig around if the column is locally present 

2383 if column in self._colset: 

2384 return column 

2385 

2386 selected_intersection, selected_metrics = None, None 

2387 target_set = column.proxy_set 

2388 

2389 pi = self._proxy_index 

2390 if not pi: 

2391 self._init_proxy_index() 

2392 

2393 for current_metrics in ( 

2394 mm for ts in target_set if ts in pi for mm in pi[ts] 

2395 ): 

2396 if not require_embedded or current_metrics.embedded(target_set): 

2397 if selected_metrics is None: 

2398 # no corresponding column yet, pick this one. 

2399 selected_metrics = current_metrics 

2400 continue 

2401 

2402 current_intersection = target_set.intersection( 

2403 current_metrics.column._expanded_proxy_set 

2404 ) 

2405 if selected_intersection is None: 

2406 selected_intersection = target_set.intersection( 

2407 selected_metrics.column._expanded_proxy_set 

2408 ) 

2409 

2410 if len(current_intersection) > len(selected_intersection): 

2411 # 'current' has a larger field of correspondence than 

2412 # 'selected'. i.e. selectable.c.a1_x->a1.c.x->table.c.x 

2413 # matches a1.c.x->table.c.x better than 

2414 # selectable.c.x->table.c.x does. 

2415 

2416 selected_metrics = current_metrics 

2417 selected_intersection = current_intersection 

2418 elif current_intersection == selected_intersection: 

2419 # they have the same field of correspondence. see 

2420 # which proxy_set has fewer columns in it, which 

2421 # indicates a closer relationship with the root 

2422 # column. Also take into account the "weight" 

2423 # attribute which CompoundSelect() uses to give 

2424 # higher precedence to columns based on vertical 

2425 # position in the compound statement, and discard 

2426 # columns that have no reference to the target 

2427 # column (also occurs with CompoundSelect) 

2428 

2429 selected_col_distance = sum( 

2430 [ 

2431 sc._annotations.get("weight", 1) 

2432 for sc in ( 

2433 selected_metrics.column._uncached_proxy_list() 

2434 ) 

2435 if sc.shares_lineage(column) 

2436 ], 

2437 ) 

2438 current_col_distance = sum( 

2439 [ 

2440 sc._annotations.get("weight", 1) 

2441 for sc in ( 

2442 current_metrics.column._uncached_proxy_list() 

2443 ) 

2444 if sc.shares_lineage(column) 

2445 ], 

2446 ) 

2447 if current_col_distance < selected_col_distance: 

2448 selected_metrics = current_metrics 

2449 selected_intersection = current_intersection 

2450 

2451 return selected_metrics.column if selected_metrics else None 

2452 

2453 

2454_NAMEDCOL = TypeVar("_NAMEDCOL", bound="NamedColumn[Any]") 

2455 

2456 

2457class DedupeColumnCollection(WriteableColumnCollection[str, _NAMEDCOL]): 

2458 """A :class:`_expression.ColumnCollection` 

2459 that maintains deduplicating behavior. 

2460 

2461 This is useful by schema level objects such as :class:`_schema.Table` and 

2462 :class:`.PrimaryKeyConstraint`. The collection includes more 

2463 sophisticated mutator methods as well to suit schema objects which 

2464 require mutable column collections. 

2465 

2466 .. versionadded:: 1.4 

2467 

2468 """ 

2469 

2470 def add( # type: ignore[override] 

2471 self, 

2472 column: _NAMEDCOL, 

2473 key: Optional[str] = None, 

2474 *, 

2475 index: Optional[int] = None, 

2476 ) -> None: 

2477 if key is not None and column.key != key: 

2478 raise exc.ArgumentError( 

2479 "DedupeColumnCollection requires columns be under " 

2480 "the same key as their .key" 

2481 ) 

2482 key = column.key 

2483 

2484 if key is None: 

2485 raise exc.ArgumentError( 

2486 "Can't add unnamed column to column collection" 

2487 ) 

2488 

2489 if key in self._index: 

2490 existing = self._index[key][1] 

2491 

2492 if existing is column: 

2493 return 

2494 

2495 self.replace(column, index=index) 

2496 

2497 # pop out memoized proxy_set as this 

2498 # operation may very well be occurring 

2499 # in a _make_proxy operation 

2500 util.memoized_property.reset(column, "proxy_set") 

2501 else: 

2502 self._append_new_column(key, column, index=index) 

2503 

2504 def _append_new_column( 

2505 self, key: str, named_column: _NAMEDCOL, *, index: Optional[int] = None 

2506 ) -> None: 

2507 collection_length = len(self._collection) 

2508 

2509 if index is None: 

2510 l = collection_length 

2511 else: 

2512 if index < 0: 

2513 index = max(0, collection_length + index) 

2514 l = index 

2515 

2516 if index is None: 

2517 self._collection.append( 

2518 (key, named_column, _ColumnMetrics(self, named_column)) 

2519 ) 

2520 else: 

2521 self._collection.insert( 

2522 index, (key, named_column, _ColumnMetrics(self, named_column)) 

2523 ) 

2524 

2525 self._colset.add(named_column._deannotate()) 

2526 

2527 if index is not None: 

2528 for idx in reversed(range(index, collection_length)): 

2529 self._index[idx + 1] = self._index[idx] 

2530 

2531 self._index[l] = (key, named_column) 

2532 self._index[key] = (key, named_column) 

2533 

2534 def _populate_separate_keys( 

2535 self, iter_: Iterable[Tuple[str, _NAMEDCOL]] 

2536 ) -> None: 

2537 """populate from an iterator of (key, column)""" 

2538 cols = list(iter_) 

2539 

2540 replace_col = [] 

2541 for k, col in cols: 

2542 if col.key != k: 

2543 raise exc.ArgumentError( 

2544 "DedupeColumnCollection requires columns be under " 

2545 "the same key as their .key" 

2546 ) 

2547 if col.name in self._index and col.key != col.name: 

2548 replace_col.append(col) 

2549 elif col.key in self._index: 

2550 replace_col.append(col) 

2551 else: 

2552 self._index[k] = (k, col) 

2553 self._collection.append((k, col, _ColumnMetrics(self, col))) 

2554 self._colset.update(c._deannotate() for (k, c, _) in self._collection) 

2555 

2556 self._index.update( 

2557 (idx, (k, c)) for idx, (k, c, _) in enumerate(self._collection) 

2558 ) 

2559 for col in replace_col: 

2560 self.replace(col) 

2561 

2562 def extend(self, iter_: Iterable[_NAMEDCOL]) -> None: 

2563 self._populate_separate_keys((col.key, col) for col in iter_) 

2564 

2565 def remove(self, column: _NAMEDCOL) -> None: 

2566 if column not in self._colset: 

2567 raise ValueError( 

2568 "Can't remove column %r; column is not in this collection" 

2569 % column 

2570 ) 

2571 del self._index[column.key] 

2572 self._colset.remove(column) 

2573 self._collection[:] = [ 

2574 (k, c, metrics) 

2575 for (k, c, metrics) in self._collection 

2576 if c is not column 

2577 ] 

2578 for metrics in self._proxy_index.get(column, ()): 

2579 metrics.dispose(self) 

2580 

2581 self._index.update( 

2582 {idx: (k, col) for idx, (k, col, _) in enumerate(self._collection)} 

2583 ) 

2584 # delete higher index 

2585 del self._index[len(self._collection)] 

2586 

2587 def replace( 

2588 self, 

2589 column: _NAMEDCOL, 

2590 *, 

2591 extra_remove: Optional[Iterable[_NAMEDCOL]] = None, 

2592 index: Optional[int] = None, 

2593 ) -> None: 

2594 """add the given column to this collection, removing unaliased 

2595 versions of this column as well as existing columns with the 

2596 same key. 

2597 

2598 e.g.:: 

2599 

2600 t = Table("sometable", metadata, Column("col1", Integer)) 

2601 t.columns.replace(Column("col1", Integer, key="columnone")) 

2602 

2603 will remove the original 'col1' from the collection, and add 

2604 the new column under the name 'columnname'. 

2605 

2606 Used by schema.Column to override columns during table reflection. 

2607 

2608 """ 

2609 

2610 if extra_remove: 

2611 remove_col = set(extra_remove) 

2612 else: 

2613 remove_col = set() 

2614 # remove up to two columns based on matches of name as well as key 

2615 if column.name in self._index and column.key != column.name: 

2616 other = self._index[column.name][1] 

2617 if other.name == other.key: 

2618 remove_col.add(other) 

2619 

2620 if column.key in self._index: 

2621 remove_col.add(self._index[column.key][1]) 

2622 

2623 if not remove_col: 

2624 self._append_new_column(column.key, column, index=index) 

2625 return 

2626 new_cols: List[Tuple[str, _NAMEDCOL, _ColumnMetrics[_NAMEDCOL]]] = [] 

2627 replace_index = None 

2628 

2629 for idx, (k, col, metrics) in enumerate(self._collection): 

2630 if col in remove_col: 

2631 if replace_index is None: 

2632 replace_index = idx 

2633 new_cols.append( 

2634 (column.key, column, _ColumnMetrics(self, column)) 

2635 ) 

2636 else: 

2637 new_cols.append((k, col, metrics)) 

2638 

2639 if remove_col: 

2640 self._colset.difference_update(remove_col) 

2641 

2642 for rc in remove_col: 

2643 for metrics in self._proxy_index.get(rc, ()): 

2644 metrics.dispose(self) 

2645 

2646 if replace_index is None: 

2647 if index is not None: 

2648 new_cols.insert( 

2649 index, (column.key, column, _ColumnMetrics(self, column)) 

2650 ) 

2651 

2652 else: 

2653 new_cols.append( 

2654 (column.key, column, _ColumnMetrics(self, column)) 

2655 ) 

2656 elif index is not None: 

2657 to_move = new_cols[replace_index] 

2658 effective_positive_index = ( 

2659 index if index >= 0 else max(0, len(new_cols) + index) 

2660 ) 

2661 new_cols.insert(index, to_move) 

2662 if replace_index > effective_positive_index: 

2663 del new_cols[replace_index + 1] 

2664 else: 

2665 del new_cols[replace_index] 

2666 

2667 self._colset.add(column._deannotate()) 

2668 self._collection[:] = new_cols 

2669 

2670 self._index.clear() 

2671 

2672 self._index.update( 

2673 {idx: (k, col) for idx, (k, col, _) in enumerate(self._collection)} 

2674 ) 

2675 self._index.update({k: (k, col) for (k, col, _) in self._collection}) 

2676 

2677 

2678class ReadOnlyColumnCollection( 

2679 util.ReadOnlyContainer, ColumnCollection[_COLKEY, _COL_co] 

2680): 

2681 __slots__ = ("_parent",) 

2682 

2683 _parent: WriteableColumnCollection[_COLKEY, _COL_co] 

2684 

2685 def __init__( 

2686 self, collection: WriteableColumnCollection[_COLKEY, _COL_co] 

2687 ): 

2688 object.__setattr__(self, "_parent", collection) 

2689 object.__setattr__(self, "_index", collection._index) 

2690 object.__setattr__(self, "_collection", collection._collection) 

2691 object.__setattr__(self, "_colset", collection._colset) 

2692 object.__setattr__(self, "_proxy_index", collection._proxy_index) 

2693 

2694 def _as_readonly(self) -> ReadOnlyColumnCollection[_COLKEY, _COL_co]: 

2695 return self 

2696 

2697 def __getstate__(self) -> Dict[str, ColumnCollection[_COLKEY, _COL_co]]: 

2698 return {"_parent": self._parent} 

2699 

2700 def __setstate__(self, state: Dict[str, Any]) -> None: 

2701 parent = state["_parent"] 

2702 self.__init__(parent) # type: ignore[misc] 

2703 

2704 def corresponding_column( 

2705 self, column: _COL, require_embedded: bool = False 

2706 ) -> Optional[Union[_COL, _COL_co]]: 

2707 """Given a :class:`_expression.ColumnElement`, return the exported 

2708 :class:`_expression.ColumnElement` object from this 

2709 :class:`_expression.ColumnCollection` 

2710 which corresponds to that original :class:`_expression.ColumnElement` 

2711 via a common 

2712 ancestor column. 

2713 

2714 See :meth:`.ColumnCollection.corresponding_column` for parameter 

2715 information. 

2716 

2717 """ 

2718 return self._parent.corresponding_column(column, require_embedded) 

2719 

2720 

2721class ColumnSet(util.OrderedSet["ColumnClause[Any]"]): 

2722 def contains_column(self, col: ColumnClause[Any]) -> bool: 

2723 return col in self 

2724 

2725 def extend(self, cols: Iterable[Any]) -> None: 

2726 for col in cols: 

2727 self.add(col) 

2728 

2729 def __eq__(self, other): 

2730 l = [] 

2731 for c in other: 

2732 for local in self: 

2733 if c.shares_lineage(local): 

2734 l.append(c == local) 

2735 return elements.and_(*l) 

2736 

2737 def __hash__(self) -> int: # type: ignore[override] 

2738 return hash(tuple(x for x in self)) 

2739 

2740 

2741def _entity_namespace( 

2742 entity: Union[_HasEntityNamespace, ExternallyTraversible], 

2743) -> _EntityNamespace: 

2744 """Return the nearest .entity_namespace for the given entity. 

2745 

2746 If not immediately available, does an iterate to find a sub-element 

2747 that has one, if any. 

2748 

2749 """ 

2750 try: 

2751 return cast(_HasEntityNamespace, entity).entity_namespace 

2752 except AttributeError: 

2753 for elem in visitors.iterate(cast(ExternallyTraversible, entity)): 

2754 if _is_has_entity_namespace(elem): 

2755 return elem.entity_namespace 

2756 else: 

2757 raise 

2758 

2759 

2760@overload 

2761def _entity_namespace_key( 

2762 entity: Union[_HasEntityNamespace, ExternallyTraversible], 

2763 key: str, 

2764) -> SQLCoreOperations[Any]: ... 

2765 

2766 

2767@overload 

2768def _entity_namespace_key( 

2769 entity: Union[_HasEntityNamespace, ExternallyTraversible], 

2770 key: str, 

2771 default: _NoArg, 

2772) -> SQLCoreOperations[Any]: ... 

2773 

2774 

2775@overload 

2776def _entity_namespace_key( 

2777 entity: Union[_HasEntityNamespace, ExternallyTraversible], 

2778 key: str, 

2779 default: _T, 

2780) -> Union[SQLCoreOperations[Any], _T]: ... 

2781 

2782 

2783def _entity_namespace_key( 

2784 entity: Union[_HasEntityNamespace, ExternallyTraversible], 

2785 key: str, 

2786 default: Union[SQLCoreOperations[Any], _T, _NoArg] = NO_ARG, 

2787) -> Union[SQLCoreOperations[Any], _T]: 

2788 """Return an entry from an entity_namespace. 

2789 

2790 

2791 Raises :class:`_exc.InvalidRequestError` rather than attribute error 

2792 on not found. 

2793 

2794 """ 

2795 

2796 try: 

2797 ns = _entity_namespace(entity) 

2798 if default is not NO_ARG: 

2799 return getattr(ns, key, default) 

2800 else: 

2801 return getattr(ns, key) # type: ignore[no-any-return] 

2802 except AttributeError as err: 

2803 raise exc.InvalidRequestError( 

2804 'Entity namespace for "%s" has no property "%s"' % (entity, key) 

2805 ) from err 

2806 

2807 

2808def _entity_namespace_key_search_all( 

2809 entities: Collection[Any], 

2810 key: str, 

2811) -> SQLCoreOperations[Any]: 

2812 """Search multiple entities for a key, raise if ambiguous or not found. 

2813 

2814 This is used by filter_by() to search across all FROM clause entities 

2815 when a single entity doesn't have the requested attribute. 

2816 

2817 .. versionadded:: 2.1 

2818 

2819 Raises: 

2820 AmbiguousColumnError: If key exists in multiple entities 

2821 InvalidRequestError: If key doesn't exist in any entity 

2822 """ 

2823 

2824 match_: SQLCoreOperations[Any] | None = None 

2825 

2826 for entity in entities: 

2827 ns = _entity_namespace(entity) 

2828 # Check if the attribute exists 

2829 if hasattr(ns, key): 

2830 if match_ is not None: 

2831 entity_desc = ", ".join(str(e) for e in list(entities)[:3]) 

2832 if len(entities) > 3: 

2833 entity_desc += f", ... ({len(entities)} total)" 

2834 raise exc.AmbiguousColumnError( 

2835 f'Attribute name "{key}" is ambiguous; it exists in ' 

2836 f"multiple FROM clause entities ({entity_desc}). " 

2837 f"Use filter() with explicit column references instead " 

2838 f"of filter_by()." 

2839 ) 

2840 match_ = getattr(ns, key) 

2841 

2842 if match_ is None: 

2843 # No entity has this attribute 

2844 entity_desc = ", ".join(str(e) for e in list(entities)[:3]) 

2845 if len(entities) > 3: 

2846 entity_desc += f", ... ({len(entities)} total)" 

2847 raise exc.InvalidRequestError( 

2848 f'None of the FROM clause entities have a property "{key}". ' 

2849 f"Searched entities: {entity_desc}" 

2850 ) 

2851 

2852 return match_