1# orm/util.py 2# Copyright (C) 2005-2016 the SQLAlchemy authors and contributors 3# <see AUTHORS file> 4# 5# This module is part of SQLAlchemy and is released under 6# the MIT License: http://www.opensource.org/licenses/mit-license.php 7 8 9from .. import sql, util, event, exc as sa_exc, inspection 10from ..sql import expression, util as sql_util, operators 11from .interfaces import PropComparator, MapperProperty 12from . import attributes 13import re 14 15from .base import instance_str, state_str, state_class_str, attribute_str, \ 16 state_attribute_str, object_mapper, object_state, _none_set, _never_set 17from .base import class_mapper, _class_to_mapper 18from .base import InspectionAttr 19from .path_registry import PathRegistry 20 21all_cascades = frozenset(("delete", "delete-orphan", "all", "merge", 22 "expunge", "save-update", "refresh-expire", 23 "none")) 24 25 26class CascadeOptions(frozenset): 27 """Keeps track of the options sent to relationship().cascade""" 28 29 _add_w_all_cascades = all_cascades.difference([ 30 'all', 'none', 'delete-orphan']) 31 _allowed_cascades = all_cascades 32 33 __slots__ = ( 34 'save_update', 'delete', 'refresh_expire', 'merge', 35 'expunge', 'delete_orphan') 36 37 def __new__(cls, value_list): 38 if isinstance(value_list, util.string_types) or value_list is None: 39 return cls.from_string(value_list) 40 values = set(value_list) 41 if values.difference(cls._allowed_cascades): 42 raise sa_exc.ArgumentError( 43 "Invalid cascade option(s): %s" % 44 ", ".join([repr(x) for x in 45 sorted(values.difference(cls._allowed_cascades))])) 46 47 if "all" in values: 48 values.update(cls._add_w_all_cascades) 49 if "none" in values: 50 values.clear() 51 values.discard('all') 52 53 self = frozenset.__new__(CascadeOptions, values) 54 self.save_update = 'save-update' in values 55 self.delete = 'delete' in values 56 self.refresh_expire = 'refresh-expire' in values 57 self.merge = 'merge' in values 58 self.expunge = 'expunge' in values 59 self.delete_orphan = "delete-orphan" in values 60 61 if self.delete_orphan and not self.delete: 62 util.warn("The 'delete-orphan' cascade " 63 "option requires 'delete'.") 64 return self 65 66 def __repr__(self): 67 return "CascadeOptions(%r)" % ( 68 ",".join([x for x in sorted(self)]) 69 ) 70 71 @classmethod 72 def from_string(cls, arg): 73 values = [ 74 c for c 75 in re.split('\s*,\s*', arg or "") 76 if c 77 ] 78 return cls(values) 79 80 81def _validator_events( 82 desc, key, validator, include_removes, include_backrefs): 83 """Runs a validation method on an attribute value to be set or 84 appended. 85 """ 86 87 if not include_backrefs: 88 def detect_is_backref(state, initiator): 89 impl = state.manager[key].impl 90 return initiator.impl is not impl 91 92 if include_removes: 93 def append(state, value, initiator): 94 if include_backrefs or not detect_is_backref(state, initiator): 95 return validator(state.obj(), key, value, False) 96 else: 97 return value 98 99 def set_(state, value, oldvalue, initiator): 100 if include_backrefs or not detect_is_backref(state, initiator): 101 return validator(state.obj(), key, value, False) 102 else: 103 return value 104 105 def remove(state, value, initiator): 106 if include_backrefs or not detect_is_backref(state, initiator): 107 validator(state.obj(), key, value, True) 108 109 else: 110 def append(state, value, initiator): 111 if include_backrefs or not detect_is_backref(state, initiator): 112 return validator(state.obj(), key, value) 113 else: 114 return value 115 116 def set_(state, value, oldvalue, initiator): 117 if include_backrefs or not detect_is_backref(state, initiator): 118 return validator(state.obj(), key, value) 119 else: 120 return value 121 122 event.listen(desc, 'append', append, raw=True, retval=True) 123 event.listen(desc, 'set', set_, raw=True, retval=True) 124 if include_removes: 125 event.listen(desc, "remove", remove, raw=True, retval=True) 126 127 128def polymorphic_union(table_map, typecolname, 129 aliasname='p_union', cast_nulls=True): 130 """Create a ``UNION`` statement used by a polymorphic mapper. 131 132 See :ref:`concrete_inheritance` for an example of how 133 this is used. 134 135 :param table_map: mapping of polymorphic identities to 136 :class:`.Table` objects. 137 :param typecolname: string name of a "discriminator" column, which will be 138 derived from the query, producing the polymorphic identity for 139 each row. If ``None``, no polymorphic discriminator is generated. 140 :param aliasname: name of the :func:`~sqlalchemy.sql.expression.alias()` 141 construct generated. 142 :param cast_nulls: if True, non-existent columns, which are represented 143 as labeled NULLs, will be passed into CAST. This is a legacy behavior 144 that is problematic on some backends such as Oracle - in which case it 145 can be set to False. 146 147 """ 148 149 colnames = util.OrderedSet() 150 colnamemaps = {} 151 types = {} 152 for key in table_map: 153 table = table_map[key] 154 155 # mysql doesn't like selecting from a select; 156 # make it an alias of the select 157 if isinstance(table, sql.Select): 158 table = table.alias() 159 table_map[key] = table 160 161 m = {} 162 for c in table.c: 163 colnames.add(c.key) 164 m[c.key] = c 165 types[c.key] = c.type 166 colnamemaps[table] = m 167 168 def col(name, table): 169 try: 170 return colnamemaps[table][name] 171 except KeyError: 172 if cast_nulls: 173 return sql.cast(sql.null(), types[name]).label(name) 174 else: 175 return sql.type_coerce(sql.null(), types[name]).label(name) 176 177 result = [] 178 for type, table in table_map.items(): 179 if typecolname is not None: 180 result.append( 181 sql.select([col(name, table) for name in colnames] + 182 [sql.literal_column( 183 sql_util._quote_ddl_expr(type)). 184 label(typecolname)], 185 from_obj=[table])) 186 else: 187 result.append(sql.select([col(name, table) for name in colnames], 188 from_obj=[table])) 189 return sql.union_all(*result).alias(aliasname) 190 191 192def identity_key(*args, **kwargs): 193 """Generate "identity key" tuples, as are used as keys in the 194 :attr:`.Session.identity_map` dictionary. 195 196 This function has several call styles: 197 198 * ``identity_key(class, ident)`` 199 200 This form receives a mapped class and a primary key scalar or 201 tuple as an argument. 202 203 E.g.:: 204 205 >>> identity_key(MyClass, (1, 2)) 206 (<class '__main__.MyClass'>, (1, 2)) 207 208 :param class: mapped class (must be a positional argument) 209 :param ident: primary key, may be a scalar or tuple argument. 210 211 212 * ``identity_key(instance=instance)`` 213 214 This form will produce the identity key for a given instance. The 215 instance need not be persistent, only that its primary key attributes 216 are populated (else the key will contain ``None`` for those missing 217 values). 218 219 E.g.:: 220 221 >>> instance = MyClass(1, 2) 222 >>> identity_key(instance=instance) 223 (<class '__main__.MyClass'>, (1, 2)) 224 225 In this form, the given instance is ultimately run though 226 :meth:`.Mapper.identity_key_from_instance`, which will have the 227 effect of performing a database check for the corresponding row 228 if the object is expired. 229 230 :param instance: object instance (must be given as a keyword arg) 231 232 * ``identity_key(class, row=row)`` 233 234 This form is similar to the class/tuple form, except is passed a 235 database result row as a :class:`.RowProxy` object. 236 237 E.g.:: 238 239 >>> row = engine.execute("select * from table where a=1 and b=2").\ 240first() 241 >>> identity_key(MyClass, row=row) 242 (<class '__main__.MyClass'>, (1, 2)) 243 244 :param class: mapped class (must be a positional argument) 245 :param row: :class:`.RowProxy` row returned by a :class:`.ResultProxy` 246 (must be given as a keyword arg) 247 248 """ 249 if args: 250 if len(args) == 1: 251 class_ = args[0] 252 try: 253 row = kwargs.pop("row") 254 except KeyError: 255 ident = kwargs.pop("ident") 256 elif len(args) == 2: 257 class_, ident = args 258 elif len(args) == 3: 259 class_, ident = args 260 else: 261 raise sa_exc.ArgumentError( 262 "expected up to three positional arguments, " 263 "got %s" % len(args)) 264 if kwargs: 265 raise sa_exc.ArgumentError("unknown keyword arguments: %s" 266 % ", ".join(kwargs)) 267 mapper = class_mapper(class_) 268 if "ident" in locals(): 269 return mapper.identity_key_from_primary_key(util.to_list(ident)) 270 return mapper.identity_key_from_row(row) 271 instance = kwargs.pop("instance") 272 if kwargs: 273 raise sa_exc.ArgumentError("unknown keyword arguments: %s" 274 % ", ".join(kwargs.keys)) 275 mapper = object_mapper(instance) 276 return mapper.identity_key_from_instance(instance) 277 278 279class ORMAdapter(sql_util.ColumnAdapter): 280 """ColumnAdapter subclass which excludes adaptation of entities from 281 non-matching mappers. 282 283 """ 284 285 def __init__(self, entity, equivalents=None, adapt_required=False, 286 chain_to=None, allow_label_resolve=True, 287 anonymize_labels=False): 288 info = inspection.inspect(entity) 289 290 self.mapper = info.mapper 291 selectable = info.selectable 292 is_aliased_class = info.is_aliased_class 293 if is_aliased_class: 294 self.aliased_class = entity 295 else: 296 self.aliased_class = None 297 298 sql_util.ColumnAdapter.__init__( 299 self, selectable, equivalents, chain_to, 300 adapt_required=adapt_required, 301 allow_label_resolve=allow_label_resolve, 302 anonymize_labels=anonymize_labels, 303 include_fn=self._include_fn 304 ) 305 306 def _include_fn(self, elem): 307 entity = elem._annotations.get('parentmapper', None) 308 return not entity or entity.isa(self.mapper) 309 310 311class AliasedClass(object): 312 """Represents an "aliased" form of a mapped class for usage with Query. 313 314 The ORM equivalent of a :func:`sqlalchemy.sql.expression.alias` 315 construct, this object mimics the mapped class using a 316 __getattr__ scheme and maintains a reference to a 317 real :class:`~sqlalchemy.sql.expression.Alias` object. 318 319 Usage is via the :func:`.orm.aliased` function, or alternatively 320 via the :func:`.orm.with_polymorphic` function. 321 322 Usage example:: 323 324 # find all pairs of users with the same name 325 user_alias = aliased(User) 326 session.query(User, user_alias).\\ 327 join((user_alias, User.id > user_alias.id)).\\ 328 filter(User.name==user_alias.name) 329 330 The resulting object is an instance of :class:`.AliasedClass`. 331 This object implements an attribute scheme which produces the 332 same attribute and method interface as the original mapped 333 class, allowing :class:`.AliasedClass` to be compatible 334 with any attribute technique which works on the original class, 335 including hybrid attributes (see :ref:`hybrids_toplevel`). 336 337 The :class:`.AliasedClass` can be inspected for its underlying 338 :class:`.Mapper`, aliased selectable, and other information 339 using :func:`.inspect`:: 340 341 from sqlalchemy import inspect 342 my_alias = aliased(MyClass) 343 insp = inspect(my_alias) 344 345 The resulting inspection object is an instance of :class:`.AliasedInsp`. 346 347 See :func:`.aliased` and :func:`.with_polymorphic` for construction 348 argument descriptions. 349 350 """ 351 352 def __init__(self, cls, alias=None, 353 name=None, 354 flat=False, 355 adapt_on_names=False, 356 # TODO: None for default here? 357 with_polymorphic_mappers=(), 358 with_polymorphic_discriminator=None, 359 base_alias=None, 360 use_mapper_path=False): 361 mapper = _class_to_mapper(cls) 362 if alias is None: 363 alias = mapper._with_polymorphic_selectable.alias( 364 name=name, flat=flat) 365 366 self._aliased_insp = AliasedInsp( 367 self, 368 mapper, 369 alias, 370 name, 371 with_polymorphic_mappers 372 if with_polymorphic_mappers 373 else mapper.with_polymorphic_mappers, 374 with_polymorphic_discriminator 375 if with_polymorphic_discriminator is not None 376 else mapper.polymorphic_on, 377 base_alias, 378 use_mapper_path, 379 adapt_on_names 380 ) 381 382 self.__name__ = 'AliasedClass_%s' % mapper.class_.__name__ 383 384 def __getattr__(self, key): 385 try: 386 _aliased_insp = self.__dict__['_aliased_insp'] 387 except KeyError: 388 raise AttributeError() 389 else: 390 for base in _aliased_insp._target.__mro__: 391 try: 392 attr = object.__getattribute__(base, key) 393 except AttributeError: 394 continue 395 else: 396 break 397 else: 398 raise AttributeError(key) 399 400 if isinstance(attr, PropComparator): 401 ret = attr.adapt_to_entity(_aliased_insp) 402 setattr(self, key, ret) 403 return ret 404 elif hasattr(attr, 'func_code'): 405 is_method = getattr(_aliased_insp._target, key, None) 406 if is_method and is_method.__self__ is not None: 407 return util.types.MethodType(attr.__func__, self, self) 408 else: 409 return None 410 elif hasattr(attr, '__get__'): 411 ret = attr.__get__(None, self) 412 if isinstance(ret, PropComparator): 413 return ret.adapt_to_entity(_aliased_insp) 414 else: 415 return ret 416 else: 417 return attr 418 419 def __repr__(self): 420 return '<AliasedClass at 0x%x; %s>' % ( 421 id(self), self._aliased_insp._target.__name__) 422 423 424class AliasedInsp(InspectionAttr): 425 """Provide an inspection interface for an 426 :class:`.AliasedClass` object. 427 428 The :class:`.AliasedInsp` object is returned 429 given an :class:`.AliasedClass` using the 430 :func:`.inspect` function:: 431 432 from sqlalchemy import inspect 433 from sqlalchemy.orm import aliased 434 435 my_alias = aliased(MyMappedClass) 436 insp = inspect(my_alias) 437 438 Attributes on :class:`.AliasedInsp` 439 include: 440 441 * ``entity`` - the :class:`.AliasedClass` represented. 442 * ``mapper`` - the :class:`.Mapper` mapping the underlying class. 443 * ``selectable`` - the :class:`.Alias` construct which ultimately 444 represents an aliased :class:`.Table` or :class:`.Select` 445 construct. 446 * ``name`` - the name of the alias. Also is used as the attribute 447 name when returned in a result tuple from :class:`.Query`. 448 * ``with_polymorphic_mappers`` - collection of :class:`.Mapper` objects 449 indicating all those mappers expressed in the select construct 450 for the :class:`.AliasedClass`. 451 * ``polymorphic_on`` - an alternate column or SQL expression which 452 will be used as the "discriminator" for a polymorphic load. 453 454 .. seealso:: 455 456 :ref:`inspection_toplevel` 457 458 """ 459 460 def __init__(self, entity, mapper, selectable, name, 461 with_polymorphic_mappers, polymorphic_on, 462 _base_alias, _use_mapper_path, adapt_on_names): 463 self.entity = entity 464 self.mapper = mapper 465 self.selectable = selectable 466 self.name = name 467 self.with_polymorphic_mappers = with_polymorphic_mappers 468 self.polymorphic_on = polymorphic_on 469 self._base_alias = _base_alias or self 470 self._use_mapper_path = _use_mapper_path 471 472 self._adapter = sql_util.ColumnAdapter( 473 selectable, equivalents=mapper._equivalent_columns, 474 adapt_on_names=adapt_on_names, anonymize_labels=True) 475 476 self._adapt_on_names = adapt_on_names 477 self._target = mapper.class_ 478 479 for poly in self.with_polymorphic_mappers: 480 if poly is not mapper: 481 setattr(self.entity, poly.class_.__name__, 482 AliasedClass(poly.class_, selectable, base_alias=self, 483 adapt_on_names=adapt_on_names, 484 use_mapper_path=_use_mapper_path)) 485 486 is_aliased_class = True 487 "always returns True" 488 489 @property 490 def class_(self): 491 """Return the mapped class ultimately represented by this 492 :class:`.AliasedInsp`.""" 493 return self.mapper.class_ 494 495 @util.memoized_property 496 def _path_registry(self): 497 if self._use_mapper_path: 498 return self.mapper._path_registry 499 else: 500 return PathRegistry.per_mapper(self) 501 502 def __getstate__(self): 503 return { 504 'entity': self.entity, 505 'mapper': self.mapper, 506 'alias': self.selectable, 507 'name': self.name, 508 'adapt_on_names': self._adapt_on_names, 509 'with_polymorphic_mappers': 510 self.with_polymorphic_mappers, 511 'with_polymorphic_discriminator': 512 self.polymorphic_on, 513 'base_alias': self._base_alias, 514 'use_mapper_path': self._use_mapper_path 515 } 516 517 def __setstate__(self, state): 518 self.__init__( 519 state['entity'], 520 state['mapper'], 521 state['alias'], 522 state['name'], 523 state['with_polymorphic_mappers'], 524 state['with_polymorphic_discriminator'], 525 state['base_alias'], 526 state['use_mapper_path'], 527 state['adapt_on_names'] 528 ) 529 530 def _adapt_element(self, elem): 531 return self._adapter.traverse(elem).\ 532 _annotate({ 533 'parententity': self, 534 'parentmapper': self.mapper} 535 ) 536 537 def _entity_for_mapper(self, mapper): 538 self_poly = self.with_polymorphic_mappers 539 if mapper in self_poly: 540 if mapper is self.mapper: 541 return self 542 else: 543 return getattr( 544 self.entity, mapper.class_.__name__)._aliased_insp 545 elif mapper.isa(self.mapper): 546 return self 547 else: 548 assert False, "mapper %s doesn't correspond to %s" % ( 549 mapper, self) 550 551 def __repr__(self): 552 if self.with_polymorphic_mappers: 553 with_poly = "(%s)" % ", ".join( 554 mp.class_.__name__ for mp in self.with_polymorphic_mappers) 555 else: 556 with_poly = "" 557 return '<AliasedInsp at 0x%x; %s%s>' % ( 558 id(self), self.class_.__name__, with_poly) 559 560 561inspection._inspects(AliasedClass)(lambda target: target._aliased_insp) 562inspection._inspects(AliasedInsp)(lambda target: target) 563 564 565def aliased(element, alias=None, name=None, flat=False, adapt_on_names=False): 566 """Produce an alias of the given element, usually an :class:`.AliasedClass` 567 instance. 568 569 E.g.:: 570 571 my_alias = aliased(MyClass) 572 573 session.query(MyClass, my_alias).filter(MyClass.id > my_alias.id) 574 575 The :func:`.aliased` function is used to create an ad-hoc mapping 576 of a mapped class to a new selectable. By default, a selectable 577 is generated from the normally mapped selectable (typically a 578 :class:`.Table`) using the :meth:`.FromClause.alias` method. 579 However, :func:`.aliased` can also be used to link the class to 580 a new :func:`.select` statement. Also, the :func:`.with_polymorphic` 581 function is a variant of :func:`.aliased` that is intended to specify 582 a so-called "polymorphic selectable", that corresponds to the union 583 of several joined-inheritance subclasses at once. 584 585 For convenience, the :func:`.aliased` function also accepts plain 586 :class:`.FromClause` constructs, such as a :class:`.Table` or 587 :func:`.select` construct. In those cases, the :meth:`.FromClause.alias` 588 method is called on the object and the new :class:`.Alias` object 589 returned. The returned :class:`.Alias` is not ORM-mapped in this case. 590 591 :param element: element to be aliased. Is normally a mapped class, 592 but for convenience can also be a :class:`.FromClause` element. 593 594 :param alias: Optional selectable unit to map the element to. This should 595 normally be a :class:`.Alias` object corresponding to the :class:`.Table` 596 to which the class is mapped, or to a :func:`.select` construct that 597 is compatible with the mapping. By default, a simple anonymous 598 alias of the mapped table is generated. 599 600 :param name: optional string name to use for the alias, if not specified 601 by the ``alias`` parameter. The name, among other things, forms the 602 attribute name that will be accessible via tuples returned by a 603 :class:`.Query` object. 604 605 :param flat: Boolean, will be passed through to the 606 :meth:`.FromClause.alias` call so that aliases of :class:`.Join` objects 607 don't include an enclosing SELECT. This can lead to more efficient 608 queries in many circumstances. A JOIN against a nested JOIN will be 609 rewritten as a JOIN against an aliased SELECT subquery on backends that 610 don't support this syntax. 611 612 .. versionadded:: 0.9.0 613 614 .. seealso:: :meth:`.Join.alias` 615 616 :param adapt_on_names: if True, more liberal "matching" will be used when 617 mapping the mapped columns of the ORM entity to those of the 618 given selectable - a name-based match will be performed if the 619 given selectable doesn't otherwise have a column that corresponds 620 to one on the entity. The use case for this is when associating 621 an entity with some derived selectable such as one that uses 622 aggregate functions:: 623 624 class UnitPrice(Base): 625 __tablename__ = 'unit_price' 626 ... 627 unit_id = Column(Integer) 628 price = Column(Numeric) 629 630 aggregated_unit_price = Session.query( 631 func.sum(UnitPrice.price).label('price') 632 ).group_by(UnitPrice.unit_id).subquery() 633 634 aggregated_unit_price = aliased(UnitPrice, 635 alias=aggregated_unit_price, adapt_on_names=True) 636 637 Above, functions on ``aggregated_unit_price`` which refer to 638 ``.price`` will return the 639 ``fund.sum(UnitPrice.price).label('price')`` column, as it is 640 matched on the name "price". Ordinarily, the "price" function 641 wouldn't have any "column correspondence" to the actual 642 ``UnitPrice.price`` column as it is not a proxy of the original. 643 644 .. versionadded:: 0.7.3 645 646 647 """ 648 if isinstance(element, expression.FromClause): 649 if adapt_on_names: 650 raise sa_exc.ArgumentError( 651 "adapt_on_names only applies to ORM elements" 652 ) 653 return element.alias(name, flat=flat) 654 else: 655 return AliasedClass(element, alias=alias, flat=flat, 656 name=name, adapt_on_names=adapt_on_names) 657 658 659def with_polymorphic(base, classes, selectable=False, 660 flat=False, 661 polymorphic_on=None, aliased=False, 662 innerjoin=False, _use_mapper_path=False, 663 _existing_alias=None): 664 """Produce an :class:`.AliasedClass` construct which specifies 665 columns for descendant mappers of the given base. 666 667 .. versionadded:: 0.8 668 :func:`.orm.with_polymorphic` is in addition to the existing 669 :class:`.Query` method :meth:`.Query.with_polymorphic`, 670 which has the same purpose but is not as flexible in its usage. 671 672 Using this method will ensure that each descendant mapper's 673 tables are included in the FROM clause, and will allow filter() 674 criterion to be used against those tables. The resulting 675 instances will also have those columns already loaded so that 676 no "post fetch" of those columns will be required. 677 678 See the examples at :ref:`with_polymorphic`. 679 680 :param base: Base class to be aliased. 681 682 :param classes: a single class or mapper, or list of 683 class/mappers, which inherit from the base class. 684 Alternatively, it may also be the string ``'*'``, in which case 685 all descending mapped classes will be added to the FROM clause. 686 687 :param aliased: when True, the selectable will be wrapped in an 688 alias, that is ``(SELECT * FROM <fromclauses>) AS anon_1``. 689 This can be important when using the with_polymorphic() 690 to create the target of a JOIN on a backend that does not 691 support parenthesized joins, such as SQLite and older 692 versions of MySQL. 693 694 :param flat: Boolean, will be passed through to the 695 :meth:`.FromClause.alias` call so that aliases of :class:`.Join` 696 objects don't include an enclosing SELECT. This can lead to more 697 efficient queries in many circumstances. A JOIN against a nested JOIN 698 will be rewritten as a JOIN against an aliased SELECT subquery on 699 backends that don't support this syntax. 700 701 Setting ``flat`` to ``True`` implies the ``aliased`` flag is 702 also ``True``. 703 704 .. versionadded:: 0.9.0 705 706 .. seealso:: :meth:`.Join.alias` 707 708 :param selectable: a table or select() statement that will 709 be used in place of the generated FROM clause. This argument is 710 required if any of the desired classes use concrete table 711 inheritance, since SQLAlchemy currently cannot generate UNIONs 712 among tables automatically. If used, the ``selectable`` argument 713 must represent the full set of tables and columns mapped by every 714 mapped class. Otherwise, the unaccounted mapped columns will 715 result in their table being appended directly to the FROM clause 716 which will usually lead to incorrect results. 717 718 :param polymorphic_on: a column to be used as the "discriminator" 719 column for the given selectable. If not given, the polymorphic_on 720 attribute of the base classes' mapper will be used, if any. This 721 is useful for mappings that don't have polymorphic loading 722 behavior by default. 723 724 :param innerjoin: if True, an INNER JOIN will be used. This should 725 only be specified if querying for one specific subtype only 726 """ 727 primary_mapper = _class_to_mapper(base) 728 if _existing_alias: 729 assert _existing_alias.mapper is primary_mapper 730 classes = util.to_set(classes) 731 new_classes = set([ 732 mp.class_ for mp in 733 _existing_alias.with_polymorphic_mappers]) 734 if classes == new_classes: 735 return _existing_alias 736 else: 737 classes = classes.union(new_classes) 738 mappers, selectable = primary_mapper.\ 739 _with_polymorphic_args(classes, selectable, 740 innerjoin=innerjoin) 741 if aliased or flat: 742 selectable = selectable.alias(flat=flat) 743 return AliasedClass(base, 744 selectable, 745 with_polymorphic_mappers=mappers, 746 with_polymorphic_discriminator=polymorphic_on, 747 use_mapper_path=_use_mapper_path) 748 749 750def _orm_annotate(element, exclude=None): 751 """Deep copy the given ClauseElement, annotating each element with the 752 "_orm_adapt" flag. 753 754 Elements within the exclude collection will be cloned but not annotated. 755 756 """ 757 return sql_util._deep_annotate(element, {'_orm_adapt': True}, exclude) 758 759 760def _orm_deannotate(element): 761 """Remove annotations that link a column to a particular mapping. 762 763 Note this doesn't affect "remote" and "foreign" annotations 764 passed by the :func:`.orm.foreign` and :func:`.orm.remote` 765 annotators. 766 767 """ 768 769 return sql_util._deep_deannotate(element, 770 values=("_orm_adapt", "parententity") 771 ) 772 773 774def _orm_full_deannotate(element): 775 return sql_util._deep_deannotate(element) 776 777 778class _ORMJoin(expression.Join): 779 """Extend Join to support ORM constructs as input.""" 780 781 __visit_name__ = expression.Join.__visit_name__ 782 783 def __init__( 784 self, 785 left, right, onclause=None, isouter=False, 786 _left_memo=None, _right_memo=None): 787 788 left_info = inspection.inspect(left) 789 left_orm_info = getattr(left, '_joined_from_info', left_info) 790 791 right_info = inspection.inspect(right) 792 adapt_to = right_info.selectable 793 794 self._joined_from_info = right_info 795 796 self._left_memo = _left_memo 797 self._right_memo = _right_memo 798 799 if isinstance(onclause, util.string_types): 800 onclause = getattr(left_orm_info.entity, onclause) 801 802 if isinstance(onclause, attributes.QueryableAttribute): 803 on_selectable = onclause.comparator._source_selectable() 804 prop = onclause.property 805 elif isinstance(onclause, MapperProperty): 806 prop = onclause 807 on_selectable = prop.parent.selectable 808 else: 809 prop = None 810 811 if prop: 812 if sql_util.clause_is_present( 813 on_selectable, left_info.selectable): 814 adapt_from = on_selectable 815 else: 816 adapt_from = left_info.selectable 817 818 pj, sj, source, dest, \ 819 secondary, target_adapter = prop._create_joins( 820 source_selectable=adapt_from, 821 dest_selectable=adapt_to, 822 source_polymorphic=True, 823 dest_polymorphic=True, 824 of_type=right_info.mapper) 825 826 if sj is not None: 827 if isouter: 828 # note this is an inner join from secondary->right 829 right = sql.join(secondary, right, sj) 830 onclause = pj 831 else: 832 left = sql.join(left, secondary, pj, isouter) 833 onclause = sj 834 else: 835 onclause = pj 836 self._target_adapter = target_adapter 837 838 expression.Join.__init__(self, left, right, onclause, isouter) 839 840 if not prop and getattr(right_info, 'mapper', None) \ 841 and right_info.mapper.single: 842 # if single inheritance target and we are using a manual 843 # or implicit ON clause, augment it the same way we'd augment the 844 # WHERE. 845 single_crit = right_info.mapper._single_table_criterion 846 if single_crit is not None: 847 if right_info.is_aliased_class: 848 single_crit = right_info._adapter.traverse(single_crit) 849 self.onclause = self.onclause & single_crit 850 851 def _splice_into_center(self, other): 852 """Splice a join into the center. 853 854 Given join(a, b) and join(b, c), return join(a, b).join(c) 855 856 """ 857 leftmost = other 858 while isinstance(leftmost, sql.Join): 859 leftmost = leftmost.left 860 861 assert self.right is leftmost 862 863 left = _ORMJoin( 864 self.left, other.left, 865 self.onclause, isouter=self.isouter, 866 _left_memo=self._left_memo, 867 _right_memo=other._left_memo 868 ) 869 870 return _ORMJoin( 871 left, 872 other.right, 873 other.onclause, isouter=other.isouter, 874 _right_memo=other._right_memo 875 ) 876 877 def join(self, right, onclause=None, isouter=False, join_to_left=None): 878 return _ORMJoin(self, right, onclause, isouter) 879 880 def outerjoin(self, right, onclause=None, join_to_left=None): 881 return _ORMJoin(self, right, onclause, True) 882 883 884def join(left, right, onclause=None, isouter=False, join_to_left=None): 885 """Produce an inner join between left and right clauses. 886 887 :func:`.orm.join` is an extension to the core join interface 888 provided by :func:`.sql.expression.join()`, where the 889 left and right selectables may be not only core selectable 890 objects such as :class:`.Table`, but also mapped classes or 891 :class:`.AliasedClass` instances. The "on" clause can 892 be a SQL expression, or an attribute or string name 893 referencing a configured :func:`.relationship`. 894 895 :func:`.orm.join` is not commonly needed in modern usage, 896 as its functionality is encapsulated within that of the 897 :meth:`.Query.join` method, which features a 898 significant amount of automation beyond :func:`.orm.join` 899 by itself. Explicit usage of :func:`.orm.join` 900 with :class:`.Query` involves usage of the 901 :meth:`.Query.select_from` method, as in:: 902 903 from sqlalchemy.orm import join 904 session.query(User).\\ 905 select_from(join(User, Address, User.addresses)).\\ 906 filter(Address.email_address=='foo@bar.com') 907 908 In modern SQLAlchemy the above join can be written more 909 succinctly as:: 910 911 session.query(User).\\ 912 join(User.addresses).\\ 913 filter(Address.email_address=='foo@bar.com') 914 915 See :meth:`.Query.join` for information on modern usage 916 of ORM level joins. 917 918 .. versionchanged:: 0.8.1 - the ``join_to_left`` parameter 919 is no longer used, and is deprecated. 920 921 """ 922 return _ORMJoin(left, right, onclause, isouter) 923 924 925def outerjoin(left, right, onclause=None, join_to_left=None): 926 """Produce a left outer join between left and right clauses. 927 928 This is the "outer join" version of the :func:`.orm.join` function, 929 featuring the same behavior except that an OUTER JOIN is generated. 930 See that function's documentation for other usage details. 931 932 """ 933 return _ORMJoin(left, right, onclause, True) 934 935 936def with_parent(instance, prop): 937 """Create filtering criterion that relates this query's primary entity 938 to the given related instance, using established :func:`.relationship()` 939 configuration. 940 941 The SQL rendered is the same as that rendered when a lazy loader 942 would fire off from the given parent on that attribute, meaning 943 that the appropriate state is taken from the parent object in 944 Python without the need to render joins to the parent table 945 in the rendered statement. 946 947 .. versionchanged:: 0.6.4 948 This method accepts parent instances in all 949 persistence states, including transient, persistent, and detached. 950 Only the requisite primary key/foreign key attributes need to 951 be populated. Previous versions didn't work with transient 952 instances. 953 954 :param instance: 955 An instance which has some :func:`.relationship`. 956 957 :param property: 958 String property name, or class-bound attribute, which indicates 959 what relationship from the instance should be used to reconcile the 960 parent/child relationship. 961 962 """ 963 if isinstance(prop, util.string_types): 964 mapper = object_mapper(instance) 965 prop = getattr(mapper.class_, prop).property 966 elif isinstance(prop, attributes.QueryableAttribute): 967 prop = prop.property 968 969 return prop._with_parent(instance) 970 971 972def has_identity(object): 973 """Return True if the given object has a database 974 identity. 975 976 This typically corresponds to the object being 977 in either the persistent or detached state. 978 979 .. seealso:: 980 981 :func:`.was_deleted` 982 983 """ 984 state = attributes.instance_state(object) 985 return state.has_identity 986 987 988def was_deleted(object): 989 """Return True if the given object was deleted 990 within a session flush. 991 992 .. versionadded:: 0.8.0 993 994 """ 995 996 state = attributes.instance_state(object) 997 return state.deleted 998 999 1000def randomize_unitofwork(): 1001 """Use random-ordering sets within the unit of work in order 1002 to detect unit of work sorting issues. 1003 1004 This is a utility function that can be used to help reproduce 1005 inconsistent unit of work sorting issues. For example, 1006 if two kinds of objects A and B are being inserted, and 1007 B has a foreign key reference to A - the A must be inserted first. 1008 However, if there is no relationship between A and B, the unit of work 1009 won't know to perform this sorting, and an operation may or may not 1010 fail, depending on how the ordering works out. Since Python sets 1011 and dictionaries have non-deterministic ordering, such an issue may 1012 occur on some runs and not on others, and in practice it tends to 1013 have a great dependence on the state of the interpreter. This leads 1014 to so-called "heisenbugs" where changing entirely irrelevant aspects 1015 of the test program still cause the failure behavior to change. 1016 1017 By calling ``randomize_unitofwork()`` when a script first runs, the 1018 ordering of a key series of sets within the unit of work implementation 1019 are randomized, so that the script can be minimized down to the 1020 fundamental mapping and operation that's failing, while still reproducing 1021 the issue on at least some runs. 1022 1023 This utility is also available when running the test suite via the 1024 ``--reversetop`` flag. 1025 1026 .. versionadded:: 0.8.1 created a standalone version of the 1027 ``--reversetop`` feature. 1028 1029 """ 1030 from sqlalchemy.orm import unitofwork, session, mapper, dependency 1031 from sqlalchemy.util import topological 1032 from sqlalchemy.testing.util import RandomSet 1033 topological.set = unitofwork.set = session.set = mapper.set = \ 1034 dependency.set = RandomSet 1035