]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
(no commit message)
authorMike Bayer <mike_mp@zzzcomputing.com>
Sun, 18 Sep 2005 21:23:35 +0000 (21:23 +0000)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sun, 18 Sep 2005 21:23:35 +0000 (21:23 +0000)
doc/build/components/formatting.myt
doc/build/content/metadata.myt
doc/build/content/roadmap.myt
lib/sqlalchemy/mapper.py
lib/sqlalchemy/objectstore.py
lib/sqlalchemy/schema.py
test/mapper.py
test/objectstore.py

index c10f259e34781853aeb68975200ee4f49e623150..3ff0a2c91e82e46a916a7fa9478f81879ff1c950 100644 (file)
 <%method code autoflush=False>
 <%args>
        title = None
-       syntaxtype = 'myghty'
+       syntaxtype = 'python'
 </%args>
 
 <%init>
index 4468c776fc9829d325ad03bfe71292bc85f9597f..159f4187ee675501fb9c220367c901dd16098e70 100644 (file)
@@ -1,6 +1,61 @@
 <%flags>inherit='document_base.myt'</%flags>
 <&|doclib.myt:item, name="metadata", description="Database Meta Data" &>
-    <&|doclib.myt:item, name="tables", description="Describing Tables with Database Meta Data" &>
+    <&|doclib.myt:item, name="tables", description="Describing Tables with MetaData" &>
+    <p>The core of SQLAlchemy's query and object mapping operations is table metadata, which are Python objects that describe tables.  Metadata objects can be created by explicitly naming the table and all its properties, using the Table, Column, and ForeignKey objects: </p>
+        <&|formatting.myt:code&>
+        from sqlalchemy.schema import *
+        import sqlalchemy.sqlite as sqlite
+        engine = sqllite.engine(':memory:', {})
+        
+        users = Table('users', engine, 
+            Column('user_id', INTEGER, primary_key = True),
+            Column('user_name', VARCHAR(16), nullable = False),
+            Column('email_address', VARCHAR(60), key='email'),
+            Column('password', VARCHAR(20), nullable = False)
+        )
+        
+        user_prefs = Table('user_prefs', engine, 
+            Column('pref_id', INTEGER, primary_key = True),
+            Column('user_id', INTEGER, nullable = False, foreign_key = ForeignKey(users.c.user_id))
+            Column('pref_name', VARCHAR(40), nullable = False),
+            Column('pref_value', VARCHAR(100))
+        )
+        </&>
+
+        <p>Metadata objects can also be <b>reflected</b> from tables that already exist in the database.  Reflection means based on a table name, the names, datatypes, and attributes of all columns, including foreign keys, will be loaded automatically.  This feature is supported by all database engines:</p>
+        <&|formatting.myt:code&>
+        >>> messages = Table('messages', engine, autoload = True)
+        >>> [c.name for c in messages.columns]
+        ['message_id', 'message_name', 'date']
+        </&>
+        
+        <p>
+        Note that if a reflected table has a foreign key referencing another table, then the metadata for the related table will be loaded as well, even if it has not been defined by the application:              
+        </p>
+        <&|formatting.myt:code&>
+        >>> shopping_cart_items = Table('shopping_cart_items', engine, autoload = True)
+        >>> print shopping_cart_items.c.cart_id.table.name
+        shopping_carts
+        </&>
+        <p>To get direct access to 'shopping_carts', simply instantiate it via the Table constructor.  You'll get the same instance of the shopping cart Table as the one that is attached to shopping_cart_items:
+        <&|formatting.myt:code&>
+        >>> shopping_carts = Table('shopping_carts', engine)
+        >>> shopping_carts is shopping_cart_items.c.cart_id.table.name
+        True
+        </&>
+        <p>This works because when the Table constructor is called for a particular name and database engine, if the table has already been created then the instance returned will be the same as the original.  This is a <b>singleton</b> constructor:</p>
+        <&|formatting.myt:code&>
+        >>> news_articles = Table('news', engine, 
+        ... Column('article_id', INTEGER, primary_key = True),
+        ... Column('url', VARCHAR(250), nullable = False)
+        ... )
+        >>> othertable = Table('news', engine)
+        >>> othertable is news_articles
+        True
+        </&>
+        
+        
+
     </&>
     <&|doclib.myt:item, name="building", description="Building and Dropping Database Tables" &>
     </&>
