root/galaxy-central/eggs/SQLAlchemy-0.5.6_dev_r6498-py2.6.egg/sqlalchemy/orm/strategies.py

リビジョン 3, 36.3 KB (コミッタ: kohda, 14 年 前)

Install Unix tools  http://hannonlab.cshl.edu/galaxy_unix_tools/galaxy.html

行番号 
1# strategies.py
2# Copyright (C) 2005, 2006, 2007, 2008, 2009 Michael Bayer mike_mp@zzzcomputing.com
3#
4# This module is part of SQLAlchemy and is released under
5# the MIT License: http://www.opensource.org/licenses/mit-license.php
6
7"""sqlalchemy.orm.interfaces.LoaderStrategy implementations, and related MapperOptions."""
8
9import sqlalchemy.exceptions as sa_exc
10from sqlalchemy import sql, util, log
11from sqlalchemy.sql import util as sql_util
12from sqlalchemy.sql import visitors, expression, operators
13from sqlalchemy.orm import mapper, attributes, interfaces
14from sqlalchemy.orm.interfaces import (
15    LoaderStrategy, StrategizedOption, MapperOption, PropertyOption,
16    serialize_path, deserialize_path, StrategizedProperty
17    )
18from sqlalchemy.orm import session as sessionlib
19from sqlalchemy.orm import util as mapperutil
20
21def _register_attribute(strategy, mapper, useobject,
22        compare_function=None,
23        typecallable=None,
24        copy_function=None,
25        mutable_scalars=False,
26        uselist=False,
27        callable_=None,
28        proxy_property=None,
29        active_history=False,
30        impl_class=None,
31        **kw       
32):
33
34    prop = strategy.parent_property
35    attribute_ext = list(util.to_list(prop.extension, default=[]))
36       
37    if useobject and prop.single_parent:
38        attribute_ext.append(_SingleParentValidator(prop))
39
40    if getattr(prop, 'backref', None):
41        attribute_ext.append(prop.backref.extension)
42   
43    if prop.key in prop.parent._validators:
44        attribute_ext.append(mapperutil.Validator(prop.key, prop.parent._validators[prop.key]))
45   
46    if useobject:
47        attribute_ext.append(sessionlib.UOWEventHandler(prop.key))
48   
49    for m in mapper.polymorphic_iterator():
50        if prop is m._props.get(prop.key):
51           
52            attributes.register_attribute_impl(
53                m.class_,
54                prop.key,
55                parent_token=prop,
56                mutable_scalars=mutable_scalars,
57                uselist=uselist,
58                copy_function=copy_function,
59                compare_function=compare_function,
60                useobject=useobject,
61                extension=attribute_ext,
62                trackparent=useobject,
63                typecallable=typecallable,
64                callable_=callable_,
65                active_history=active_history,
66                impl_class=impl_class,
67                **kw
68                )
69
70class UninstrumentedColumnLoader(LoaderStrategy):
71    """Represent the strategy for a MapperProperty that doesn't instrument the class.
72   
73    The polymorphic_on argument of mapper() often results in this,
74    if the argument is against the with_polymorphic selectable.
75   
76    """
77    def init(self):
78        self.columns = self.parent_property.columns
79
80    def setup_query(self, context, entity, path, adapter, column_collection=None, **kwargs):
81        for c in self.columns:
82            if adapter:
83                c = adapter.columns[c]
84            column_collection.append(c)
85
86    def create_row_processor(self, selectcontext, path, mapper, row, adapter):
87        return (None, None)
88
89class ColumnLoader(LoaderStrategy):
90    """Strategize the loading of a plain column-based MapperProperty."""
91   
92    def init(self):
93        self.columns = self.parent_property.columns
94        self.is_composite = hasattr(self.parent_property, 'composite_class')
95       
96    def setup_query(self, context, entity, path, adapter, column_collection=None, **kwargs):
97        for c in self.columns:
98            if adapter:
99                c = adapter.columns[c]
100            column_collection.append(c)
101       
102    def init_class_attribute(self, mapper):
103        self.is_class_level = True
104        coltype = self.columns[0].type
105        active_history = self.columns[0].primary_key  # TODO: check all columns ?  check for foreign Key as well?
106
107        _register_attribute(self, mapper, useobject=False,
108            compare_function=coltype.compare_values,
109            copy_function=coltype.copy_value,
110            mutable_scalars=self.columns[0].type.is_mutable(),
111            active_history = active_history
112       )
113       
114    def create_row_processor(self, selectcontext, path, mapper, row, adapter):
115        key, col = self.key, self.columns[0]
116        if adapter:
117            col = adapter.columns[col]
118        if col is not None and col in row:
119            def new_execute(state, dict_, row, **flags):
120                dict_[key] = row[col]
121               
122            if self._should_log_debug:
123                new_execute = self.debug_callable(new_execute, self.logger,
124                    "%s returning active column fetcher" % self,
125                    lambda state, dict_, row, **flags: "%s populating %s" % \
126                                                      (self,
127                                                       mapperutil.state_attribute_str(state, key))
128                )
129            return (new_execute, None)
130        else:
131            def new_execute(state, dict_, row, isnew, **flags):
132                if isnew:
133                    state.expire_attributes([key])
134            if self._should_log_debug:
135                self.logger.debug("%s deferring load" % self)
136            return (new_execute, None)
137
138log.class_logger(ColumnLoader)
139
140class CompositeColumnLoader(ColumnLoader):
141    """Strategize the loading of a composite column-based MapperProperty."""
142
143    def init_class_attribute(self, mapper):
144        self.is_class_level = True
145        self.logger.info("%s register managed composite attribute" % self)
146
147        def copy(obj):
148            if obj is None:
149                return None
150            return self.parent_property.composite_class(*obj.__composite_values__())
151           
152        def compare(a, b):
153            if a is None or b is None:
154                return a is b
155               
156            for col, aprop, bprop in zip(self.columns,
157                                         a.__composite_values__(),
158                                         b.__composite_values__()):
159                if not col.type.compare_values(aprop, bprop):
160                    return False
161            else:
162                return True
163
164        _register_attribute(self, mapper, useobject=False,
165            compare_function=compare,
166            copy_function=copy,
167            mutable_scalars=True
168            #active_history ?
169        )
170
171    def create_row_processor(self, selectcontext, path, mapper, row, adapter):
172        key, columns, composite_class = self.key, self.columns, self.parent_property.composite_class
173        if adapter:
174            columns = [adapter.columns[c] for c in columns]
175        for c in columns:
176            if c not in row:
177                def new_execute(state, dict_, row, isnew, **flags):
178                    if isnew:
179                        state.expire_attributes([key])
180                if self._should_log_debug:
181                    self.logger.debug("%s deferring load" % self)
182                return (new_execute, None)
183        else:
184            def new_execute(state, dict_, row, **flags):
185                dict_[key] = composite_class(*[row[c] for c in columns])
186
187            if self._should_log_debug:
188                new_execute = self.debug_callable(new_execute, self.logger,
189                    "%s returning active composite column fetcher" % self,
190                    lambda state, dict_, row, **flags: "populating %s" % \
191                                                      (mapperutil.state_attribute_str(state, key))
192                )
193
194            return (new_execute, None)
195
196log.class_logger(CompositeColumnLoader)
197   
198class DeferredColumnLoader(LoaderStrategy):
199    """Strategize the loading of a deferred column-based MapperProperty."""
200   
201    def create_row_processor(self, selectcontext, path, mapper, row, adapter):
202        col = self.columns[0]
203        if adapter:
204            col = adapter.columns[col]
205        if col in row:
206            return self.parent_property._get_strategy(ColumnLoader).create_row_processor(selectcontext, path, mapper, row, adapter)
207
208        elif not self.is_class_level:
209            def new_execute(state, dict_, row, **flags):
210                state.set_callable(self.key, LoadDeferredColumns(state, self.key))
211        else:
212            def new_execute(state, dict_, row, **flags):
213                # reset state on the key so that deferred callables
214                # fire off on next access.
215                state.reset(self.key, dict_)
216
217        if self._should_log_debug:
218            new_execute = self.debug_callable(new_execute, self.logger, None,
219                lambda state, dict_, row, **flags: "set deferred callable on %s" % \
220                                                  mapperutil.state_attribute_str(state, self.key)
221            )
222        return (new_execute, None)
223
224    def init(self):
225        if hasattr(self.parent_property, 'composite_class'):
226            raise NotImplementedError("Deferred loading for composite types not implemented yet")
227        self.columns = self.parent_property.columns
228        self.group = self.parent_property.group
229
230    def init_class_attribute(self, mapper):
231        self.is_class_level = True
232   
233        _register_attribute(self, mapper, useobject=False,
234             compare_function=self.columns[0].type.compare_values,
235             copy_function=self.columns[0].type.copy_value,
236             mutable_scalars=self.columns[0].type.is_mutable(),
237             callable_=self._class_level_loader,
238             dont_expire_missing=True
239        )
240
241    def setup_query(self, context, entity, path, adapter, only_load_props=None, **kwargs):
242        if \
243            (self.group is not None and context.attributes.get(('undefer', self.group), False)) or \
244            (only_load_props and self.key in only_load_props):
245           
246            self.parent_property._get_strategy(ColumnLoader).setup_query(context, entity, path, adapter, **kwargs)
247   
248    def _class_level_loader(self, state):
249        if not mapperutil._state_has_identity(state):
250            return None
251           
252        return LoadDeferredColumns(state, self.key)
253       
254               
255log.class_logger(DeferredColumnLoader)
256
257class LoadDeferredColumns(object):
258    """serializable loader object used by DeferredColumnLoader"""
259   
260    def __init__(self, state, key):
261        self.state, self.key = state, key
262
263    def __call__(self):
264        state = self.state
265       
266       
267        localparent = mapper._state_mapper(state)
268       
269        prop = localparent.get_property(self.key)
270        strategy = prop._get_strategy(DeferredColumnLoader)
271
272        if strategy.group:
273            toload = [
274                    p.key for p in
275                    localparent.iterate_properties
276                    if isinstance(p, StrategizedProperty) and
277                      isinstance(p.strategy, DeferredColumnLoader) and
278                      p.group==strategy.group
279                    ]
280        else:
281            toload = [self.key]
282
283        # narrow the keys down to just those which have no history
284        group = [k for k in toload if k in state.unmodified]
285
286        if strategy._should_log_debug:
287            strategy.logger.debug(
288                    "deferred load %s group %s" %
289                    (mapperutil.state_attribute_str(state, self.key), group and ','.join(group) or 'None')
290                )
291
292        session = sessionlib._state_session(state)
293        if session is None:
294            raise sa_exc.UnboundExecutionError(
295                        "Parent instance %s is not bound to a Session; "
296                        "deferred load operation of attribute '%s' cannot proceed" %
297                        (mapperutil.state_str(state), self.key)
298                    )
299
300        query = session.query(localparent)
301        ident = state.key[1]
302        query._get(None, ident=ident, only_load_props=group, refresh_state=state)
303        return attributes.ATTR_WAS_SET
304
305class DeferredOption(StrategizedOption):
306    propagate_to_loaders = True
307   
308    def __init__(self, key, defer=False):
309        super(DeferredOption, self).__init__(key)
310        self.defer = defer
311
312    def get_strategy_class(self):
313        if self.defer:
314            return DeferredColumnLoader
315        else:
316            return ColumnLoader
317
318class UndeferGroupOption(MapperOption):
319    propagate_to_loaders = True
320
321    def __init__(self, group):
322        self.group = group
323    def process_query(self, query):
324        query._attributes[('undefer', self.group)] = True
325
326class AbstractRelationLoader(LoaderStrategy):
327    """LoaderStratgies which deal with related objects as opposed to scalars."""
328
329    def init(self):
330        for attr in ['mapper', 'target', 'table', 'uselist']:
331            setattr(self, attr, getattr(self.parent_property, attr))
332       
333    def _init_instance_attribute(self, state, callable_=None):
334        if callable_:
335            state.set_callable(self.key, callable_)
336        else:
337            state.initialize(self.key)
338
339class NoLoader(AbstractRelationLoader):
340    """Strategize a relation() that doesn't load data automatically."""
341
342    def init_class_attribute(self, mapper):
343        self.is_class_level = True
344
345        _register_attribute(self, mapper,
346            useobject=True,
347            uselist=self.parent_property.uselist,
348            typecallable = self.parent_property.collection_class,
349        )
350
351    def create_row_processor(self, selectcontext, path, mapper, row, adapter):
352        def new_execute(state, dict_, row, **flags):
353            self._init_instance_attribute(state)
354
355        if self._should_log_debug:
356            new_execute = self.debug_callable(new_execute, self.logger, None,
357                lambda state, dict_, row, **flags: "initializing blank scalar/collection on %s" % \
358                                                  mapperutil.state_attribute_str(state, self.key)
359            )
360        return (new_execute, None)
361
362log.class_logger(NoLoader)
363       
364class LazyLoader(AbstractRelationLoader):
365    """Strategize a relation() that loads when first accessed."""
366
367    def init(self):
368        super(LazyLoader, self).init()
369        (self.__lazywhere, self.__bind_to_col, self._equated_columns) = self._create_lazy_clause(self.parent_property)
370       
371        self.logger.info("%s lazy loading clause %s" % (self, self.__lazywhere))
372
373        # determine if our "lazywhere" clause is the same as the mapper's
374        # get() clause.  then we can just use mapper.get()
375        #from sqlalchemy.orm import query
376        self.use_get = not self.uselist and self.mapper._get_clause[0].compare(self.__lazywhere)
377        if self.use_get:
378            self.logger.info("%s will use query.get() to optimize instance loads" % self)
379
380    def init_class_attribute(self, mapper):
381        self.is_class_level = True
382       
383        # MANYTOONE currently only needs the "old" value for delete-orphan
384        # cascades.  the required _SingleParentValidator will enable active_history
385        # in that case.  otherwise we don't need the "old" value during backref operations.
386        _register_attribute(self,
387                mapper,
388                useobject=True,
389                callable_=self._class_level_loader,
390                uselist = self.parent_property.uselist,
391                typecallable = self.parent_property.collection_class,
392                active_history = self.parent_property.direction is not interfaces.MANYTOONE,
393                )
394
395    def lazy_clause(self, state, reverse_direction=False, alias_secondary=False, adapt_source=None):
396        if state is None:
397            return self._lazy_none_clause(reverse_direction, adapt_source=adapt_source)
398           
399        if not reverse_direction:
400            (criterion, bind_to_col, rev) = (self.__lazywhere, self.__bind_to_col, self._equated_columns)
401        else:
402            (criterion, bind_to_col, rev) = LazyLoader._create_lazy_clause(self.parent_property, reverse_direction=reverse_direction)
403
404        def visit_bindparam(bindparam):
405            mapper = reverse_direction and self.parent_property.mapper or self.parent_property.parent
406            if bindparam.key in bind_to_col:
407                # use the "committed" (database) version to get query column values
408                # also its a deferred value; so that when used by Query, the committed value is used
409                # after an autoflush occurs
410                o = state.obj() # strong ref
411                bindparam.value = lambda: mapper._get_committed_attr_by_column(o, bind_to_col[bindparam.key])
412
413        if self.parent_property.secondary and alias_secondary:
414            criterion = sql_util.ClauseAdapter(self.parent_property.secondary.alias()).traverse(criterion)
415
416        criterion = visitors.cloned_traverse(criterion, {}, {'bindparam':visit_bindparam})
417        if adapt_source:
418            criterion = adapt_source(criterion)
419        return criterion
420       
421    def _lazy_none_clause(self, reverse_direction=False, adapt_source=None):
422        if not reverse_direction:
423            (criterion, bind_to_col, rev) = (self.__lazywhere, self.__bind_to_col, self._equated_columns)
424        else:
425            (criterion, bind_to_col, rev) = LazyLoader._create_lazy_clause(self.parent_property, reverse_direction=reverse_direction)
426
427        def visit_binary(binary):
428            mapper = reverse_direction and self.parent_property.mapper or self.parent_property.parent
429            if isinstance(binary.left, expression._BindParamClause) and binary.left.key in bind_to_col:
430                # reverse order if the NULL is on the left side
431                binary.left = binary.right
432                binary.right = expression.null()
433                binary.operator = operators.is_
434                binary.negate = operators.isnot
435            elif isinstance(binary.right, expression._BindParamClause) and binary.right.key in bind_to_col:
436                binary.right = expression.null()
437                binary.operator = operators.is_
438                binary.negate = operators.isnot
439
440        criterion = visitors.cloned_traverse(criterion, {}, {'binary':visit_binary})
441        if adapt_source:
442            criterion = adapt_source(criterion)
443        return criterion
444       
445    def _class_level_loader(self, state):
446        if not mapperutil._state_has_identity(state):
447            return None
448
449        return LoadLazyAttribute(state, self.key)
450
451    def create_row_processor(self, selectcontext, path, mapper, row, adapter):
452        if not self.is_class_level:
453            def new_execute(state, dict_, row, **flags):
454                # we are not the primary manager for this attribute on this class - set up a
455                # per-instance lazyloader, which will override the class-level behavior.
456                # this currently only happens when using a "lazyload" option on a "no load"
457                # attribute - "eager" attributes always have a class-level lazyloader
458                # installed.
459                self._init_instance_attribute(state, callable_=LoadLazyAttribute(state, self.key))
460
461            if self._should_log_debug:
462                new_execute = self.debug_callable(new_execute, self.logger, None,
463                    lambda state, dict_, row, **flags: "set instance-level lazy loader on %s" % \
464                                                      mapperutil.state_attribute_str(state,
465                                                                                     self.key)
466                )
467
468            return (new_execute, None)
469        else:
470            def new_execute(state, dict_, row, **flags):
471                # we are the primary manager for this attribute on this class - reset its
472                # per-instance attribute state, so that the class-level lazy loader is
473                # executed when next referenced on this instance.  this is needed in
474                # populate_existing() types of scenarios to reset any existing state.
475                state.reset(self.key, dict_)
476
477            if self._should_log_debug:
478                new_execute = self.debug_callable(new_execute, self.logger, None,
479                    lambda state, dict_, row, **flags: "set class-level lazy loader on %s" % \
480                                                      mapperutil.state_attribute_str(state,
481                                                                                     self.key)
482                )
483
484            return (new_execute, None)
485           
486    def _create_lazy_clause(cls, prop, reverse_direction=False):
487        binds = util.column_dict()
488        lookup = util.column_dict()
489        equated_columns = util.column_dict()
490
491        if reverse_direction and not prop.secondaryjoin:
492            for l, r in prop.local_remote_pairs:
493                _list = lookup.setdefault(r, [])
494                _list.append((r, l))
495                equated_columns[l] = r
496        else:
497            for l, r in prop.local_remote_pairs:
498                _list = lookup.setdefault(l, [])
499                _list.append((l, r))
500                equated_columns[r] = l
501               
502        def col_to_bind(col):
503            if col in lookup:
504                for tobind, equated in lookup[col]:
505                    if equated in binds:
506                        return None
507                if col not in binds:
508                    binds[col] = sql.bindparam(None, None, type_=col.type)
509                return binds[col]
510            return None
511       
512        lazywhere = prop.primaryjoin
513
514        if not prop.secondaryjoin or not reverse_direction:
515            lazywhere = visitors.replacement_traverse(lazywhere, {}, col_to_bind)
516       
517        if prop.secondaryjoin is not None:
518            secondaryjoin = prop.secondaryjoin
519            if reverse_direction:
520                secondaryjoin = visitors.replacement_traverse(secondaryjoin, {}, col_to_bind)
521            lazywhere = sql.and_(lazywhere, secondaryjoin)
522   
523        bind_to_col = dict((binds[col].key, col) for col in binds)
524       
525        return (lazywhere, bind_to_col, equated_columns)
526    _create_lazy_clause = classmethod(_create_lazy_clause)
527   
528log.class_logger(LazyLoader)
529
530class LoadLazyAttribute(object):
531    """serializable loader object used by LazyLoader"""
532
533    def __init__(self, state, key):
534        self.state, self.key = state, key
535       
536    def __getstate__(self):
537        return (self.state, self.key)
538
539    def __setstate__(self, state):
540        self.state, self.key = state
541       
542    def __call__(self):
543        state = self.state
544
545        instance_mapper = mapper._state_mapper(state)
546        prop = instance_mapper.get_property(self.key)
547        strategy = prop._get_strategy(LazyLoader)
548       
549        if strategy._should_log_debug:
550            strategy.logger.debug("loading %s" % mapperutil.state_attribute_str(state, self.key))
551
552        session = sessionlib._state_session(state)
553        if session is None:
554            raise sa_exc.UnboundExecutionError(
555                "Parent instance %s is not bound to a Session; "
556                "lazy load operation of attribute '%s' cannot proceed" %
557                (mapperutil.state_str(state), self.key)
558            )
559       
560        q = session.query(prop.mapper)._adapt_all_clauses()
561       
562        if state.load_path:
563            q = q._with_current_path(state.load_path + (self.key,))
564           
565        # if we have a simple primary key load, use mapper.get()
566        # to possibly save a DB round trip
567        if strategy.use_get:
568            ident = []
569            allnulls = True
570            for primary_key in prop.mapper.primary_key:
571                val = instance_mapper._get_committed_state_attr_by_column(state, strategy._equated_columns[primary_key])
572                allnulls = allnulls and val is None
573                ident.append(val)
574            if allnulls:
575                return None
576            if state.load_options:
577                q = q._conditional_options(*state.load_options)
578            return q.get(ident)
579
580        if prop.order_by:
581            q = q.order_by(*util.to_list(prop.order_by))
582
583        if state.load_options:
584            q = q._conditional_options(*state.load_options)
585        q = q.filter(strategy.lazy_clause(state))
586
587        result = q.all()
588        if strategy.uselist:
589            return result
590        else:
591            if result:
592                return result[0]
593            else:
594                return None
595
596class EagerLoader(AbstractRelationLoader):
597    """Strategize a relation() that loads within the process of the parent object being selected."""
598   
599    def init(self):
600        super(EagerLoader, self).init()
601        self.join_depth = self.parent_property.join_depth
602
603    def init_class_attribute(self, mapper):
604        self.parent_property._get_strategy(LazyLoader).init_class_attribute(mapper)
605       
606    def setup_query(self, context, entity, path, adapter, column_collection=None, parentmapper=None, **kwargs):
607        """Add a left outer join to the statement thats being constructed."""
608
609        if not context.enable_eagerloads:
610            return
611           
612        path = path + (self.key,)
613
614        # check for user-defined eager alias
615        if ("user_defined_eager_row_processor", path) in context.attributes:
616            clauses = context.attributes[("user_defined_eager_row_processor", path)]
617           
618            adapter = entity._get_entity_clauses(context.query, context)
619            if adapter and clauses:
620                context.attributes[("user_defined_eager_row_processor", path)] = clauses = clauses.wrap(adapter)
621            elif adapter:
622                context.attributes[("user_defined_eager_row_processor", path)] = clauses = adapter
623           
624            add_to_collection = context.primary_columns
625           
626        else:
627            clauses = self._create_eager_join(context, entity, path, adapter, parentmapper)
628            if not clauses:
629                return
630
631            context.attributes[("eager_row_processor", path)] = clauses
632
633            add_to_collection = context.secondary_columns
634           
635        for value in self.mapper._iterate_polymorphic_properties():
636            value.setup(context, entity, path + (self.mapper.base_mapper,), clauses, parentmapper=self.mapper, column_collection=add_to_collection)
637   
638    def _create_eager_join(self, context, entity, path, adapter, parentmapper):
639        # check for join_depth or basic recursion,
640        # if the current path was not explicitly stated as
641        # a desired "loaderstrategy" (i.e. via query.options())
642        if ("loaderstrategy", path) not in context.attributes:
643            if self.join_depth:
644                if len(path) / 2 > self.join_depth:
645                    return
646            else:
647                if self.mapper.base_mapper in path:
648                    return
649
650        if parentmapper is None:
651            localparent = entity.mapper
652        else:
653            localparent = parentmapper
654   
655        # whether or not the Query will wrap the selectable in a subquery,
656        # and then attach eager load joins to that (i.e., in the case of LIMIT/OFFSET etc.)
657        should_nest_selectable = context.query._should_nest_selectable
658
659        if entity in context.eager_joins:
660            entity_key, default_towrap = entity, entity.selectable
661
662        elif should_nest_selectable or not context.from_clause:
663            # if no from_clause, or a subquery is going to be generated,
664            # store eager joins per _MappedEntity; Query._compile_context will
665            # add them as separate selectables to the select(), or splice them together
666            # after the subquery is generated
667            entity_key, default_towrap = entity, entity.selectable
668        else:
669            index, clause = sql_util.find_join_source(context.from_clause, entity.selectable)
670            if clause:
671                # join to an existing FROM clause on the query.
672                # key it to its list index in the eager_joins dict.
673                # Query._compile_context will adapt as needed and append to the
674                # FROM clause of the select().
675                entity_key, default_towrap = index, clause
676            else:
677                # if no from_clause to join to,
678                # store eager joins per _MappedEntity
679                entity_key, default_towrap = entity, entity.selectable
680               
681        towrap = context.eager_joins.setdefault(entity_key, default_towrap)
682
683        # create AliasedClauses object to build up the eager query. 
684        clauses = mapperutil.ORMAdapter(mapperutil.AliasedClass(self.mapper),
685                    equivalents=self.mapper._equivalent_columns, adapt_required=True)
686
687        join_to_left = False
688        if adapter:
689            if getattr(adapter, 'aliased_class', None):
690                onclause = getattr(adapter.aliased_class, self.key, self.parent_property)
691            else:
692                onclause = getattr(mapperutil.AliasedClass(self.parent, adapter.selectable), self.key, self.parent_property)
693               
694            if onclause is self.parent_property:
695                # TODO: this is a temporary hack to account for polymorphic eager loads where
696                # the eagerload is referencing via of_type().
697                join_to_left = True
698        else:
699            onclause = self.parent_property
700           
701        context.eager_joins[entity_key] = eagerjoin = mapperutil.outerjoin(towrap, clauses.aliased_class, onclause, join_to_left=join_to_left)
702       
703        # send a hint to the Query as to where it may "splice" this join
704        eagerjoin.stop_on = entity.selectable
705       
706        if not self.parent_property.secondary and context.query._should_nest_selectable and not parentmapper:
707            # for parentclause that is the non-eager end of the join,
708            # ensure all the parent cols in the primaryjoin are actually in the
709            # columns clause (i.e. are not deferred), so that aliasing applied by the Query propagates
710            # those columns outward.  This has the effect of "undefering" those columns.
711            for col in sql_util.find_columns(self.parent_property.primaryjoin):
712                if localparent.mapped_table.c.contains_column(col):
713                    if adapter:
714                        col = adapter.columns[col]
715                    context.primary_columns.append(col)
716       
717        if self.parent_property.order_by:
718            context.eager_order_by += eagerjoin._target_adapter.copy_and_process(util.to_list(self.parent_property.order_by))
719           
720        return clauses
721       
722    def _create_eager_adapter(self, context, row, adapter, path):
723        if ("user_defined_eager_row_processor", path) in context.attributes:
724            decorator = context.attributes[("user_defined_eager_row_processor", path)]
725            # user defined eagerloads are part of the "primary" portion of the load.
726            # the adapters applied to the Query should be honored.
727            if context.adapter and decorator:
728                decorator = decorator.wrap(context.adapter)
729            elif context.adapter:
730                decorator = context.adapter
731        elif ("eager_row_processor", path) in context.attributes:
732            decorator = context.attributes[("eager_row_processor", path)]
733        else:
734            if self._should_log_debug:
735                self.logger.debug("Could not locate aliased clauses for key: " + str(path))
736            return False
737
738        try:
739            identity_key = self.mapper.identity_key_from_row(row, decorator)
740            return decorator
741        except KeyError, k:
742            # no identity key - dont return a row processor, will cause a degrade to lazy
743            if self._should_log_debug:
744                self.logger.debug("could not locate identity key from row; missing column '%s'" % k)
745            return False
746
747    def create_row_processor(self, context, path, mapper, row, adapter):
748        path = path + (self.key,)
749           
750        eager_adapter = self._create_eager_adapter(context, row, adapter, path)
751       
752        if eager_adapter is not False:
753            key = self.key
754            _instance = self.mapper._instance_processor(context, path + (self.mapper.base_mapper,), eager_adapter)
755           
756            if not self.uselist:
757                def execute(state, dict_, row, isnew, **flags):
758                    if isnew:
759                        # set a scalar object instance directly on the
760                        # parent object, bypassing InstrumentedAttribute
761                        # event handlers.
762                        dict_[key] = _instance(row, None)
763                    else:
764                        # call _instance on the row, even though the object has been created,
765                        # so that we further descend into properties
766                        _instance(row, None)
767            else:
768                def execute(state, dict_, row, isnew, **flags):
769                    if isnew or (state, key) not in context.attributes:
770                        # appender_key can be absent from context.attributes with isnew=False
771                        # when self-referential eager loading is used; the same instance may be present
772                        # in two distinct sets of result columns
773
774                        collection = attributes.init_state_collection(state, dict_, key)
775                        appender = util.UniqueAppender(collection, 'append_without_event')
776
777                        context.attributes[(state, key)] = appender
778
779                    result_list = context.attributes[(state, key)]
780                   
781                    _instance(row, result_list)
782
783            if self._should_log_debug:
784                execute = self.debug_callable(execute, self.logger,
785                    "%s returning eager instance loader" % self,
786                    lambda state, dict_, row, isnew, **flags: "%s eagerload %s" % \
787                                                  (self,
788                                                   self.uselist and "scalar attribute"
789                                                   or "collection")
790                )
791
792            return (execute, execute)
793        else:
794            if self._should_log_debug:
795                self.logger.debug("%s degrading to lazy loader" % self)
796            return self.parent_property._get_strategy(LazyLoader).create_row_processor(context, path, mapper, row, adapter)
797
798log.class_logger(EagerLoader)
799
800class EagerLazyOption(StrategizedOption):
801
802    def __init__(self, key, lazy=True, chained=False, mapper=None, propagate_to_loaders=True):
803        super(EagerLazyOption, self).__init__(key, mapper)
804        self.lazy = lazy
805        self.chained = chained
806        self.propagate_to_loaders = propagate_to_loaders
807       
808    def is_chained(self):
809        return not self.lazy and self.chained
810       
811    def get_strategy_class(self):
812        if self.lazy:
813            return LazyLoader
814        elif self.lazy is False:
815            return EagerLoader
816        elif self.lazy is None:
817            return NoLoader
818
819class LoadEagerFromAliasOption(PropertyOption):
820   
821    def __init__(self, key, alias=None):
822        super(LoadEagerFromAliasOption, self).__init__(key)
823        if alias:
824            if not isinstance(alias, basestring):
825                m, alias, is_aliased_class = mapperutil._entity_info(alias)
826        self.alias = alias
827
828    def process_query_property(self, query, paths, mappers):
829        if self.alias:
830            if isinstance(self.alias, basestring):
831                mapper = mappers[-1]
832                (root_mapper, propname) = paths[-1][-2:]
833                prop = mapper.get_property(propname, resolve_synonyms=True)
834                self.alias = prop.target.alias(self.alias)
835            query._attributes[("user_defined_eager_row_processor", paths[-1])] = sql_util.ColumnAdapter(self.alias)
836        else:
837            (root_mapper, propname) = paths[-1][-2:]
838            mapper = mappers[-1]
839            prop = mapper.get_property(propname, resolve_synonyms=True)
840            adapter = query._polymorphic_adapters.get(prop.mapper, None)
841            query._attributes[("user_defined_eager_row_processor", paths[-1])] = adapter
842
843class _SingleParentValidator(interfaces.AttributeExtension):
844    def __init__(self, prop):
845        self.prop = prop
846
847    def _do_check(self, state, value, oldvalue, initiator):
848        if value is not None:
849            hasparent = initiator.hasparent(attributes.instance_state(value))
850            if hasparent and oldvalue is not value:
851                raise sa_exc.InvalidRequestError("Instance %s is already associated with an instance "
852                    "of %s via its %s attribute, and is only allowed a single parent." %
853                    (mapperutil.instance_str(value), state.class_, self.prop)
854                )
855        return value
856       
857    def append(self, state, value, initiator):
858        return self._do_check(state, value, None, initiator)
859
860    def set(self, state, value, oldvalue, initiator):
861        return self._do_check(state, value, oldvalue, initiator)
862
863
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。