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)
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
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):
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
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):
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):
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):
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):
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 *
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)
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')
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')
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)
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 (
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)
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)
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)