~ubuntu-branches/debian/jessie/sqlalchemy/jessie

« back to all changes in this revision

Viewing changes to lib/sqlalchemy/orm/query.py

  • Committer: Bazaar Package Importer
  • Author(s): Piotr Ożarowski
  • Date: 2011-08-01 23:18:16 UTC
  • mfrom: (1.4.15 upstream) (16.1.14 experimental)
  • Revision ID: james.westby@ubuntu.com-20110801231816-6lx797pi3q1fpqst
Tags: 0.7.2-1
* New upstream release
* Bump minimum required python-mako version to 0.4.1 (closes: 635898)

Show diffs side-by-side

added added

removed removed

Lines of Context:
32
32
from sqlalchemy.orm.util import (
33
33
    AliasedClass, ORMAdapter, _entity_descriptor, _entity_info,
34
34
    _is_aliased_class, _is_mapped_class, _orm_columns, _orm_selectable,
35
 
    join as orm_join,with_parent, _attr_as_key
 
35
    join as orm_join,with_parent, _attr_as_key, aliased
36
36
    )
37
37
 
38
38
 
39
39
__all__ = ['Query', 'QueryContext', 'aliased']
40
40
 
41
41
 
42
 
aliased = AliasedClass
43
 
 
44
42
def _generative(*assertions):
45
43
    """Mark a method as generative."""
46
44
 
84
82
    _statement = None
85
83
    _correlate = frozenset()
86
84
    _populate_existing = False
 
85
    _invoke_all_eagers = True
87
86
    _version_check = False
88
87
    _autoflush = True
89
88
    _current_path = ()
93
92
    _select_from_entity = None
94
93
    _filter_aliases = None
95
94
    _from_obj_alias = None
96
 
    _joinpath = _joinpoint = util.frozendict()
97
 
    _execution_options = util.frozendict()
98
 
    _params = util.frozendict()
99
 
    _attributes = util.frozendict()
 
95
    _joinpath = _joinpoint = util.immutabledict()
 
96
    _execution_options = util.immutabledict()
 
97
    _params = util.immutabledict()
 
98
    _attributes = util.immutabledict()
100
99
    _with_options = ()
101
100
    _with_hints = ()
102
101
    _enable_single_crit = True
162
161
 
163
162
        fa = []
164
163
        for from_obj in obj:
165
 
            if isinstance(from_obj, expression._SelectBaseMixin):
 
164
            if isinstance(from_obj, expression._SelectBase):
166
165
                from_obj = from_obj.alias()
167
166
            fa.append(from_obj)
168
167
 
199
198
            if alias:
200
199
                return alias.adapt_clause(element)
201
200
 
202
 
    def __replace_element(self, adapters):
203
 
        def replace(elem):
204
 
            if '_halt_adapt' in elem._annotations:
205
 
                return elem
206
 
 
207
 
            for adapter in adapters:
208
 
                e = adapter(elem)
209
 
                if e is not None:
210
 
                    return e
211
 
        return replace
212
 
 
213
 
    def __replace_orm_element(self, adapters):
214
 
        def replace(elem):
215
 
            if '_halt_adapt' in elem._annotations:
216
 
                return elem
217
 
 
218
 
            if "_orm_adapt" in elem._annotations \
219
 
                    or "parententity" in elem._annotations:
220
 
                for adapter in adapters:
221
 
                    e = adapter(elem)
222
 
                    if e is not None:
223
 
                        return e
224
 
        return replace
225
 
 
226
 
    @_generative()
227
 
    def _adapt_all_clauses(self):
228
 
        self._disable_orm_filtering = True
229
 
 
230
201
    def _adapt_col_list(self, cols):
231
202
        return [
232
203
                    self._adapt_clause(
235
206
                    for o in cols
236
207
                ]
237
208
 
 
209
    @_generative()
 
210
    def _adapt_all_clauses(self):
 
211
        self._orm_only_adapt = False
 
212
 
238
213
    def _adapt_clause(self, clause, as_filter, orm_only):
 
214
        """Adapt incoming clauses to transformations which have been applied 
 
215
        within this query."""
 
216
 
239
217
        adapters = []
 
218
 
 
219
        # do we adapt all expression elements or only those
 
220
        # tagged as 'ORM' constructs ?
 
221
        orm_only = getattr(self, '_orm_only_adapt', orm_only)
 
222
 
240
223
        if as_filter and self._filter_aliases:
241
224
            for fa in self._filter_aliases._visitor_iterator:
242
 
                adapters.append(fa.replace)
 
225
                adapters.append(
 
226
                    (
 
227
                        orm_only, fa.replace
 
228
                    )
 
229
                )
243
230
 
244
231
        if self._from_obj_alias:
245
 
            adapters.append(self._from_obj_alias.replace)
 
232
            # for the "from obj" alias, apply extra rule to the
 
233
            # 'ORM only' check, if this query were generated from a 
 
234
            # subquery of itself, i.e. _from_selectable(), apply adaption
 
235
            # to all SQL constructs.
 
236
            adapters.append(
 
237
                (
 
238
                    getattr(self, '_orm_only_from_obj_alias', orm_only), 
 
239
                    self._from_obj_alias.replace
 
240
                )
 
241
            )
246
242
 
247
243
        if self._polymorphic_adapters:
248
 
            adapters.append(self.__adapt_polymorphic_element)
 
244
            adapters.append(
 
245
                (
 
246
                    orm_only, self.__adapt_polymorphic_element
 
247
                )
 
248
            )
249
249
 
250
250
        if not adapters:
251
251
            return clause
252
252
 
253
 
        if getattr(self, '_disable_orm_filtering', not orm_only):
254
 
            return visitors.replacement_traverse(
255
 
                                clause, 
256
 
                                {'column_collections':False}, 
257
 
                                self.__replace_element(adapters)
258
 
                            )
259
 
        else:
260
 
            return visitors.replacement_traverse(
261
 
                                clause, 
262
 
                                {'column_collections':False}, 
263
 
                                self.__replace_orm_element(adapters)
264
 
                            )
 
253
        def replace(elem):
 
254
            for _orm_only, adapter in adapters:
 
255
                # if 'orm only', look for ORM annotations
 
256
                # in the element before adapting.
 
257
                if not _orm_only or \
 
258
                    '_orm_adapt' in elem._annotations or \
 
259
                    "parententity" in elem._annotations:
 
260
 
 
261
                    e = adapter(elem)
 
262
                    if e is not None:
 
263
                        return e
 
264
 
 
265
        return visitors.replacement_traverse(
 
266
                            clause, 
 
267
                            {}, 
 
268
                            replace
 
269
                        )
265
270
 
266
271
    def _entity_zero(self):
267
272
        return self._entities[0]
270
275
        return self._select_from_entity or \
271
276
            self._entity_zero().entity_zero
272
277
 
273
 
    def _extension_zero(self):
274
 
        ent = self._entity_zero()
275
 
        return getattr(ent, 'extension', ent.mapper.extension)
276
 
 
277
278
    @property
278
279
    def _mapper_entities(self):
279
280
        # TODO: this is wrong, its hardcoded to "primary entity" when
309
310
                    "a single mapped class." % methname)
