summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/engine/base.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy/engine/base.py')
-rw-r--r--lib/sqlalchemy/engine/base.py139
1 files changed, 94 insertions, 45 deletions
diff --git a/lib/sqlalchemy/engine/base.py b/lib/sqlalchemy/engine/base.py
index f69bd3d4b..1f2b7a3e5 100644
--- a/lib/sqlalchemy/engine/base.py
+++ b/lib/sqlalchemy/engine/base.py
@@ -1,5 +1,5 @@
# engine/base.py
-# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
+# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -11,8 +11,8 @@ from __future__ import with_statement
import sys
-from .. import exc, schema, util, log, interfaces
-from ..sql import expression, util as sql_util
+from .. import exc, util, log, interfaces
+from ..sql import expression, util as sql_util, schema, ddl
from .interfaces import Connectable, Compiled
from .util import _distill_params
import contextlib
@@ -303,20 +303,40 @@ class Connection(Connectable):
def invalidate(self, exception=None):
"""Invalidate the underlying DBAPI connection associated with
- this Connection.
+ this :class:`.Connection`.
- The underlying DB-API connection is literally closed (if
+ The underlying DBAPI connection is literally closed (if
possible), and is discarded. Its source connection pool will
typically lazily create a new connection to replace it.
- Upon the next usage, this Connection will attempt to reconnect
- to the pool with a new connection.
+ Upon the next use (where "use" typically means using the
+ :meth:`.Connection.execute` method or similar),
+ this :class:`.Connection` will attempt to
+ procure a new DBAPI connection using the services of the
+ :class:`.Pool` as a source of connectivty (e.g. a "reconnection").
+
+ If a transaction was in progress (e.g. the
+ :meth:`.Connection.begin` method has been called) when
+ :meth:`.Connection.invalidate` method is called, at the DBAPI
+ level all state associated with this transaction is lost, as
+ the DBAPI connection is closed. The :class:`.Connection`
+ will not allow a reconnection to proceed until the :class:`.Transaction`
+ object is ended, by calling the :meth:`.Transaction.rollback`
+ method; until that point, any attempt at continuing to use the
+ :class:`.Connection` will raise an
+ :class:`~sqlalchemy.exc.InvalidRequestError`.
+ This is to prevent applications from accidentally
+ continuing an ongoing transactional operations despite the
+ fact that the transaction has been lost due to an
+ invalidation.
+
+ The :meth:`.Connection.invalidate` method, just like auto-invalidation,
+ will at the connection pool level invoke the :meth:`.PoolEvents.invalidate`
+ event.
- Transactions in progress remain in an "opened" state (even though the
- actual transaction is gone); these must be explicitly rolled back
- before a reconnect on this Connection can proceed. This is to prevent
- applications from accidentally continuing their transactional
- operations in a non-transactional state.
+ .. seealso::
+
+ :ref:`pool_connection_invalidation`
"""
if self.invalidated:
@@ -403,7 +423,6 @@ class Connection(Connectable):
See also :meth:`.Connection.begin`,
:meth:`.Connection.begin_twophase`.
"""
-
if self.__transaction is None:
self.__transaction = RootTransaction(self)
else:
@@ -450,7 +469,7 @@ class Connection(Connectable):
return self.__transaction is not None
- def _begin_impl(self):
+ def _begin_impl(self, transaction):
if self._echo:
self.engine.logger.info("BEGIN (implicit)")
@@ -459,6 +478,8 @@ class Connection(Connectable):
try:
self.engine.dialect.do_begin(self.connection)
+ if self.connection._reset_agent is None:
+ self.connection._reset_agent = transaction
except Exception as e:
self._handle_dbapi_exception(e, None, None, None, None)
@@ -471,9 +492,12 @@ class Connection(Connectable):
self.engine.logger.info("ROLLBACK")
try:
self.engine.dialect.do_rollback(self.connection)
- self.__transaction = None
except Exception as e:
self._handle_dbapi_exception(e, None, None, None, None)
+ finally:
+ if self.connection._reset_agent is self.__transaction:
+ self.connection._reset_agent = None
+ self.__transaction = None
else:
self.__transaction = None
@@ -485,9 +509,12 @@ class Connection(Connectable):
self.engine.logger.info("COMMIT")
try:
self.engine.dialect.do_commit(self.connection)
- self.__transaction = None
except Exception as e:
self._handle_dbapi_exception(e, None, None, None, None)
+ finally:
+ if self.connection._reset_agent is self.__transaction:
+ self.connection._reset_agent = None
+ self.__transaction = None
def _savepoint_impl(self, name=None):
if self._has_events:
@@ -516,14 +543,17 @@ class Connection(Connectable):
self.engine.dialect.do_release_savepoint(self, name)
self.__transaction = context
- def _begin_twophase_impl(self, xid):
+ def _begin_twophase_impl(self, transaction):
if self._echo:
self.engine.logger.info("BEGIN TWOPHASE (implicit)")
if self._has_events:
- self.dispatch.begin_twophase(self, xid)
+ self.dispatch.begin_twophase(self, transaction.xid)
if self._still_open_and_connection_is_valid:
- self.engine.dialect.do_begin_twophase(self, xid)
+ self.engine.dialect.do_begin_twophase(self, transaction.xid)
+
+ if self.connection._reset_agent is None:
+ self.connection._reset_agent = transaction
def _prepare_twophase_impl(self, xid):
if self._has_events:
@@ -539,8 +569,14 @@ class Connection(Connectable):
if self._still_open_and_connection_is_valid:
assert isinstance(self.__transaction, TwoPhaseTransaction)
- self.engine.dialect.do_rollback_twophase(self, xid, is_prepared)
- self.__transaction = None
+ try:
+ self.engine.dialect.do_rollback_twophase(self, xid, is_prepared)
+ finally:
+ if self.connection._reset_agent is self.__transaction:
+ self.connection._reset_agent = None
+ self.__transaction = None
+ else:
+ self.__transaction = None
def _commit_twophase_impl(self, xid, is_prepared):
if self._has_events:
@@ -548,8 +584,14 @@ class Connection(Connectable):
if self._still_open_and_connection_is_valid:
assert isinstance(self.__transaction, TwoPhaseTransaction)
- self.engine.dialect.do_commit_twophase(self, xid, is_prepared)
- self.__transaction = None
+ try:
+ self.engine.dialect.do_commit_twophase(self, xid, is_prepared)
+ finally:
+ if self.connection._reset_agent is self.__transaction:
+ self.connection._reset_agent = None
+ self.__transaction = None
+ else:
+ self.__transaction = None
def _autorollback(self):
if not self.in_transaction():
@@ -581,6 +623,8 @@ class Connection(Connectable):
else:
if not self.__branch:
conn.close()
+ if conn._reset_agent is self.__transaction:
+ conn._reset_agent = None
del self.__connection
self.__can_reconnect = False
self.__transaction = None
@@ -652,17 +696,16 @@ class Connection(Connectable):
DBAPI-agnostic way, use the :func:`~.expression.text` construct.
"""
- for c in type(object).__mro__:
- if c in Connection.executors:
- return Connection.executors[c](
- self,
- object,
- multiparams,
- params)
- else:
+ if isinstance(object, util.string_types[0]):
+ return self._execute_text(object, multiparams, params)
+ try:
+ meth = object._execute_on_connection
+ except AttributeError:
raise exc.InvalidRequestError(
"Unexecutable object type: %s" %
type(object))
+ else:
+ return meth(self, multiparams, params)
def _execute_function(self, func, multiparams, params):
"""Execute a sql.FunctionElement object."""
@@ -825,7 +868,7 @@ class Connection(Connectable):
context = constructor(dialect, self, conn, *args)
except Exception as e:
self._handle_dbapi_exception(e,
- str(statement), parameters,
+ util.text_type(statement), parameters,
None, None)
if context.compiled:
@@ -898,6 +941,11 @@ class Connection(Connectable):
elif not context._is_explicit_returning:
result.close(_autoclose_connection=False)
result._metadata = None
+ elif context.isupdate and context._is_implicit_returning:
+ context._fetch_implicit_update_returning(result)
+ result.close(_autoclose_connection=False)
+ result._metadata = None
+
elif result._metadata is None:
# no results, get rowcount
# (which requires open cursor on some drivers
@@ -1033,16 +1081,6 @@ class Connection(Connectable):
if self.should_close_with_result:
self.close()
- # poor man's multimethod/generic function thingy
- executors = {
- expression.FunctionElement: _execute_function,
- expression.ClauseElement: _execute_clauseelement,
- Compiled: _execute_compiled,
- schema.SchemaItem: _execute_default,
- schema.DDLElement: _execute_ddl,
- util.string_types[0]: _execute_text
- }
-
def default_schema_name(self):
return self.engine.dialect.get_default_schema_name(self)
@@ -1210,7 +1248,7 @@ class Transaction(object):
class RootTransaction(Transaction):
def __init__(self, connection):
super(RootTransaction, self).__init__(connection, None)
- self.connection._begin_impl()
+ self.connection._begin_impl(self)
def _do_rollback(self):
if self.is_active:
@@ -1259,7 +1297,7 @@ class TwoPhaseTransaction(Transaction):
super(TwoPhaseTransaction, self).__init__(connection, None)
self._is_prepared = False
self.xid = xid
- self.connection._begin_twophase_impl(self.xid)
+ self.connection._begin_twophase_impl(self)
def prepare(self):
"""Prepare this :class:`.TwoPhaseTransaction`.
@@ -1423,7 +1461,7 @@ class Engine(Connectable, log.Identified):
echo = log.echo_property()
def __repr__(self):
- return 'Engine(%s)' % str(self.url)
+ return 'Engine(%r)' % self.url
def dispose(self):
"""Dispose of the connection pool used by this :class:`.Engine`.
@@ -1667,6 +1705,17 @@ class Engine(Connectable, log.Identified):
return self.dialect.get_table_names(conn, schema)
def has_table(self, table_name, schema=None):
+ """Return True if the given backend has a table of the given name.
+
+ .. seealso::
+
+ :ref:`metadata_reflection_inspector` - detailed schema inspection using
+ the :class:`.Inspector` interface.
+
+ :class:`.quoted_name` - used to pass quoting information along
+ with a schema identifier.
+
+ """
return self.run_callable(self.dialect.has_table, table_name, schema)
def raw_connection(self):