index deed372463673c718aa95af2377e588cd8f8b64f..398e14fc19931d621c7d628ecb8f68d6dab1c5f4 100644 (file)
@@ -13,13 +13,13 @@ Start
   |              |------ Connection Pooling Configuration
   |                                         |              
   |                                         |
-  |--- <&formatting.myt:link, path="dbengine_establishing" &>       |
+  +--- <&formatting.myt:link, path="dbengine_establishing" &>       |
                    |                        |
                    |                        | 
                    |--------- <&formatting.myt:link, path="dbengine_options" &>
                    |
                    |
-                   |---- <&formatting.myt:link, path="metadata_tables" &>
+                   +---- <&formatting.myt:link, path="metadata_tables" &>
                                    |
                                    |
                                    |---- <&formatting.myt:link, path="metadata_building" &>
@@ -31,9 +31,9 @@ Start
                                    |---- Basic Data Mapping               |                
                                    |               |                      |  
                                    |               |                      |              
-                                   |               |----------- Advanced Data Mapping
+                                   |               +----------- Advanced Data Mapping
                                    |                                        
                                    |                
-                                   |----- Basic Active Record
+                                   +----- Basic Active Record
 </pre>
 </&>
\ No newline at end of file
index d03b99988735347e632a2f099b8e8ba95de98572..53f4596d993514591f27304ec43d6bdc5a0a3937 100644 (file)
@@ -181,7 +181,7 @@ class Mapper(object):
         list of primary keys in the order of the table def's primary keys."""
         key = objectstore.get_id_key(ident, self.class_, self.table)
         try:
-            return objectstore.get(key)
+            return objectstore.uow()._get(key)
         except KeyError:
             clause = sql.and_()
             i = 0
@@ -195,11 +195,6 @@ class Mapper(object):
             except IndexError:
                 return None
 
-    def put(self, instance):
-        key = self.identity_key(instance)
-        objectstore.put(key, instance, self.scope)
-        return key
-
     def identity_key(self, instance):
         return objectstore.get_id_key(tuple([self._getattrbycolumn(instance, column) for column in self.primary_keys[self.selectable]]), self.class_, self.table)
 
@@ -290,9 +285,12 @@ class Mapper(object):
                                 self._setattrbycolumn(obj, col, primary_key)
                                 found = True
 
-    def register_dependencies(self, obj, uow):
+    def delete_obj(self, objects, uow):
+        pass
+
+    def register_dependencies(self, *args, **kwargs):
         for prop in self.props.values():
-            prop.register_dependencies(obj, uow)
+            prop.register_dependencies(*args, **kwargs)
             
     def transaction(self, f):
         return self.table.engine.multi_transaction(self.tables, f)
@@ -330,7 +328,7 @@ class Mapper(object):
         # been exposed to being modified by the application.
         identitykey = self._identity_key(row)
         if objectstore.has_key(identitykey):
-            instance = objectstore.get(identitykey)
+            instance = objectstore.uow()._get(identitykey)
             if result is not None:
                 result.append_nohistory(instance)
 
@@ -371,8 +369,6 @@ class Mapper(object):
             
         return instance
 
