]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- got rudimental "mapping to multiple tables" functionality cleaned up,
authorMike Bayer <mike_mp@zzzcomputing.com>
Fri, 2 Jun 2006 01:10:20 +0000 (01:10 +0000)
committerMike Bayer <mike_mp@zzzcomputing.com>
Fri, 2 Jun 2006 01:10:20 +0000 (01:10 +0000)
more correctly documented

CHANGES
doc/build/content/adv_datamapping.txt
lib/sqlalchemy/orm/mapper.py
lib/sqlalchemy/sql_util.py
test/mapper.py
test/objectstore.py
test/tables.py

diff --git a/CHANGES b/CHANGES
index 5d62f4ae9b445c8c7573febafc889bef948a65aa..3e65c50ef88c3aa55206ce1ec3877b53302c588b 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -14,6 +14,8 @@ thanks to James Ralston and Brad Clements for their efforts.
 - count() function on selectables now uses table primary key or 
 first column instead of "1" for criterion, also uses label "rowcount"
 instead of "count".  
+- got rudimental "mapping to multiple tables" functionality cleaned up, 
+more correctly documented
 
 0.2.1
 - "pool" argument to create_engine() properly propigates
index 6124f45c3185e05334a034181de3c89c8dd3897b..7026b50a84e2abd9224cfe7f68b79a4a342c571e 100644 (file)
@@ -500,9 +500,12 @@ Mappers can be constructed against arbitrary relational units (called `Selectabl
     
     # map to it - the identity of an AddressUser object will be 
     # based on (user_id, address_id) since those are the primary keys involved
-    m = mapper(AddressUser, j)
+    m = mapper(AddressUser, j, properties={
+        'user_id':[users_table.c.user_id, addresses_table.c.user_id]
+    })
+
+A second example:
 
-    A second example:        
     {python}
     # many-to-many join on an association table
     j = join(users_table, userkeywords, 
@@ -515,7 +518,12 @@ Mappers can be constructed against arbitrary relational units (called `Selectabl
 
     # map to it - the identity of a KeywordUser object will be
     # (user_id, keyword_id) since those are the primary keys involved
-    m = mapper(KeywordUser, j)
+    m = mapper(KeywordUser, j, properties={
+        'user_id':[users_table.c.user_id, userkeywords.c.user_id],
+        'keyword_id':[userkeywords.c.keyword_id, keywords.c.keyword_id]
+    })
+
+In both examples above, "composite" columns were added as properties to the mappers; these are aggregations of multiple columns into one mapper property, which instructs the mapper to keep both of those columns set at the same value.
 
 ### Mapping a Class against Arbitary Selects {@name=selects}
 
index bde887b2afaf40cff73f005a7756e98643bbb682..3e9f2724ca74695eca9acd09ddb52d3c1f03aaf0 100644 (file)
@@ -193,11 +193,12 @@ class Mapper(object):
                     l = self.pks_by_table[t]
                 except KeyError:
                     l = self.pks_by_table.setdefault(t, util.HashSet(ordered=True))
-                if not len(t.primary_key):
-                    raise exceptions.ArgumentError("Table " + t.name + " has no primary key columns. Specify primary_key argument to mapper.")
                 for k in t.primary_key:
                     l.append(k)
-
+                    
+        if len(self.pks_by_table[self.mapped_table]) == 0:
+            raise exceptions.ArgumentError("Could not assemble any primary key columsn from given tables for table '%s'" % (self.mapped_table.name))
+            
         # make table columns addressable via the mapper
         self.columns = util.OrderedProperties()
         self.c = self.columns
@@ -532,7 +533,7 @@ class Mapper(object):
         list."""
         #print "SAVE_OBJ MAPPER", self.class_.__name__, objects
         connection = uow.transaction.connection(self)
-        for table in self.tables:
+        for table in self.tables.sort(reverse=False):
             #print "SAVE_OBJ table ", self.class_.__name__, table.name
             # looping through our set of tables, which are all "real" tables, as opposed
             # to our main table which might be a select statement or something non-writeable
@@ -700,7 +701,7 @@ class Mapper(object):
         connection = uow.transaction.connection(self)
         #print "DELETE_OBJ MAPPER", self.class_.__name__, objects
         
-        for table in util.reversed(self.tables):
+        for table in self.tables.sort(reverse=True):
             if not self._has_pks(table):
                 continue
             delete = []
index 0728bba47f581a8d4242b7533facb5a0ee8004d4..1a720d1d505effd4f8441eea03a5def677665c37 100644 (file)
@@ -9,7 +9,22 @@ class TableCollection(object):
         self.tables = []
     def add(self, table):
         self.tables.append(table)
-    def sort(self, reverse=False ):
+        if hasattr(self, '_sorted'):
+            del self._sorted
+    def sort(self, reverse=False):
+        try:
+            sorted = self._sorted
+        except AttributeError, e:
+            self._sorted = self._do_sort()
+            return self.sort(reverse=reverse)
+        if reverse:
+            x = sorted[:]
+            x.reverse()
+            return x
+        else:
+            return sorted
+            
+    def _do_sort(self):
         import sqlalchemy.orm.topological
         tuples = []
         class TVisitor(schema.SchemaVisitor):
@@ -29,8 +44,6 @@ class TableCollection(object):
                 to_sequence( child )
         if head is not None:
             to_sequence( head )
-        if reverse:
-            sequence.reverse()
         return sequence
         
 
index 10025397abb99aef5806851dc1a55e5b33f99a52..d4225d412ea7021b95c2092ebd14a2f7cc0d9284 100644 (file)
@@ -87,7 +87,8 @@ class MapperTest(MapperSuperTest):
     def testunicodeget(self):
         """tests that Query.get properly sets up the type for the bind parameter.  using unicode would normally fail 
         on postgres, mysql and oracle unless it is converted to an encoded string"""
-        table = Table('foo', db, 
+        metadata = BoundMetaData(db)
+        table = Table('foo', metadata, 
             Column('id', Unicode(10), primary_key=True),
             Column('data', Unicode(40)))
         try:
index 404f9ff94ef8ecf1efaaff267aaef810bd2410fa..ef20bbdbead3dd8650ec9c62d6726cba849bf35b 100644 (file)
@@ -509,8 +509,6 @@ class SaveTest(SessionTest):
         """tests a save of an object where each instance spans two tables. also tests
         redefinition of the keynames for the column properties."""
         usersaddresses = sql.join(users, addresses, users.c.user_id == addresses.c.user_id)
-        print usersaddresses.corresponding_column(users.c.user_id)
-        print repr(usersaddresses._orig_cols)
         m = mapper(User, usersaddresses, 
             properties = dict(
                 email = addresses.c.email_address, 
@@ -523,7 +521,14 @@ class SaveTest(SessionTest):
         u.email = 'multi@test.org'
 
         ctx.current.flush()
+        id = m.identity(u)
+        print id
 
+        ctx.current.clear()
+        
+        u = m.get(id)
+        assert u.user_name == 'multitester'
+        
         usertable = users.select(users.c.user_id.in_(u.foo_id)).execute().fetchall()
         self.assertEqual(usertable[0].values(), [u.foo_id, 'multitester'])
         addresstable = addresses.select(addresses.c.address_id.in_(u.address_id)).execute().fetchall()
@@ -538,9 +543,40 @@ class SaveTest(SessionTest):
         addresstable = addresses.select(addresses.c.address_id.in_(u.address_id)).execute().fetchall()
         self.assertEqual(addresstable[0].values(), [u.address_id, u.foo_id, 'lala@hey.com'])
 
-        u = m.select(users.c.user_id==u.foo_id)[0]
-        self.echo( repr(u.__dict__))
+        ctx.current.clear()
+        u = m.get(id)
+        assert u.user_name == 'imnew'
+        
+    def testm2mmultitable(self):
+        # many-to-many join on an association table
+        j = join(users, userkeywords, 
+                users.c.user_id==userkeywords.c.user_id).join(keywords, 
+                   userkeywords.c.keyword_id==keywords.c.keyword_id)
+
+        # a class 
+        class KeywordUser(object):
+            pass
 
+        # map to it - the identity of a KeywordUser object will be
+        # (user_id, keyword_id) since those are the primary keys involved
+        m = mapper(KeywordUser, j, properties={
+            'user_id':[users.c.user_id, userkeywords.c.user_id],
+            'keyword_id':[userkeywords.c.keyword_id, keywords.c.keyword_id],
+            'keyword_name':keywords.c.name
+            
+        })
+        
+        k = KeywordUser()
+        k.user_name = 'keyworduser'
+        k.keyword_name = 'a keyword'
+        ctx.current.flush()
+        print m.instance_key(k)
+        id = (k.user_id, k.keyword_id)
+        ctx.current.clear()
+        k = ctx.current.query(KeywordUser).get(id)
+        assert k.user_name == 'keyworduser'
+        assert k.keyword_name == 'a keyword'
+        
     def testonetoone(self):
         m = mapper(User, users, properties = dict(
             address = relation(mapper(Address, addresses), lazy = True, uselist = False)
index 2bfc7586897904be0c608c8d3a51bcd143da2e0b..7085e5a775867c2048706942e3f19542dff88fba 100644 (file)
@@ -3,7 +3,7 @@ from sqlalchemy import *
 import os
 import testbase
 
-__all__ = ['db', 'users', 'addresses', 'orders', 'orderitems', 'keywords', 'itemkeywords',
+__all__ = ['db', 'users', 'addresses', 'orders', 'orderitems', 'keywords', 'itemkeywords', 'userkeywords',
             'User', 'Address', 'Order', 'Item', 'Keyword'
         ]
 
@@ -45,6 +45,11 @@ keywords = Table('keywords', metadata,
     
 )
 
+userkeywords = Table('userkeywords', metadata, 
+    Column('user_id', INT, ForeignKey("users")),
+    Column('keyword_id', INT, ForeignKey("keywords")),
+)
+
 itemkeywords = Table('itemkeywords', metadata,
     Column('item_id', INT, ForeignKey("items")),
     Column('keyword_id', INT, ForeignKey("keywords")),