]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- added optional constructor to sql.ColumnCollection
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 15 Jan 2007 21:54:16 +0000 (21:54 +0000)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 15 Jan 2007 21:54:16 +0000 (21:54 +0000)
- mapper sets its "primary_key" attribute to be the ultimately decided primary_key column collection post-compilation
- added compare() method to MapperProperty, defines a comparison operation of the columns represented by the property to some value
- all the above combines into today's controversial feature: saying query.select_by(somerelationname=someinstance) will create the join of the primary key columns represented by "somerelationname"'s mapper to the actual primary key in "someinstance".
- docs for the above

CHANGES
doc/build/content/datamapping.txt
lib/sqlalchemy/orm/interfaces.py
lib/sqlalchemy/orm/mapper.py
lib/sqlalchemy/orm/properties.py
lib/sqlalchemy/orm/query.py
lib/sqlalchemy/sql.py
test/orm/mapper.py

diff --git a/CHANGES b/CHANGES
index 892d28e26ab643ce7416c1566af92ca2cc906ef3..97710b56cf1904a7010c01eb0b1b7350e484a960 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -42,6 +42,9 @@
   - Firebird fix to autoload multifield foreign keys [ticket:409]
   - Firebird NUMERIC type properly handles a type without precision [ticket:409]
 - orm:
+  - poked the first hole in the can of worms: saying query.select_by(somerelationname=someinstance)
+  will create the join of the primary key columns represented by "somerelationname"'s mapper to the
+  actual primary key in "someinstance".
   - added a mutex to the mapper compilation step.  ive been reluctant to add any kind
   of threading anything to SA but this is one spot that its its really needed since mappers
   are typically "global", and while their state does not change during normal operation, the 
index 428c72d7502dedf772eecf0221133e17f23e80c9..68a17bea90517f228d28a295a71b066035981bef 100644 (file)
@@ -474,6 +474,18 @@ All keyword arguments sent to `select_by` are used to create query criterion.  T
 
 Note that the `select_by` method, while it primarily uses keyword arguments, also can accomodate `ClauseElement` objects positionally; recall that a `ClauseElement` is genearated when producing a comparison off of a `Column` expression, such as `users.c.name=='ed'`.  When using `ClauseElements` with `select_by`, these clauses are passed directly to the generated SQL and are **not** used to further locate join criterion.  If criterion is being constructed with these kinds of expressions, consider using the `select()` method which is better designed to accomodate these expressions.
 
+As of SA 0.3.4, `select_by()` and related functions can compare not only column-based attributes to column-based values, but also relations to object instances:
+
+    {python}
+    # get an instance of Address
+    someaddress = session.query(Address).get_by(street='123 Green Street')
+    
+    # look for User instances which have the 
+    # "someaddress" instance in their "addresses" collection
+    l = session.query(User).select_by(addresses=someaddress)
+
+Where above, the comparison denoted by `addresses=someaddress` is constructed by comparing all the primary key columns in the `Address` mapper to each corresponding primary key value in the `someaddress` entity.  In other words, its equivalent to saying `select_by(address_id=someaddress.address_id)` ([Alpha API][alpha_api]).
+
 #### Generating Join Criterion Using join\_to, join\_via {@name=jointo}
 
 Feature Status: [Alpha API][alpha_api] 
index 4e9fe55f4363a52d015941034c8f45ebc99fbd60..834f18e281e16b045df1a29797484f2dc2074e6e 100644 (file)
@@ -58,6 +58,10 @@ class MapperProperty(object):
     def merge(self, session, source, dest):
         """merges the attribute represented by this MapperProperty from source to destination object"""
         raise NotImplementedError()
+    def compare(self, value):
+        """returns a compare operation for the columns represented by this MapperProperty to the given value,
+        which may be a column value or an instance."""
+        raise NotImplementedError()
         
 class StrategizedProperty(MapperProperty):
     """a MapperProperty which uses selectable strategies to affect loading behavior.
index 3b1bb1dad9f1653211bc322607bf76054e2fd87f..872f3d1212c7a35b8bbc16aada2717d6cf592fc5 100644 (file)
@@ -441,7 +441,7 @@ class Mapper(object):
 
         if len(self.pks_by_table[self.mapped_table]) == 0:
             raise exceptions.ArgumentError("Could not assemble any primary key columns for mapped table '%s'" % (self.mapped_table.name))
-
+        self.primary_key = self.pks_by_table[self.mapped_table]
 
     def _compile_properties(self):
         """inspects the properties dictionary sent to the Mapper's constructor as well as the mapped_table, and creates
