diff options
-rw-r--r-- | CHANGES | 5 | ||||
-rw-r--r-- | doc/build/reference/sqlalchemy/expressions.rst | 4 | ||||
-rw-r--r-- | doc/build/reference/sqlalchemy/interfaces.rst | 2 | ||||
-rw-r--r-- | lib/sqlalchemy/types.py | 52 | ||||
-rw-r--r-- | test/aaa_profiling/test_compiler.py | 2 | ||||
-rw-r--r-- | test/sql/test_types.py | 7 |
6 files changed, 55 insertions, 17 deletions
@@ -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 diff --git a/doc/build/reference/sqlalchemy/expressions.rst b/doc/build/reference/sqlalchemy/expressions.rst index 84210f04a..98e41fcb8 100644 --- a/doc/build/reference/sqlalchemy/expressions.rst +++ b/doc/build/reference/sqlalchemy/expressions.rst @@ -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: + + diff --git a/doc/build/reference/sqlalchemy/interfaces.rst b/doc/build/reference/sqlalchemy/interfaces.rst index c629c3250..f9f60882c 100644 --- a/doc/build/reference/sqlalchemy/interfaces.rst +++ b/doc/build/reference/sqlalchemy/interfaces.rst @@ -4,3 +4,5 @@ Interfaces .. automodule:: sqlalchemy.interfaces :members: :undoc-members: + + diff --git a/lib/sqlalchemy/types.py b/lib/sqlalchemy/types.py index fc9ad2d53..5c4e2ca3f 100644 --- a/lib/sqlalchemy/types.py +++ b/lib/sqlalchemy/types.py @@ -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.""" diff --git a/test/aaa_profiling/test_compiler.py b/test/aaa_profiling/test_compiler.py index e49a3df7f..dc02cece4 100644 --- a/test/aaa_profiling/test_compiler.py +++ b/test/aaa_profiling/test_compiler.py @@ -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() diff --git a/test/sql/test_types.py b/test/sql/test_types.py index d7f0d1ed7..3e9242481 100644 --- a/test/sql/test_types.py +++ b/test/sql/test_types.py @@ -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 |