]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- backport of r6540
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 8 Dec 2009 01:54:08 +0000 (01:54 +0000)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 8 Dec 2009 01:54:08 +0000 (01:54 +0000)
- The "use get" behavior of many-to-one relations, i.e. that a
lazy load will fallback to the possibly cached query.get()
value, now works across join conditions where the two compared
types are not exactly the same class, but share the same
"affinity" - i.e. Integer and SmallInteger.  Also allows
combinations of reflected and non-reflected types to work
with 0.5 style type reflection, such as PGText/Text (note 0.6
reflects types as their generic versions).   [ticket:1556]
- types now support an "affinity comparison" operation, i.e.
that an Integer/SmallInteger are "compatible", or
a Text/String, PickleType/Binary, etc.  Part of
[ticket:1556].

CHANGES
lib/sqlalchemy/orm/strategies.py
lib/sqlalchemy/sql/expression.py
lib/sqlalchemy/types.py
test/orm/test_lazy_relations.py
test/sql/test_types.py

diff --git a/CHANGES b/CHANGES
index e9ddf26ae2172c8f830489ba91349549c41c112d..b97828ff05c0607a394a8603e824b46ec71beed9 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -45,6 +45,15 @@ CHANGES
       when configured on a joined-table subclass, introduced in
       version 0.5.6 as a result of the fix for [ticket:1480].
       [ticket:1616] thx to Scott Torborg.
+    
+    - The "use get" behavior of many-to-one relations, i.e. that a 
+      lazy load will fallback to the possibly cached query.get() 
+      value, now works across join conditions where the two compared 
+      types are not exactly the same class, but share the same 
+      "affinity" - i.e. Integer and SmallInteger.  Also allows
+      combinations of reflected and non-reflected types to work
+      with 0.5 style type reflection, such as PGText/Text (note 0.6 
+      reflects types as their generic versions).   [ticket:1556]
       
 - sql
     - Fixed bug in two-phase transaction whereby commit() method
@@ -65,6 +74,11 @@ CHANGES
       (i.e. _CursorFairy) now proxies `__iter__()` to the 
       underlying cursor correctly. [ticket:1632]
 
+    - types now support an "affinity comparison" operation, i.e.
+      that an Integer/SmallInteger are "compatible", or 
+      a Text/String, PickleType/Binary, etc.  Part of 
+      [ticket:1556].
+      
 - sqlite
     - sqlite dialect properly generates CREATE INDEX for a table
       that is in an alternate schema.  [ticket:1439]
index 68669a1502716bf68b5306e58c45fbcaed5ecc37..01af1de9209f558685624c070dbaa3ce6bc791bc 100644 (file)
@@ -372,7 +372,6 @@ class LazyLoader(AbstractRelationLoader):
 
         # determine if our "lazywhere" clause is the same as the mapper's
         # get() clause.  then we can just use mapper.get()
-        #from sqlalchemy.orm import query
         self.use_get = not self.uselist and self.mapper._get_clause[0].compare(self.__lazywhere)
         if self.use_get:
             self.logger.info("%s will use query.get() to optimize instance loads" % self)
index 83897ef051cca420576616ee5c7b8dcaf5749f58..cf08ec19511e7f55088a92cdf60032930d799faa 100644 (file)
@@ -2003,15 +2003,12 @@ class _BindParamClause(ColumnElement):
         else:
             return obj.type
 
-    def compare(self, other):
-        """Compare this ``_BindParamClause`` to the given clause.
-
-        Since ``compare()`` is meant to compare statement syntax, this
-        method returns True if the two ``_BindParamClauses`` have just
-        the same type.
-
-        """
-        return isinstance(other, _BindParamClause) and other.type.__class__ == self.type.__class__ and self.value == other.value
+    def compare(self, other, **kw):
+        """Compare this ``_BindParamClause`` to the given clause."""
+        return isinstance(other, _BindParamClause) and \
+                 self.type._compare_type_affinity(other.type) and \
+                  self.value == other.value
 
     def __getstate__(self):
         """execute a deferred value for serialization purposes."""
