]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
(no commit message)
authorMike Bayer <mike_mp@zzzcomputing.com>
Sat, 23 Jul 2005 17:40:32 +0000 (17:40 +0000)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sat, 23 Jul 2005 17:40:32 +0000 (17:40 +0000)
lib/sqlalchemy/engine.py
lib/sqlalchemy/mapper.py
lib/sqlalchemy/sql.py
lib/sqlalchemy/util.py
test/mapper.py

index 7362ba1ae0ff68420dbc23a41ddc967213a9833d..855c2e801ebd9d604b86153bfe66f71b80ae3d16 100644 (file)
@@ -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
index 080caab0e0f5aa4c41876422a9a538015a0326e6..59b1d5a2afe6e7aea279859c49357cddd93f0b29 100644 (file)
@@ -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):
index b26f3fc93936d1c1a3efa0152a7640ac380625df..1c7efd8777055fee1acc41fe5d9593cb01cd64d6 100644 (file)
@@ -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()
index 04d827c08ca38236f1808720487bcc9729c1dd6a..2eda7a1cbf0767f838d7bf674553b7ed1e185ad3 100644 (file)
@@ -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())
index fe1ba62c76e0532e02d59fee8d2a411521cd0a35..0d5f3f46a4d2a6df8945f06a2c5981958d9ea50a 100644 (file)
@@ -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)