.. 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`
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"""
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,
"""
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,
from collections import deque
from .. import util
import operator
+from .. import exc
__all__ = ['VisitableType', 'Visitable', 'ClauseVisitor',
'CloningVisitor', 'ReplacingCloningVisitor', 'iterate',
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__
)
+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
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'))