index a03d6137dfa93d7204c0e43a4061c30f26a4d698..8e6accdbf54d6be2b6aea7e4e37e25c80d1c73a6 100644 (file)
@@ -81,6 +81,19 @@ class AbstractType(object):
         By default, returns the operator unchanged.
         """
         return op
+        
+    @util.memoized_property
+    def _type_affinity(self):
+        """Return a rudimental 'affinity' value expressing the general class of type."""
+        
+        for i, t in enumerate(self.__class__.__mro__):
+            if t is TypeEngine:
+                return self.__class__.__mro__[i - 1]
+        else:
+            return self.__class__
+        
+    def _compare_type_affinity(self, other):
+        return self._type_affinity is other._type_affinity
 
     def __repr__(self):
         return "%s(%s)" % (
@@ -236,6 +249,10 @@ class TypeDecorator(AbstractType):
         self._impl_dict[dialect] = tt
         return tt
 
+    @util.memoized_property
+    def _type_affinity(self):
+        return self.impl._type_affinity
+
     def load_dialect_impl(self, dialect):
         """Loads the dialect-specific implementation of this type.
 
index 8867232b244660618f558e4fdc5c417b05283cc0..ea084e0458cf142b9c90311b65538d7b572fd9fc 100644 (file)
@@ -6,7 +6,8 @@ from sqlalchemy import exc as sa_exc
 from sqlalchemy.orm import attributes
 import sqlalchemy as sa
 from sqlalchemy.test import testing
-from sqlalchemy import Integer, String, ForeignKey
+from sqlalchemy import Integer, String, ForeignKey, SmallInteger
+from sqlalchemy.types import TypeDecorator
 from sqlalchemy.test.schema import Table
 from sqlalchemy.test.schema import Column
 from sqlalchemy.orm import mapper, relation, create_session
@@ -285,6 +286,55 @@ class LazyTest(_fixtures.FixtureTest):
             self.assert_sql_count(testing.db, go, 0)
             sa.orm.clear_mappers()
 
+    @testing.resolve_artifact_names
+    def test_uses_get_compatible_types(self):
+        """test the use_get optimization with compatible but non-identical types"""
+
+        class IntDecorator(TypeDecorator):
+            impl = Integer
+
+        class SmallintDecorator(TypeDecorator):
+            impl = SmallInteger
+        
+        class SomeDBInteger(sa.Integer):
+            pass
+            
+        for tt in [
+            Integer,
+            SmallInteger,
+            IntDecorator,
+            SmallintDecorator,
+            SomeDBInteger,
+        ]:
+            m = sa.MetaData()
+            users = Table('users', m, 
+                Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
+                Column('name', String(30), nullable=False),
+            )
+            addresses = Table('addresses', m,
+                  Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
+                  Column('user_id', tt, ForeignKey('users.id')),
+                  Column('email_address', String(50), nullable=False),
+            )
+
+            mapper(Address, addresses, properties = dict(
+                user = relation(mapper(User, users))
+            ))
+
+            sess = create_session(bind=testing.db)
+
+            # load address
+            a1 = sess.query(Address).filter_by(email_address="ed@wood.com").one()
+
+            # load user that is attached to the address
+            u1 = sess.query(User).get(8)
+
+            def go():
+                # lazy load of a1.user should get it from the session
+                assert a1.user is u1
+            self.assert_sql_count(testing.db, go, 0)
+            sa.orm.clear_mappers()
+
     @testing.resolve_artifact_names
     def test_many_to_one(self):
         mapper(Address, addresses, properties = dict(
index 15799358a7094e1c2ea2882128cea93267624d1a..67096755d18c454d1e741f41451484d131989f02 100644 (file)
@@ -95,6 +95,21 @@ class AdaptTest(TestBase):
         ]:
             assert isinstance(start.dialect_impl(dialect), test), "wanted %r got %r" % (test, start.dialect_impl(dialect))
 
+class TypeAffinityTest(TestBase):
+    def test_type_affinity(self):
+        for t1, t2, comp in [
+            (Integer(), SmallInteger(), True),
+            (Integer(), String(), False),
+            (Integer(), Integer(), True),
+            (Text(), String(), True),
+            (Text(), Unicode(), True),
+            (Binary(), Integer(), False),
+            (Binary(), PickleType(), True),
+            (PickleType(), Binary(), True),
+            (PickleType(), PickleType(), True),
+        ]:
+            eq_(t1._compare_type_affinity(t2), comp, "%s %s" % (t1, t2))
+
 
 
 class UserDefinedTest(TestBase):