310
311
        entity = self._entity_zero()
311
312
        if not hasattr(entity, 'primary_entity'):
312
 
            util.warn("Calling %s() with a "
313
 
                    "column-based entity is deprecated." % 
314
 
                    methname)
 
313
            raise sa_exc.InvalidRequestError(
 
314
                    "%s() can only be used against "
 
315
                    "a single mapped class." % methname)
315
316
        return entity.entity_zero
316
317
 
317
318
    def _only_entity_zero(self, rationale=None):
367
368
    def _no_statement_condition(self, meth):
368
369
        if not self._enable_assertions:
369
370
            return
370
 
        if self._statement:
 
371
        if self._statement is not None:
371
372
            raise sa_exc.InvalidRequestError(
372
373
                ("Query.%s() being called on a Query with an existing full "
373
374
                 "statement - can't apply criterion.") % meth)
434
435
                        statement
435
436
        if self._params:
436
437
            stmt = stmt.params(self._params)
437
 
        return stmt._annotate({'_halt_adapt': True})
 
438
        # TODO: there's no tests covering effects of
 
439
        # the annotation not being there
 
440
        return stmt._annotate({'no_replacement_traverse': True})
438
441
 
439
442
    def subquery(self, name=None):
440
443
        """return the full SELECT statement represented by this :class:`.Query`, 
451
454
            this is passed through to :meth:`.FromClause.alias`.
452
455
            If ``None``, a name will be deterministically generated
453
456
            at compile time.
454
 
        
 
457
 
455
458
 
456
459
        """
457
460
        return self.enable_eagerloads(False).statement.alias(name=name)
631
634
        self._execution_options['stream_results'] = True
632
635
 
633
636
    def get(self, ident):
634
 
        """Return an instance of the object based on the 
635
 
        given identifier, or ``None`` if not found.
636
 
 
637
 
        The ``ident`` argument is a scalar or tuple of 
638
 
        primary key column values
639
 
        in the order of the mapper's "primary key" setting, which
640
 
        defaults to the list of primary key columns for the 
641
 
        mapped :class:`.Table`.
642
 
        
643
 
        :meth:`get` returns only a single mapped instance, or
644
 
        ``None``.  It is not intended to return rows or scalar
645
 
        column values, therefore the :class:`.Query` must be 
646
 
        constructed only against a single mapper or mapped class,
647
 
        not a SQL expression or multiple entities.
648
 
        Other usages raise an error, or in the case of a single
649
 
        column a deprecation warning is raised as of 0.6.8.
650
 
 
 
637
        """Return an instance based on the given primary key identifier, 
 
638
        or ``None`` if not found.
 
639
        
 
640
        E.g.::
 
641
        
 
642
            my_user = session.query(User).get(5)
 
643
            
 
644
            some_object = session.query(VersionedFoo).get((5, 10))
 
645
        
 
646
        :meth:`~.Query.get` is special in that it provides direct 
 
647
        access to the identity map of the owning :class:`.Session`.
 
648
        If the given primary key identifier is present
 
649
        in the local identity map, the object is returned
 
650
        directly from this collection and no SQL is emitted, 
 
651
        unless the object has been marked fully expired.
 
652
        If not present,
 
653
        a SELECT is performed in order to locate the object.
 
654
        
 
655
        :meth:`~.Query.get` also will perform a check if 
 
656
        the object is present in the identity map and 
 
657
        marked as expired - a SELECT 
 
658
        is emitted to refresh the object as well as to
 
659
        ensure that the row is still present.
 
660
        If not, :class:`~sqlalchemy.orm.exc.ObjectDeletedError` is raised.
 
661
        
 
662
        :meth:`~.Query.get` is only used to return a single
 
663
        mapped instance, not multiple instances or 
 
664
        individual column constructs, and strictly
 
665
        on a single primary key value.  The originating
 
666
        :class:`.Query` must be constructed in this way,
 
667
        i.e. against a single mapped entity,
 
668
        with no additional filtering criterion.  Loading
 
669
        options via :meth:`~.Query.options` may be applied
 
670
        however, and will be used if the object is not
 
671
        yet locally present.
 
672
        
 
673
        A lazy-loading, many-to-one attribute configured
 
674
        by :func:`.relationship`, using a simple
 
675
        foreign-key-to-primary-key criterion, will also use an 
 
676
        operation equivalent to :meth:`~.Query.get` in order to retrieve
 
677
        the target value from the local identity map
 
678
        before querying the database.  See :ref:`loading_toplevel`
 
679
        for further details on relationship loading.
 
680
        
 
681
        :param ident: A scalar or tuple value representing
 
682
         the primary key.   For a composite primary key,
 
683
         the order of identifiers corresponds in most cases
 
684
         to that of the mapped :class:`.Table` object's 
 
685
         primary key columns.  For a :func:`.mapper` that
 
686
         was given the ``primary key`` argument during
 
687
         construction, the order of identifiers corresponds 
 
688
         to the elements present in this collection.
 
689
 
 
690
        :return: The object instance, or ``None``.
 
691
        
651
692
        """
652
693
 
653
694
        # convert composite types to individual args
654
695
        if hasattr(ident, '__composite_values__'):
655
696
            ident = ident.__composite_values__()
656
697
 
657
 
        key = self._only_full_mapper_zero("get"
658
 
                ).identity_key_from_primary_key(ident)
659
 
        return self._get(key, ident)
 
698
        ident = util.to_list(ident)
 
699
 
 
700
        mapper = self._only_full_mapper_zero("get")
 
701
 
 
702
        if len(ident) != len(mapper.primary_key):
 
703
            raise sa_exc.InvalidRequestError(
 
704
            "Incorrect number of values in identifier to formulate "
 
705
            "primary key for query.get(); primary key columns are %s" %
 
706
            ','.join("'%s'" % c for c in mapper.primary_key))
 
707
 
 
708
        key = mapper.identity_key_from_primary_key(ident)
 
709
 
 
710
        if not self._populate_existing and \
 
711
                not mapper.always_refresh and \
 
712
                self._lockmode is None:
 
713
 
 
714
            instance = self._get_from_identity(self.session, key, False)
 
715
            if instance is not None:
 
716
                # reject calls for id in identity map but class
 
717
                # mismatch.
 
718
                if not issubclass(instance.__class__, mapper.class_):
 
719
                    return None
 
720
                return instance
 
721
 
 
722
        return self._load_on_ident(key)
660
723
 
661
724
    @_generative()
662
725
    def correlate(self, *args):
697
760
 
698
761
    @_generative()
699
762
    def populate_existing(self):
700
 
        """Return a :class:`Query` that will expire and refresh all instances 
 
