summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/build/changelog/changelog_09.rst10
-rw-r--r--lib/sqlalchemy/sql/functions.py18
-rw-r--r--test/orm/test_query.py14
-rw-r--r--test/sql/test_functions.py5
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