Commits

Mike Bayer committed b152e30

- fix the labeled column with column_expression() issue, finishes [ticket:1534]
- epic documentation sweep for new operator system, making ORM links consistent
and complete, full documentation and examples for type/SQL expression feature
- type_coerce() explicitly accepts BindParamClause objects
- change UserDefinedType to coerce the other side to itself by default as this
is much more likely what's desired
- make coerce_compared_type() fully public on all types
- have profiling run the test no matter what so that the test_zoomarks don't fail
when callcounts are missing

Comments (0)

Files changed (16)

doc/build/core/tutorial.rst

 Functions
 ---------
 
-SQL functions are created using the :attr:`~.expression.func` keyword, which
+SQL functions are created using the :data:`~.expression.func` keyword, which
 generates functions using attribute access:
 
 .. sourcecode:: pycon+sql
     >>> s.compile().params
     {u'x_2': 5, u'y_2': 12, u'y_1': 45, u'x_1': 17}
 
-See also :attr:`sqlalchemy.sql.expression.func`.
+See also :data:`~.expression.func`.
 
 Window Functions
 -----------------
 
 Any :class:`.FunctionElement`, including functions generated by
-:attr:`~.expression.func`, can be turned into a "window function", that is an
+:data:`~.expression.func`, can be turned into a "window function", that is an
 OVER clause, using the :meth:`~.FunctionElement.over` method:
 
 .. sourcecode:: pycon+sql

doc/build/core/types.rst

 See the section :ref:`type_compilation_extension`, a subsection of
 :ref:`sqlalchemy.ext.compiler_toplevel`, for additional examples.
 
+.. _types_typedecorator:
+
 Augmenting Existing Types
 ~~~~~~~~~~~~~~~~~~~~~~~~~
 
       def adapt(self, impltype):
           return MySpecialTime(self.special_argument)
 
