]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Fixed bug where the expression system relied upon the ``str()``
authorMike Bayer <mike_mp@zzzcomputing.com>
Fri, 12 Jul 2013 15:32:34 +0000 (11:32 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Fri, 12 Jul 2013 15:32:34 +0000 (11:32 -0400)
form of a some expressions when referring to the ``.c`` collection
on a ``select()`` construct, but the ``str()`` form isn't available
since the element relies on dialect-specific compilation constructs,
notably the ``__getitem__()`` operator as used with a Postgresql
``ARRAY`` element.  The fix also adds a new exception class
:class:`.UnsupportedCompilationError` which is raised in those cases
where a compiler is asked to compile something it doesn't know
how to.  Also in 0.8.3.
[ticket:2780]

doc/build/changelog/changelog_08.rst
doc/build/changelog/changelog_09.rst
lib/sqlalchemy/exc.py
lib/sqlalchemy/sql/compiler.py
lib/sqlalchemy/sql/expression.py
lib/sqlalchemy/sql/visitors.py
test/sql/test_compiler.py
test/sql/test_selectable.py

index 5c03b9007d2e7379ab4038915c0413698c012dc2..8ec248b8937c37627966b8e638b6a09222f12c38 100644 (file)
@@ -6,6 +6,22 @@
 .. changelog::
     :version: 0.8.3
 
+    .. change::
+        :tags: bug, sql, postgresql
+        :tickets: 2780
+
+        Fixed bug where the expression system relied upon the ``str()``
+        form of a some expressions when referring to the ``.c`` collection
+        on a ``select()`` construct, but the ``str()`` form isn't available
+        since the element relies on dialect-specific compilation constructs,
+        notably the ``__getitem__()`` operator as used with a Postgresql
+        ``ARRAY`` element.  The fix also adds a new exception class
+        :class:`.UnsupportedCompilationError` which is raised in those cases
+        where a compiler is asked to compile something it doesn't know
+        how to.
+
+    .. change::
+        :tags: bug, engine, oracle
         :tickets: 2776
 
         Dialect.initialize() is not called a second time if an :class:`.Engine`
index 4eab720e0adb1175c59c622b710a1f8d42528077..b1966f899ec0ce2bae85a4c0d0d866d7c186eced 100644 (file)
@@ -6,6 +6,20 @@
 .. changelog::
     :version: 0.9.0
 
+    .. change::
+        :tags: bug, sql, postgresql
+        :tickets: 2780
+
+        Fixed bug where the expression system relied upon the ``str()``
+        form of a some expressions when referring to the ``.c`` collection
+        on a ``select()`` construct, but the ``str()`` form isn't available
+        since the element relies on dialect-specific compilation constructs,
+        notably the ``__getitem__()`` operator as used with a Postgresql
+        ``ARRAY`` element.  The fix also adds a new exception class
+        :class:`.UnsupportedCompilationError` which is raised in those cases
+        where a compiler is asked to compile something it doesn't know
+        how to.  Also in 0.8.3.
+
     .. change::
         :tags: bug, engine, oracle
         :tickets: 2776
index f5dc1119dcf18f526b4324ed7c98611c85f208cf..cfd1e2bc7b52f13f8249cf6ce70f30630dd58de3 100644 (file)
@@ -72,6 +72,18 @@ class CircularDependencyError(SQLAlchemyError):
 class CompileError(SQLAlchemyError):
     """Raised when an error occurs during SQL compilation"""
 
+class UnsupportedCompilationError(CompileError):
+    """Raised when an operation is not supported by the given compiler.
+
+
+    .. versionadded:: 0.8.3
+
+    """
+
+    def __init__(self, compiler, element_type):
+        super(UnsupportedCompilationError, self).__init__(
+                    "Compiler %r can't render element of type %s" %
+                                (compiler, element_type))
 
 class IdentifierError(SQLAlchemyError):
     """Raised when a schema name is beyond the max character limit"""
index 93dc3fc4d073104b841ad01edd3274df86965a93..a5f545de9a49d03aac75d4175eb4a233adb59ed2 100644 (file)
@@ -696,8 +696,12 @@ class SQLCompiler(engine.Compiled):
         if disp:
             return disp(binary, operator, **kw)
         else:
-            return self._generate_generic_binary(binary,
-                                OPERATORS[operator], **kw)
+            try:
+                opstring = OPERATORS[operator]
+            except KeyError:
+                raise exc.UnsupportedCompilationError(self, operator)
+            else:
+                return self._generate_generic_binary(binary, opstring, **kw)
 
     def visit_custom_op_binary(self, element, operator, **kw):
         return self._generate_generic_binary(element,
index 6ee110e9c48baced5677de68e75fc7c4e533cda5..9e5c4cfcb67fbc2ee7d77b887e75183761c349e1 100644 (file)
@@ -2360,7 +2360,10 @@ class ColumnElement(ClauseElement, ColumnOperators):
         """
         if name is None:
             name = self.anon_label
-            key = str(self)
+            try:
+                key = str(self)
+            except exc.UnsupportedCompilationError:
+                key = self.anon_label
         else:
             key = name
         co = ColumnClause(_as_truncated(name) if name_is_truncatable else name,
index 24ada8d9f2368ff86d48da32039d726da1946092..7b729bf7f8de4206abc30c840f39f013ae76e471 100644 (file)
@@ -26,6 +26,7 @@ http://techspot.zzzeek.org/2008/01/23/expression-transformations/
 from collections import deque
 from .. import util
 import operator
+from .. import exc
 
 __all__ = ['VisitableType', 'Visitable', 'ClauseVisitor',
     'CloningVisitor', 'ReplacingCloningVisitor', 'iterate',
@@ -70,14 +71,24 @@ def _generate_dispatch(cls):
             getter = operator.attrgetter("visit_%s" % visit_name)
 
             def _compiler_dispatch(self, visitor, **kw):
-                return getter(visitor)(self, **kw)
+                try:
+                    meth = getter(visitor)
+                except AttributeError:
+                    raise exc.UnsupportedCompilationError(visitor, cls)
+                else:
+                    return meth(self, **kw)
         else:
             # The optimization opportunity is lost for this case because the
             # __visit_name__ is not yet a string. As a result, the visit
             # string has to be recalculated with each compilation.
             def _compiler_dispatch(self, visitor, **kw):
                 visit_attr = 'visit_%s' % self.__visit_name__
-                return getattr(visitor, visit_attr)(self, **kw)
+                try:
+                    meth = getattr(visitor, visit_attr)
+                except AttributeError:
+                    raise exc.UnsupportedCompilationError(visitor, cls)
+                else:
+                    return meth(self, **kw)
 
         _compiler_dispatch.__doc__ = \
           """Look for an attribute named "visit_" + self.__visit_name__
index 8b9f9cfa497335894393d852cbc99ae7e4285612..bdfcccb22ee4966e4d79b7452bf92c451c42582c 100644 (file)
@@ -2482,6 +2482,46 @@ class SelectTest(fixtures.TestBase, AssertsCompiledSQL):
         )
 
 
+class UnsupportedTest(fixtures.TestBase):
+    def test_unsupported_element_str_visit_name(self):
+        from sqlalchemy.sql.expression import ClauseElement
+        class SomeElement(ClauseElement):
+            __visit_name__ = 'some_element'
+
+        assert_raises_message(
+            exc.UnsupportedCompilationError,
+            r"Compiler <sqlalchemy.sql.compiler.SQLCompiler .*"
+            r"can't render element of type <class '.*SomeElement'>",
+            SomeElement().compile
+        )
+
+    def test_unsupported_element_meth_visit_name(self):
+        from sqlalchemy.sql.expression import ClauseElement
+        class SomeElement(ClauseElement):
+            @classmethod
+            def __visit_name__(cls):
+                return "some_element"
+
+        assert_raises_message(
+            exc.UnsupportedCompilationError,
+            r"Compiler <sqlalchemy.sql.compiler.SQLCompiler .*"
+            r"can't render element of type <class '.*SomeElement'>",
+            SomeElement().compile
+        )
+
+    def test_unsupported_operator(self):
+        from sqlalchemy.sql.expression import BinaryExpression
+        def myop(x, y):
+            pass
+        binary = BinaryExpression(column("foo"), column("bar"), myop)
+        assert_raises_message(
+            exc.UnsupportedCompilationError,
+            r"Compiler <sqlalchemy.sql.compiler.SQLCompiler .*"
+            r"can't render element of type <function.*",
+            binary.compile
+        )
+
+
 class KwargPropagationTest(fixtures.TestBase):
 
     @classmethod
index 335083ce19b34419b0dcad24dccb037164c2eeea..df174fb25511ce7b0cf2cbc670e6f3d1e5a42a2f 100644 (file)
@@ -148,6 +148,19 @@ class SelectableTest(fixtures.TestBase, AssertsExecutionResults, AssertsCompiled
         s = select([t])._clone()
         assert c in s.c.bar.proxy_set
 
+
+    def test_no_error_on_unsupported_expr_key(self):
+        from sqlalchemy.dialects.postgresql import ARRAY
+
+        t = table('t', column('x', ARRAY(Integer)))
+
+        expr = t.c.x[5]
+        s = select([t, expr])
+        eq_(
+            s.c.keys(),
+            ['x', expr.anon_label]
+        )
+
     def test_cloned_intersection(self):
         t1 = table('t1', column('x'))
         t2 = table('t2', column('x'))