diff options
-rw-r--r-- | doc/build/changelog/changelog_09.rst | 10 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/functions.py | 18 | ||||
-rw-r--r-- | test/orm/test_query.py | 14 | ||||
-rw-r--r-- | test/sql/test_functions.py | 5 |
4 files changed, 38 insertions, 9 deletions
diff --git a/doc/build/changelog/changelog_09.rst b/doc/build/changelog/changelog_09.rst index b6bc07847..8f1daf003 100644 --- a/doc/build/changelog/changelog_09.rst +++ b/doc/build/changelog/changelog_09.rst @@ -16,6 +16,16 @@ .. change:: :tags: bug, sql + :tickets: 2927 + + Fixed regression whereby the "annotation" system used by the ORM was leaking + into the names used by standard functions in :mod:`sqlalchemy.sql.functions`, + such as ``func.coalesce()`` and ``func.max()``. Using these functions + in ORM attributes and thus producing annotated versions of them could + corrupt the actual function name rendered in the SQL. + + .. change:: + :tags: bug, sql :tickets: 2924, 2848 Fixed 0.9 regression where the new sortable support for :class:`.RowProxy` diff --git a/lib/sqlalchemy/sql/functions.py b/lib/sqlalchemy/sql/functions.py index f300e2416..a9b88b13b 100644 --- a/lib/sqlalchemy/sql/functions.py +++ b/lib/sqlalchemy/sql/functions.py @@ -17,6 +17,7 @@ from .selectable import FromClause, Select from . import operators from .visitors import VisitableType from .. import util +from . import annotation _registry = util.defaultdict(dict) @@ -308,16 +309,16 @@ class Function(FunctionElement): _compared_to_type=self.type, unique=True) - class _GenericMeta(VisitableType): def __init__(cls, clsname, bases, clsdict): - cls.name = name = clsdict.get('name', clsname) - cls.identifier = identifier = clsdict.get('identifier', name) - package = clsdict.pop('package', '_default') - # legacy - if '__return_type__' in clsdict: - cls.type = clsdict['__return_type__'] - register_function(identifier, cls, package) + if annotation.Annotated not in cls.__mro__: + cls.name = name = clsdict.get('name', clsname) + cls.identifier = identifier = clsdict.get('identifier', name) + package = clsdict.pop('package', '_default') + # legacy + if '__return_type__' in clsdict: + cls.type = clsdict['__return_type__'] + register_function(identifier, cls, package) super(_GenericMeta, cls).__init__(clsname, bases, clsdict) @@ -407,7 +408,6 @@ class GenericFunction(util.with_metaclass(_GenericMeta, Function)): self.type = sqltypes.to_instance( kwargs.pop("type_", None) or getattr(self, 'type', None)) - register_function("cast", Cast) register_function("extract", Extract) diff --git a/test/orm/test_query.py b/test/orm/test_query.py index fea2337ca..64b8cfdc4 100644 --- a/test/orm/test_query.py +++ b/test/orm/test_query.py @@ -348,6 +348,20 @@ class RawSelectTest(QueryTest, AssertsCompiledSQL): checkparams={"name": "ed"} ) + def test_col_prop_builtin_function(self): + class Foo(object): + pass + + mapper(Foo, self.tables.users, properties={ + 'foob': column_property(func.coalesce(self.tables.users.c.name)) + }) + + self.assert_compile( + select([Foo]).where(Foo.foob == 'somename').order_by(Foo.foob), + "SELECT users.id, users.name FROM users " + "WHERE coalesce(users.name) = :coalesce_1 ORDER BY coalesce(users.name)" + ) + class GetTest(QueryTest): def test_get(self): User = self.classes.User diff --git a/test/sql/test_functions.py b/test/sql/test_functions.py index ee1d61f85..3e47e018b 100644 --- a/test/sql/test_functions.py +++ b/test/sql/test_functions.py @@ -76,6 +76,11 @@ class CompileTest(fixtures.TestBase, AssertsCompiledSQL): ]: self.assert_compile(func.random(), ret, dialect=dialect) + def test_generic_annotation(self): + fn = func.coalesce('x', 'y')._annotate({"foo": "bar"}) + self.assert_compile( + fn, "coalesce(:param_1, :param_2)" + ) def test_custom_default_namespace(self): class myfunc(GenericFunction): pass |