+.. _types_sql_value_processing:
+
+Applying SQL-level Bind/Result Processing
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+As seen in the sections :ref:`types_typedecorator` and :ref:`replacing_processors`,
+SQLAlchemy allows Python functions to be invoked both when parameters are sent
+to a statement, as well as when result rows are loaded from the database, to apply
+transformations to the values as they are sent to or from the database.   It is also
+possible to define SQL-level transformations as well.  The rationale here is when
+only the relational database contains a particular series of functions that are necessary
+to coerce incoming and outgoing data between an application and persistence format.
+Examples include using database-defined encryption/decryption functions, as well
+as stored procedures that handle geographic data.  The Postgis extension to Postgresql
+includes an extensive array of SQL functions that are necessary for coercing
+data into particular formats.
+
+Any :class:`.TypeEngine`, :class:`.UserDefinedType` or :class:`.TypeDecorator` subclass
+can include implementations of
+:meth:`.TypeEngine.bind_expression` and/or :meth:`.TypeEngine.column_expression`, which
+when defined to return a non-``None`` value should return a :class:`.ColumnElement`
+expression to be injected into the SQL statement, either surrounding
+bound parameters or a column expression.  For example, to build a ``Geometry``
+type which will apply the Postgis function ``ST_GeomFromText`` to all outgoing
+values and the function ``ST_AsText`` to all incoming data, we can create
+our own subclass of :class:`.UserDefinedType` which provides these methods
+in conjunction with :data:`~.sqlalchemy.sql.expression.func`::
+
+    from sqlalchemy import func
+    from sqlalchemy.types import UserDefinedType
+
+    class Geometry(UserDefinedType):
+        def get_col_spec(self):
+            return "GEOMETRY"
+
+        def bind_expression(self, bindvalue):
+            return func.ST_GeomFromText(bindvalue, type_=self)
+
+        def column_expression(self, col):
+            return func.ST_AsText(col, type_=self)
+
+We can apply the ``Geometry`` type into :class:`.Table` metadata
+and use it in a :func:`.select` construct::
+
+    geometry = Table('geometry', metadata,
+                  Column('geom_id', Integer, primary_key=True),
+                  Column('geom_data', Geometry)
+                )
+
+    print select([geometry]).where(
+      geometry.c.geom_data == 'LINESTRING(189412 252431,189631 259122)')
+
+The resulting SQL embeds both functions as appropriate.   ``ST_AsText``
+is applied to the columns clause so that the return value is run through
+the function before passing into a result set, and ``ST_GeomFromText``
+is run on the bound parameter so that the passed-in value is converted::
+
+    SELECT geometry.geom_id, ST_AsText(geometry.geom_data) AS geom_data_1
+    FROM geometry
+    WHERE geometry.geom_data = ST_GeomFromText(:geom_data_2)
+
+The :meth:`.TypeEngine.column_expression` method interacts with the
+mechanics of the compiler such that the SQL expression does not interfere
+with the labeling of the wrapped expression.   Such as, if we rendered
+a :func:`.select` against a :func:`.label` of our expression, the string
+label is moved to the outside of the wrapped expression::
+
+    print select([geometry.c.geom_data.label('my_data')])
+
+Output::
+
+    SELECT ST_AsText(geometry.geom_data) AS my_data
+    FROM geometry
+
+For an example of subclassing a built in type directly, we subclass
+:class:`.postgresql.BYTEA` to provide a ``PGPString``, which will make use of the
+Postgresql ``pgcrypto`` extension to encrpyt/decrypt values
+transparently::
+
+    from sqlalchemy import create_engine, String, select, func, \
+            MetaData, Table, Column, type_coerce
+
+    from sqlalchemy.dialects.postgresql import BYTEA
+
+    class PGPString(BYTEA):
+        def __init__(self, passphrase, length=None):
+            super(PGPString, self).__init__(length)
+            self.passphrase = passphrase
+
+        def bind_expression(self, bindvalue):
+            # convert the bind's type from PGPString to
+            # String, so that it's passed to psycopg2 as is without
+            # a dbapi.Binary wrapper
+            bindvalue = type_coerce(bindvalue, String)
+            return func.pgp_sym_encrypt(bindvalue, self.passphrase)
+
+        def column_expression(self, col):
+            return func.pgp_sym_decrypt(col, self.passphrase)
+
+    metadata = MetaData()
+    message = Table('message', metadata,
+                    Column('username', String(50)),
+                    Column('message',
+                        PGPString("this is my passphrase", length=1000)),
+                )
+
+    engine = create_engine("postgresql://scott:tiger@localhost/test", echo=True)
+    with engine.begin() as conn:
+        metadata.create_all(conn)
+
+        conn.execute(message.insert(), username="some user",
+                                    message="this is my message")
+
+        print conn.scalar(
+                select([message.c.message]).\
+                    where(message.c.username == "some user")
+            )
+
+The ``pgp_sym_encrypt`` and ``pgp_sym_decrypt`` functions are applied
+to the INSERT and SELECT statements::
+
+  INSERT INTO message (username, message)
+    VALUES (%(username)s, pgp_sym_encrypt(%(message)s, %(pgp_sym_encrypt_1)s))
+    {'username': 'some user', 'message': 'this is my message',
+      'pgp_sym_encrypt_1': 'this is my passphrase'}
+
+  SELECT pgp_sym_decrypt(message.message, %(pgp_sym_decrypt_1)s) AS message_1
+    FROM message
+    WHERE message.username = %(username_1)s
+    {'pgp_sym_decrypt_1': 'this is my passphrase', 'username_1': 'some user'}
+
+
+.. versionadded:: 0.8  Added the :meth:`.TypeEngine.bind_expression` and
+   :meth:`.TypeEngine.column_expression` methods.
+
+See also:
+
+:ref:`examples_postgis`
+
 .. _types_operators:
 
 Redefining and Creating New Operators
 Some of these operations have the effect of overloading Python's built in operators;
 examples of such operators include
 :meth:`.ColumnOperators.__eq__` (``table.c.somecolumn == 'foo'``),
-:meth:`.ColumnOperators.neg` (``~table.c.flag``),
+:meth:`.ColumnOperators.__invert__` (``~table.c.flag``),
 and :meth:`.ColumnOperators.__add__` (``table.c.x + table.c.y``).  Other operators are exposed as
 explicit methods on column expressions, such as
 :meth:`.ColumnOperators.in_` (``table.c.value.in_(['x', 'y'])``) and :meth:`.ColumnOperators.like`

doc/build/orm/examples.rst

 
 See :ref:`examples_generic_associations` for a modern version of polymorphic associations.
 
+.. _examples_postgis:
+
 PostGIS Integration
 -------------------
 

doc/build/orm/mapper_config.rst

 type level -  see the
 section :ref:`types_operators` for a description.
 