-    def rollback(self, obj):
-        objectstore.uow().rollback_object(obj)
         
 class MapperOption:
     """describes a modification to a Mapper in the context of making a copy
@@ -403,7 +399,7 @@ class MapperProperty:
     def delete(self, object):
         """called when the instance is being deleted"""
         pass
-    def register_dependencies(self, obj, uow):
+    def register_dependencies(self, *args, **kwargs):
         pass
 
 class ColumnProperty(MapperProperty):
@@ -514,23 +510,25 @@ class PropertyLoader(MapperProperty):
         else:
             return sql.and_(crit)
             
-    def register_dependencies(self, objlist, uow):
+    def register_dependencies(self, uowcommit):
         if self.secondaryjoin is not None:
             # with many-to-many, set the parent as dependent on us, then the 
             # list of associations as dependent on the parent
             # if only a list changes, the parent mapper is the only mapper that
             # gets added to the "todo" list
-            uow.register_dependency(self.mapper, self.parent, None, None)
-            uow.register_dependency(self.parent, None, self, objlist)
+            uowcommit.register_dependency(self.mapper, self.parent)
+            uowcommit.register_task(self.parent, self, uowcommit.get_objects(self.parent), False)
         elif self.foreignkey.table == self.target:
-            uow.register_dependency(self.parent, self.mapper, self, objlist)
+            uowcommit.register_dependency(self.parent, self.mapper)
+            uowcommit.register_task(self.parent, self, uowcommit.get_objects(self.parent), False)
         elif self.foreignkey.table == self.parent.table:
-            uow.register_dependency(self.mapper, self.parent, self, objlist)
+            uowcommit.register_dependency(self.mapper, self.parent)
+            uowcommit.register_task(self.mapper, self, uowcommit.get_objects(self.parent), False)
         else:
             raise " no foreign key ?"
-
+                
     def process_dependencies(self, deplist, uowcommit, delete = False):
-
+        print self.mapper.table.name + " process_dep"
         def getlist(obj):
             if self.uselist:
                 return uowcommit.uow.attributes.get_list_history(obj, self.key)
@@ -548,20 +546,28 @@ class PropertyLoader(MapperProperty):
             secondary_insert = []
             for obj in deplist:
                 childlist = getlist(obj)
-                for child in childlist.added_items():
-                    associationrow = {}
-                    self.primaryjoin.accept_visitor(setter)
-                    self.secondaryjoin.accept_visitor(setter)
-                    secondary_insert.append(associationrow)
-                for child in childlist.deleted_items():
-                    associationrow = {}
+                if delete:
                     clearkeys = True
-                    self.primaryjoin.accept_visitor(setter)
-                    self.secondaryjoin.accept_visitor(setter)
-                    secondary_delete.append(associationrow)
-                    if self.private:
-                        uowcommit.add_item_to_delete(obj)
-                uowcommit.register_saved_list(childlist)
+                    for child in childlist.deleted_items() + childlist.unchanged_items():
+                        associationrow = {}
+                        self.primaryjoin.accept_visitor(setter)
+                        self.secondaryjoin.accept_visitor(setter)
+                        secondary_delete.append(associationrow)
+                    uowcommit.register_removed_list(childlist)
+                else:
+                    clearkeys = False
+                    for child in childlist.added_items():
+                        associationrow = {}
+                        self.primaryjoin.accept_visitor(setter)
+                        self.secondaryjoin.accept_visitor(setter)
+                        secondary_insert.append(associationrow)
+                    clearkeys = True
+                    for child in childlist.deleted_items():
+                        associationrow = {}
+                        self.primaryjoin.accept_visitor(setter)
+                        self.secondaryjoin.accept_visitor(setter)
+                        secondary_delete.append(associationrow)
+                    uowcommit.register_saved_list(childlist)
             if len(secondary_delete):
                 statement = self.secondary.delete(sql.and_(*[c == sql.bindparam(c.key) for c in self.secondary.c]))
                 statement.execute(*secondary_delete)
@@ -577,8 +583,6 @@ class PropertyLoader(MapperProperty):
                     for child in childlist.deleted_items() + childlist.current_items():
                         self.primaryjoin.accept_visitor(setter)
                         uowcommit.register_saved_list(childlist)
-                        if self.private:
-                            uowcommit.add_item_to_delete(child)
                 else:
                     clearkeys = False
                     for child in childlist.added_items():
@@ -588,8 +592,6 @@ class PropertyLoader(MapperProperty):
                     for child in childlist.deleted_items():
                          self.primaryjoin.accept_visitor(setter)
                          uowcommit.register_saved_list(childlist)
-                         if self.private:
-                             uowcommit.add_item_to_delete(child)
         elif self.foreignkey.table == self.parent.table:
             associationrow = {}
             for child in deplist:
@@ -598,8 +600,6 @@ class PropertyLoader(MapperProperty):
                     for obj in childlist.deleted_items() + childlist.current_items():
                         self.primaryjoin.accept_visitor(setter)
                         uowcommit.register_saved_list(childlist)
-                        if self.private:
-                            uowcommit.add_item_to_delete(obj)
                 else:
                     clearkeys = False
                     for obj in childlist.added_items():
@@ -607,8 +607,6 @@ class PropertyLoader(MapperProperty):
                         uowcommit.register_saved_list(childlist)
                     clearkeys = True
                     for obj in childlist.deleted_items():
-                        if self.private:
-                            uowcommit.add_item_to_delete(obj)
                         self.primaryjoin.accept_visitor(setter)
                         uowcommit.register_saved_list(childlist)
         else:
index 7038c165763a1c95bdf254bcbf8ffcd9e0477ea1..2b00e7d66fc8fd60d598b818820507bc5b7b1d65 100644 (file)
@@ -52,49 +52,11 @@ def get_row_key(row, class_, table, primary_keys):
     """
     return (class_, table, tuple([row[column.label] for column in primary_keys]))
 