763
        """Return a :class:`.Query` that will expire and refresh all instances 
701
764
        as they are loaded, or reused from the current :class:`.Session`.
702
765
 
703
766
        :meth:`.populate_existing` does not improve behavior when 
709
772
        """
710
773
        self._populate_existing = True
711
774
 
 
775
    @_generative()
 
776
    def _with_invoke_all_eagers(self, value):
 
777
        """Set the 'invoke all eagers' flag which causes joined- and
 
778
        subquery loaders to traverse into already-loaded related objects
 
779
        and collections.
 
780
        
 
781
        Default is that of :attr:`.Query._invoke_all_eagers`.
 
782
 
 
783
        """
 
784
        self._invoke_all_eagers = value
 
785
 
712
786
    def with_parent(self, instance, property=None):
713
787
        """Add filtering criterion that relates the given instance
714
788
        to a child object or collection, using its attribute state 
756
830
        m = _MapperEntity(self, entity)
757
831
        self._setup_aliasizers([m])
758
832
 
 
833
    @_generative()
 
834
    def with_session(self, session):
 
835
        """Return a :class:`Query` that will use the given :class:`.Session`.
 
836
 
 
837
        """
 
838
 
 
839
        self.session = session
 
840
 
759
841
    def from_self(self, *entities):
760
842
        """return a Query that selects from this Query's 
761
843
        SELECT statement.
787
869
        ):
788
870
            self.__dict__.pop(attr, None)
789
871
        self._set_select_from(fromclause)
 
872
 
 
873
        # this enables clause adaptation for non-ORM
 
874
        # expressions.
 
875
        self._orm_only_from_obj_alias = False
 
876
 
790
877
        old_entities = self._entities
791
878
        self._entities = []
792
879
        for e in old_entities:
904
991
    @_generative()
905
992
    def with_hint(self, selectable, text, dialect_name='*'):
906
993
        """Add an indexing hint for the given entity or selectable to 
907
 
        this :class:`Query`.
 
994
        this :class:`.Query`.
908
995
 
909
996
        Functionality is passed straight through to 
910
997
        :meth:`~sqlalchemy.sql.expression.Select.with_hint`, 
911
998
        with the addition that ``selectable`` can be a 
912
 
        :class:`Table`, :class:`Alias`, or ORM entity / mapped class 
 
999
        :class:`.Table`, :class:`.Alias`, or ORM entity / mapped class 
913
1000
        /etc.
914
1001
        """
915
1002
        mapper, selectable, is_aliased_class = _entity_info(selectable)
921
1008
        """ Set non-SQL options which take effect during execution.
922
1009
 
923
1010
        The options are the same as those accepted by 
924
 
        :meth:`sqlalchemy.sql.expression.Executable.execution_options`.
 
1011
        :meth:`.Connection.execution_options`.
925
1012
 
926
1013
        Note that the ``stream_results`` execution option is enabled
927
1014
        automatically if the :meth:`~sqlalchemy.orm.query.Query.yield_per()`
987
1074
 
988
1075
        clauses = [_entity_descriptor(self._joinpoint_zero(), key) == value
989
1076
            for key, value in kwargs.iteritems()]
990
 
 
991
1077
        return self.filter(sql.and_(*clauses))
992
1078
 
993
1079
    @_generative(_no_statement_condition, _no_limit_offset)
994
 
    @util.accepts_a_list_as_starargs(list_deprecation='deprecated')
995
1080
    def order_by(self, *criterion):
996
1081
        """apply one or more ORDER BY criterion to the query and return 
997
1082
        the newly resulting ``Query``
1024
1109
            self._order_by = self._order_by + criterion
1025
1110
 
1026
1111
    @_generative(_no_statement_condition, _no_limit_offset)
1027
 
    @util.accepts_a_list_as_starargs(list_deprecation='deprecated')
1028
1112
    def group_by(self, *criterion):
1029
1113
        """apply one or more GROUP BY criterion to the query and return 
1030
1114
        the newly resulting ``Query``"""
1088
1172
            SELECT * FROM (SELECT * FROM X UNION SELECT * FROM y UNION 
1089
1173
                            SELECT * FROM Z)
1090
1174
 
 
1175
        Note that many database backends do not allow ORDER BY to
 
1176
        be rendered on a query called within UNION, EXCEPT, etc.
 
1177
        To disable all ORDER BY clauses including those configured
 
1178
        on mappers, issue ``query.order_by(None)`` - the resulting
 
1179
        :class:`.Query` object will not render ORDER BY within 
 
1180
        its SELECT statement.
 
1181
 
1091
1182
        """
1092
1183
 
1093
1184
 
1149
1240
                    expression.except_all(*([self]+ list(q)))
1150
1241
                )
1151
1242
 
1152
 
    @util.accepts_a_list_as_starargs(list_deprecation='deprecated')
1153
1243
    def join(self, *props, **kwargs):
1154
1244
        """Create a join against this ``Query`` object's criterion
1155
1245
        and apply generatively, returning the newly resulting ``Query``.
1163
1253
          * a class-mapped attribute, i.e. Houses.rooms.  This will create a
1164
1254
            join from "Houses" table to that of the "rooms" relationship.
1165
1255
 
1166
 
          * a 2-tuple containing a target class or selectable, and an "ON"
1167
 
            clause.  The ON clause can be the property name/ attribute like
1168
 
            above, or a SQL expression.
 
1256
        A two-element form of \*props may also be passed.   In this form,
 
1257
        the first element is a target class or selectable, the second
 
1258
        is a string property name, class-mapped attribute, or clause
 
1259
        construct representing an "ON" clause.   This supersedes the
 
1260
        previous "tuple" calling form - multiple join() calls should
 
1261
        be used for multiple (target, onclause) pairs.
1169
1262
 
1170
1263
        e.g.::
1171
1264
 
1176
1269
            # join the Person entity to an alias of itself,
1177
1270
            # along the "friends" relationship
1178
1271
            PAlias = aliased(Person)
1179
 
            session.query(Person).join((Palias, Person.friends))
 