+ORM level functions like :func:`.column_property`, :func:`.relationship`,
+and :func:`.composite` also provide for operator redefinition at the ORM
+level, by passing a :class:`.PropComparator` subclass to the ``comparator_factory``
+argument of each function.  Customization of operators at this level is a
+rare use case.  See the documentation at :class:`.PropComparator`
+for an overview.
+
 .. _mapper_composite:
 
 Composite Column Types
 
 .. autofunction:: composite
 
+
 Tracking In-Place Mutations on Composites
 -----------------------------------------
 
 Please see the example in :ref:`mutable_composites`.
 
 .. versionchanged:: 0.7
-    No automatic tracking of in-place changes to an existing composite value.
+    In-place changes to an existing composite value are no longer
+    tracked automatically; the functionality is superseded by the
+    :class:`.MutableComposite` class.
+
+.. _composite_operations:
 
 Redefining Comparison Operations for Composites
 -----------------------------------------------
 
 The "equals" comparison operation by default produces an AND of all
 corresponding columns equated to one another. This can be changed using
-the ``comparator_factory``, described in :ref:`custom_comparators`.
+the ``comparator_factory`` argument to :func:`.composite`, where we
+specify a custom :class:`.CompositeProperty.Comparator` class
+to define existing or new operations.
 Below we illustrate the "greater than" operator, implementing
 the same expression that the base "greater than" does::
 

lib/sqlalchemy/orm/__init__.py

     See the mapping documentation section :ref:`mapper_composite` for a full
     usage example.
 
+    The :class:`.MapperProperty` returned by :func:`.composite`
+    is the :class:`.CompositeProperty`.
+
     :param class\_:
       The "composite type" class.
 

lib/sqlalchemy/orm/descriptor_props.py

 
 
 class CompositeProperty(DescriptorProperty):
+    """Defines a "composite" mapped attribute, representing a collection
+    of columns as one attribute.
 
+    :class:`.CompositeProperty` is constructed using the :func:`.composite`
+    function.
+
+    See also:
+
+    :ref:`mapper_composite`
+
+    """
     def __init__(self, class_, *attrs, **kwargs):
         self.attrs = attrs
         self.composite_class = class_
         return self.comparator_factory(self)
 
     class Comparator(PropComparator):
+        """Produce boolean, comparison, and other operators for
+        :class:`.CompositeProperty` attributes.
+
+        See the example in :ref:`composite_operations` for an overview
+        of usage , as well as the documentation for :class:`.PropComparator`.
+
+        See also:
+
+        :class:`.PropComparator`
+
+        :class:`.ColumnOperators`
+
+        :ref:`types_operators`
+
+        :attr:`.TypeEngine.comparator_factory`
+
+        """
         def __init__(self, prop, adapter=None):
             self.prop = self.property = prop
             self.adapter = adapter

lib/sqlalchemy/orm/interfaces.py

         return operator(self.comparator, value)
 
 class PropComparator(operators.ColumnOperators):
-    """Defines comparison operations for MapperProperty objects.
+    """Defines boolean, comparison, and other operators for
+    :class:`.MapperProperty` objects.
+
+    SQLAlchemy allows for operators to
+    be redefined at both the Core and ORM level.  :class:`.PropComparator`
+    is the base class of operator redefinition for ORM-level operations,
+    including those of :class:`.ColumnProperty`, :class:`.RelationshipProperty`,
+    and :class:`.CompositeProperty`.
+
+    .. note:: With the advent of Hybrid properties introduced in SQLAlchemy
+       0.7, as well as Core-level operator redefinition in
+       SQLAlchemy 0.8, the use case for user-defined :class:`.PropComparator`
+       instances is extremely rare.  See :ref:`hybrids_toplevel` as well
+       as :ref:`types_operators`.
 
     User-defined subclasses of :class:`.PropComparator` may be created. The
     built-in Python comparison and math operator methods, such as
-    ``__eq__()``, ``__lt__()``, ``__add__()``, can be overridden to provide
+    :meth:`.operators.ColumnOperators.__eq__`,
+    :meth:`.operators.ColumnOperators.__lt__`, and
+    :meth:`.operators.ColumnOperators.__add__`, can be overridden to provide
     new operator behavior. The custom :class:`.PropComparator` is passed to
-    the mapper property via the ``comparator_factory`` argument. In each case,
+    the :class:`.MapperProperty` instance via the ``comparator_factory``
+    argument. In each case,
     the appropriate subclass of :class:`.PropComparator` should be used::
 