-identity_map = {}
-
-def get(key):
-    val = identity_map[key]
-    if isinstance(val, dict):
-        return val[thread.get_ident()]
-    else:
-        return val
-    
-def put(key, obj, scope='thread'):
-    if isinstance(obj, dict):
-        raise "cant put a dict in the object store"
-    
-    if scope == 'thread':
-        try:
-            d = identity_map[key]
-        except KeyError:
-            d = identity_map.setdefault(key, {})
-        d[thread.get_ident()] = obj
-    else:
-        identity_map[key] = obj
-
-def clear(scope='thread'):
-    if scope == 'thread':
-        for k in identity_map.keys():
-            if isinstance(identity_map[k], dict):
-                identity_map[k].clear()
-        uow.set(UnitOfWork())
-    else:
-        for k in identity_map.keys():
-            if not isinstance(identity_map[k], dict):
-                del identity_map[k]
-        uow.set(UnitOfWork(), scope="application")
+def clear():
+    uow.set(UnitOfWork())
             
 def has_key(key):
-    if identity_map.has_key(key):
-        d = identity_map[key]
-        if isinstance(d, dict):
-            return d.has_key(thread.get_ident())
-        else:
-            return True
-    else:
-        return False
+    return uow().identity_map.has_key(key)
 
 class UOWSmartProperty(attributes.SmartProperty):
     def attribute_registry(self):
@@ -124,6 +86,10 @@ class UOWAttributeManager(attributes.AttributeManager):
 class UnitOfWork(object):
     def __init__(self, parent = None, is_begun = False):
         self.is_begun = is_begun
+        if parent is not None:
+            self.identity_map = parent.identity_map
+        else:
+            self.identity_map = {}
         self.attributes = UOWAttributeManager(self)
         self.new = util.HashSet()
         self.dirty = util.HashSet()
@@ -131,16 +97,29 @@ class UnitOfWork(object):
         self.deleted = util.HashSet()
         self.parent = parent
 