@@ -799,7 +799,7 @@ class Mapper(object):
         prop = self._getpropbycolumn(column, raiseerror)
         if prop is None:
             return NO_ATTRIBUTE
-        #self.__log_debug("get column attribute '%s' from instance %s" % (column.key, mapperutil.instance_str(obj)))
+        #print "get column attribute '%s' from instance %s" % (column.key, mapperutil.instance_str(obj))
         return prop.getattr(obj)
 
     def set_attr_by_column(self, obj, column, value):
index 980424c4e737bd150d6b9050ef15861814f98740..fc945fc7236850c6c1715acfce51aa9bf28b49b1 100644 (file)
@@ -57,13 +57,15 @@ class ColumnProperty(StrategizedProperty):
         else:
             return strategies.ColumnLoader(self)
     def getattr(self, object):
-        return getattr(object, self.key, None)
+        return getattr(object, self.key)
     def setattr(self, object, value):
         setattr(object, self.key, value)
     def get_history(self, obj, passive=False):
         return sessionlib.attribute_manager.get_history(obj, self.key, passive=passive)
     def merge(self, session, source, dest):
         setattr(dest, self.key, getattr(source, self.key, None))
+    def compare(self, value):
+        return self.columns[0] == value
         
 ColumnProperty.logger = logging.class_logger(ColumnProperty)
         
@@ -109,6 +111,9 @@ class PropertyLoader(StrategizedProperty):
         else:
             self.backref = backref
         self.is_backref = is_backref
+    
+    def compare(self, value):
+        return sql.and_(*[x==y for (x, y) in zip(self.mapper.primary_key, self.mapper.primary_key_from_instance(value))])
         
     private = property(lambda s:s.cascade.delete_orphan)
 
index da2da61d89978720b3076127b8905298ad817a0a..5d82b594f0d042c452725a572fc9aa9af2a66af1 100644 (file)
@@ -126,7 +126,7 @@ class Query(object):
 
         for key, value in params.iteritems():
             (keys, prop) = self._locate_prop(key)
-            c = (prop.columns[0]==value) & self.join_via(keys)
+            c = prop.compare(value) & self.join_via(keys)
             if clause is None:
                 clause =  c
             else:                
index f6e23d583a356663476a23bc5f1a00373049de17..323173cc56150ae5e65b82e000ae6dc306ff4f5c 100644 (file)
@@ -676,6 +676,9 @@ class ColumnCollection(util.OrderedProperties):
     
     overrides the __eq__() method to produce SQL clauses between sets of
     correlated columns."""
+    def __init__(self, *cols):
+        super(ColumnCollection, self).__init__()
+        [self.add(c) for c in cols]
     def add(self, column):
         """add a column to this collection.
         
index f8dc7e7a44f44265289157743d8bbf1510c33be7..519d2364015f1e77f6867bb249ceb105c3b5c97a 100644 (file)
@@ -285,7 +285,8 @@ class MapperTest(MapperSuperTest):
             }))
         })
 
-        q = create_session().query(m)
+        sess = create_session()
+        q = sess.query(m)
 
         l = q.select((orderitems.c.item_name=='item 4') & q.join_via(['orders', 'items']))
         self.assert_result(l, User, user_result[0])
@@ -298,7 +299,19 @@ class MapperTest(MapperSuperTest):
 
         l = q.select((orderitems.c.item_name=='item 4') & q.join_to('items'))
         self.assert_result(l, User, user_result[0])
+
+        # test comparing to an object instance
+        item = sess.query(Item).get_by(item_name='item 4')
+        l = q.select_by(items=item)
+        self.assert_result(l, User, user_result[0])
     
+        try:
+            # this should raise AttributeError
+            l = q.select_by(items=5)
+            assert False
+        except AttributeError:
+            assert True
+        
     def testcustomjoin(self):
         """test that the from_obj parameter to query.select() can be used
         to totally replace the FROM parameters of the generated query."""