+        # definition of custom PropComparator subclasses
+
         from sqlalchemy.orm.properties import \\
                                 ColumnProperty,\\
                                 CompositeProperty,\\
                                 RelationshipProperty
 
         class MyColumnComparator(ColumnProperty.Comparator):
-            pass
+            def __eq__(self, other):
+                return self.__clause_element__() == other
+
+        class MyRelationshipComparator(RelationshipProperty.Comparator):
+            def any(self, expression):
+                "define the 'any' operation"
+                # ...
 
         class MyCompositeComparator(CompositeProperty.Comparator):
-            pass
+            def __gt__(self, other):
+                "redefine the 'greater than' operation"
 
-        class MyRelationshipComparator(RelationshipProperty.Comparator):
-            pass
+                return sql.and_(*[a>b for a, b in
+                                  zip(self.__clause_element__().clauses,
+                                      other.__composite_values__())])
+
+
+        # application of custom PropComparator subclasses
+
+        from sqlalchemy.orm import column_property, relationship, composite
+        from sqlalchemy import Column, String
+
+        class SomeMappedClass(Base):
+            some_column = column_property(Column("some_column", String),
+                                    comparator_factory=MyColumnComparator)
+
+            some_relationship = relationship(SomeOtherClass,
+                                    comparator_factory=MyRelationshipComparator)
+
+            some_composite = composite(
+                    Column("a", String), Column("b", String),
+                    comparator_factory=MyCompositeComparator
+                )
+
+    Note that for column-level operator redefinition, it's usually
+    simpler to define the operators at the Core level, using the
+    :attr:`.TypeEngine.comparator_factory` attribute.  See
+    :ref:`types_operators` for more detail.
+
+    See also:
+
+    :class:`.ColumnProperty.Comparator`
+
+    :class:`.RelationshipProperty.Comparator`
+
+    :class:`.CompositeProperty.Comparator`
+
+    :class:`.ColumnOperators`
+
+    :ref:`types_operators`
+
+    :attr:`.TypeEngine.comparator_factory`
 
     """
 

lib/sqlalchemy/orm/properties.py

             dest_state._expire_attributes(dest_dict, [self.key])
 
     class Comparator(PropComparator):
+        """Produce boolean, comparison, and other operators for
+        :class:`.ColumnProperty` attributes.
+
+        See the documentation for :class:`.PropComparator` for a brief overview.
+
+        See also:
+
+        :class:`.PropComparator`
+
+        :class:`.ColumnOperators`
+
+        :ref:`types_operators`
+
+        :attr:`.TypeEngine.comparator_factory`
+
+        """
         @util.memoized_instancemethod
         def __clause_element__(self):
             if self.adapter:
 
     Public constructor is the :func:`.orm.relationship` function.
 
-    Of note here is the :class:`.RelationshipProperty.Comparator`
-    class, which implements comparison operations for scalar-
-    and collection-referencing mapped attributes.
+    See also:
+
+    :ref:`relationship_config_toplevel`
 
     """
 
             )
 
     class Comparator(PropComparator):
-        """Produce comparison operations for :func:`~.orm.relationship`-based
-         attributes."""
+        """Produce boolean, comparison, and other operators for
+        :class:`.RelationshipProperty` attributes.
+
+        See the documentation for :class:`.PropComparator` for a brief overview
+        of ORM level operator definition.
+
+        See also:
+
+        :class:`.PropComparator`
+
+        :class:`.ColumnProperty.Comparator`
+
+        :class:`.ColumnOperators`
+
+        :ref:`types_operators`
+
+        :attr:`.TypeEngine.comparator_factory`
+
+        """
 
         def __init__(self, prop, mapper, of_type=None, adapter=None):
             """Construction of :class:`.RelationshipProperty.Comparator`

lib/sqlalchemy/sql/compiler.py

             else:
                 add_to_result_map = None
 
-        if isinstance(col_expr, sql.Label):
-            result_expr = col_expr
+        if isinstance(column, sql.Label):
+            if col_expr is not column:
+                result_expr = _CompileLabel(
+                        col_expr,
+                        column.name,
+                        alt_names=(column.element,)
+                    )
+            else:
+                result_expr = col_expr
 
         elif select is not None and \
                 select.use_labels and \

lib/sqlalchemy/sql/expression.py

 
     See also:
 
-    :ref:`coretutorial_selecting` - Core Tutorial description
-     of :func:`.select`.
+    :ref:`coretutorial_selecting` - Core Tutorial description of
+    :func:`.select`.
 
     :param columns:
       A list of :class:`.ClauseElement` objects, typically
         )
 
     """
