]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- Added type_coerce(expr, type_) expression element.
authorMike Bayer <mike_mp@zzzcomputing.com>
Sat, 23 Oct 2010 20:40:39 +0000 (16:40 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sat, 23 Oct 2010 20:40:39 +0000 (16:40 -0400)
Treats the given expression as the given type when evaluating
expressions and processing result rows, but does not
affect the generation of SQL, other than an anonymous label.

CHANGES
doc/build/core/expression_api.rst
lib/sqlalchemy/__init__.py
lib/sqlalchemy/sql/__init__.py
lib/sqlalchemy/sql/compiler.py
lib/sqlalchemy/sql/expression.py
test/sql/test_types.py

diff --git a/CHANGES b/CHANGES
index d1ea9cb920a1f6111b1d064d86e7b4f7e481b318..6df2e8006e835bbe26f70f47fc7cfb6cf96d7d07 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -160,6 +160,12 @@ CHANGES
      "self.impl".   This to support compilation correctly.  
      Behavior can be user-overridden in exactly the same way 
      as before to the same effect.
+
+   - Added type_coerce(expr, type_) expression element.  
+     Treats the given expression as the given type when evaluating
+     expressions and processing result rows, but does not 
+     affect the generation of SQL, other than an anonymous
+     label.
      
    - Table.tometadata() now copies Index objects associated
      with the Table as well.
index c39701a5966b6c802cf1d3613a089b7f67be2e0f..1b4a35f84d8d3383bc34f2bfde7729972589ff54 100644 (file)
@@ -115,6 +115,8 @@ The expression package uses functions to construct SQL expressions.  The return
 
 .. autofunction:: tuple_
 
+.. autofunction:: type_coerce
+
 .. autofunction:: union
 
 .. autofunction:: union_all
index cb4e8e10b109d8c7e2b70dd4414f530396bfa483..65ecc9a889a0c7e0c4ab05ae2a99065c8be38bbe 100644 (file)
@@ -43,6 +43,7 @@ from sqlalchemy.sql import (
     subquery,
     text,
     tuple_,
+    type_coerce,
     union,
     union_all,
     update,
index aa18eac1711c018862af11709d022ee2e5765c2a..2bb5f6ab49abad5ef58003d0a1b48f7afcb71247 100644 (file)
@@ -47,6 +47,7 @@ from sqlalchemy.sql.expression import (
     table,
     text,
     tuple_,
+    type_coerce,
     union,
     union_all,
     update,
index e47db7e283c88cd505c01db2fefdebdfe89cb277..d3b8bf023ab195c2af18c92646f0b8a8fe78a75d 100644 (file)
@@ -153,6 +153,10 @@ class _CompileLabel(visitors.Visitable):
     def __init__(self, col, name):
         self.element = col
         self.name = name
+    
+    @property
+    def type(self):
+        return self.element.type
         
     @property
     def quote(self):
@@ -317,7 +321,7 @@ class SQLCompiler(engine.Compiled):
 
             if result_map is not None:
                 result_map[labelname.lower()] = \
-                        (label.name, (label, label.element, labelname), label.element.type)
+                        (label.name, (label, label.element, labelname), label.type)
 
             return self.process(label.element, 
                                     within_columns_clause=True,
@@ -329,7 +333,7 @@ class SQLCompiler(engine.Compiled):
             return self.process(label.element, 
                                     within_columns_clause=False, 
                                     **kw)
-            
+    
     def visit_column(self, column, result_map=None, **kwargs):
         name = column.name
         if name is None:
index 219e3bf14b26a505881267b263591560a30f9eca..625893a68a1085785c45e8a2ad715fabd9b0d213 100644 (file)
@@ -45,8 +45,8 @@ __all__ = [
     'except_', 'except_all', 'exists', 'extract', 'func', 'modifier',
     'collate', 'insert', 'intersect', 'intersect_all', 'join', 'label',
     'literal', 'literal_column', 'not_', 'null', 'or_', 'outparam',
-    'outerjoin', 'select', 'subquery', 'table', 'text', 'tuple_', 'union',
-    'union_all', 'update', ]
+    'outerjoin', 'select', 'subquery', 'table', 'text', 'tuple_', 'type_coerce',
+    'union', 'union_all', 'update', ]
 
 PARSE_AUTOCOMMIT = util._symbol('PARSE_AUTOCOMMIT')
 
@@ -666,6 +666,54 @@ def tuple_(*expr):
     
     """
     return _Tuple(*expr)
+
+def type_coerce(expr, type_):
+    """Coerce the given expression into the given type, on the Python side only.
+    
+    :func:`.type_coerce` is roughly similar to :func:.`cast`, except no
+    "CAST" expression is rendered - the given type is only applied towards
+    expression typing and against received result values.
+    
+    e.g.::
+    
+        from sqlalchemy.types import TypeDecorator
+        import uuid
+        
+        class AsGuid(TypeDecorator):
+            impl = String
+
+            def process_bind_param(self, value, dialect):
+                if value is not None:
+                    return str(value)
+                else:
+                    return None
+            
+            def process_result_value(self, value, dialect):
+                if value is not None:
+                    return uuid.UUID(value)
+                else:
+                    return None
+        
+        conn.execute(
+            select([type_coerce(mytable.c.ident, AsGuid)]).\\
+                    where(
+                        type_coerce(mytable.c.ident, AsGuid) == 
+                        uuid.uuid3(uuid.NAMESPACE_URL, 'bar')
+                    )
+        )            
+    
+    """
+    if hasattr(expr, '__clause_expr__'):
+        return type_coerce(expr.__clause_expr__())
+        
+    elif not isinstance(expr, Visitable):
+        if expr is None:
+            return null()
+        else:
+            return literal(expr, type_=type_)
+    else:
+        return _Label(None, expr, type_=type_)
+    
     
 def label(name, obj):
     """Return a :class:`_Label` object for the
index 437d69fffa0fde5bf4b9e83ddb50f171fe09a139..99384389136eb1684afda52fb26372fe2ab358cc 100644 (file)
@@ -201,7 +201,63 @@ class UserDefinedTest(TestBase, AssertsCompiledSQL):
                 t.dialect_impl(dialect=pg).impl.__class__, 
                 Float().dialect_impl(pg).__class__
         )
-                
+    
+    @testing.provide_metadata
+    def test_type_coerce(self):
+        """test ad-hoc usage of custom types with type_coerce()."""
+        
+        class MyType(types.TypeDecorator):
+            impl = String
+
+            def process_bind_param(self, value, dialect):
+                return value[0:-8]
+            
+            def process_result_value(self, value, dialect):
+                return value + "BIND_OUT"
+        
+        t = Table('t', metadata, Column('data', String(50)))
+        metadata.create_all()
+        
+        t.insert().values(data=type_coerce('d1BIND_OUT',MyType)).execute()
+
+        eq_(
+            select([type_coerce(t.c.data, MyType)]).execute().fetchall(),
+            [('d1BIND_OUT', )]
+        )
+        
+        eq_(
+            select([t.c.data, type_coerce(t.c.data, MyType)]).execute().fetchall(),
+            [('d1', 'd1BIND_OUT')]
+        )
+        
+        eq_(
+            select([t.c.data, type_coerce(t.c.data, MyType)]).\
+                        where(type_coerce(t.c.data, MyType) == 'd1BIND_OUT').\
+                        execute().fetchall(),
+            [('d1', 'd1BIND_OUT')]
+        )
+
+        eq_(
+            select([t.c.data, type_coerce(t.c.data, MyType)]).\
+                        where(t.c.data == type_coerce('d1BIND_OUT', MyType)).\
+                        execute().fetchall(),
+            [('d1', 'd1BIND_OUT')]
+        )
+
+        eq_(
+            select([t.c.data, type_coerce(t.c.data, MyType)]).\
+                        where(t.c.data == type_coerce(None, MyType)).\
+                        execute().fetchall(),
+            []
+        )
+
+        eq_(
+            select([t.c.data, type_coerce(t.c.data, MyType)]).\
+                        where(type_coerce(t.c.data, MyType) == None).\
+                        execute().fetchall(),
+            []
+        )
+        
     @classmethod
     def setup_class(cls):
         global users, metadata