UnicodeDecodeError is thrown instead of StatementError if the statement with non-ASCII symbols lacks parameter bind value

Issue #2871 resolved
Former user created an issue

Environment: Ubuntu 12.04, python 2.7.3, SA 0.8.2, sys.getdefaultencoding() => 'ascii'

What we do: run Session.execute against SQL statement with a non-ASCII symbols that declares a bind parameter but has it not fullfilled.

What do we expect: A StatementError with text A value is required for bind parameter 'x' is thrown

What do we observe: A UnicodeDecodeError is thrown instead.

Sample script:

# encoding: utf-8
from sqlalchemy.engine import create_engine
from sqlalchemy.orm.session import sessionmaker


def test_foo():
    engine = create_engine('sqlite://')
    engine.execute('create table test (value varchar(100))')
    session = sessionmaker(bind=engine)()
    session.execute(u"""
    --- КИРИЛЛИЦА
    insert into test values (:x)
    """)


if __name__ == '__main__':
    test_foo()

In the absence of --- КИРИЛЛИЦА line this throws a proper StatementError:

Traceback (most recent call last):
  File "test_a.py", line 15, in <module>
    test_foo()
  File "test_a.py", line 12, in test_foo
    """)
  File "/usr/lib/python2.7/dist-packages/sqlalchemy/orm/session.py", line 934, in execute
    clause, params or {})
  File "/usr/lib/python2.7/dist-packages/sqlalchemy/engine/base.py", line 662, in execute
    params)
  File "/usr/lib/python2.7/dist-packages/sqlalchemy/engine/base.py", line 761, in _execute_clauseelement
    compiled_sql, distilled_params
  File "/usr/lib/python2.7/dist-packages/sqlalchemy/engine/base.py", line 828, in _execute_context
    None, None)
  File "/usr/lib/python2.7/dist-packages/sqlalchemy/engine/base.py", line 1024, in _handle_dbapi_exception
    exc_info
  File "/usr/lib/python2.7/dist-packages/sqlalchemy/util/compat.py", line 195, in raise_from_cause
    reraise(type(exception), exception, tb=exc_tb)
  File "/usr/lib/python2.7/dist-packages/sqlalchemy/engine/base.py", line 824, in _execute_context
    context = constructor(dialect, self, conn, *args)
  File "/usr/lib/python2.7/dist-packages/sqlalchemy/engine/default.py", line 438, in _init_compiled
    grp, m in enumerate(parameters)]
  File "/usr/lib/python2.7/dist-packages/sqlalchemy/sql/compiler.py", line 367, in construct_params
    % bindparam.key)
sqlalchemy.exc.StatementError: A value is required for bind parameter u'x' (original cause: InvalidRequestError: A value is required for bind parameter u'x') '\n    insert into test values (?)\n    ' [{}]({})

But the actual error is:

Traceback (most recent call last):
  File "test_a.py", line 16, in <module>
    test_foo()
  File "test_a.py", line 13, in test_foo
    """)
  File "/usr/lib/python2.7/dist-packages/sqlalchemy/orm/session.py", line 934, in execute
    clause, params or {})
  File "/usr/lib/python2.7/dist-packages/sqlalchemy/engine/base.py", line 662, in execute
    params)
  File "/usr/lib/python2.7/dist-packages/sqlalchemy/engine/base.py", line 761, in _execute_clauseelement
    compiled_sql, distilled_params
  File "/usr/lib/python2.7/dist-packages/sqlalchemy/engine/base.py", line 827, in _execute_context
    str(statement), parameters,
UnicodeEncodeError: 'ascii' codec can't encode characters in position 9-17: ordinal not in range(128)

This is causes here: https://github.com/zzzeek/sqlalchemy/blob/master/lib/sqlalchemy/engine/base.py#L827

Strangely, but it is the only place where str(statement) is called in preparation for self._handle_dbapi_exception call. Looks like the str part is superfluous.

Comments (2)

  1. Mike Bayer repo owner

    the code is unclear in this regard in that the variable name statement within base.py is used to represent SQL statements in at least three forms; as a string, as a Compiled object, and as a ClauseElement. In this case, the statement enters the method as a Compiled construct, so in order for it to be part of the StatementException object's message, we need to call its __str__() or __unicode__() method. After we get an ExecutionContext, the same name statement is re-assigned to the stringified SQL statement, so is then handled as a string. So the name statement throughout engine/base.py might be better if it were renamed to clarify its type.

    thanks for the test!

    ad85ab12d62e65b0310c778057551bcdd460f0d9 0.8

    f112dc1d533033f19186eb65227aba1660d03102 0.9

  2. Log in to comment