1272
            session.query(Person).join(Palias, Person.friends)
1180
1273
 
1181
1274
            # join from Houses to the "rooms" attribute on the
1182
1275
            # "Colonials" subclass of Houses, then join to the
1186
1279
            # join from Company entities to the "employees" collection,
1187
1280
            # using "people JOIN engineers" as the target.  Then join
1188
1281
            # to the "computers" collection on the Engineer entity.
1189
 
            session.query(Company).\
1190
 
                        join((people.join(engineers), 'employees'),
1191
 
                        Engineer.computers)
 
1282
            session.query(Company).\\
 
1283
                        join(people.join(engineers), 'employees').\\
 
1284
                        join(Engineer.computers)
1192
1285
 
1193
1286
            # join from Articles to Keywords, using the "keywords" attribute.
1194
1287
            # assume this is a many-to-many relationship.
1196
1289
 
1197
1290
            # same thing, but spelled out entirely explicitly
1198
1291
            # including the association table.
1199
 
            session.query(Article).join(
1200
 
                (article_keywords,
1201
 
                Articles.id==article_keywords.c.article_id),
1202
 
                (Keyword, Keyword.id==article_keywords.c.keyword_id)
1203
 
                )
 
1292
            session.query(Article).join(article_keywords,
 
1293
                        Articles.id==article_keywords.c.article_id).\\
 
1294
                    join(Keyword, 
 
1295
                        Keyword.id==article_keywords.c.keyword_id)
1204
1296
 
1205
1297
        \**kwargs include:
1206
1298
 
1209
1301
            same table. Consider usage of the aliased(SomeClass) construct as
1210
1302
            a more explicit approach to this.
1211
1303
 
1212
 
            from_joinpoint - when joins are specified using string property
1213
 
            names, locate the property from the mapper found in the most
1214
 
            recent previous join() call, instead of from the root entity.
1215
 
 
 
1304
            from_joinpoint - the given join conditions will attempt 
 
1305
            to join from the right endpoint of the most recent join(),
 
1306
            instead of from the query's root entity.  I.e. any chain 
 
1307
            of joins, such as::
 
1308
 
 
1309
                query.join(a, b, c) 
 
1310
 
 
1311
            is equivalent to::
 
1312
 
 
1313
                query.join(a).\\
 
1314
                        join(b, from_joinpoint=True).\\
 
1315
                        join(c, from_joinpoint=True)
 
1316
        
 
1317
        See also :ref:`ormtutorial_joins` in the ORM tutorial.
 
1318
        
1216
1319
        """
1217
1320
        aliased, from_joinpoint = kwargs.pop('aliased', False),\
1218
1321
                                    kwargs.pop('from_joinpoint', False)
1223
1326
                            outerjoin=False, create_aliases=aliased, 
1224
1327
                            from_joinpoint=from_joinpoint)
1225
1328
 
1226
 
    @util.accepts_a_list_as_starargs(list_deprecation='deprecated')
1227
1329
    def outerjoin(self, *props, **kwargs):
1228
1330
        """Create a left outer join against this ``Query`` object's criterion
1229
1331
        and apply generatively, returning the newly resulting ``Query``.
1251
1353
        if not from_joinpoint:
1252
1354
            self._reset_joinpoint()
1253
1355
 
1254
 
        if len(keys) >= 2 and \
1255
 
                isinstance(keys[1], expression.ClauseElement) and \
1256
 
                not isinstance(keys[1], expression.FromClause):
1257
 
            raise sa_exc.ArgumentError(
1258
 
                "You appear to be passing a clause expression as the second "
1259
 
                "argument to query.join().   Did you mean to use the form "
1260
 
                "query.join((target, onclause))?  Note the tuple.")
 
1356
        if len(keys) == 2 and \
 
1357
            isinstance(keys[0], (expression.FromClause, 
 
1358
                                    type, AliasedClass)) and \
 
1359
            isinstance(keys[1], (basestring, expression.ClauseElement, 
 
1360
                                        interfaces.PropComparator)):
 
1361
            # detect 2-arg form of join and
 
1362
            # convert to a tuple.
 
1363
            keys = (keys,)
1261
1364
 
1262
1365
        for arg1 in util.to_list(keys):
1263
1366
            if isinstance(arg1, tuple):
 
1367
                # "tuple" form of join, multiple
 
1368
                # tuples are accepted as well.   The simpler
 
1369
                # "2-arg" form is preferred.  May deprecate 
 
1370
                # the "tuple" usage.
1264
1371
                arg1, arg2 = arg1
1265
1372
            else:
1266
1373
                arg2 = None
1416
1523
        # until reset_joinpoint() is called.
1417
1524
        if need_adapter:
1418
1525
            self._filter_aliases = ORMAdapter(right,
1419
 
                        equivalents=right_mapper._equivalent_columns,
 
1526
                        equivalents=right_mapper and right_mapper._equivalent_columns or {},
1420
1527
                        chain_to=self._filter_aliases)
1421
1528
 
1422
1529
        # if the onclause is a ClauseElement, adapt it with any 
1428
1535
        # which is intended to wrap a the right side in a subquery,
1429
1536
        # ensure that columns retrieved from this target in the result
1430
1537
        # set are also adapted.
1431
 
        if aliased_entity:
 
1538
        if aliased_entity and not create_aliases:
1432
1539
            self.__mapper_loads_polymorphically_with(
1433
1540
                        right_mapper,
1434
1541
                        ORMAdapter(
1595
1702
        self._offset = offset
1596
1703
 
1597
1704
    @_generative(_no_statement_condition)
1598
 
    def distinct(self):
 
1705
    def distinct(self, *criterion):
1599
1706
        """Apply a ``DISTINCT`` to the query and return the newly resulting
1600
1707
        ``Query``.
1601
1708
 
 
1709
        :param \*expr: optional column expressions.  When present,
 
1710
         the Postgresql dialect will render a ``DISTINCT ON (<expressions>>)``
 
1711
         construct.
 
1712
 
1602
1713
        """
1603
 
        self._distinct = True
 
1714
        if not criterion: 
 
1715
            self._distinct = True 
 
1716
        else: 
 
1717
            criterion = self._adapt_col_list(criterion)
 
1718
            if isinstance(self._distinct, list):
 
1719
                self._distinct += criterion
 
1720
            else: 
 
1721
                self._distinct = criterion 
1604
1722
 
1605
1723
    def all(self):
1606
1724
        """Return the results represented by this ``Query`` as a list.
1627
1745
 
1628
1746
        if not isinstance(statement, 
1629
1747
                            (expression._TextClause,
1630
 
                            expression._SelectBaseMixin)):
 
1748
                            expression._SelectBase)):
1631
1749
            raise sa_exc.ArgumentError(
1632
1750
                            "from_statement accepts text(), select(), "
1633
1751
                            "and union() objects only.")
1720
1838
            self.session._autoflush()
1721
1839
        return self._execute_and_instances(context)
1722
1840
 
 
1841
    def _connection_from_session(self, **kw):
 
1842
        conn = self.session.connection(
 
1843
                        **kw)
 
1844
        if self._execution_options:
 
1845
            conn = conn.execution_options(**self._execution_options)
 
1846
        return conn
 
1847
 
1723
1848
    def _execute_and_instances(self, querycontext):
1724
 
        result = self.session.execute(
1725
 
                        querycontext.statement, params=self._params,
1726
 
                        mapper=self._mapper_zero_or_none())
 
1849
        conn = self._connection_from_session(
 
1850
                        mapper = self._mapper_zero_or_none(),
 
1851
                        clause = querycontext.statement,
 
1852
                        close_with_result=True)
 
1853
 
 
1854
        result = conn.execute(querycontext.statement, self._params)
1727
1855
        return self.instances(result, querycontext)
1728
1856
 
1729
1857
    @property
1730
1858
    def column_descriptions(self):
1731
1859
        """Return metadata about the columns which would be 
1732
 
        returned by this :class:`Query`.
 
1860
        returned by this :class:`.Query`.
1733
1861
 
1734
1862
        Format is a list of dictionaries::
1735
1863
 
1737
1865
            q = sess.query(User, User.id, user_alias)
1738
1866
 
1739
1867
            # this expression:
1740
 
            q.columns
 
1868
            q.column_descriptions
1741
1869
 
1742
1870
            # would return:
1743
1871
            [
1790
1918
 
1791
1919
        context.runid = _new_runid()
1792
1920
 
1793
 
        filtered = bool(list(self._mapper_entities))
 
1921
        filter_fns = [ent.filter_fn
 
1922
                    for ent in self._entities]
 
1923
        filtered = id in filter_fns
 
1924
 
1794
1925
        single_entity = filtered and len(self._entities) == 1
1795
1926
 
1796
1927
        if filtered:
1797
1928
            if single_entity:
1798
 
                filter = lambda x: util.unique_list(x, util.IdentitySet)
 
1929
                filter_fn = id
1799
1930
            else:
1800
 
                filter = util.unique_list
1801
 
        else:
1802
 
            filter = None
 
1931
                def filter_fn(row):
 
1932
                    return tuple(fn(x) for x, fn in zip(row, filter_fns))
1803
1933
 
1804
1934
        custom_rows = single_entity and \
1805
 
                        'append_result' in self._entities[0].extension
 
1935
                        self._entities[0].mapper.dispatch.append_result
1806
1936
 
1807
1937
        (process, labels) = \
1808
1938
                    zip(*[
1832
1962
                rows = [util.NamedTuple([proc(row, None) for proc in process],
1833
1963
                                        labels) for row in fetch]
1834
1964
 
1835
 
            if filter:
1836
 
                rows = filter(rows)
 
1965
            if filtered:
 
1966
                rows = util.unique_list(rows, filter_fn)
1837
1967
 
1838
1968
            if context.refresh_state and self._only_load_props \
1839
1969
                        and context.refresh_state in context.progress:
1905
2035
        finally:
1906
2036
            session.autoflush = autoflush
1907
2037
 
1908
 
 
1909
 
    def _get(self, key=None, ident=None, refresh_state=None, lockmode=None,
1910
 
                                        only_load_props=None, passive=None):
 
2038
    @classmethod
 
2039
    def _get_from_identity(cls, session, key, passive):
 
2040
        """Look up the given key in the given session's identity map, 
 
2041
        check the object for expired state if found.
 
2042
 
 
2043
        """
 
2044
        instance = session.identity_map.get(key)
 
2045
        if instance:
 
2046
 
 
2047
            state = attributes.instance_state(instance)
 
2048
 
 
2049
            # expired - ensure it still exists
 
2050
            if state.expired:
 
2051
                if passive is attributes.PASSIVE_NO_FETCH:
 
2052
                    # TODO: no coverage here
 
2053
                    return attributes.PASSIVE_NO_RESULT
 
2054
                elif passive is attributes.PASSIVE_NO_FETCH_RELATED:
 
2055
                    # this mode is used within a flush and the instance's
 
2056
                    # expired state will be checked soon enough, if necessary
 
2057
                    return instance
 
2058
                try:
 
2059
                    state(passive)
 
2060
                except orm_exc.ObjectDeletedError:
 
2061
                    session._remove_newly_deleted(state)
 
2062
                    return None
 
2063
            return instance
 
2064
        else:
 
2065
            return None
 
2066
 
 
2067
    def _load_on_ident(self, key, refresh_state=None, lockmode=None,
 
2068
                                        only_load_props=None):
 
2069
        """Load the given identity key from the database."""
 
2070
 
1911
2071
        lockmode = lockmode or self._lockmode
1912
2072
 
1913
 
        mapper = self._mapper_zero()
1914
 
        if not self._populate_existing and \
1915
 
                not refresh_state and \
1916
 
                not mapper.always_refresh and \
1917
 
                lockmode is None:
1918
 
            instance = self.session.identity_map.get(key)
1919
 
            if instance:
1920
 
                # item present in identity map with a different class
1921
 
                if not issubclass(instance.__class__, mapper.class_):
1922
 
                    return None
1923
 
 
1924
 
                state = attributes.instance_state(instance)
1925
 
 
1926
 
                # expired - ensure it still exists
1927
 
                if state.expired:
1928
 
                    if passive is attributes.PASSIVE_NO_FETCH:
1929
 
                        return attributes.PASSIVE_NO_RESULT
1930
 
                    try:
1931
 
                        state()
1932
 
                    except orm_exc.ObjectDeletedError:
1933
 
                        self.session._remove_newly_deleted(state)
1934
 
                        return None
1935
 
                return instance
1936
 
            elif passive is attributes.PASSIVE_NO_FETCH:
1937
 
                return attributes.PASSIVE_NO_RESULT
1938
 
 
1939
 
        if ident is None:
1940
 
            if key is not None:
1941
 
                ident = key[1]
 
2073
        if key is not None:
 
2074
            ident = key[1]
1942
2075
        else:
1943
 
            ident = util.to_list(ident)
 
2076
            ident = None
1944
2077
 
1945
2078
        if refresh_state is None:
1946
2079
            q = self._clone()
1949
2082
            q = self._clone()
1950
2083
 
1951
2084
        if ident is not None:
1952
 
            if len(ident) != len(mapper.primary_key):
1953
 
                raise sa_exc.InvalidRequestError(
1954
 
                "Incorrect number of values in identifier to formulate "
1955
 
                "primary key for query.get(); primary key columns are %s" %
1956
 
                ','.join("'%s'" % c for c in mapper.primary_key))
 
2085
            mapper = self._mapper_zero()
1957
2086
 
1958
2087
            (_get_clause, _get_params) = mapper._get_clause
1959
2088
 
2010
2139
 
2011
2140
    def count(self):
2012
2141
        """Return a count of rows this Query would return.
2013
 
 
2014
 
        For simple entity queries, count() issues
2015
 
        a SELECT COUNT, and will specifically count the primary
2016
 
        key column of the first entity only.  If the query uses 
2017
 
        LIMIT, OFFSET, or DISTINCT, count() will wrap the statement 
2018
 
        generated by this Query in a subquery, from which a SELECT COUNT
2019
 
        is issued, so that the contract of "how many rows
2020
 
        would be returned?" is honored.
2021
 
 
2022
 
        For queries that request specific columns or expressions, 
2023
 
        count() again makes no assumptions about those expressions
2024
 
        and will wrap everything in a subquery.  Therefore,
2025
 
        ``Query.count()`` is usually not what you want in this case.
2026
 
        To count specific columns, often in conjunction with 
2027
 
        GROUP BY, use ``func.count()`` as an individual column expression
2028
 
        instead of ``Query.count()``.  See the ORM tutorial
2029
 
        for an example.
2030
 
 
 
2142
        
 
2143
        This generates the SQL for this Query as follows::
 
2144
        
 
2145
            SELECT count(1) AS count_1 FROM (
 
2146
                SELECT <rest of query follows...>
 
2147
            ) AS anon_1
 
2148
 
 
2149
        Note the above scheme is newly refined in 0.7 
 
2150
        (as of 0.7b3).
 
2151
        
 
2152
        For fine grained control over specific columns 
 
2153
        to count, to skip the usage of a subquery or
 
2154
        otherwise control of the FROM clause,
 
2155
        or to use other aggregate functions,
 
2156
        use :attr:`.func` expressions in conjunction
 
2157
        with :meth:`~.Session.query`, i.e.::
 
2158
        
 
2159
            from sqlalchemy import func
 
2160
            
 
2161
            # count User records, without
 
2162
            # using a subquery.
 
2163
            session.query(func.count(User.id))
 
2164
                        
 
2165
            # return count of user "id" grouped
 
2166
            # by "name"
 
2167
            session.query(func.count(User.id)).\\
 
2168
                    group_by(User.name)
 
2169
 
 
2170
            from sqlalchemy import distinct
 
2171
            
 
2172
            # count distinct "name" values
 
2173
            session.query(func.count(distinct(User.name)))
 
2174
            
2031
2175
        """
2032
 
        should_nest = [self._should_nest_selectable]
2033
 
        def ent_cols(ent):
2034
 
            if isinstance(ent, _MapperEntity):
2035
 
                return ent.mapper.primary_key
2036
 
            else:
2037
 
                should_nest[0] = True
2038
 
                return [ent.column]
2039
 
 
2040
 
        return self._col_aggregate(sql.literal_column('1'), sql.func.count,
2041
 
            nested_cols=chain(*[ent_cols(ent) for ent in self._entities]),
2042
 
            should_nest = should_nest[0]
2043
 
        )
2044
 
 
2045
 
    def _col_aggregate(self, col, func, nested_cols=None, should_nest=False):
2046
 
        context = QueryContext(self)
2047
 
 
2048
 
        for entity in self._entities:
2049
 
            entity.setup_context(self, context)
2050
 
 
2051
 
        if context.from_clause:
2052
 
            from_obj = list(context.from_clause)
2053
 
        else:
2054
 
            from_obj = context.froms
2055
 
 
2056
 
        if self._enable_single_crit:
2057
 
            self._adjust_for_single_inheritance(context)
2058
 
 
2059
 
        whereclause  = context.whereclause
2060
 
 
2061
 
        if should_nest:
2062
 
            if not nested_cols:
2063
 
                nested_cols = [col]
2064
 
            else:
2065
 
                nested_cols = list(nested_cols)
2066
 
            s = sql.select(nested_cols, whereclause, 
2067
 
                        from_obj=from_obj, use_labels=True,
2068
 
                        **self._select_args)
2069
 
            s = s.alias()
2070
 
            s = sql.select(
2071
 
                [func(s.corresponding_column(col) or col)]).select_from(s)
2072
 
        else:
2073
 
            s = sql.select([func(col)], whereclause, from_obj=from_obj,
2074
 
            **self._select_args)
2075
 
 
2076
 
        if self._autoflush and not self._populate_existing:
2077
 
            self.session._autoflush()
2078
 
        return self.session.scalar(s, params=self._params,
2079
 
            mapper=self._mapper_zero())
 
2176
        col = sql.func.count(sql.literal_column('*'))
 
2177
        return self.from_self(col).scalar()
2080
2178
 
2081
2179
    def delete(self, synchronize_session='evaluate'):
2082
2180
        """Perform a bulk delete query.
2144
2242
 
2145
2243
        session = self.session
2146
2244
 
 
2245
        if self._autoflush:
 
2246
            session._autoflush()
 
2247
 
2147
2248
        if synchronize_session == 'evaluate':
2148
2249
            try:
2149
2250
                evaluator_compiler = evaluator.EvaluatorCompiler()
2160
2261
                    "Specify 'fetch' or False for the synchronize_session "
2161
2262
                    "parameter.")
2162
2263
 
2163
 
        delete_stmt = sql.delete(primary_table, context.whereclause)
2164
 
 
2165
 
        if synchronize_session == 'fetch':
2166
 
            #TODO: use RETURNING when available
2167
 
            select_stmt = context.statement.with_only_columns(
2168
 
                                                primary_table.primary_key)
2169
 
            matched_rows = session.execute(
2170
 
                                        select_stmt,
2171
 
                                        params=self._params).fetchall()
2172
 
 
2173
 
        if self._autoflush:
2174
 
            session._autoflush()
2175
 
        result = session.execute(delete_stmt, params=self._params)
2176
 
 
2177
 
        if synchronize_session == 'evaluate':
2178
2264
            target_cls = self._mapper_zero().class_
2179
2265
 
2180
2266
            #TODO: detect when the where clause is a trivial primary key match
2183
2269
                                session.identity_map.iteritems()
2184
2270
                                if issubclass(cls, target_cls) and
2185
2271
                                eval_condition(obj)]
 
2272
 
 
2273
        elif synchronize_session == 'fetch':
 
2274
            #TODO: use RETURNING when available
 
2275
            select_stmt = context.statement.with_only_columns(
 
2276
                                                primary_table.primary_key)
 
2277
            matched_rows = session.execute(
 
2278
                                        select_stmt,
 
2279
                                        params=self._params).fetchall()
 
2280
 
 
2281
        delete_stmt = sql.delete(primary_table, context.whereclause)
 
2282
 
 
2283
        result = session.execute(delete_stmt, params=self._params)
 
2284
 
 
2285
        if synchronize_session == 'evaluate':
2186
2286
            for obj in objs_to_expunge:
2187
2287
                session._remove_newly_deleted(attributes.instance_state(obj))
2188
2288
        elif synchronize_session == 'fetch':
2197
2297
                        )
2198
2298
                    )
2199
2299
 
2200
 
        for ext in session.extensions:
2201
 
            ext.after_bulk_delete(session, self, context, result)
 
2300
        session.dispatch.after_bulk_delete(session, self, context, result)
2202
2301
 
2203
2302
        return result.rowcount
2204
2303
 
2278
2377
 
2279
2378
        session = self.session
2280
2379
 
 
2380
        if self._autoflush:
 
2381
            session._autoflush()
 
2382
 
2281
2383
        if synchronize_session == 'evaluate':
2282
2384
            try:
2283
2385
                evaluator_compiler = evaluator.EvaluatorCompiler()
2298
2400
                        "Could not evaluate current criteria in Python. "
2299
2401
                        "Specify 'fetch' or False for the "
2300
2402
                        "synchronize_session parameter.")
2301
 
 
2302
 
        update_stmt = sql.update(primary_table, context.whereclause, values)
2303
 
 
2304
 
        if synchronize_session == 'fetch':
2305
 
            select_stmt = context.statement.with_only_columns(
2306
 
                                                primary_table.primary_key)
2307
 
            matched_rows = session.execute(
2308
 
                                        select_stmt,
2309
 
                                        params=self._params).fetchall()
2310
 
 
2311
 
        if self._autoflush:
2312
 
            session._autoflush()
2313
 
        result = session.execute(update_stmt, params=self._params)
2314
 
 
2315
 
        if synchronize_session == 'evaluate':
2316
2403
            target_cls = self._mapper_zero().class_
2317
 
 
 
2404
            matched_objects = []
2318
2405
            for (cls, pk),obj in session.identity_map.iteritems():
2319
2406
                evaluated_keys = value_evaluators.keys()
2320
2407
 
2321
2408
                if issubclass(cls, target_cls) and eval_condition(obj):
2322
 
                    state, dict_ = attributes.instance_state(obj),\
2323
 
                                            attributes.instance_dict(obj)
2324
 
 
2325
 
                    # only evaluate unmodified attributes
2326
 
                    to_evaluate = state.unmodified.intersection(
2327
 
                                                            evaluated_keys)
2328
 
                    for key in to_evaluate:
2329
 
                        dict_[key] = value_evaluators[key](obj)
2330
 
 
2331
 
                    state.commit(dict_, list(to_evaluate))
2332
 
 
2333
 
                    # expire attributes with pending changes 
2334
 
                    # (there was no autoflush, so they are overwritten)
2335
 
                    state.expire_attributes(dict_,
2336
 
                                    set(evaluated_keys).
2337
 
                                        difference(to_evaluate))
 
2409
                    matched_objects.append(obj)
 
2410
 
 
2411
        elif synchronize_session == 'fetch':
 
2412
            select_stmt = context.statement.with_only_columns(
 
2413
                                                primary_table.primary_key)
 
2414
            matched_rows = session.execute(
 
2415
                                        select_stmt,
 
2416
                                        params=self._params).fetchall()
 
2417
 
 
2418
        update_stmt = sql.update(primary_table, context.whereclause, values)
 
2419
 
 
2420
        result = session.execute(update_stmt, params=self._params)
 
2421
 
 
2422
        if synchronize_session == 'evaluate':
 
2423
            target_cls = self._mapper_zero().class_
 
2424
 
 
2425
            for obj in matched_objects:
 
2426
                state, dict_ = attributes.instance_state(obj),\
 
2427
                                        attributes.instance_dict(obj)
 
2428
 
 
2429
                # only evaluate unmodified attributes
 
2430
                to_evaluate = state.unmodified.intersection(
 
2431
                                                        evaluated_keys)
 
2432
                for key in to_evaluate:
 
2433
                    dict_[key] = value_evaluators[key](obj)
 
2434
 
 
2435
                state.commit(dict_, list(to_evaluate))
 
2436
 
 
2437
                # expire attributes with pending changes 
 
2438
                # (there was no autoflush, so they are overwritten)
 
2439
                state.expire_attributes(dict_,
 
2440
                                set(evaluated_keys).
 
2441
                                    difference(to_evaluate))
2338
2442
 
2339
2443
        elif synchronize_session == 'fetch':
2340
2444
            target_mapper = self._mapper_zero()
2348
2452
                                [_attr_as_key(k) for k in values]
2349
2453
                                )
2350
2454
 
2351
 
        for ext in session.extensions:
2352
 
            ext.after_bulk_update(session, self, context, result)
 
2455
        session.dispatch.after_bulk_update(session, self, context, result)
2353
2456
 
2354
2457
        return result.rowcount
2355
2458
 
2411
2514
            if context.order_by:
2412
2515
                order_by_col_expr = list(
2413
2516
                                        chain(*[
2414
 
                                            sql_util.find_columns(o) 
 
2517
                                            sql_util.unwrap_order_by(o)
2415
2518
                                            for o in context.order_by
2416
2519
                                        ])
2417
2520
                                    )
2425
2528
                        from_obj=froms,
2426
2529
                        use_labels=labels,
2427
2530
                        correlate=False,
 
2531
                        # TODO: this order_by is only needed if 
 
2532
                        # LIMIT/OFFSET is present in self._select_args,
 
2533
                        # else the application on the outside is enough
2428
2534
                        order_by=context.order_by,
2429
2535
                        **self._select_args
2430
2536
                    )
2446
2552
                                for_update=for_update, 
2447
2553
                                use_labels=labels)
2448
2554
 
2449
 
            if self._execution_options:
2450
 
                statement = statement.execution_options(
2451
 
                                                **self._execution_options)
2452
 
 
2453
2555
            from_clause = inner
2454
2556
            for eager_join in eager_joins:
2455
2557
                # EagerLoader places a 'stop_on' attribute on the join,
2462
2564
            statement.append_from(from_clause)
2463
2565
 
2464
2566
            if context.order_by:
2465
 
                    statement.append_order_by(
2466
 
                        *context.adapter.copy_and_process(
2467
 
                            context.order_by
2468
 
                        )
 
2567
                statement.append_order_by(
 
2568
                    *context.adapter.copy_and_process(
 
2569
                        context.order_by
2469
2570
                    )
 
2571
                )
2470
2572
 
2471
2573
            statement.append_order_by(*context.eager_order_by)
2472
2574
        else:
2476
2578
            if self._distinct and context.order_by:
2477
2579
                order_by_col_expr = list(
2478
2580
                                        chain(*[
2479
 
                                            sql_util.find_columns(o) 
 
2581
                                            sql_util.unwrap_order_by(o) 
2480
2582
                                            for o in context.order_by
2481
2583
                                        ])
2482
2584
                                    )
2499
2601
            for hint in self._with_hints:
2500
2602
                statement = statement.with_hint(*hint)
2501
2603
 
2502
 
            if self._execution_options:
2503
 
                statement = statement.execution_options(
2504
 
                                            **self._execution_options)
2505
 
 
2506
2604
            if self._correlate:
2507
2605
                statement = statement.correlate(*self._correlate)
2508
2606
 
2568
2666
    def setup_entity(self, entity, mapper, adapter, 
2569
2667
                        from_obj, is_aliased_class, with_polymorphic):
2570
2668
        self.mapper = mapper
2571
 
        self.extension = self.mapper.extension
2572
2669
        self.adapter = adapter
2573
2670
        self.selectable  = from_obj
2574
2671
        self._with_polymorphic = with_polymorphic
2575
2672
        self._polymorphic_discriminator = None
2576
2673
        self.is_aliased_class = is_aliased_class
2577
2674
        if is_aliased_class:
2578
 
            self.path_entity = self.entity = self.entity_zero = entity
2579
 
            self._label_name = self.entity._sa_label_name
 
2675
            self.path_entity = self.entity_zero = entity
 
2676
            self._path = (entity,)
 
2677
            self._label_name = self.entity_zero._sa_label_name
 
2678
            self._reduced_path = (self.path_entity, )
2580
2679
        else:
2581
2680
            self.path_entity = mapper
2582
 
            self.entity = self.entity_zero = mapper
 
2681
            self._path = (mapper,)
 
2682
            self._reduced_path = (mapper.base_mapper, )
 
2683
            self.entity_zero = mapper
2583
2684
            self._label_name = self.mapper.class_.__name__
2584
2685
 
 
2686
 
2585
2687
    def set_with_polymorphic(self, query, cls_or_mappers, 
2586
2688
                                selectable, discriminator):
2587
2689
        if cls_or_mappers is None:
2599
2701
            self.selectable = from_obj
2600
2702
            self.adapter = query._get_polymorphic_adapter(self, from_obj)
2601
2703
 
 
2704
    filter_fn = id
 
2705
 
2602
2706
    @property
2603
2707
    def type(self):
2604
2708
        return self.mapper.class_
2650
2754
        if self.primary_entity:
2651
2755
            _instance = self.mapper._instance_processor(
2652
2756
                                context, 
2653
 
                                (self.path_entity,), 
 
2757
                                self._path,
 
2758
                                self._reduced_path,
2654
2759
                                adapter,
2655
 
                                extension=self.extension,
2656
2760
                                only_load_props=query._only_load_props,
2657
2761
                                refresh_state=context.refresh_state,
2658
2762
                                polymorphic_discriminator=
2661
2765
        else:
2662
2766
            _instance = self.mapper._instance_processor(
2663
2767
                                context, 
2664
 
                                (self.path_entity,), 
 
2768
                                self._path,
 
2769
                                self._reduced_path,
2665
2770
                                adapter,
2666
2771
                                polymorphic_discriminator=
2667
2772
                                    self._polymorphic_discriminator)
2689
2794
                self._with_polymorphic)
2690
2795
        else:
2691
2796
            poly_properties = self.mapper._polymorphic_properties
 
2797
 
2692
2798
        for value in poly_properties:
2693
2799
            if query._only_load_props and \
2694
2800
                    value.key not in query._only_load_props:
2696
2802
            value.setup(
2697
2803
                context,
2698
2804
                self,
2699
 
                (self.path_entity,),
 
2805
                self._path,
 
2806
                self._reduced_path,
2700
2807
                adapter,
2701
2808
                only_load_props=query._only_load_props,
2702
2809
                column_collection=context.primary_columns
2721
2828
        if isinstance(column, basestring):
2722
2829
            column = sql.literal_column(column)
2723
2830
            self._label_name = column.name
2724
 
        elif isinstance(column, attributes.QueryableAttribute):
 
2831
        elif isinstance(column, (
 
2832
                                    attributes.QueryableAttribute,
 
2833
                                    interfaces.PropComparator
 
2834
                                )):
2725
2835
            self._label_name = column.key
2726
2836
            column = column.__clause_element__()
2727
2837
        else:
2780
2890
    def type(self):
2781
2891
        return self.column.type
2782
2892
 
 
2893
    def filter_fn(self, item):
 
2894
        return item
 
2895
 
2783
2896
    def adapt_to_selectable(self, query, sel):
2784
2897
        c = _ColumnEntity(query, sel.corresponding_column(self.column))
 
2898
        c._label_name = self._label_name 
2785
2899
        c.entity_zero = self.entity_zero
2786
2900
        c.entities = self.entities
2787
2901
 
2788
2902
    def setup_entity(self, entity, mapper, adapter, from_obj,
2789
2903
                                is_aliased_class, with_polymorphic):
2790
 
        self.selectable = from_obj
 
2904
        if 'selectable' not in self.__dict__: 
 
2905
            self.selectable = from_obj
2791
2906
        self.froms.add(from_obj)
2792
2907
 
2793
2908
    def corresponds_to(self, entity):
2833
2948
    def __init__(self, query):
2834
2949
 
2835
2950
        if query._statement is not None:
2836
 
            if isinstance(query._statement, expression._SelectBaseMixin) and \
 
2951
            if isinstance(query._statement, expression._SelectBase) and \
2837
2952
                                not query._statement.use_labels:
2838
2953
                self.statement = query._statement.apply_labels()
2839
2954
            else:
2847
2962
        self.query = query
2848
2963
        self.session = query.session
2849
2964
        self.populate_existing = query._populate_existing
 
2965
        self.invoke_all_eagers = query._invoke_all_eagers
2850
2966
        self.version_check = query._version_check
2851
2967
        self.refresh_state = query._refresh_state
2852
2968
        self.primary_columns = []