of 64 for index names, separate from their
overall max length of 255. [ticket:1412]
+ - Calling fetchone() or similar on a result that
+ has already been exhausted, has been closed,
+ or is not a result-returning result now
+ raises ResourceClosedError, a subclass of
+ InvalidRequestError, in all cases, regardless
+ of backend. Previously, some DBAPIs would
+ raise ProgrammingError (i.e. pysqlite), others
+ would return None leading to downstream breakages
+ (i.e. MySQL-python).
+
+ - Connection, ResultProxy, as well as Session use
+ ResourceClosedError for all "this
+ connection/transaction/result is closed" types of
+ errors.
+
- declarative
- if @classproperty is used with a regular class-bound
mapper property attribute, it will be called to get the
"""work around SQLite issue whereby cursor.description is blank when PRAGMA returns no rows."""
if cursor.closed:
- cursor._fetchone_impl = lambda: None
+ cursor.fetchone = lambda: None
return cursor
self.__connection = self.engine.raw_connection()
self.__invalid = False
return self.__connection
- raise exc.InvalidRequestError("This Connection is closed")
+ raise exc.ResourceClosedError("This Connection is closed")
@property
def info(self):
"""
if self.closed:
- raise exc.InvalidRequestError("This Connection is closed")
+ raise exc.ResourceClosedError("This Connection is closed")
if self.__connection.is_valid:
self.__connection.invalidate(exception)
if _autoclose_connection and \
self.connection.should_close_with_result:
self.connection.close()
-
+ # allow consistent errors
+ self.cursor = None
+
def __iter__(self):
while True:
row = self.fetchone()
return self.dialect.supports_sane_multi_rowcount
def _fetchone_impl(self):
- return self.cursor.fetchone()
+ try:
+ return self.cursor.fetchone()
+ except AttributeError:
+ self._non_result()
def _fetchmany_impl(self, size=None):
- return self.cursor.fetchmany(size)
+ try:
+ return self.cursor.fetchmany(size)
+ except AttributeError:
+ self._non_result()
def _fetchall_impl(self):
- return self.cursor.fetchall()
-
+ try:
+ return self.cursor.fetchall()
+ except AttributeError:
+ self._non_result()
+
+ def _non_result(self):
+ if self._metadata is None:
+ raise exc.ResourceClosedError(
+ "This result object does not return rows. "
+ "It has been closed automatically.",
+ )
+ else:
+ raise exc.ResourceClosedError("This result object is closed.")
+
def process_rows(self, rows):
process_row = self._process_row
metadata = self._metadata
Else the cursor is automatically closed and None is returned.
"""
-
try:
row = self._fetchone_impl()
if row is not None:
Returns None if no row is present.
"""
+ if self._metadata is None:
+ self._non_result()
+
try:
row = self._fetchone_impl()
except Exception, e:
"""
+class ResourceClosedError(InvalidRequestError):
+ """An operation was requested from a connection, cursor, or other
+ object that's in a closed state."""
+
class NoSuchColumnError(KeyError, InvalidRequestError):
"""A nonexistent column is requested from a ``RowProxy``."""
def _assert_is_open(self, error_msg="The transaction is closed"):
if self.session is None:
- raise sa_exc.InvalidRequestError(error_msg)
+ raise sa_exc.ResourceClosedError(error_msg)
@property
def _is_transaction_boundary(self):
return self
def __exit__(self, type, value, traceback):
- self._assert_is_open("Cannot end transaction context. The transaction was closed from within the context")
+ self._assert_is_open("Cannot end transaction context. The transaction "
+ "was closed from within the context")
if self.session.transaction is None:
return
if type is None:
users = Table('query_users', metadata,
Column('user_id', INT, primary_key=True, test_needs_autoincrement=True),
Column('user_name', VARCHAR(20)),
+ test_needs_acid=True
)
addresses = Table('query_addresses', metadata,
Column('address_id', Integer, primary_key=True, test_needs_autoincrement=True),
Column('user_id', Integer, ForeignKey('query_users.user_id')),
- Column('address', String(30)))
+ Column('address', String(30)),
+ test_needs_acid=True
+ )
users2 = Table('u2', metadata,
Column('user_id', INT, primary_key = True),
Column('user_name', VARCHAR(20)),
+ test_needs_acid=True
)
metadata.create_all()
eq_(r[users.c.user_name], 'jack')
eq_(r.user_name, 'jack')
+ def test_graceful_fetch_on_non_rows(self):
+ """test that calling fetchone() etc. on a result that doesn't
+ return rows fails gracefully.
+
+ """
+
+ # these proxies don't work with no cursor.description present.
+ # so they don't apply to this test at the moment.
+ # base.FullyBufferedResultProxy,
+ # base.BufferedRowResultProxy,
+ # base.BufferedColumnResultProxy
+
+ conn = testing.db.connect()
+ for meth in ('fetchone', 'fetchall', 'first', 'scalar', 'fetchmany'):
+ trans = conn.begin()
+ result = conn.execute(users.insert(), user_id=1)
+ assert_raises_message(
+ exc.ResourceClosedError,
+ "This result object does not return rows. "
+ "It has been closed automatically.",
+ getattr(result, meth),
+ )
+ trans.rollback()
+
+ def test_fetchone_til_end(self):
+ result = testing.db.execute("select * from query_users")
+ eq_(result.fetchone(), None)
+ assert_raises_message(
+ exc.ResourceClosedError,
+ "This result object is closed.",
+ result.fetchone
+ )
+
def test_result_case_sensitivity(self):
"""test name normalization for result sets."""