diff options
Diffstat (limited to 'lib/sqlalchemy/mapping/properties.py')
-rw-r--r-- | lib/sqlalchemy/mapping/properties.py | 217 |
1 files changed, 87 insertions, 130 deletions
diff --git a/lib/sqlalchemy/mapping/properties.py b/lib/sqlalchemy/mapping/properties.py index 3a2aaab2f..571bfc0b5 100644 --- a/lib/sqlalchemy/mapping/properties.py +++ b/lib/sqlalchemy/mapping/properties.py @@ -107,7 +107,7 @@ class PropertyLoader(MapperProperty): """describes an object property that holds a single item or list of items that correspond to a related database table.""" - def __init__(self, argument, secondary, primaryjoin, secondaryjoin, foreignkey=None, uselist=None, private=False, association=None, use_alias=False, selectalias=None, order_by=False, attributeext=None, backref=None, is_backref=False, post_update=False): + def __init__(self, argument, secondary, primaryjoin, secondaryjoin, foreignkey=None, uselist=None, private=False, association=None, use_alias=None, selectalias=None, order_by=False, attributeext=None, backref=None, is_backref=False, post_update=False): self.uselist = uselist self.argument = argument self.secondary = secondary @@ -127,11 +127,10 @@ class PropertyLoader(MapperProperty): self.private = private self.association = association - if isinstance(selectalias, str): - print "'selectalias' argument to property is deprecated. please use 'use_alias=True'" - self.use_alias = True - else: - self.use_alias = use_alias + if selectalias is not None: + print "'selectalias' argument to relation() is deprecated. eager loads automatically alias-ize tables now." + if use_alias is not None: + print "'use_alias' argument to relation() is deprecated. eager loads automatically alias-ize tables now." self.order_by = order_by self.attributeext=attributeext self.backref = backref @@ -289,7 +288,6 @@ class PropertyLoader(MapperProperty): elif self.association is not None: c = self.mapper._get_criterion(key, value) & self.primaryjoin return c.copy_container() - return None def register_deleted(self, obj, uow): @@ -743,97 +741,71 @@ class EagerLoader(PropertyLoader): if recursion_stack is None: recursion_stack = {} - - if self.use_alias: - pass - - # figure out tables in the various join clauses we have, because user-defined - # whereclauses that reference the same tables will be converted to use - # aliases of those tables - self.to_alias = util.HashSet() - [self.to_alias.append(f) for f in self.primaryjoin._get_from_objects()] - if self.secondaryjoin is not None: - [self.to_alias.append(f) for f in self.secondaryjoin._get_from_objects()] - try: - del self.to_alias[parent.table] - except KeyError: - pass - # if this eagermapper is to select using an "alias" to isolate it from other - # eager mappers against the same table, we have to redefine our secondary - # or primary join condition to reference the aliased table (and the order_by). - # else we set up the target clause objects as what they are defined in the - # superclass. - if self.use_alias: - self.eagertarget = self.target.alias() - aliasizer = Aliasizer(self.target, aliases={self.target:self.eagertarget}) - if self.secondaryjoin is not None: - self.eagersecondary = self.secondaryjoin.copy_container() - self.eagersecondary.accept_visitor(aliasizer) - self.eagerprimary = self.primaryjoin.copy_container() - self.eagerprimary.accept_visitor(aliasizer) - else: - self.eagerprimary = self.primaryjoin.copy_container() - self.eagerprimary.accept_visitor(aliasizer) - if self.order_by: - self.eager_order_by = [o.copy_container() for o in util.to_list(self.order_by)] - for i in range(0, len(self.eager_order_by)): - if isinstance(self.eager_order_by[i], schema.Column): - self.eager_order_by[i] = self.eagertarget._get_col_by_original(self.eager_order_by[i]) - else: - self.eager_order_by[i].accept_visitor(aliasizer) - else: - self.eager_order_by = self.order_by - - # we have to propigate the "use_alias" fact into - # any sub-mappers that are also eagerloading so that they create a unique tablename - # as well. this copies our child mapper and replaces any eager properties on the - # new mapper with an equivalent eager property, just containing use_alias=True - eagerprops = [] - for key, prop in self.mapper.props.iteritems(): - if isinstance(prop, EagerLoader) and not prop.use_alias: - eagerprops.append(prop) - if len(eagerprops): - recursion_stack[self] = True - self.mapper = self.mapper.copy() - try: - for prop in eagerprops: - p = prop.copy() - p.use_alias=True - - self.mapper.props[prop.key] = p - - if recursion_stack.has_key(prop): - raise ArgumentError("Circular eager load relationship detected on " + str(self.mapper) + " " + key + repr(self.mapper.props)) - - p.do_init_subclass(prop.key, prop.parent, recursion_stack) - - p.eagerprimary = p.eagerprimary.copy_container() - aliasizer = Aliasizer(p.parent.table, aliases={p.parent.table:self.eagertarget}) - p.eagerprimary.accept_visitor(aliasizer) - finally: - del recursion_stack[self] - + self.eagertarget = self.target.alias() + if self.secondary: + self.eagersecondary = self.secondary.alias() + self.aliasizer = Aliasizer(self.target, self.secondary, aliases={ + self.target:self.eagertarget, + self.secondary:self.eagersecondary + }) + self.eagersecondaryjoin = self.secondaryjoin.copy_container() + self.eagersecondaryjoin.accept_visitor(self.aliasizer) + self.eagerprimary = self.primaryjoin.copy_container() + self.eagerprimary.accept_visitor(self.aliasizer) else: - self.eagertarget = self.target - self.eagerprimary = self.primaryjoin - self.eagersecondary = self.secondaryjoin - self.eager_order_by = self.order_by - - def setup(self, key, statement, recursion_stack = None, eagertable=None, **options): - """add a left outer join to the statement thats being constructed""" - - if recursion_stack is None: - recursion_stack = {} + self.aliasizer = Aliasizer(self.target, aliases={self.target:self.eagertarget}) + self.eagerprimary = self.primaryjoin.copy_container() + self.eagerprimary.accept_visitor(self.aliasizer) - if statement.whereclause is not None: - # "aliasize" the tables referenced in the user-defined whereclause to not - # collide with the tables used by the eager load - # note that we arent affecting the mapper's table, nor our own primary or secondary joins - aliasizer = Aliasizer(*self.to_alias) - statement.whereclause.accept_visitor(aliasizer) - for alias in aliasizer.aliases.values(): - statement.append_from(alias) + if self.order_by: + self.eager_order_by = self._aliasize_orderby(self.order_by) + else: + self.eager_order_by = None + + eagerprops = [] + # create a new "eager chain", starting from this eager loader and descending downwards + # through all sub-eagerloaders. this will copy all those eagerloaders and have them set up + # aliases distinct to this eager chain. if a recursive relationship to any of the tables is detected, + # the chain will terminate by copying that eager loader into a lazy loader. + for key, prop in self.mapper.props.iteritems(): + if isinstance(prop, EagerLoader): + eagerprops.append(prop) + if len(eagerprops): + recursion_stack[self.parent.table] = True + self.mapper = self.mapper.copy() + try: + for prop in eagerprops: + if recursion_stack.has_key(prop.target): + # recursion - set the relationship as a LazyLoader + p = EagerLazyOption(None, False).create_prop(self.mapper, prop.key) + continue + p = prop.copy() + self.mapper.props[prop.key] = p + #print "we are:", id(self), self.target.name, (self.secondary and self.secondary.name or "None"), self.parent.table.name + #print "prop is",id(prop), prop.target.name, (prop.secondary and prop.secondary.name or "None"), prop.parent.table.name + p.do_init_subclass(prop.key, prop.parent, recursion_stack) + p.eagerprimary = p.eagerprimary.copy_container() + aliasizer = Aliasizer(p.parent.table, aliases={p.parent.table:self.eagertarget}) + p.eagerprimary.accept_visitor(aliasizer) + #print "new eagertqarget", p.eagertarget.name, (p.secondary and p.secondary.name or "none"), p.parent.table.name + finally: + del recursion_stack[self.parent.table] + + def _aliasize_orderby(self, orderby, copy=True): + if copy: + orderby = [o.copy_container() for o in util.to_list(orderby)] + else: + orderby = util.to_list(orderby) + for i in range(0, len(orderby)): + if isinstance(orderby[i], schema.Column): + orderby[i] = self.eagertarget._get_col_by_original(orderby[i]) + else: + orderby[i].accept_visitor(self.aliasizer) + return orderby + + def setup(self, key, statement, eagertable=None, **options): + """add a left outer join to the statement thats being constructed""" if hasattr(statement, '_outerjoin'): towrap = statement._outerjoin @@ -841,9 +813,9 @@ class EagerLoader(PropertyLoader): towrap = self.parent.table if self.secondaryjoin is not None: - statement._outerjoin = sql.outerjoin(towrap, self.secondary, self.eagerprimary).outerjoin(self.eagertarget, self.eagersecondary) + statement._outerjoin = sql.outerjoin(towrap, self.eagersecondary, self.eagerprimary).outerjoin(self.eagertarget, self.eagersecondaryjoin) if self.order_by is False and self.secondary.default_order_by() is not None: - statement.order_by(*self.secondary.default_order_by()) + statement.order_by(*self.eagersecondary.default_order_by()) else: statement._outerjoin = towrap.outerjoin(self.eagertarget, self.eagerprimary) if self.order_by is False and self.eagertarget.default_order_by() is not None: @@ -851,16 +823,12 @@ class EagerLoader(PropertyLoader): if self.eager_order_by: statement.order_by(*util.to_list(self.eager_order_by)) - + elif getattr(statement, 'order_by_clause', None): + self._aliasize_orderby(statement.order_by_clause, False) + statement.append_from(statement._outerjoin) - recursion_stack[self] = True - try: - for key, value in self.mapper.props.iteritems(): - if recursion_stack.has_key(value): - raise InvalidRequestError("Circular eager load relationship detected on " + str(self.mapper) + " " + key + repr(self.mapper.props)) - value.setup(key, statement, recursion_stack=recursion_stack, eagertable=self.eagertarget) - finally: - del recursion_stack[self] + for key, value in self.mapper.props.iteritems(): + value.setup(key, statement, eagertable=self.eagertarget) def execute(self, instance, row, identitykey, imap, isnew): """receive a row. tell our mapper to look for a new object instance in the row, and attach @@ -884,16 +852,10 @@ class EagerLoader(PropertyLoader): def _instance(self, row, imap, result_list=None): """gets an instance from a row, via this EagerLoader's mapper.""" - # if we have an alias for our mapper's table via the use_alias - # parameter, we need to translate the - # aliased columns from the incoming row into a new row that maps - # the values against the columns of the mapper's original non-aliased table. - if self.use_alias: - fakerow = {} - fakerow = util.DictDecorator(row) - for c in self.eagertarget.c: - fakerow[c.original] = row[c] - row = fakerow + fakerow = util.DictDecorator(row) + for c in self.eagertarget.c: + fakerow[c.parent] = row[c] + row = fakerow return self.mapper._instance(row, imap, result_list) class GenericOption(MapperOption): @@ -918,6 +880,7 @@ class GenericOption(MapperOption): def create_prop(self, mapper, key): kwargs = util.constructor_args(oldprop) mapper.set_property(key, class_(**kwargs )) + class EagerLazyOption(GenericOption): """an option that switches a PropertyLoader to be an EagerLoader or LazyLoader""" @@ -941,10 +904,6 @@ class EagerLazyOption(GenericOption): newprop = class_.__new__(class_) newprop.__dict__.update(oldprop.__dict__) newprop.do_init_subclass(key, mapper) - if self.kwargs.get('selectalias', None): - newprop.use_alias = True - elif self.kwargs.get('use_alias', None) is not None: - newprop.use_alias = self.kwargs['use_alias'] mapper.set_property(key, newprop) class DeferredOption(GenericOption): @@ -969,28 +928,26 @@ class Aliasizer(sql.ClauseVisitor): for t in tables: self.tables[t] = t self.binary = None - self.match = False self.aliases = kwargs.get('aliases', {}) - def get_alias(self, table): try: return self.aliases[table] except: return self.aliases.setdefault(table, sql.alias(table)) - def visit_compound(self, compound): - for i in range(0, len(compound.clauses)): - if isinstance(compound.clauses[i], schema.Column) and self.tables.has_key(compound.clauses[i].table): - compound.clauses[i] = self.get_alias(compound.clauses[i].table)._get_col_by_original(compound.clauses[i]) - self.match = True - + self.visit_clauselist(compound) + def visit_clauselist(self, clist): + for i in range(0, len(clist.clauses)): + if isinstance(clist.clauses[i], schema.Column) and self.tables.has_key(clist.clauses[i].table): + orig = clist.clauses[i] + clist.clauses[i] = self.get_alias(clist.clauses[i].table)._get_col_by_original(clist.clauses[i]) + if clist.clauses[i] is None: + raise "cant get orig for " + str(orig) + " against table " + orig.table.name + " " + self.get_alias(orig.table).name def visit_binary(self, binary): if isinstance(binary.left, schema.Column) and self.tables.has_key(binary.left.table): binary.left = self.get_alias(binary.left.table)._get_col_by_original(binary.left) - self.match = True if isinstance(binary.right, schema.Column) and self.tables.has_key(binary.right.table): binary.right = self.get_alias(binary.right.table)._get_col_by_original(binary.right) - self.match = True class BinaryVisitor(sql.ClauseVisitor): def __init__(self, func): |