+    type_ = sqltypes.to_instance(type_)
+
     if hasattr(expr, '__clause_expr__'):
         return type_coerce(expr.__clause_expr__())
-
+    elif isinstance(expr, BindParameter):
+        bp = expr._clone()
+        bp.type = type_
+        return bp
     elif not isinstance(expr, Visitable):
         if expr is None:
             return null()
     Would produce "ROW_NUMBER() OVER(ORDER BY x)".
 
     :param func: a :class:`.FunctionElement` construct, typically
-     generated by :attr:`~.expression.func`.
+     generated by :data:`~.expression.func`.
     :param partition_by: a column element or string, or a list
      of such, that will be used as the PARTITION BY clause
      of the OVER construct.
      of such, that will be used as the ORDER BY clause
      of the OVER construct.
 
-    This function is also available from the :attr:`~.expression.func`
+    This function is also available from the :data:`~.expression.func`
     construct itself via the :meth:`.FunctionElement.over` method.
 
     .. versionadded:: 0.7
         if type_ is None:
             if _compared_to_type is not None:
                 self.type = \
-                    _compared_to_type._coerce_compared_value(
+                    _compared_to_type.coerce_compared_value(
                         _compared_to_operator, value)
             else:
                 self.type = sqltypes._type_map.get(type(value),
     def __init__(self, name, *clauses, **kw):
         """Construct a :class:`.Function`.
 
-        The :attr:`.func` construct is normally used to construct
+        The :data:`.func` construct is normally used to construct
         new :class:`.Function` instances.
 
         """

lib/sqlalchemy/sql/operators.py

 
 
 class ColumnOperators(Operators):
-    """Defines comparison and math operations.
+    """Defines boolean, comparison, and other operators for
+    :class:`.ColumnElement` expressions.
 
     By default, all methods call down to
     :meth:`.operate` or :meth:`.reverse_operate`,
     so that the ``==`` operation above is replaced by a clause
     construct.
 
-    The docstrings here will describe column-oriented
-    behavior of each operator.  For ORM-based operators
-    on related objects and collections, see
-    :class:`.RelationshipProperty.Comparator`.
+    See also:
+
+    :ref:`types_operators`
+
+    :attr:`.TypeEngine.comparator_factory`
+
+    :class:`.ColumnOperators`
+
+    :class:`.PropComparator`
 
     """
 

lib/sqlalchemy/types.py

         return None
 
     def column_expression(self, colexpr):
-        """Given a SELECT column expression, return a wrapping SQL expression."""
+        """Given a SELECT column expression, return a wrapping SQL expression.
+
+        This is typically a SQL function that wraps a column expression
+        as rendered in the columns clause of a SELECT statement.
+        It is used for special data types that require
+        columns to be wrapped in some special database function in order
+        to coerce the value before being sent back to the application.
+        It is the SQL analogue of the :meth:`.TypeEngine.result_processor`
+        method.
+
+        The method is evaluated at statement compile time, as opposed
+        to statement construction time.
+
+        See also:
+
+        :ref:`types_sql_value_processing`
+
+        """
 
         return None
 
         """"Given a bind value (i.e. a :class:`.BindParameter` instance),
         return a SQL expression in its place.
 
-        This is typically a SQL function that wraps the existing value
-        in a bind.   It is used for special data types that require
-        literals being wrapped in some special database function in all
-        cases, such as Postgis GEOMETRY types.
+        This is typically a SQL function that wraps the existing bound
+        parameter within the statement.  It is used for special data types
+        that require literals being wrapped in some special database function
+        in order to coerce an application-level value into a database-specific
+        format.  It is the SQL analogue of the :meth:`.TypeEngine.bind_processor`
+        method.
 
         The method is evaluated at statement compile time, as opposed
         to statement construction time.
 
         Note that this method, when implemented, should always return
         the exact same structure, without any conditional logic, as it
-        will be used during executemany() calls as well.
+        may be used in an executemany() call against an arbitrary number
+        of bound parameter sets.
+
+        See also:
+
+        :ref:`types_sql_value_processing`
 
         """
         return None
         """
         return util.constructor_copy(self, cls, **kw)
 
-    def _coerce_compared_value(self, op, value):
+    def coerce_compared_value(self, op, value):
         """Suggest a type for a 'coerced' Python value in an expression.
 
         Given an operator and value, gives the type a chance
 
     comparator_factory = Comparator
 
+    def coerce_compared_value(self, op, value):
+        """Suggest a type for a 'coerced' Python value in an expression.
+
+        Default behavior for :class:`.UserDefinedType` is the
+        same as that of :class:`.TypeDecorator`; by default it returns
+        ``self``, assuming the compared value should be coerced into
+        the same type as this one.  See :meth:`.TypeDecorator.coerce_compared_value`
+        for more detail.
+
+        .. versionchanged:: 0.8 :meth:`.UserDefinedType.coerce_compared_value`
+           now returns ``self`` by default, rather than falling onto the
+           more fundamental behavior of :meth:`.TypeEngine.coerce_compared_value`.
+
+        """
+
+        return self
+
 
 class TypeDecorator(TypeEngine):
     """Allows the creation of types which add additional functionality
         return self.impl._type_affinity
 
     def type_engine(self, dialect):
-        """Return a dialect-specific :class:`.TypeEngine` instance for this :class:`.TypeDecorator`.
+        """Return a dialect-specific :class:`.TypeEngine` instance
+        for this :class:`.TypeDecorator`.
 
         In most cases this returns a dialect-adapted form of
         the :class:`.TypeEngine` type represented by ``self.impl``.
         """
         return self
 
-    def _coerce_compared_value(self, op, value):
-        """See :meth:`.TypeEngine._coerce_compared_value` for a description."""
-
-        return self.coerce_compared_value(op, value)
-
     def copy(self):
         """Produce a copy of this :class:`.TypeDecorator` instance.
 
         return process
     # end Py2K
 
-    def _coerce_compared_value(self, op, value):
-        """See :meth:`.TypeEngine._coerce_compared_value` for a description."""
+    def coerce_compared_value(self, op, value):
+        """See :meth:`.TypeEngine.coerce_compared_value` for a description."""
 
         if isinstance(value, basestring):
             return self
         else:
-            return super(_Binary, self)._coerce_compared_value(op, value)
+            return super(_Binary, self).coerce_compared_value(op, value)
 
     def get_dbapi_type(self, dbapi):
         return dbapi.BINARY
     def _type_affinity(self):
         return Interval
 
-    def _coerce_compared_value(self, op, value):
-        """See :meth:`.TypeEngine._coerce_compared_value` for a description."""
-
-        return self.impl._coerce_compared_value(op, value)
+    def coerce_compared_value(self, op, value):
+        """See :meth:`.TypeEngine.coerce_compared_value` for a description."""
+
+        return self.impl.coerce_compared_value(op, value)
 
 
 class REAL(Float):

test/lib/profiles.txt

 
 # TEST: test.aaa_profiling.test_compiler.CompileTest.test_select
 
+test.aaa_profiling.test_compiler.CompileTest.test_select 2.7_mysql_mysqldb_cextensions 133
+test.aaa_profiling.test_compiler.CompileTest.test_select 2.7_postgresql_psycopg2_cextensions 133
 test.aaa_profiling.test_compiler.CompileTest.test_select 2.7_postgresql_psycopg2_nocextensions 133
+test.aaa_profiling.test_compiler.CompileTest.test_select 2.7_sqlite_pysqlite_cextensions 133
 test.aaa_profiling.test_compiler.CompileTest.test_select 2.7_sqlite_pysqlite_nocextensions 133
 
 # TEST: test.aaa_profiling.test_compiler.CompileTest.test_select_second_time
 
 # TEST: test.aaa_profiling.test_resultset.ResultSetTest.test_contains_doesnt_compile
 
+test.aaa_profiling.test_resultset.ResultSetTest.test_contains_doesnt_compile 2.7_mysql_mysqldb_cextensions 14
+test.aaa_profiling.test_resultset.ResultSetTest.test_contains_doesnt_compile 2.7_postgresql_psycopg2_cextensions 14
 test.aaa_profiling.test_resultset.ResultSetTest.test_contains_doesnt_compile 2.7_postgresql_psycopg2_nocextensions 14
+test.aaa_profiling.test_resultset.ResultSetTest.test_contains_doesnt_compile 2.7_sqlite_pysqlite_cextensions 14
 test.aaa_profiling.test_resultset.ResultSetTest.test_contains_doesnt_compile 2.7_sqlite_pysqlite_nocextensions 14
 
 # TEST: test.aaa_profiling.test_resultset.ResultSetTest.test_string
 
 # TEST: test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_3_properties
 
+test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_3_properties 2.7_postgresql_psycopg2_cextensions 3302
 test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_3_properties 2.7_postgresql_psycopg2_nocextensions 3526
 
 # TEST: test.aaa_profiling.test_zoomark.ZooMarkTest.test_profile_4_expressions

test/lib/profiling.py

                 raise SkipTest("cProfile is not installed")
 
             if not _profile_stats.has_stats() and not _profile_stats.write:
+                # run the function anyway, to support dependent tests
+                # (not a great idea but we have these in test_zoomark)
+                fn(*args, **kw)
                 raise SkipTest("No profiling stats available on this "
                             "platform for this function.  Run tests with "
                             "--write-profiles to add statistics to %s for "

test/sql/test_type_expressions.py

-from sqlalchemy import Table, Column, String, func, MetaData, select
+from sqlalchemy import Table, Column, String, func, MetaData, select, TypeDecorator
 from test.lib import fixtures, AssertsCompiledSQL, testing
 from test.lib.testing import eq_
 
             "test_table WHERE test_table.y = lower(:y_2)"
         )
 
-class RoundTripTest(fixtures.TablesTest):
-    @classmethod
-    def define_tables(cls, metadata):
-        class MyString(String):
-            def bind_expression(self, bindvalue):
-                return func.lower(bindvalue)
-
-            def column_expression(self, col):
-                return func.upper(col)
-
-        Table(
-                'test_table',
-                metadata,
-                    Column('x', String(50)),
-                    Column('y', MyString(50)
-                )
-        )
-
+class RoundTripTestBase(object):
     def test_round_trip(self):
         testing.db.execute(
             self.tables.test_table.insert(),
             "Y1"
         )
 
-    @testing.fails_if(lambda: True, "still need to propagate "
-                "result_map more effectively")
     def test_targeting_individual_labels(self):
         testing.db.execute(
             self.tables.test_table.insert(),
             "Y1"
         )
 
+class StringRoundTripTest(fixtures.TablesTest, RoundTripTestBase):
+    @classmethod
+    def define_tables(cls, metadata):
+        class MyString(String):
+            def bind_expression(self, bindvalue):
+                return func.lower(bindvalue)
+
+            def column_expression(self, col):
+                return func.upper(col)
+
+        Table(
+                'test_table',
+                metadata,
+                    Column('x', String(50)),
+                    Column('y', MyString(50)
+                )
+        )
+
+
+class TypeDecRoundTripTest(fixtures.TablesTest, RoundTripTestBase):
+    @classmethod
+    def define_tables(cls, metadata):
+        class MyString(TypeDecorator):
+            impl = String
+            def bind_expression(self, bindvalue):
+                return func.lower(bindvalue)
+
+            def column_expression(self, col):
+                return func.upper(col)
+
+        Table(
+                'test_table',
+                metadata,
+                    Column('x', String(50)),
+                    Column('y', MyString(50)
+                )
+        )
+
 class ReturningTest(fixtures.TablesTest):
     __requires__ = 'returning',
 

test/sql/test_types.py

             []
         )
 
+        eq_(
+            testing.db.scalar(
+                select([type_coerce(literal('d1BIND_OUT'), MyType)])
+            ),
+            'd1BIND_OUT'
+        )
+
     @classmethod
     def define_tables(cls, metadata):
         class MyType(types.UserDefinedType):
             pass
 
         # unknown type + integer, right hand bind
-        # is an Integer
+        # coerces to given type
         expr = column("foo", MyFoobarType) + 5
-        assert expr.right.type._type_affinity is types.Integer
+        assert expr.right.type._type_affinity is MyFoobarType
 
         # untyped bind - it gets assigned MyFoobarType
         expr = column("foo", MyFoobarType) + bindparam("foo")
         assert expr.right.type._type_affinity is MyFoobarType
 
         expr = column("foo", MyFoobarType) - datetime.date(2010, 8, 25)
-        assert expr.right.type._type_affinity is types.Date
+        assert expr.right.type._type_affinity is MyFoobarType
 
     def test_date_coercion(self):
         from sqlalchemy.sql import column