From: Mike Bayer Date: Sat, 23 Jul 2005 17:40:32 +0000 (+0000) Subject: (no commit message) X-Git-Tag: rel_0_1_0~852 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=4c6095a02817e1d47f8cfded9c3e38146f2997e1;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git --- diff --git a/lib/sqlalchemy/engine.py b/lib/sqlalchemy/engine.py index 7362ba1ae0..855c2e801e 100644 --- a/lib/sqlalchemy/engine.py +++ b/lib/sqlalchemy/engine.py @@ -151,6 +151,7 @@ class ResultProxy: def fetchone(self): row = self.cursor.fetchone() if row is not None: + print repr(row) return RowProxy(self, row) else: return None diff --git a/lib/sqlalchemy/mapper.py b/lib/sqlalchemy/mapper.py index 080caab0e0..59b1d5a2af 100644 --- a/lib/sqlalchemy/mapper.py +++ b/lib/sqlalchemy/mapper.py @@ -40,14 +40,14 @@ def relation(*args, **params): else: return relation_mapper(*args, **params) -def relation_loader(mapper, whereclause, lazy = True, **options): +def relation_loader(mapper, secondary = None, primaryjoin = None, secondaryjoin = None, lazy = True, **options): if lazy: - return LazyLoader(mapper, whereclause, **options) + return LazyLoader(mapper, secondary, primaryjoin, secondaryjoin, **options) else: - return EagerLoader(mapper, whereclause, **options) + return EagerLoader(mapper, secondary, primaryjoin, secondaryjoin, **options) -def relation_mapper(class_, selectable, whereclause, table = None, properties = None, lazy = True, **options): - return relation_loader(mapper(class_, selectable, table = table, properties = properties, isroot = False), whereclause, lazy = lazy, **options) +def relation_mapper(class_, selectable, secondary = None, primaryjoin = None, secondaryjoin = None, table = None, properties = None, lazy = True, **options): + return relation_loader(mapper(class_, selectable, table = table, properties = properties, isroot = False), secondary, primaryjoin, secondaryjoin, lazy = lazy, **options) def mapper(class_, selectable, table = None, properties = None, identitymap = None, use_smart_properties = True, isroot = True): return Mapper(class_, selectable, table = table, properties = properties, identitymap = identitymap, use_smart_properties = use_smart_properties, isroot = isroot) @@ -225,11 +225,6 @@ class Mapper(object): instance = self.identitymap[identitykey] instance.dirty = False - # call further mapper properties on the row, to pull further - # instances from the row and possibly populate this item. - for key, prop in self.props.iteritems(): - prop.execute(instance, row, identitykey, localmap, exists) - # now add to the result list, but we only want to add # to the result list uniquely, so get another identity map # that is associated with that list @@ -237,10 +232,17 @@ class Mapper(object): imap = localmap[id(result)] except KeyError: imap = localmap.setdefault(id(result), IdentityMap()) - if not imap.has_key(identitykey): + + isduplicate = imap.has_key(identitykey) + if not isduplicate: imap[identitykey] = instance result.append(instance) + # call further mapper properties on the row, to pull further + # instances from the row and possibly populate this item. + for key, prop in self.props.iteritems(): + prop.execute(instance, row, identitykey, localmap, isduplicate) + class MapperOption: def process(self, mapper): @@ -284,9 +286,12 @@ class ColumnProperty(MapperProperty): class PropertyLoader(MapperProperty): - def __init__(self, mapper, whereclause, **options): + def __init__(self, mapper, secondary, primaryjoin, secondaryjoin, **options): self.mapper = mapper - self.whereclause = whereclause + self.target = self.mapper.selectable + self.secondary = secondary + self.primaryjoin = primaryjoin + self.secondaryjoin = secondaryjoin def init(self, key, parent, root): self.key = key @@ -305,16 +310,6 @@ class PropertyLoader(MapperProperty): def delete(self): self.mapper.delete() -class LazyRow(MapperProperty): - def __init__(self, table, whereclause, **options): - self.table = table - self.whereclause = whereclause - - def init(self, key, parent, root): - self.keys.append(key) - - def execute(self, instance, row, identitykey, localmap, isduplicate): - pass class LazyLoader(PropertyLoader): @@ -340,39 +335,61 @@ class LazyLoader(PropertyLoader): setattr(instance, self.key, load) class EagerLoader(PropertyLoader): + def init(self, key, parent, root): + PropertyLoader.init(self, key, parent, root) + + if self.secondary is not None: + if self.secondaryjoin is None: + self.secondaryjoin = match_primaries(self.target, self.secondary) + if self.primaryjoin is None: + self.primaryjoin = match_primaries(parent.selectable, self.secondary) + else: + if self.primaryjoin is None: + self.primaryjoin = match_primaries(parent.selectable, self.target) + + self.to_alias = util.Set() + [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()] + del self.to_alias[parent.selectable] + def setup(self, key, primarytable, statement, **options): """add a left outer join to the statement thats being constructed""" - targettable = self.mapper.selectable if statement.whereclause is not None: # if the whereclause of the statement references tables that are also # in the outer join we are constructing, then convert those objects to # reference "aliases" of those tables so that their where condition does not interfere # with ours - targets = util.Set([targettable] + self.whereclause._get_from_objects()) - del targets[primarytable] - for target in targets: + for target in self.to_alias: aliasizer = Aliasizer(target, "aliased_" + target.name + "_" + hex(random.randint(0, 65535))[2:]) statement.whereclause.accept_visitor(aliasizer) statement.append_from(aliasizer.alias) if hasattr(statement, '_outerjoin'): - statement._outerjoin = sql.outerjoin(statement._outerjoin, targettable, self.whereclause) + towrap = statement._outerjoin else: - statement._outerjoin = sql.outerjoin(primarytable, targettable, self.whereclause) + towrap = primarytable + + if self.secondaryjoin is not None: + statement._outerjoin = sql.outerjoin(sql.outerjoin(towrap, self.secondary, self.secondaryjoin), self.target, self.primaryjoin) + else: + statement._outerjoin = sql.outerjoin(towrap, self.target, self.primaryjoin) + statement.append_from(statement._outerjoin) - statement.append_column(targettable) + statement.append_column(self.target) for key, value in self.mapper.props.iteritems(): value.setup(key, self.mapper.selectable, statement) def execute(self, instance, row, identitykey, localmap, isduplicate): """receive a row. tell our mapper to look for a new object instance in the row, and attach it to a list on the parent instance.""" - try: - list = getattr(instance, self.key) - except AttributeError: + if not isduplicate: list = [] setattr(instance, self.key, list) + else: + list = getattr(instance, self.key) + self.mapper._instance(row, localmap, list) class EagerOption(MapperOption): @@ -419,6 +436,16 @@ class LazyIzer(sql.ClauseVisitor): binary.right = self.binds.setdefault(self.table.name + "_" + binary.right.name, sql.BindParamClause(self.table.name + "_" + binary.right.name, None, shortname = binary.left.name)) +class LazyRow(MapperProperty): + def __init__(self, table, whereclause, **options): + self.table = table + self.whereclause = whereclause + + def init(self, key, parent, root): + self.keys.append(key) + + def execute(self, instance, row, identitykey, localmap, isduplicate): + pass class SmartProperty(object): @@ -439,6 +466,12 @@ class SmartProperty(object): return s.__dict__[self.key] return property(get_prop, set_prop, del_prop) +def match_primaries(primary, secondary): + pk = primary.primary_keys + if len(pk) == 1: + return (pk[0] == secondary.c[pk[0].name]) + else: + return sql.and_([pk == secondary.c[pk.name] for pk in primary.primary_keys]) class IdentityMap(dict): def get_key(self, row, class_, table, selectable): diff --git a/lib/sqlalchemy/sql.py b/lib/sqlalchemy/sql.py index b26f3fc939..1c7efd8777 100644 --- a/lib/sqlalchemy/sql.py +++ b/lib/sqlalchemy/sql.py @@ -259,7 +259,7 @@ class BinaryClause(ClauseElement): self.parens = False def copy_structure(self): - return BinaryClause(self.left, self.right, self.operator) + return BinaryClause(self.left.copy_structure(), self.right.copy_structure(), self.operator) def _get_from_objects(self): return self.left._get_from_objects() + self.right._get_from_objects() diff --git a/lib/sqlalchemy/util.py b/lib/sqlalchemy/util.py index 04d827c08c..2eda7a1cbf 100644 --- a/lib/sqlalchemy/util.py +++ b/lib/sqlalchemy/util.py @@ -103,10 +103,11 @@ class ThreadLocal(object): class Set(object): - def __init__(self, iter): + def __init__(self, iter = None): self.map = {} - for i in iter: - self.append(i) + if iter is not None: + for i in iter: + self.append(i) def __iter__(self): return iter(self.map.values()) diff --git a/test/mapper.py b/test/mapper.py index fe1ba62c76..0d5f3f46a4 100644 --- a/test/mapper.py +++ b/test/mapper.py @@ -3,9 +3,13 @@ import unittest, sys, os import sqlalchemy.databases.sqlite as sqllite -if os.access('querytest.db', os.F_OK): - os.remove('querytest.db') -db = sqllite.engine('querytest.db', opts = {'isolation_level':None}, echo = True) +memory = True +if memory: + db = sqllite.engine(':memory:', {}, echo = True) +else: + if os.access('querytest.db', os.F_OK): + os.remove('querytest.db') + db = sqllite.engine('querytest.db', opts = {}, echo = True) from sqlalchemy.sql import * from sqlalchemy.schema import * @@ -54,6 +58,7 @@ addresses.build() addresses.insert().execute(address_id = 1, user_id = 7, email_address = "jack@bean.com") addresses.insert().execute(address_id = 2, user_id = 8, email_address = "ed@wood.com") addresses.insert().execute(address_id = 3, user_id = 8, email_address = "ed@lala.com") +db.connection().commit() orders.build() orders.insert().execute(order_id = 1, user_id = 7, description = 'order 1', isopen=0) @@ -61,6 +66,7 @@ orders.insert().execute(order_id = 2, user_id = 9, description = 'order 2', isop orders.insert().execute(order_id = 3, user_id = 7, description = 'order 3', isopen=1) orders.insert().execute(order_id = 4, user_id = 9, description = 'order 4', isopen=1) orders.insert().execute(order_id = 5, user_id = 7, description = 'order 5', isopen=0) +db.connection().commit() orderitems.build() orderitems.insert().execute(item_id=1, order_id=2, item_name='item 1') @@ -68,6 +74,7 @@ orderitems.insert().execute(item_id=3, order_id=3, item_name='item 3') orderitems.insert().execute(item_id=2, order_id=2, item_name='item 2') orderitems.insert().execute(item_id=5, order_id=3, item_name='item 5') orderitems.insert().execute(item_id=4, order_id=3, item_name='item 4') +db.connection().commit() keywords.build() keywords.insert().execute(keyword_id=1, name='blue') @@ -77,6 +84,7 @@ keywords.insert().execute(keyword_id=4, name='big') keywords.insert().execute(keyword_id=5, name='small') keywords.insert().execute(keyword_id=6, name='round') keywords.insert().execute(keyword_id=7, name='square') +db.connection().commit() itemkeywords.build() itemkeywords.insert().execute(keyword_id=2, item_id=1) @@ -88,9 +96,9 @@ itemkeywords.insert().execute(keyword_id=6, item_id=3) itemkeywords.insert().execute(keyword_id=3, item_id=3) itemkeywords.insert().execute(keyword_id=5, item_id=2) itemkeywords.insert().execute(keyword_id=4, item_id=3) +db.connection().commit() -sys.exit() class User(object): def __repr__(self): return ( @@ -183,7 +191,7 @@ class EagerTest(PersistTest): def testbasic(self): """tests a basic one-to-many eager load""" m = mapper(User, users, properties = dict( - addresses = relation(Address, addresses, users.c.user_id==addresses.c.user_id, lazy = False) + addresses = relation(Address, addresses, lazy = False) )) l = m.select() print repr(l) @@ -193,7 +201,7 @@ class EagerTest(PersistTest): criterion is using the same tables that are used within the eager load. the mapper must insure that the criterion doesnt interfere with the eager load criterion.""" m = mapper(User, users, properties = dict( - addresses = relation(Address, addresses, users.c.user_id==addresses.c.user_id, lazy = False) + addresses = relation(Address, addresses, primaryjoin = users.c.user_id==addresses.c.user_id, lazy = False) )) l = m.select(and_(addresses.c.email_address == 'ed@lala.com', addresses.c.user_id==users.c.user_id)) print repr(l) @@ -249,8 +257,7 @@ class EagerTest(PersistTest): items = orderitems m = mapper(Item, items, properties = dict( - keywords = relation(Keyword, keywords, - and_(items.c.item_id == itemkeywords.c.item_id, keywords.c.keyword_id == itemkeywords.c.keyword_id), lazy = False), + keywords = relation(Keyword, keywords, itemkeywords, lazy = False), )) l = m.select() print repr(l)