]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
almost there, some tests failing still.
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 9 Mar 2010 18:41:43 +0000 (13:41 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 9 Mar 2010 18:41:43 +0000 (13:41 -0500)
CHANGES
doc/build/reference/sqlalchemy/expressions.rst
doc/build/reference/sqlalchemy/interfaces.rst
lib/sqlalchemy/types.py
test/aaa_profiling/test_compiler.py
test/sql/test_types.py

diff --git a/CHANGES b/CHANGES
index 3a1376b32863ee3c47e3a66d8ddb9cdfebe6f84e..f49c61392005fb3e691a9ff8a67c5c9147436b89 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -153,7 +153,10 @@ CHANGES
     types, before falling back to that of the known type
     on the other side of the expression.  If the 
     "fallback" type is compatible (i.e. CHAR from String),
-    the literal side will use that.  Also part of 
+    the literal side will use that.  TypeDecorator
+    types override this by default to coerce the "literal"
+    side unconditionally, which can be changed by implementing
+    the coerce_compared_value() method. Also part of 
     [ticket:1683].
 
   - Made sqlalchemy.sql.expressions.Executable part of public 
index 84210f04af857b0bafeb34d0c282c3c341c81c27..98e41fcb8cdfd8488ea05596e2bf6cb6d89e3542 100644 (file)
@@ -208,4 +208,6 @@ SQL functions which are known to SQLAlchemy with regards to database-specific re
 .. automodule:: sqlalchemy.sql.functions
    :members:
    :undoc-members:
-   :show-inheritance:
\ No newline at end of file
+   :show-inheritance:
+   
+   
index c629c3250ce1c4c58856f6277e34b69ff533f387..f9f60882c19799b0ee96796c62b9412eb6e0b8a2 100644 (file)
@@ -4,3 +4,5 @@ Interfaces
 .. automodule:: sqlalchemy.interfaces
    :members:
    :undoc-members:
+
+
index fc9ad2d531e5eafaf811c77ba5caf2a99641096a..5c4e2ca3f58cb21a69c8d480870c9499c633be79 100644 (file)
@@ -119,7 +119,7 @@ class AbstractType(Visitable):
     
     def _coerce_compared_value(self, op, value):
         _coerced_type = type_map.get(type(value), NULLTYPE)
-        if _coerced_type._type_affinity == self._type_affinity:
+        if _coerced_type is NULLTYPE or _coerced_type._type_affinity is self._type_affinity:
             return self
         else:
             return _coerced_type
@@ -281,12 +281,10 @@ class TypeDecorator(AbstractType):
     The expression system does the right thing by not attempting to
     coerce the "date()" value into an integer-oriented bind parameter.
     
-    However, suppose "somecol" is a ``TypeDecorator`` that is wrapping
-    an ``Integer``, and our ``TypeDecorator`` is actually storing dates
-    as an "epoch", i.e. a total number of days from a fixed starting
-    date.  So in this case, we *do* want the expression system to wrap 
-    the date() into our ``TypeDecorator`` type's system of coercing 
-    dates into integers.   So we would want to define::
+    However, in the case of ``TypeDecorator``, we are usually changing
+    an incoming Python type to something new - ``TypeDecorator`` by 
+    default will "coerce" the non-typed side to be the same type as itself.
+    Such as below, we define an "epoch" type that stores a date value as an integer::
     
         class MyEpochType(types.TypeDecorator):
             impl = types.Integer
@@ -298,12 +296,20 @@ class TypeDecorator(AbstractType):
             
             def process_result_value(self, value, dialect):
                 return self.epoch + timedelta(days=value)
-                
-            def coerce_compared_value(self, op, value):
-                if isinstance(value, datetime.date):
-                    return self
-                else:
-                    raise ValueError("Python date expected.")
+
+    Our expression of ``somecol + date`` with the above type will coerce the
+    "date" on the right side to also be treated as ``MyEpochType``.  
+    
+    This behavior can be overridden via the :meth:`~TypeDecorator.coerce_compared_value`
+    method, which returns a type that should be used for the value of the expression.
+    Below we set it such that an integer value will be treated as an ``Integer``,
+    and any other value is assumed to be a date and will be treated as a ``MyEpochType``::
+    
+        def coerce_compared_value(self, op, value):
+            if isinstance(value, int):
+                return Integer()
+            else:
+                return self
 
     """
 
@@ -408,7 +414,22 @@ class TypeDecorator(AbstractType):
             return self.impl.result_processor(dialect, coltype)
     
     def coerce_compared_value(self, op, value):
-        return self.impl._coerce_compared_value(op, value)
+        """Suggest a type for a 'coerced' Python value in an expression.
+        
+        By default, returns self.   This method is called by
+        the expression system when an object using this type is 
+        on the left or right side of an expression against a plain Python
+        object which does not yet have a SQLAlchemy type assigned::
+        
+            expr = table.c.somecolumn + 35
+            
+        Where above, if ``somecolumn`` uses this type, this method will
+        be called with the value ``operator.add``
+        and ``35``.  The return value is whatever SQLAlchemy type should
+        be used for ``35`` for this particular operation.
+        
+        """
+        return self
 
     def _coerce_compared_value(self, op, value):
         return self.coerce_compared_value(op, value)
@@ -1547,6 +1568,9 @@ class Interval(_DateAffinity, TypeDecorator):
     def _type_affinity(self):
         return Interval
 
+    def _coerce_compared_value(self, op, value):
+        return self.impl._coerce_compared_value(op, value)
+
 
 class FLOAT(Float):
     """The SQL FLOAT type."""
index e49a3df7fc40a30d2efb3cd52204221089534d3d..dc02cece456b461911da86faa67e536c72e6ef4a 100644 (file)
@@ -33,7 +33,7 @@ class CompileTest(TestBase, AssertsExecutionResults):
     def test_update(self):
         t1.update().compile()
 
-    @profiling.function_call_count(122, {'2.4': 81, '3':132})
+    @profiling.function_call_count(129, {'2.4': 81, '3':132})
     def test_update_whereclause(self):
         t1.update().where(t1.c.c2==12).compile()
 
index d7f0d1ed7519ccca52cf9cdf60d7e2c23d8de54d..3e9242481da9ba58fb6b7cf25e1191c71eabeb26 100644 (file)
@@ -869,6 +869,13 @@ class ExpressionTest(TestBase, AssertsExecutionResults, AssertsCompiledSQL):
         # is an Integer
         expr = column("foo", MyFoobarType) + 5
         assert expr.right.type._type_affinity is types.Integer
+        
+        # untyped bind - it gets assigned MyFoobarType
+        expr = column("foo", MyFoobarType) + bindparam("foo")
+        assert expr.right.type._type_affinity is MyFoobarType
+
+        expr = column("foo", MyFoobarType) + bindparam("foo", type_=Integer)
+        assert expr.right.type._type_affinity is types.Integer
 
         # unknown type + unknown, right hand bind
         # coerces to the left