]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
dev
authorMike Bayer <mike_mp@zzzcomputing.com>
Sun, 4 Dec 2005 00:19:07 +0000 (00:19 +0000)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sun, 4 Dec 2005 00:19:07 +0000 (00:19 +0000)
doc/build/content/adv_datamapping.myt
doc/build/content/datamapping.myt
doc/build/content/dbengine.myt
doc/build/content/docstrings.myt
doc/build/content/metadata.myt

index 54f4dbaaf24adead1d574b856665a2379c2a6d31..31da6f77168830a7b4b6c8e1a3dedb6ae891296e 100644 (file)
@@ -6,7 +6,7 @@
 <p>To start, heres the tables we will work with again:</p>
        <&|formatting.myt:code&>
         from sqlalchemy import *
-        db = create_engine('sqlite', {'filename':'mydb'}, echo=True)
+        db = create_engine('sqlite://filename=mydb', echo=True)
         
         # a table to store users
         users = Table('users', db,
        
        </&>
 
-<&|doclib.myt:item, name="creating", description="Creating Mappers" &>
+<&|doclib.myt:item, name="creatingrelations", description="Creating Mapper Relations" &>
     <&|doclib.myt:item, name="customjoin", description="Custom Join Conditions" &>
+        <p>When creating relations on a mapper, most examples so far have illustrated the mapper and relationship joining up based on the foreign keys of the tables they represent.  in fact, this "automatic" inspection can be completely circumvented using the <span class="codeline">primaryjoin</span> and <span class="codeline">secondaryjoin</span> arguments to <span class="codeline">relation</span>, as in this example which creates a User object which has a relationship to all of its Addresses which are in Boston:
         <&|formatting.myt:code&>
-        </&>    
+            class User(object):
+                pass
+            class Address(object):
+                pass
+            Address.mapper = mapper(Address, addresses)
+            User.mapper = mapper(User, users, properties={
+                'boston_addreses' : relation(Address.mapper, primaryjoin=
+                            and_(users.c.user_id==Address.c.user_id, 
+                            Addresses.c.city=='Boston'))
+            })
+        </&>
+        <P>Many to many relationships can be customized by one or both of <span class="codeline">primaryjoin</span> and <span class="codeline">secondaryjoin</span>, shown below with just the default many-to-many relationship explicitly set:</p>
+        <&|formatting.myt:code&>
+        class User(object):
+            pass
+        class Keyword(object):
+            pass
+        Keyword.mapper = mapper(Keyword, keywords)
+        User.mapper = mapper(User, users, properties={
+            'keywords':relation(Keyword.mapper, 
+                primaryjoin=users.c.user_id==userkeywords.c.user_id,
+                secondaryjoin=userkeywords.c.keyword_id==keywords.c.keyword_id
+                )
+        })
+        </&>
     </&>
+    <&|doclib.myt:item, name="multiplejoin", description="Lazy/Eager Joins Multiple Times to One Table" &>
 
-    <&|doclib.myt:item, name="loadingoptions", description="Loading Options" &>
+        <p>The previous example leads in to the idea of joining against the same table multiple times.  Below is a User object that has lists of its Boston and New York addresses, both lazily loaded when they are first accessed:</p>
         <&|formatting.myt:code&>
-        </&>    
+        User.mapper = mapper(User, users, properties={
+            'boston_addreses' : relation(Address.mapper, primaryjoin=
+                        and_(users.c.user_id==Address.c.user_id, 
+                        Addresses.c.city=='Boston')),
+            'newyork_addresses' : relation(Address.mapper, primaryjoin=
+                        and_(users.c.user_id==Address.c.user_id, 
+                        Addresses.c.city=='New York')),
+        })
+        </&>
+        <p>A complication arises with the above pattern if you want the relations to be eager loaded.  Since there will be two separate joins to the addresses table during an eager load, an alias needs to be used to separate them.  You can create an alias of the addresses table to separate them, but then you are in effect creating a brand new mapper for each property, unrelated to the main Address mapper, which can create problems with commit operations.  So an additional argument <span class="codeline">selectalias</span> can be used with an eager relationship to specify the alias to be used just within the eager query:</p>
+        <&|formatting.myt:code&>
+        User.mapper = mapper(User, users, properties={
+            'boston_addreses' : relation(Address.mapper, primaryjoin=
+                        and_(User.c.user_id==Address.c.user_id, 
+                        Addresses.c.city=='Boston'), lazy=False, selectalias='boston_ad'),
+            'newyork_addresses' : relation(Address.mapper, primaryjoin=
+                        and_(User.c.user_id==Address.c.user_id, 
+                        Addresses.c.city=='New York'), lazy=False, selectalias='newyork_ad'),
+        })
+        
+        <&formatting.myt:poplink&>u = User.mapper.select()
+
+        <&|formatting.myt:codepopper, link="sql" &>
+        SELECT users.user_id AS users_user_id, users.user_name AS users_user_name, 
+        users.password AS users_password, 
+        boston_ad.address_id AS boston_ad_address_id, boston_ad.user_id AS boston_ad_user_id, 
+        boston_ad.street AS boston_ad_street, boston_ad.city AS boston_ad_city, 
+        boston_ad.state AS boston_ad_state, boston_ad.zip AS boston_ad_zip, 
+        newyork_ad.address_id AS newyork_ad_address_id, newyork_ad.user_id AS newyork_ad_user_id, 
+        newyork_ad.street AS newyork_ad_street, newyork_ad.city AS newyork_ad_city, 
+        newyork_ad.state AS newyork_ad_state, newyork_ad.zip AS newyork_ad_zip 
+        FROM users 
+        LEFT OUTER JOIN addresses AS boston_ad ON users.user_id = boston_ad.user_id 
+        AND boston_ad.city = :addresses_city 
+        LEFT OUTER JOIN addresses AS newyork_ad ON users.user_id = newyork_ad.user_id 
+        AND newyork_ad.city = :addresses_city_1
+        ORDER BY users.oid, boston_ad.oid, newyork_ad.oid
+        {'addresses_city_1': 'New York', 'addresses_city': 'Boston'}
+        </&>
+        </&>
+    </&>
+
+    <&|doclib.myt:item, name="relationoptions", description="Relation Options" &>
+    Keyword options to the <span class="codeline">relation</span> function include:
+    <ul>
+        <li>primaryjoin - a ClauseElement that will be used as the primary join of this child object against the parent object, or in a many-to-many relationship the join of the primary object to the association table.  By default, this value is computed based on the foreign key relationships of the parent and child tables (or association table).</li>
+        <li>secondaryjoin - a ClauseElement that will be used as the join of an association table to the child object.  By default, this value is computed based on the foreign key relationships of the association and child tables.</li>
+        <li>foreignkey - specifies which column in this relationship is "foreign", i.e. which column refers to the parent object.  This value is automatically determined in all cases, based on the primary and secondary join conditions, except in the case of a self-referential mapper, where it is needed to indicate the child object's reference back to it's parent.</li>
+        <li>uselist - a boolean that indicates if this property should be loaded as a list or a scalar.  In most cases, this value is determined based on the type and direction of the relationship - one to many forms a list, one to one forms a scalar, many to many is a list.  If a scalar is desired where normally a list would be present, set uselist to False.</li>
+        <li>private - indicates if these child objects are "private" to the parent; removed items will also be deleted, and if the parent item is deleted, all child objects are deleted as well.</li>
+        <li>live - a special type of "lazy load" where the list values will be loaded on every access.  A "live" property should be treated as read-only.  This type of property is useful in combination with "private" when used with a parent object which wants to force a delete of all its child items, attached or not, when it is deleted; since it always loads everything when accessed, you can be guaranteed that all child objects will be properly removed as well.</li>
+        <li>association - When specifying a many to many relationship with an association object, this keyword should reference the mapper of the target object of the association.  See the example in <&formatting.myt:link, path="datamapping_association"&>.</li>
+        <li>selectalias - Useful with eager loads, this specifies a table alias name that will be used when creating joins against the parent table.  The property is still created against the original table, and the aliased table is used only for the actual query.  Aliased columns in the result set are translated back to that of the original table when creating object instances.</li>
+    </ul>
     </&>
 
 </&>
 
 <&|doclib.myt:item, name="options", description="Mapper Options" &>
+    <P>The <span class="codeline">options</span> method of mapper produces a copy of the mapper, with modified properties and/or options.  This makes it easy to take a mapper and just change a few things on it.  The method takes a variable number of <span class="codeline">MapperOption</span> objects which know how to change specific things about the mapper.  The four available options are <span class="codeline">eagerload</span>, <span class="codeline">lazyload</span>, <span class="codeline">noload</span> and <span class="codeline">extension</span>.</p>
+    <P>An example of a mapper with a lazy load relationship, upgraded to an eager load relationship:
+        <&|formatting.myt:code&>
+        class User(object):
+            pass
+        class Address(object):
+            pass
+        
+        # a 'lazy' relationship
+        User.mapper = mapper(User, users, properties = {
+            'addreses':relation(Address, addresses, lazy=True)
+        })
+    
+        # copy the mapper and convert 'addresses' to be eager
+        eagermapper = User.mapper.options(eagerload('addresses'))
+        </&>
+    
+    <p>The load options also can take keyword arguments that apply to the new relationship.  To take the "double" address lazy relationship from the previous section and upgrade it to eager, adding the "selectalias" keywords as well:</p>
     <&|formatting.myt:code&>
-    </&>    
+        m = User.mapper.options(
+                eagerload('boston_addresses', selectalias='boston_ad'), 
+                eagerload('newyork_addresses', selectalias='newyork_ad')
+            )
+    </&>
 </&>
 
 <&|doclib.myt:item, name="custom", description="Custom Queries" &>
     </&>    
     <p>This kind of mapper goes through a lot of extra effort when saving and deleting items, to determine the correct dependency graph of nodes within the tree.</p>
 </&>
+<&|doclib.myt:item, name="circular", description="Circular Mapping" &>
+<p>Oftentimes it is necessary for two mappers to be related to each other.  With a datamodel that consists of Users that store Addresses, you might have an Address object and want to access the "user" attribute on it, or have a User object and want to get the list of Address objects.  To achieve this involves creating the first mapper not referencing the second, then creating the second mapper referencing the first, then adding references to the first mapper to reference the second:</p>
+<&|formatting.myt:code&>
+    class User(object):
+        pass
+    class Address(object):
+        pass
+    User.mapper = mapper(User, users)
+    Address.mapper = mapper(Address, addresses, properties={
+        'user':relation(User.mapper)
+    })
+    User.mapper.add_property('addresses', relation(Address.mapper))
+</&>
+<p>Note that with a circular relationship as above, you cannot declare both relationships as "eager" relationships, since that produces a circular query situation which will generate a recursion exception.  So what if you want to then load an Address and its User eagerly?  Just make a second mapper using options:
+<&|formatting.myt:code&>
+    eagermapper = Address.mapper.options(eagerload('user'))
+    s = eagermapper.select(Address.c.address_id==12)
+</&>
+</&>
 <&|doclib.myt:item, name="resultset", description="Result-Set Mapping" &>
     <p>Take any result set and feed it into a mapper to produce objects.  Multiple mappers can be combined to retrieve unrelated objects from the same row in one step.</p>
     <&|formatting.myt:code&>
index 5c4c3e4e06b439cf871e2ec0ff09e2157444e185..71747b420043858c16efddc8db50ddefb36b50ba 100644 (file)
@@ -596,7 +596,7 @@ INSERT INTO article_keywords (article_id, keyword_id) VALUES (:article_id, :keyw
             # lazy loading for that.
             m = mapper(Article, articles, properties=dict(
                 keywords = relation(KeywordAssociation, itemkeywords, lazy=False, association=Keyword, 
-                    primary_keys = [itemkeywords.c.article_id, itemkeywords.c.keyword_id],
+                    primary_key = [itemkeywords.c.article_id, itemkeywords.c.keyword_id],
                     properties={
                         'keyword' : relation(Keyword, lazy = False),
                         'user' : relation(User, lazy = True)
index 754b4f71f983894049a85c2c378ab8b51edab248..2af9fb25b4a54c4fd2ab78e69efeaa2e120a2ed7 100644 (file)
                             'user':'scott', 
                             'password':'tiger'}, **opts)
 
+    # mysql
+    mysql_engine = create_engine('mysql',
+                            {
+                                'db':'mydb',
+                                'user':'scott',
+                                'passwd':'tiger',
+                                'host':'127.0.0.1'
+                            }
+                            **opts)
     # oracle
     oracle_engine = create_engine('oracle', 
                             {'dsn':'mydsn', 
                     )
     </&>
     <p>The second argument is a dictionary whose key/value pairs will be passed to the underlying DBAPI connect() method as keyword arguments.  Any keyword argument supported by the DBAPI module can be in this dictionary.</p>
-    <p>An additional URL-string based calling style will also be added soon, as this is a highly requested feature.
+    <p>Engines can also be loaded by URL.  The above format is converted into <span class="codeline"><% '<enginename>://key=val&key=val' |h %></span>:
+        <&|formatting.myt:code&>
+            sqlite_engine = create_engine('sqlite://filename=querytest.db')
+            postgres_engine = create_engine('postgres://database=test&user=scott&password=tiger')
+        </&>
     </p>
     </&>
     <&|doclib.myt:item, name="options", description="Database Engine Options" &>
index b45c90f757404e7be0aa54763f87e99a2b729968..24f6e52c4e358f4c7e2fdf039eab742b011e0901 100644 (file)
@@ -6,8 +6,7 @@
     import sqlalchemy.engine as engine
     import sqlalchemy.sql as sql
     import sqlalchemy.pool as pool
-    import sqlalchemy.mapper as mapper
-    import sqlalchemy.objectstore as objectstore
+    import sqlalchemy.mapping as mapping
 </%init>
 
 
@@ -15,6 +14,6 @@
 <& pydoc.myt:obj_doc, obj=engine, classes=[engine.SQLEngine, engine.ResultProxy, engine.RowProxy] &>
 <& pydoc.myt:obj_doc, obj=sql &>
 <& pydoc.myt:obj_doc, obj=pool, classes=[pool.DBProxy, pool.Pool, pool.QueuePool, pool.SingletonThreadPool] &>
-<& pydoc.myt:obj_doc, obj=mapper &>
-<& pydoc.myt:obj_doc, obj=objectstore, classes=[objectstore.UnitOfWork] &>
+<& pydoc.myt:obj_doc, obj=mapping &>
+<& pydoc.myt:obj_doc, obj=mapping.objectstore, classes=[mapping.objectstore.UnitOfWork] &>
 </&>
\ No newline at end of file
index b0d66a498681604b5efa14c1a4bac53f4f8ab1c3..dcc0e5a11ddeca719c822c7da51a3680ea3502e6 100644 (file)
@@ -43,7 +43,7 @@
             # ...
             
         # get the table's primary key columns
-        for primary_key in employees.primary_keys:
+        for primary_key in employees.primary_key:
             # ...
         
         # get the table's foreign key objects: