codekingpro/portable-devtools
115k
1# orm/relationships.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
8"""Heuristics related to join conditions as used in
9:func:`_orm.relationship`.
10
11Provides the :class:`.JoinCondition` object, which encapsulates
12SQL annotation and aliasing behavior focused on the `primaryjoin`
13and `secondaryjoin` aspects of :func:`_orm.relationship`.
14
15"""
16from __future__ import annotations
17
18import collections
19from collections import abc
20import dataclasses
21import inspect as _py_inspect
22import itertools
23import re
24import typing
25from typing import Any
26from typing import Callable
27from typing import cast
28from typing import Collection
29from typing import Dict
30from typing import FrozenSet
31from typing import Generic
32from typing import Iterable
33from typing import Iterator
34from typing import List
35from typing import NamedTuple
36from typing import NoReturn
37from typing import Optional
38from typing import Sequence
39from typing import Set
40from typing import Tuple
41from typing import Type
42from typing import TypeVar
43from typing import Union
44import weakref
45
46from . import attributes
47from . import strategy_options
48from ._typing import insp_is_aliased_class
49from ._typing import is_has_collection_adapter
50from .base import _DeclarativeMapped
51from .base import _is_mapped_class
52from .base import class_mapper
53from .base import DynamicMapped
54from .base import LoaderCallableStatus
55from .base import PassiveFlag
56from .base import state_str
57from .base import WriteOnlyMapped
58from .interfaces import _AttributeOptions
59from .interfaces import _IntrospectsAnnotations
60from .interfaces import MANYTOMANY
61from .interfaces import MANYTOONE
62from .interfaces import ONETOMANY
63from .interfaces import PropComparator
64from .interfaces import RelationshipDirection
65from .interfaces import StrategizedProperty
66from .util import _orm_annotate
67from .util import _orm_deannotate
68from .util import CascadeOptions
69from .. import exc as sa_exc
70from .. import Exists
71from .. import log
72from .. import schema
73from .. import sql
74from .. import util
75from ..inspection import inspect
76from ..sql import coercions
77from ..sql import expression
78from ..sql import operators
79from ..sql import roles
80from ..sql import visitors
81from ..sql._typing import _ColumnExpressionArgument
82from ..sql._typing import _HasClauseElement
83from ..sql.annotation import _safe_annotate
84from ..sql.elements import ColumnClause
85from ..sql.elements import ColumnElement
86from ..sql.util import _deep_annotate
87from ..sql.util import _deep_deannotate
88from ..sql.util import _shallow_annotate
89from ..sql.util import adapt_criterion_to_null
90from ..sql.util import ClauseAdapter
91from ..sql.util import join_condition
92from ..sql.util import selectables_overlap
93from ..sql.util import visit_binary_product
94from ..util.typing import de_optionalize_union_types
95from ..util.typing import Literal
96from ..util.typing import resolve_name_to_real_class_name
97
98if typing.TYPE_CHECKING:
99 from ._typing import _EntityType
100 from ._typing import _ExternalEntityType
101 from ._typing import _IdentityKeyType
102 from ._typing import _InstanceDict
103 from ._typing import _InternalEntityType
104 from ._typing import _O
105 from ._typing import _RegistryType
106 from .base import Mapped
107 from .clsregistry import _class_resolver
108 from .clsregistry import _ModNS
109 from .decl_base import _ClassScanMapperConfig
110 from .dependency import DependencyProcessor
111 from .mapper import Mapper
112 from .query import Query
113 from .session import Session
114 from .state import InstanceState
115 from .strategies import LazyLoader
116 from .util import AliasedClass
117 from .util import AliasedInsp
118 from ..sql._typing import _CoreAdapterProto
119 from ..sql._typing import _EquivalentColumnMap
120 from ..sql._typing import _InfoType
121 from ..sql.annotation import _AnnotationDict
122 from ..sql.annotation import SupportsAnnotations
123 from ..sql.elements import BinaryExpression
124 from ..sql.elements import BindParameter
125 from ..sql.elements import ClauseElement
126 from ..sql.schema import Table
127 from ..sql.selectable import FromClause
128 from ..util.typing import _AnnotationScanType
129 from ..util.typing import RODescriptorReference
130
131_T = TypeVar("_T", bound=Any)
132_T1 = TypeVar("_T1", bound=Any)
133_T2 = TypeVar("_T2", bound=Any)
134
135_PT = TypeVar("_PT", bound=Any)
136
137_PT2 = TypeVar("_PT2", bound=Any)
138
139
140_RelationshipArgumentType = Union[
141 str,
142 Type[_T],
143 Callable[[], Type[_T]],
144 "Mapper[_T]",
145 "AliasedClass[_T]",
146 Callable[[], "Mapper[_T]"],
147 Callable[[], "AliasedClass[_T]"],
148]
149
150_LazyLoadArgumentType = Literal[
151 "select",
152 "joined",
153 "selectin",
154 "subquery",
155 "raise",
156 "raise_on_sql",
157 "noload",
158 "immediate",
159 "write_only",
160 "dynamic",
161 True,
162 False,
163 None,
164]
165
166
167_RelationshipJoinConditionArgument = Union[
168 str, _ColumnExpressionArgument[bool]
169]
170_RelationshipSecondaryArgument = Union[
171 "FromClause", str, Callable[[], "FromClause"]
172]
173_ORMOrderByArgument = Union[
174 Literal[False],
175 str,
176 _ColumnExpressionArgument[Any],
177 Callable[[], _ColumnExpressionArgument[Any]],
178 Callable[[], Iterable[_ColumnExpressionArgument[Any]]],
179 Iterable[Union[str, _ColumnExpressionArgument[Any]]],
180]
181ORMBackrefArgument = Union[str, Tuple[str, Dict[str, Any]]]
182
183_ORMColCollectionElement = Union[
184 ColumnClause[Any],
185 _HasClauseElement[Any],
186 roles.DMLColumnRole,
187 "Mapped[Any]",
188]
189_ORMColCollectionArgument = Union[
190 str,
191 Sequence[_ORMColCollectionElement],
192 Callable[[], Sequence[_ORMColCollectionElement]],
193 Callable[[], _ORMColCollectionElement],
194 _ORMColCollectionElement,
195]
196
197
198_CEA = TypeVar("_CEA", bound=_ColumnExpressionArgument[Any])
199
200_CE = TypeVar("_CE", bound="ColumnElement[Any]")
201
202
203_ColumnPairIterable = Iterable[Tuple[ColumnElement[Any], ColumnElement[Any]]]
204
205_ColumnPairs = Sequence[Tuple[ColumnElement[Any], ColumnElement[Any]]]
206
207_MutableColumnPairs = List[Tuple[ColumnElement[Any], ColumnElement[Any]]]
208
209
210def remote(expr: _CEA) -> _CEA:
211 """Annotate a portion of a primaryjoin expression
212 with a 'remote' annotation.
213
214 See the section :ref:`relationship_custom_foreign` for a
215 description of use.
216
217 .. seealso::
218
219 :ref:`relationship_custom_foreign`
220
221 :func:`.foreign`
222
223 """
224 return _annotate_columns( # type: ignore
225 coercions.expect(roles.ColumnArgumentRole, expr), {"remote": True}
226 )
227
228
229def foreign(expr: _CEA) -> _CEA:
230 """Annotate a portion of a primaryjoin expression
231 with a 'foreign' annotation.
232
233 See the section :ref:`relationship_custom_foreign` for a
234 description of use.
235
236 .. seealso::
237
238 :ref:`relationship_custom_foreign`
239
240 :func:`.remote`
241
242 """
243
244 return _annotate_columns( # type: ignore
245 coercions.expect(roles.ColumnArgumentRole, expr), {"foreign": True}
246 )
247
248
249@dataclasses.dataclass
250class _RelationshipArg(Generic[_T1, _T2]):
251 """stores a user-defined parameter value that must be resolved and
252 parsed later at mapper configuration time.
253
254 """
255
256 __slots__ = "name", "argument", "resolved"
257 name: str
258 argument: _T1
259 resolved: Optional[_T2]
260
261 def _is_populated(self) -> bool:
262 return self.argument is not None
263
264 def _resolve_against_registry(
265 self, clsregistry_resolver: Callable[[str, bool], _class_resolver]
266 ) -> None:
267 attr_value = self.argument
268
269 if isinstance(attr_value, str):
270 self.resolved = clsregistry_resolver(
271 attr_value, self.name == "secondary"
272 )()
273 elif callable(attr_value) and not _is_mapped_class(attr_value):
274 self.resolved = attr_value()
275 else:
276 self.resolved = attr_value
277
278
279_RelationshipOrderByArg = Union[Literal[False], Tuple[ColumnElement[Any], ...]]
280
281
282class _RelationshipArgs(NamedTuple):
283 """stores user-passed parameters that are resolved at mapper configuration
284 time.
285
286 """
287
288 secondary: _RelationshipArg[
289 Optional[_RelationshipSecondaryArgument],
290 Optional[FromClause],
291 ]
292 primaryjoin: _RelationshipArg[
293 Optional[_RelationshipJoinConditionArgument],
294 Optional[ColumnElement[Any]],
295 ]
296 secondaryjoin: _RelationshipArg[
297 Optional[_RelationshipJoinConditionArgument],
298 Optional[ColumnElement[Any]],
299 ]
300 order_by: _RelationshipArg[_ORMOrderByArgument, _RelationshipOrderByArg]
301 foreign_keys: _RelationshipArg[
302 Optional[_ORMColCollectionArgument], Set[ColumnElement[Any]]
303 ]
304 remote_side: _RelationshipArg[
305 Optional[_ORMColCollectionArgument], Set[ColumnElement[Any]]
306 ]
307
308
309@log.class_logger
310class RelationshipProperty(
311 _IntrospectsAnnotations, StrategizedProperty[_T], log.Identified
312):
313 """Describes an object property that holds a single item or list
314 of items that correspond to a related database table.
315
316 Public constructor is the :func:`_orm.relationship` function.
317
318 .. seealso::
319
320 :ref:`relationship_config_toplevel`
321
322 """
323
324 strategy_wildcard_key = strategy_options._RELATIONSHIP_TOKEN
325 inherit_cache = True
326 """:meta private:"""
327
328 _links_to_entity = True
329 _is_relationship = True
330
331 _overlaps: Sequence[str]
332
333 _lazy_strategy: LazyLoader
334
335 _persistence_only = dict(
336 passive_deletes=False,
337 passive_updates=True,
338 enable_typechecks=True,
339 active_history=False,
340 cascade_backrefs=False,
341 )
342
343 _dependency_processor: Optional[DependencyProcessor] = None
344
345 primaryjoin: ColumnElement[bool]
346 secondaryjoin: Optional[ColumnElement[bool]]
347 secondary: Optional[FromClause]
348 _join_condition: JoinCondition
349 order_by: _RelationshipOrderByArg
350
351 _user_defined_foreign_keys: Set[ColumnElement[Any]]
352 _calculated_foreign_keys: Set[ColumnElement[Any]]
353
354 remote_side: Set[ColumnElement[Any]]
355 local_columns: Set[ColumnElement[Any]]
356
357 synchronize_pairs: _ColumnPairs
358 secondary_synchronize_pairs: Optional[_ColumnPairs]
359
360 local_remote_pairs: Optional[_ColumnPairs]
361
362 direction: RelationshipDirection
363
364 _init_args: _RelationshipArgs
365
366 def __init__(
367 self,
368 argument: Optional[_RelationshipArgumentType[_T]] = None,
369 secondary: Optional[_RelationshipSecondaryArgument] = None,
370 *,
371 uselist: Optional[bool] = None,
372 collection_class: Optional[
373 Union[Type[Collection[Any]], Callable[[], Collection[Any]]]
374 ] = None,
375 primaryjoin: Optional[_RelationshipJoinConditionArgument] = None,
376 secondaryjoin: Optional[_RelationshipJoinConditionArgument] = None,
377 back_populates: Optional[str] = None,
378 order_by: _ORMOrderByArgument = False,
379 backref: Optional[ORMBackrefArgument] = None,
380 overlaps: Optional[str] = None,
381 post_update: bool = False,
382 cascade: str = "save-update, merge",
383 viewonly: bool = False,
384 attribute_options: Optional[_AttributeOptions] = None,
385 lazy: _LazyLoadArgumentType = "select",
386 passive_deletes: Union[Literal["all"], bool] = False,
387 passive_updates: bool = True,
388 active_history: bool = False,
389 enable_typechecks: bool = True,
390 foreign_keys: Optional[_ORMColCollectionArgument] = None,
391 remote_side: Optional[_ORMColCollectionArgument] = None,
392 join_depth: Optional[int] = None,
393 comparator_factory: Optional[
394 Type[RelationshipProperty.Comparator[Any]]
395 ] = None,
396 single_parent: bool = False,
397 innerjoin: bool = False,
398 distinct_target_key: Optional[bool] = None,
399 load_on_pending: bool = False,
400 query_class: Optional[Type[Query[Any]]] = None,
401 info: Optional[_InfoType] = None,
402 omit_join: Literal[None, False] = None,
403 sync_backref: Optional[bool] = None,
404 doc: Optional[str] = None,
405 bake_queries: Literal[True] = True,
406 cascade_backrefs: Literal[False] = False,
407 _local_remote_pairs: Optional[_ColumnPairs] = None,
408 _legacy_inactive_history_style: bool = False,
409 ):
410 super().__init__(attribute_options=attribute_options)
411
412 self.uselist = uselist
413 self.argument = argument
414
415 self._init_args = _RelationshipArgs(
416 _RelationshipArg("secondary", secondary, None),
417 _RelationshipArg("primaryjoin", primaryjoin, None),
418 _RelationshipArg("secondaryjoin", secondaryjoin, None),
419 _RelationshipArg("order_by", order_by, None),
420 _RelationshipArg("foreign_keys", foreign_keys, None),
421 _RelationshipArg("remote_side", remote_side, None),
422 )
423
424 self.post_update = post_update
425 self.viewonly = viewonly
426 if viewonly:
427 self._warn_for_persistence_only_flags(
428 passive_deletes=passive_deletes,
429 passive_updates=passive_updates,
430 enable_typechecks=enable_typechecks,
431 active_history=active_history,
432 cascade_backrefs=cascade_backrefs,
433 )
434 if viewonly and sync_backref:
435 raise sa_exc.ArgumentError(
436 "sync_backref and viewonly cannot both be True"
437 )
438 self.sync_backref = sync_backref
439 self.lazy = lazy
440 self.single_parent = single_parent
441 self.collection_class = collection_class
442 self.passive_deletes = passive_deletes
443
444 if cascade_backrefs:
445 raise sa_exc.ArgumentError(
446 "The 'cascade_backrefs' parameter passed to "
447 "relationship() may only be set to False."
448 )
449
450 self.passive_updates = passive_updates
451 self.enable_typechecks = enable_typechecks
452 self.query_class = query_class
453 self.innerjoin = innerjoin
454 self.distinct_target_key = distinct_target_key
455 self.doc = doc
456 self.active_history = active_history
457 self._legacy_inactive_history_style = _legacy_inactive_history_style
458
459 self.join_depth = join_depth
460 if omit_join:
461 util.warn(
462 "setting omit_join to True is not supported; selectin "
463 "loading of this relationship may not work correctly if this "
464 "flag is set explicitly. omit_join optimization is "
465 "automatically detected for conditions under which it is "
466 "supported."
467 )
468
469 self.omit_join = omit_join
470 self.local_remote_pairs = _local_remote_pairs
471 self.load_on_pending = load_on_pending
472 self.comparator_factory = (
473 comparator_factory or RelationshipProperty.Comparator
474 )
475 util.set_creation_order(self)
476
477 if info is not None:
478 self.info.update(info)
479
480 self.strategy_key = (("lazy", self.lazy),)
481
482 self._reverse_property: Set[RelationshipProperty[Any]] = set()
483
484 if overlaps:
485 self._overlaps = set(re.split(r"\s*,\s*", overlaps)) # type: ignore # noqa: E501
486 else:
487 self._overlaps = ()
488
489 self.cascade = cascade
490
491 self.back_populates = back_populates
492
493 if self.back_populates:
494 if backref:
495 raise sa_exc.ArgumentError(
496 "backref and back_populates keyword arguments "
497 "are mutually exclusive"
498 )
499 self.backref = None
500 else:
501 self.backref = backref
502
503 def _warn_for_persistence_only_flags(self, **kw: Any) -> None:
504 for k, v in kw.items():
505 if v != self._persistence_only[k]:
506 # we are warning here rather than warn deprecated as this is a
507 # configuration mistake, and Python shows regular warnings more
508 # aggressively than deprecation warnings by default. Unlike the
509 # case of setting viewonly with cascade, the settings being
510 # warned about here are not actively doing the wrong thing
511 # against viewonly=True, so it is not as urgent to have these
512 # raise an error.
513 util.warn(
514 "Setting %s on relationship() while also "
515 "setting viewonly=True does not make sense, as a "
516 "viewonly=True relationship does not perform persistence "
517 "operations. This configuration may raise an error "
518 "in a future release." % (k,)
519 )
520
521 def instrument_class(self, mapper: Mapper[Any]) -> None:
522 attributes.register_descriptor(
523 mapper.class_,
524 self.key,
525 comparator=self.comparator_factory(self, mapper),
526 parententity=mapper,
527 doc=self.doc,
528 )
529
530 class Comparator(util.MemoizedSlots, PropComparator[_PT]):
531 """Produce boolean, comparison, and other operators for
532 :class:`.RelationshipProperty` attributes.
533
534 See the documentation for :class:`.PropComparator` for a brief
535 overview of ORM level operator definition.
536
537 .. seealso::
538
539 :class:`.PropComparator`
540
541 :class:`.ColumnProperty.Comparator`
542
543 :class:`.ColumnOperators`
544
545 :ref:`types_operators`
546
547 :attr:`.TypeEngine.comparator_factory`
548
549 """
550
551 __slots__ = (
552 "entity",
553 "mapper",
554 "property",
555 "_of_type",
556 "_extra_criteria",
557 )
558
559 prop: RODescriptorReference[RelationshipProperty[_PT]]
560 _of_type: Optional[_EntityType[_PT]]
561
562 def __init__(
563 self,
564 prop: RelationshipProperty[_PT],
565 parentmapper: _InternalEntityType[Any],
566 adapt_to_entity: Optional[AliasedInsp[Any]] = None,
567 of_type: Optional[_EntityType[_PT]] = None,
568 extra_criteria: Tuple[ColumnElement[bool], ...] = (),
569 ):
570 """Construction of :class:`.RelationshipProperty.Comparator`
571 is internal to the ORM's attribute mechanics.
572
573 """
574 self.prop = prop
575 self._parententity = parentmapper
576 self._adapt_to_entity = adapt_to_entity
577 if of_type:
578 self._of_type = of_type
579 else:
580 self._of_type = None
581 self._extra_criteria = extra_criteria
582
583 def adapt_to_entity(
584 self, adapt_to_entity: AliasedInsp[Any]
585 ) -> RelationshipProperty.Comparator[Any]:
586 return self.__class__(
587 self.prop,
588 self._parententity,
589 adapt_to_entity=adapt_to_entity,
590 of_type=self._of_type,
591 )
592
593 entity: _InternalEntityType[_PT]
594 """The target entity referred to by this
595 :class:`.RelationshipProperty.Comparator`.
596
597 This is either a :class:`_orm.Mapper` or :class:`.AliasedInsp`
598 object.
599
600 This is the "target" or "remote" side of the
601 :func:`_orm.relationship`.
602
603 """
604
605 mapper: Mapper[_PT]
606 """The target :class:`_orm.Mapper` referred to by this
607 :class:`.RelationshipProperty.Comparator`.
608
609 This is the "target" or "remote" side of the
610 :func:`_orm.relationship`.
611
612 """
613
614 def _memoized_attr_entity(self) -> _InternalEntityType[_PT]:
615 if self._of_type:
616 return inspect(self._of_type) # type: ignore
617 else:
618 return self.prop.entity
619
620 def _memoized_attr_mapper(self) -> Mapper[_PT]:
621 return self.entity.mapper
622
623 def _source_selectable(self) -> FromClause:
624 if self._adapt_to_entity:
625 return self._adapt_to_entity.selectable
626 else:
627 return self.property.parent._with_polymorphic_selectable
628
629 def __clause_element__(self) -> ColumnElement[bool]:
630 adapt_from = self._source_selectable()
631 if self._of_type:
632 of_type_entity = inspect(self._of_type)
633 else:
634 of_type_entity = None
635
636 (
637 pj,
638 sj,
639 source,
640 dest,
641 secondary,
642 target_adapter,
643 ) = self.prop._create_joins(
644 source_selectable=adapt_from,
645 source_polymorphic=True,
646 of_type_entity=of_type_entity,
647 alias_secondary=True,
648 extra_criteria=self._extra_criteria,
649 )
650 if sj is not None:
651 return pj & sj
652 else:
653 return pj
654
655 def of_type(self, class_: _EntityType[Any]) -> PropComparator[_PT]:
656 r"""Redefine this object in terms of a polymorphic subclass.
657
658 See :meth:`.PropComparator.of_type` for an example.
659
660
661 """
662 return RelationshipProperty.Comparator(
663 self.prop,
664 self._parententity,
665 adapt_to_entity=self._adapt_to_entity,
666 of_type=class_,
667 extra_criteria=self._extra_criteria,
668 )
669
670 def and_(
671 self, *criteria: _ColumnExpressionArgument[bool]
672 ) -> PropComparator[Any]:
673 """Add AND criteria.
674
675 See :meth:`.PropComparator.and_` for an example.
676
677 .. versionadded:: 1.4
678
679 """
680 exprs = tuple(
681 coercions.expect(roles.WhereHavingRole, clause)
682 for clause in util.coerce_generator_arg(criteria)
683 )
684
685 return RelationshipProperty.Comparator(
686 self.prop,
687 self._parententity,
688 adapt_to_entity=self._adapt_to_entity,
689 of_type=self._of_type,
690 extra_criteria=self._extra_criteria + exprs,
691 )
692
693 def in_(self, other: Any) -> NoReturn:
694 """Produce an IN clause - this is not implemented
695 for :func:`_orm.relationship`-based attributes at this time.
696
697 """
698 raise NotImplementedError(
699 "in_() not yet supported for "
700 "relationships. For a simple "
701 "many-to-one, use in_() against "
702 "the set of foreign key values."
703 )
704
705 # https://github.com/python/mypy/issues/4266
706 __hash__ = None # type: ignore
707
708 def __eq__(self, other: Any) -> ColumnElement[bool]: # type: ignore[override] # noqa: E501
709 """Implement the ``==`` operator.
710
711 In a many-to-one context, such as:
712
713 .. sourcecode:: text
714
715 MyClass.some_prop == <some object>
716
717 this will typically produce a
718 clause such as:
719
720 .. sourcecode:: text
721
722 mytable.related_id == <some id>
723
724 Where ``<some id>`` is the primary key of the given
725 object.
726
727 The ``==`` operator provides partial functionality for non-
728 many-to-one comparisons:
729
730 * Comparisons against collections are not supported.
731 Use :meth:`~.RelationshipProperty.Comparator.contains`.
732 * Compared to a scalar one-to-many, will produce a
733 clause that compares the target columns in the parent to
734 the given target.
735 * Compared to a scalar many-to-many, an alias
736 of the association table will be rendered as
737 well, forming a natural join that is part of the
738 main body of the query. This will not work for
739 queries that go beyond simple AND conjunctions of
740 comparisons, such as those which use OR. Use
741 explicit joins, outerjoins, or
742 :meth:`~.RelationshipProperty.Comparator.has` for
743 more comprehensive non-many-to-one scalar
744 membership tests.
745 * Comparisons against ``None`` given in a one-to-many
746 or many-to-many context produce a NOT EXISTS clause.
747
748 """
749 if other is None or isinstance(other, expression.Null):
750 if self.property.direction in [ONETOMANY, MANYTOMANY]:
751 return ~self._criterion_exists()
752 else:
753 return _orm_annotate(
754 self.property._optimized_compare(
755 None, adapt_source=self.adapter
756 )
757 )
758 elif self.property.uselist:
759 raise sa_exc.InvalidRequestError(
760 "Can't compare a collection to an object or collection; "
761 "use contains() to test for membership."
762 )
763 else:
764 return _orm_annotate(
765 self.property._optimized_compare(
766 other, adapt_source=self.adapter
767 )
768 )
769
770 def _criterion_exists(
771 self,
772 criterion: Optional[_ColumnExpressionArgument[bool]] = None,
773 **kwargs: Any,
774 ) -> Exists:
775 where_criteria = (
776 coercions.expect(roles.WhereHavingRole, criterion)
777 if criterion is not None
778 else None
779 )
780
781 if getattr(self, "_of_type", None):
782 info: Optional[_InternalEntityType[Any]] = inspect(
783 self._of_type
784 )
785 assert info is not None
786 target_mapper, to_selectable, is_aliased_class = (
787 info.mapper,
788 info.selectable,
789 info.is_aliased_class,
790 )
791 if self.property._is_self_referential and not is_aliased_class:
792 to_selectable = to_selectable._anonymous_fromclause()
793
794 single_crit = target_mapper._single_table_criterion
795 if single_crit is not None:
796 if where_criteria is not None:
797 where_criteria = single_crit & where_criteria
798 else:
799 where_criteria = single_crit
800 else:
801 is_aliased_class = False
802 to_selectable = None
803
804 if self.adapter:
805 source_selectable = self._source_selectable()
806 else:
807 source_selectable = None
808
809 (
810 pj,
811 sj,
812 source,
813 dest,
814 secondary,
815 target_adapter,
816 ) = self.property._create_joins(
817 dest_selectable=to_selectable,
818 source_selectable=source_selectable,
819 )
820
821 for k in kwargs:
822 crit = getattr(self.property.mapper.class_, k) == kwargs[k]
823 if where_criteria is None:
824 where_criteria = crit
825 else:
826 where_criteria = where_criteria & crit
827
828 # annotate the *local* side of the join condition, in the case
829 # of pj + sj this is the full primaryjoin, in the case of just
830 # pj its the local side of the primaryjoin.
831 if sj is not None:
832 j = _orm_annotate(pj) & sj
833 else:
834 j = _orm_annotate(pj, exclude=self.property.remote_side)
835
836 if (
837 where_criteria is not None
838 and target_adapter
839 and not is_aliased_class
840 ):
841 # limit this adapter to annotated only?
842 where_criteria = target_adapter.traverse(where_criteria)
843
844 # only have the "joined left side" of what we
845 # return be subject to Query adaption. The right
846 # side of it is used for an exists() subquery and
847 # should not correlate or otherwise reach out
848 # to anything in the enclosing query.
849 if where_criteria is not None:
850 where_criteria = where_criteria._annotate(
851 {"no_replacement_traverse": True}
852 )
853
854 crit = j & sql.True_._ifnone(where_criteria)
855
856 if secondary is not None:
857 ex = (
858 sql.exists(1)
859 .where(crit)
860 .select_from(dest, secondary)
861 .correlate_except(dest, secondary)
862 )
863 else:
864 ex = (
865 sql.exists(1)
866 .where(crit)
867 .select_from(dest)
868 .correlate_except(dest)
869 )
870 return ex
871
872 def any(
873 self,
874 criterion: Optional[_ColumnExpressionArgument[bool]] = None,
875 **kwargs: Any,
876 ) -> ColumnElement[bool]:
877 """Produce an expression that tests a collection against
878 particular criterion, using EXISTS.
879
880 An expression like::
881
882 session.query(MyClass).filter(
883 MyClass.somereference.any(SomeRelated.x == 2)
884 )
885
886 Will produce a query like:
887
888 .. sourcecode:: sql
889
890 SELECT * FROM my_table WHERE
891 EXISTS (SELECT 1 FROM related WHERE related.my_id=my_table.id
892 AND related.x=2)
893
894 Because :meth:`~.RelationshipProperty.Comparator.any` uses
895 a correlated subquery, its performance is not nearly as
896 good when compared against large target tables as that of
897 using a join.
898
899 :meth:`~.RelationshipProperty.Comparator.any` is particularly
900 useful for testing for empty collections::
901
902 session.query(MyClass).filter(~MyClass.somereference.any())
903
904 will produce:
905
906 .. sourcecode:: sql
907
908 SELECT * FROM my_table WHERE
909 NOT (EXISTS (SELECT 1 FROM related WHERE
910 related.my_id=my_table.id))
911
912 :meth:`~.RelationshipProperty.Comparator.any` is only
913 valid for collections, i.e. a :func:`_orm.relationship`
914 that has ``uselist=True``. For scalar references,
915 use :meth:`~.RelationshipProperty.Comparator.has`.
916
917 """
918 if not self.property.uselist:
919 raise sa_exc.InvalidRequestError(
920 "'any()' not implemented for scalar "
921 "attributes. Use has()."
922 )
923
924 return self._criterion_exists(criterion, **kwargs)
925
926 def has(
927 self,
928 criterion: Optional[_ColumnExpressionArgument[bool]] = None,
929 **kwargs: Any,
930 ) -> ColumnElement[bool]:
931 """Produce an expression that tests a scalar reference against
932 particular criterion, using EXISTS.
933
934 An expression like::
935
936 session.query(MyClass).filter(
937 MyClass.somereference.has(SomeRelated.x == 2)
938 )
939
940 Will produce a query like:
941
942 .. sourcecode:: sql
943
944 SELECT * FROM my_table WHERE
945 EXISTS (SELECT 1 FROM related WHERE
946 related.id==my_table.related_id AND related.x=2)
947
948 Because :meth:`~.RelationshipProperty.Comparator.has` uses
949 a correlated subquery, its performance is not nearly as
950 good when compared against large target tables as that of
951 using a join.
952
953 :meth:`~.RelationshipProperty.Comparator.has` is only
954 valid for scalar references, i.e. a :func:`_orm.relationship`
955 that has ``uselist=False``. For collection references,
956 use :meth:`~.RelationshipProperty.Comparator.any`.
957
958 """
959 if self.property.uselist:
960 raise sa_exc.InvalidRequestError(
961 "'has()' not implemented for collections. Use any()."
962 )
963 return self._criterion_exists(criterion, **kwargs)
964
965 def contains(
966 self, other: _ColumnExpressionArgument[Any], **kwargs: Any
967 ) -> ColumnElement[bool]:
968 """Return a simple expression that tests a collection for
969 containment of a particular item.
970
971 :meth:`~.RelationshipProperty.Comparator.contains` is
972 only valid for a collection, i.e. a
973 :func:`_orm.relationship` that implements
974 one-to-many or many-to-many with ``uselist=True``.
975
976 When used in a simple one-to-many context, an
977 expression like::
978
979 MyClass.contains(other)
980
981 Produces a clause like:
982
983 .. sourcecode:: sql
984
985 mytable.id == <some id>
986
987 Where ``<some id>`` is the value of the foreign key
988 attribute on ``other`` which refers to the primary
989 key of its parent object. From this it follows that
990 :meth:`~.RelationshipProperty.Comparator.contains` is
991 very useful when used with simple one-to-many
992 operations.
993
994 For many-to-many operations, the behavior of
995 :meth:`~.RelationshipProperty.Comparator.contains`
996 has more caveats. The association table will be
997 rendered in the statement, producing an "implicit"
998 join, that is, includes multiple tables in the FROM
999 clause which are equated in the WHERE clause::
1000
1001 query(MyClass).filter(MyClass.contains(other))
1002
1003 Produces a query like:
1004
1005 .. sourcecode:: sql
1006
1007 SELECT * FROM my_table, my_association_table AS
1008 my_association_table_1 WHERE
1009 my_table.id = my_association_table_1.parent_id
1010 AND my_association_table_1.child_id = <some id>
1011
1012 Where ``<some id>`` would be the primary key of
1013 ``other``. From the above, it is clear that
1014 :meth:`~.RelationshipProperty.Comparator.contains`
1015 will **not** work with many-to-many collections when
1016 used in queries that move beyond simple AND
1017 conjunctions, such as multiple
1018 :meth:`~.RelationshipProperty.Comparator.contains`
1019 expressions joined by OR. In such cases subqueries or
1020 explicit "outer joins" will need to be used instead.
1021 See :meth:`~.RelationshipProperty.Comparator.any` for
1022 a less-performant alternative using EXISTS, or refer
1023 to :meth:`_query.Query.outerjoin`
1024 as well as :ref:`orm_queryguide_joins`
1025 for more details on constructing outer joins.
1026
1027 kwargs may be ignored by this operator but are required for API
1028 conformance.
1029 """
1030 if not self.prop.uselist:
1031 raise sa_exc.InvalidRequestError(
1032 "'contains' not implemented for scalar "
1033 "attributes. Use =="
1034 )
1035
1036 clause = self.prop._optimized_compare(
1037 other, adapt_source=self.adapter
1038 )
1039
1040 if self.prop.secondaryjoin is not None:
1041 clause.negation_clause = self.__negated_contains_or_equals(
1042 other
1043 )
1044
1045 return clause
1046
1047 def __negated_contains_or_equals(
1048 self, other: Any
1049 ) -> ColumnElement[bool]:
1050 if self.prop.direction == MANYTOONE:
1051 state = attributes.instance_state(other)
1052
1053 def state_bindparam(
1054 local_col: ColumnElement[Any],
1055 state: InstanceState[Any],
1056 remote_col: ColumnElement[Any],
1057 ) -> BindParameter[Any]:
1058 dict_ = state.dict
1059 return sql.bindparam(
1060 local_col.key,
1061 type_=local_col.type,
1062 unique=True,
1063 callable_=self.prop._get_attr_w_warn_on_none(
1064 self.prop.mapper, state, dict_, remote_col
1065 ),
1066 )
1067
1068 def adapt(col: _CE) -> _CE:
1069 if self.adapter:
1070 return self.adapter(col)
1071 else:
1072 return col
1073
1074 if self.property._use_get:
1075 return sql.and_(
1076 *[
1077 sql.or_(
1078 adapt(x)
1079 != state_bindparam(adapt(x), state, y),
1080 adapt(x) == None,
1081 )
1082 for (x, y) in self.property.local_remote_pairs
1083 ]
1084 )
1085
1086 criterion = sql.and_(
1087 *[
1088 x == y
1089 for (x, y) in zip(
1090 self.property.mapper.primary_key,
1091 self.property.mapper.primary_key_from_instance(other),
1092 )
1093 ]
1094 )
1095
1096 return ~self._criterion_exists(criterion)
1097
1098 def __ne__(self, other: Any) -> ColumnElement[bool]: # type: ignore[override] # noqa: E501
1099 """Implement the ``!=`` operator.
1100
1101 In a many-to-one context, such as:
1102
1103 .. sourcecode:: text
1104
1105 MyClass.some_prop != <some object>
1106
1107 This will typically produce a clause such as:
1108
1109 .. sourcecode:: sql
1110
1111 mytable.related_id != <some id>
1112
1113 Where ``<some id>`` is the primary key of the
1114 given object.
1115
1116 The ``!=`` operator provides partial functionality for non-
1117 many-to-one comparisons:
1118
1119 * Comparisons against collections are not supported.
1120 Use
1121 :meth:`~.RelationshipProperty.Comparator.contains`
1122 in conjunction with :func:`_expression.not_`.
1123 * Compared to a scalar one-to-many, will produce a
1124 clause that compares the target columns in the parent to
1125 the given target.
1126 * Compared to a scalar many-to-many, an alias
1127 of the association table will be rendered as
1128 well, forming a natural join that is part of the
1129 main body of the query. This will not work for
1130 queries that go beyond simple AND conjunctions of
1131 comparisons, such as those which use OR. Use
1132 explicit joins, outerjoins, or
1133 :meth:`~.RelationshipProperty.Comparator.has` in
1134 conjunction with :func:`_expression.not_` for
1135 more comprehensive non-many-to-one scalar
1136 membership tests.
1137 * Comparisons against ``None`` given in a one-to-many
1138 or many-to-many context produce an EXISTS clause.
1139
1140 """
1141 if other is None or isinstance(other, expression.Null):
1142 if self.property.direction == MANYTOONE:
1143 return _orm_annotate(
1144 ~self.property._optimized_compare(
1145 None, adapt_source=self.adapter
1146 )
1147 )
1148
1149 else:
1150 return self._criterion_exists()
1151 elif self.property.uselist:
1152 raise sa_exc.InvalidRequestError(
1153 "Can't compare a collection"
1154 " to an object or collection; use "
1155 "contains() to test for membership."
1156 )
1157 else:
1158 return _orm_annotate(self.__negated_contains_or_equals(other))
1159
1160 def _memoized_attr_property(self) -> RelationshipProperty[_PT]:
1161 self.prop.parent._check_configure()
1162 return self.prop
1163
1164 def _with_parent(
1165 self,
1166 instance: object,
1167 alias_secondary: bool = True,
1168 from_entity: Optional[_EntityType[Any]] = None,
1169 ) -> ColumnElement[bool]:
1170 assert instance is not None
1171 adapt_source: Optional[_CoreAdapterProto] = None
1172 if from_entity is not None:
1173 insp: Optional[_InternalEntityType[Any]] = inspect(from_entity)
1174 assert insp is not None
1175 if insp_is_aliased_class(insp):
1176 adapt_source = insp._adapter.adapt_clause
1177 return self._optimized_compare(
1178 instance,
1179 value_is_parent=True,
1180 adapt_source=adapt_source,
1181 alias_secondary=alias_secondary,
1182 )
1183
1184 def _optimized_compare(
1185 self,
1186 state: Any,
1187 value_is_parent: bool = False,
1188 adapt_source: Optional[_CoreAdapterProto] = None,
1189 alias_secondary: bool = True,
1190 ) -> ColumnElement[bool]:
1191 if state is not None:
1192 try:
1193 state = inspect(state)
1194 except sa_exc.NoInspectionAvailable:
1195 state = None
1196
1197 if state is None or not getattr(state, "is_instance", False):
1198 raise sa_exc.ArgumentError(
1199 "Mapped instance expected for relationship "
1200 "comparison to object. Classes, queries and other "