+    def get(self, class_, *id):
+        return sqlalchemy.mapper.object_mapper(class_).get(*id)
+
+    def _get(self, key):
+        return self.identity_map[key]
+        
+    def _put(self, key, obj):
+        self.identity_map[key] = obj
+        
+    def update(self, obj):
+        """called to add an object to this UnitOfWork as though it were loaded from the DB, but is
+        actually coming from somewhere else, like a web session or similar."""
+        self._put(obj._instance_key, obj)
+        self.register_dirty(obj)
+        
     def register_attribute(self, class_, key, uselist):
         self.attributes.register_attribute(class_, key, uselist)
         
     def attribute_set_callable(self, obj, key, func):
         obj.__dict__[key] = func
 
-    def rollback_object(self, obj):
-        self.attributes.rollback(obj)
     
-    def register_clean(self, obj, scope="thread"):
+    def register_clean(self, obj):
         try:
             del self.dirty[obj]
         except KeyError:
@@ -149,8 +128,7 @@ class UnitOfWork(object):
             del self.new[obj]
         except KeyError:
             pass
-        # TODO: figure scope out from what scope of this UOW is
-        put(obj._instance_key, obj, scope=scope)
+        self._put(obj._instance_key, obj)
         # TODO: get lists off the object and make sure theyre clean too ?
         
     def register_new(self, obj):
@@ -217,6 +195,9 @@ class UnitOfWork(object):
         if self.parent:
             uow.set(self.parent)
 
+    def rollback_object(self, obj):
+        self.attributes.rollback(obj)
+
     def rollback(self):
         if not self.is_begun:
             raise "UOW transaction is not begun"
@@ -233,25 +214,38 @@ class UOWTransaction(object):
         self.saved_objects = util.HashSet()
         self.saved_lists = util.HashSet()
         self.deleted_objects = util.HashSet()
-        self.todelete = util.HashSet()
 
     def append_task(self, obj):
         mapper = self.object_mapper(obj)
         task = self.get_task_by_mapper(mapper)
         task.objects.append(obj)
 
-    def get_task_by_mapper(self, mapper):
+    def add_item_to_delete(self, obj):
+        mapper = self.object_mapper(obj)
+        task = self.get_task_by_mapper(mapper)
+        task.todelete.append(obj)
+
+    def get_task_by_mapper(self, mapper, isdelete = False):
         try:
-            return self.tasks[mapper]
+            return self.tasks[(mapper, isdelete)]
         except KeyError:
-            return self.tasks.setdefault(mapper, UOWTask(mapper))
+            return self.tasks.setdefault((mapper, isdelete), UOWTask(mapper, isdelete))
 
+    def get_objects(self, mapper, isdelete = False):
+        try:
+            task = self.tasks[(mapper, isdelete)]
+        except KeyError:
+            return []
+            
+        return task.objects
+            
     # TODO: better interface for tasks with no object save, or multiple dependencies
-    def register_dependency(self, mapper, dependency, processor, stuff_to_process):
+    def register_dependency(self, mapper, dependency):
         self.dependencies[(mapper, dependency)] = True
-        task = self.get_task_by_mapper(mapper)
-        if processor is not None:
-            task.dependencies.append((processor, stuff_to_process))
+
+    def register_task(self, mapper, processor, objects, isdelete):
+        task = self.get_task_by_mapper(mapper, isdelete)
+        task.dependencies.append((processor, objects))
 
     def register_saved_object(self, obj):
         self.saved_objects.append(obj)
@@ -262,8 +256,6 @@ class UOWTransaction(object):
     def register_deleted(self, obj):
         self.deleted_objects.append(obj)
         
-    def add_item_to_delete(self, obj):
-        self.todelete.append(obj)
         
     def object_mapper(self, obj):
         import sqlalchemy.mapper
@@ -277,9 +269,9 @@ class UOWTransaction(object):
             
     def execute(self):
         for task in self.tasks.values():
-            task.mapper.register_dependencies(task.objects, self)
+            task.mapper.register_dependencies(self)
             
-        mapperlist = self.tasks.values()
+        tasklist = self.tasks.values()
         def compare(a, b):
             if self.dependencies.has_key((a.mapper, b.mapper)):
                 return -1
@@ -287,15 +279,21 @@ class UOWTransaction(object):
                 return 1
             else:
                 return 0
-        mapperlist.sort(compare)
+        tasklist.sort(compare)
 
-        for task in mapperlist:
+        import string
+        for task in tasklist:
             obj_list = task.objects
-            task.mapper.save_obj(obj_list, self)
+            if len(obj_list):
+                print "t:" + string.join([o.__class__.__name__ for o in obj_list])
+            if not task.isdelete:
+                task.mapper.save_obj(obj_list, self)
             for dep in task.dependencies:
-                (processor, stuff_to_process) = dep
-                processor.process_dependencies(stuff_to_process, self)
-
+                (processor, stuff) = dep
+                processor.process_dependencies(stuff, self, delete = task.isdelete)
+            if task.isdelete:
+                task.mapper.delete_obj(obj_list, self)
+            
     def post_exec(self):
         for obj in self.saved_objects:
             mapper = self.object_mapper(obj)
@@ -310,8 +308,9 @@ class UOWTransaction(object):
 
         
 class UOWTask(object):
-    def __init__(self, mapper):
+    def __init__(self, mapper, isdelete = False):
         self.mapper = mapper
+        self.isdelete = isdelete
         self.objects = util.HashSet()
         self.dependencies = []
         
index c1982dfe4dbb49ef2a87314e621d31bc406ede5a..4df8d77a35c95e4a2fd944c89e5ca9b1fafb275c 100644 (file)
@@ -28,6 +28,7 @@ class INT:
     """integer datatype"""
     pass
 
+INTEGER = INT
 class CHAR:
     """character datatype"""
     def __init__(self, length):
index e2db30ca8c3a86540412ee1678f2d0f850afcb19..ceea987af18edf2b7925cac821d838d7aad0fa40 100644 (file)
@@ -21,7 +21,7 @@ class MapperTest(AssertMixin):
         u = m.get(7)
         u2 = m.get(7)
         self.assert_(u is u2)
-        objectstore.clear("thread")
+        objectstore.clear()
         u2 = m.get(7)
         self.assert_(u is not u2)
 
index 96839acccdb2a31d6ca7d9b03ac9b6ba0de6f3d3..262fa0c465c0a47f775a02e3df9502aeecbd2970 100644 (file)
@@ -7,7 +7,6 @@ import sqlalchemy.objectstore as objectstore
 ECHO = False
 DATA = False
 execfile("test/tables.py")
-db.echo = True
 
 keywords.insert().execute(
     dict(keyword_id=1, name='blue'),
@@ -21,6 +20,8 @@ keywords.insert().execute(
 
 db.connection().commit()
 
+db.echo = True
+
 class HistoryTest(AssertMixin):
     def testattr(self):
         m = mapper(User, users, properties = dict(addresses = relation(Address, addresses)))
@@ -33,7 +34,7 @@ class HistoryTest(AssertMixin):
         u.addresses[1].email_address = 'there'
         print repr(u.__dict__)
         print repr(u.addresses)
-        m.rollback(u)
+        objectstore.uow().rollback_object(u)
         print repr(u.__dict__)
         
 class SaveTest(AssertMixin):
@@ -130,6 +131,7 @@ class SaveTest(AssertMixin):
 #        m = mapper(Address, addresses, properties = dict(
 #            user = relation(User, users, foreignkey = addresses.c.user_id, primaryjoin = users.c.user_id == addresses.c.user_id, lazy = True, uselist = False)
 #        ))
+        # TODO: put assertion in here !!!
         m = mapper(Address, addresses, properties = dict(
             user = relation(User, users, lazy = True, uselist = False)
         ))