]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- columns can be overridden in a reflected table with a "key"
authorMike Bayer <mike_mp@zzzcomputing.com>
Sun, 15 Jul 2007 04:50:11 +0000 (04:50 +0000)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sun, 15 Jul 2007 04:50:11 +0000 (04:50 +0000)
attribute different than the column's name, including for primary key
columns [ticket:650]
- more docs

CHANGES
doc/build/content/datamapping.txt
lib/sqlalchemy/sql.py
test/engine/reflection.py

diff --git a/CHANGES b/CHANGES
index 6caab88289ae2856a2b5f9915bcaf220f5504c82..cdd08b9069174fe92937386db4990463b257d128 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -62,6 +62,9 @@
       this is also mostly to help inheritance scenarios formulate the best 
       choice of primary key columns.  [ticket:185]
     - added 'bind' argument to Sequence.create()/drop(), ColumnDefault.execute()
+    - columns can be overridden in a reflected table with a "key" 
+      attribute different than the column's name, including for primary key
+      columns [ticket:650]
     - some enhancements to "column targeting", the ability to match a column
       to a "corresponding" column in another selectable.  this affects mostly
       ORM ability to map to complex joins
index b0bcf220c3fcadbfff8a823a00e2d95778b7c76f..85c32adb2ceccacd5f633b09ba485d16ac07a267 100644 (file)
@@ -562,6 +562,15 @@ But the easiest way to join is automatically, using the `join()` method on `Quer
     ORDER BY users.oid
     {'addresses_street', '123 Green Street'}
 
+Each time the `join()` is called on `Query`, the **joinpoint** of the query is moved to be that of the endpoint of the join.  As above, when we joined from `users_table` to `addresses_table`, all subsequent criterion used by `filter_by()` are against the `addresses` table.  If we wanted to filter back on the starting table again, we can use the `reset_joinpoint()` function:
+
+    {python}
+    l = session.query(User).join('addresses').
+            filter(addresses_table.c.street=='123 Green Street').
+            reset_joinpoint().filter_by(user_name='ed').all()
+
+With `reset_joinpoint()`, we can also issue new `join()`s which will start back from the root table.
+
 In all cases, we can get the `User` and the matching `Address` objects back at the same time, by telling the session we want both.  This returns the results as a tuple:
 
     {python}
@@ -616,6 +625,29 @@ You can also create joins in "reverse", that is, to find an object with a certai
     addresses.user_id = :users_user_id ORDER BY addresses.oid
     {'users_user_id': 1, 'addresses_email_address': 'foo@bar.com'}
 
+Sometimes it's necessary to create repeated joins that are independent of each other, even though they reference the same tables.  Using our one-to-many setup, an example is to locate users who have two partcular email addresses.  We can do this using table aliases:
+
+    {python}
+    ad1 = addresses_table.alias('ad1')
+    ad2 = addresses_table.alias('ad2')
+    {sql}result = session.query(User).filter(and_(
+        ad1.c.user_id==users.c.user_id,
+        ad1.c.email_address=='foo@bar.com',
+        ad2.c.user_id==users.c.user_id,
+        ad2.c.email_address=='lala@yahoo.com'
+        )).all()
+    SELECT users.user_id AS users_user_id, 
+    users.user_name AS users_user_name, users.password AS users_password
+    FROM users, addresses AS ad1, addresses AS ad2
+    WHERE users.user_id=ad1.user_id
+    AND ad1.email_address=:ad1_email_address
+    AND users.user_id=ad2.user_id
+    AND ad2.email_address=:ad2_email_address
+    ORDER BY users.oid
+    {'ad1_email_address': 'foo@bar.com', 'ad2_email_address': 'lala@yahoo.com'}
+
+Version 0.4 of SQLAlchemy will include better ability to issue queries like the above with less verbosity.
+
 ### Loading Relationships {@name=selectrelations}
 
 We've seen how the `relation` specifier affects the saving of an object and its child items, and also how it allows us to build joins.  How to we get the actual related items loaded ?  By default, the `relation()` function indicates that the related property should be attached a *lazy loader* when instances of the parent object are loaded from the database; this is just a callable function that when accessed will invoke a second SQL query to load the child objects of the parent.
index 0961cd4ee35c610cbc64cfe7c3a20a5b1d784ee8..4c720dfa2e1b1f395d3cb6d83c964e5d466a3252 100644 (file)
@@ -1592,6 +1592,13 @@ class ColumnCollection(util.OrderedProperties):
         The key attribute of the column will be used as the hash key
         for this dictionary.
         """
+
+        # Allow an aliased column to replace an unaliased column of the
+        # same name.
+        if self.has_key(column.name):
+            other = self[column.name]
+            if other.name == other.key:
+                del self[other.name]
         self[column.key] = column
     
     def remove(self, column):
index 26cbe3722ae26ba10044b9a599af2d591c255d74..fc0ff400fb8956c2a76b1dd5a492247a3b970354 100644 (file)
@@ -136,7 +136,24 @@ class ReflectionTest(PersistTest):
                 autoload=True)
             
             assert u3.join(a3).onclause == u3.c.id==a3.c.user_id
-            
+
+            meta4 = MetaData(testbase.db)
+            u4 = Table('users', meta4,
+                       Column('id', Integer, key='u_id', primary_key=True),
+                       autoload=True)
+            a4 = Table('addresses', meta4,
+                       Column('id', Integer, key='street', primary_key=True),
+                       Column('street', String(30), key='user_id'),
+                       Column('user_id', Integer, ForeignKey('users.u_id'),
+                              key='id'),
+                       autoload=True)
+
+            assert u4.join(a4).onclause.compare(u4.c.u_id==a4.c.id)
+            assert list(u4.primary_key) == [u4.c.u_id]
+            assert len(u4.columns) == 2
+            assert len(u4.constraints) == 1
+            assert len(a4.columns) == 3
+            assert len(a4.constraints) == 2
         finally:
             meta.drop_all()