summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/sqlalchemy/connectors/mxodbc.py6
-rw-r--r--lib/sqlalchemy/connectors/pyodbc.py2
-rw-r--r--lib/sqlalchemy/connectors/zxJDBC.py2
-rw-r--r--lib/sqlalchemy/dialects/firebird/kinterbasdb.py2
-rw-r--r--lib/sqlalchemy/dialects/informix/informixdb.py2
-rw-r--r--lib/sqlalchemy/dialects/mssql/adodbapi.py2
-rw-r--r--lib/sqlalchemy/dialects/mssql/pymssql.py2
-rw-r--r--lib/sqlalchemy/dialects/mysql/base.py2
-rw-r--r--lib/sqlalchemy/dialects/mysql/mysqlconnector.py2
-rw-r--r--lib/sqlalchemy/dialects/mysql/oursql.py2
-rw-r--r--lib/sqlalchemy/dialects/oracle/cx_oracle.py2
-rw-r--r--lib/sqlalchemy/dialects/postgresql/pg8000.py2
-rw-r--r--lib/sqlalchemy/dialects/postgresql/psycopg2.py8
-rw-r--r--lib/sqlalchemy/dialects/postgresql/pypostgresql.py2
-rw-r--r--lib/sqlalchemy/dialects/sqlite/pysqlite.py2
-rw-r--r--lib/sqlalchemy/dialects/sybase/pysybase.py2
-rw-r--r--lib/sqlalchemy/engine/base.py4
-rw-r--r--lib/sqlalchemy/engine/default.py2
-rw-r--r--lib/sqlalchemy/pool.py6
-rw-r--r--test/engine/test_reconnect.py39
20 files changed, 63 insertions, 30 deletions
diff --git a/lib/sqlalchemy/connectors/mxodbc.py b/lib/sqlalchemy/connectors/mxodbc.py
index f467234ca..5573dda40 100644
--- a/lib/sqlalchemy/connectors/mxodbc.py
+++ b/lib/sqlalchemy/connectors/mxodbc.py
@@ -106,9 +106,9 @@ class MxODBCConnector(Connector):
opts.pop('database', None)
return (args,), opts
- def is_disconnect(self, e):
- # eGenix recommends checking connection.closed here,
- # but how can we get a handle on the current connection?
+ def is_disconnect(self, e, connection, cursor):
+ # TODO: eGenix recommends checking connection.closed here
+ # Does that detect dropped connections ?
if isinstance(e, self.dbapi.ProgrammingError):
return "connection already closed" in str(e)
elif isinstance(e, self.dbapi.Error):
diff --git a/lib/sqlalchemy/connectors/pyodbc.py b/lib/sqlalchemy/connectors/pyodbc.py
index c66a8a8ae..3f6d6cb5f 100644
--- a/lib/sqlalchemy/connectors/pyodbc.py
+++ b/lib/sqlalchemy/connectors/pyodbc.py
@@ -81,7 +81,7 @@ class PyODBCConnector(Connector):
connectors.extend(['%s=%s' % (k,v) for k,v in keys.iteritems()])
return [[";".join (connectors)], connect_args]
- def is_disconnect(self, e):
+ def is_disconnect(self, e, connection, cursor):
if isinstance(e, self.dbapi.ProgrammingError):
return "The cursor's connection has been closed." in str(e) or \
'Attempt to use a closed connection.' in str(e)
diff --git a/lib/sqlalchemy/connectors/zxJDBC.py b/lib/sqlalchemy/connectors/zxJDBC.py
index a9ff5ec95..20bf9d9cf 100644
--- a/lib/sqlalchemy/connectors/zxJDBC.py
+++ b/lib/sqlalchemy/connectors/zxJDBC.py
@@ -46,7 +46,7 @@ class ZxJDBCConnector(Connector):
self.jdbc_driver_name],
opts]
- def is_disconnect(self, e):
+ def is_disconnect(self, e, connection, cursor):
if not isinstance(e, self.dbapi.ProgrammingError):
return False
e = str(e)
diff --git a/lib/sqlalchemy/dialects/firebird/kinterbasdb.py b/lib/sqlalchemy/dialects/firebird/kinterbasdb.py
index 216fec270..ebb7805ae 100644
--- a/lib/sqlalchemy/dialects/firebird/kinterbasdb.py
+++ b/lib/sqlalchemy/dialects/firebird/kinterbasdb.py
@@ -153,7 +153,7 @@ class FBDialect_kinterbasdb(FBDialect):
else:
return tuple([int(x) for x in m.group(1, 2, 3)] + ['interbase'])
- def is_disconnect(self, e):
+ def is_disconnect(self, e, connection, cursor):
if isinstance(e, (self.dbapi.OperationalError,
self.dbapi.ProgrammingError)):
msg = str(e)
diff --git a/lib/sqlalchemy/dialects/informix/informixdb.py b/lib/sqlalchemy/dialects/informix/informixdb.py
index c81983816..1b6833af7 100644
--- a/lib/sqlalchemy/dialects/informix/informixdb.py
+++ b/lib/sqlalchemy/dialects/informix/informixdb.py
@@ -62,7 +62,7 @@ class InformixDialect_informixdb(InformixDialect):
v = VERSION_RE.split(connection.connection.dbms_version)
return (int(v[1]), int(v[2]), v[3])
- def is_disconnect(self, e):
+ def is_disconnect(self, e, connection, cursor):
if isinstance(e, self.dbapi.OperationalError):
return 'closed the connection' in str(e) \
or 'connection not open' in str(e)
diff --git a/lib/sqlalchemy/dialects/mssql/adodbapi.py b/lib/sqlalchemy/dialects/mssql/adodbapi.py
index 355214d89..f2d945de2 100644
--- a/lib/sqlalchemy/dialects/mssql/adodbapi.py
+++ b/lib/sqlalchemy/dialects/mssql/adodbapi.py
@@ -62,7 +62,7 @@ class MSDialect_adodbapi(MSDialect):
connectors.append("Integrated Security=SSPI")
return [[";".join (connectors)], {}]
- def is_disconnect(self, e):
+ def is_disconnect(self, e, connection, cursor):
return isinstance(e, self.dbapi.adodbapi.DatabaseError) and \
"'connection failure'" in str(e)
diff --git a/lib/sqlalchemy/dialects/mssql/pymssql.py b/lib/sqlalchemy/dialects/mssql/pymssql.py
index 192e63366..8bc0ad95b 100644
--- a/lib/sqlalchemy/dialects/mssql/pymssql.py
+++ b/lib/sqlalchemy/dialects/mssql/pymssql.py
@@ -95,7 +95,7 @@ class MSDialect_pymssql(MSDialect):
opts['host'] = "%s:%s" % (opts['host'], port)
return [[], opts]
- def is_disconnect(self, e):
+ def is_disconnect(self, e, connection, cursor):
for msg in (
"Error 10054",
"Not connected to any MS SQL server",
diff --git a/lib/sqlalchemy/dialects/mysql/base.py b/lib/sqlalchemy/dialects/mysql/base.py
index b495cc36e..882e13d2e 100644
--- a/lib/sqlalchemy/dialects/mysql/base.py
+++ b/lib/sqlalchemy/dialects/mysql/base.py
@@ -1711,7 +1711,7 @@ class MySQLDialect(default.DefaultDialect):
resultset = connection.execute("XA RECOVER")
return [row['data'][0:row['gtrid_length']] for row in resultset]
- def is_disconnect(self, e):
+ def is_disconnect(self, e, connection, cursor):
if isinstance(e, self.dbapi.OperationalError):
return self._extract_error_code(e) in \
(2006, 2013, 2014, 2045, 2055)
diff --git a/lib/sqlalchemy/dialects/mysql/mysqlconnector.py b/lib/sqlalchemy/dialects/mysql/mysqlconnector.py
index d3ec1f5cf..035ebe459 100644
--- a/lib/sqlalchemy/dialects/mysql/mysqlconnector.py
+++ b/lib/sqlalchemy/dialects/mysql/mysqlconnector.py
@@ -118,7 +118,7 @@ class MySQLDialect_mysqlconnector(MySQLDialect):
def _extract_error_code(self, exception):
return exception.errno
- def is_disconnect(self, e):
+ def is_disconnect(self, e, connection, cursor):
errnos = (2006, 2013, 2014, 2045, 2055, 2048)
exceptions = (self.dbapi.OperationalError,self.dbapi.InterfaceError)
if isinstance(e, exceptions):
diff --git a/lib/sqlalchemy/dialects/mysql/oursql.py b/lib/sqlalchemy/dialects/mysql/oursql.py
index d3ef839b1..8caa1eaec 100644
--- a/lib/sqlalchemy/dialects/mysql/oursql.py
+++ b/lib/sqlalchemy/dialects/mysql/oursql.py
@@ -195,7 +195,7 @@ class MySQLDialect_oursql(MySQLDialect):
execution_options(_oursql_plain_query=True),
table, charset, full_name)
- def is_disconnect(self, e):
+ def is_disconnect(self, e, connection, cursor):
if isinstance(e, self.dbapi.ProgrammingError):
return e.errno is None and 'cursor' not in e.args[1] and e.args[1].endswith('closed')
else:
diff --git a/lib/sqlalchemy/dialects/oracle/cx_oracle.py b/lib/sqlalchemy/dialects/oracle/cx_oracle.py
index bc1c87703..b00adcd63 100644
--- a/lib/sqlalchemy/dialects/oracle/cx_oracle.py
+++ b/lib/sqlalchemy/dialects/oracle/cx_oracle.py
@@ -680,7 +680,7 @@ class OracleDialect_cx_oracle(OracleDialect):
for x in connection.connection.version.split('.')
)
- def is_disconnect(self, e):
+ def is_disconnect(self, e, connection, cursor):
if isinstance(e, self.dbapi.InterfaceError):
return "not connected" in str(e)
else:
diff --git a/lib/sqlalchemy/dialects/postgresql/pg8000.py b/lib/sqlalchemy/dialects/postgresql/pg8000.py
index d3c2f1d50..c4f00eabe 100644
--- a/lib/sqlalchemy/dialects/postgresql/pg8000.py
+++ b/lib/sqlalchemy/dialects/postgresql/pg8000.py
@@ -108,7 +108,7 @@ class PGDialect_pg8000(PGDialect):
opts.update(url.query)
return ([], opts)
- def is_disconnect(self, e):
+ def is_disconnect(self, e, connection, cursor):
return "connection is closed" in str(e)
dialect = PGDialect_pg8000
diff --git a/lib/sqlalchemy/dialects/postgresql/psycopg2.py b/lib/sqlalchemy/dialects/postgresql/psycopg2.py
index 50ea9d437..10d6e0269 100644
--- a/lib/sqlalchemy/dialects/postgresql/psycopg2.py
+++ b/lib/sqlalchemy/dialects/postgresql/psycopg2.py
@@ -227,6 +227,7 @@ class PGDialect_psycopg2(PGDialect):
execution_ctx_cls = PGExecutionContext_psycopg2
statement_compiler = PGCompiler_psycopg2
preparer = PGIdentifierPreparer_psycopg2
+ psycopg2_version = (0, 0)
colspecs = util.update_copy(
PGDialect.colspecs,
@@ -243,6 +244,11 @@ class PGDialect_psycopg2(PGDialect):
self.server_side_cursors = server_side_cursors
self.use_native_unicode = use_native_unicode
self.supports_unicode_binds = use_native_unicode
+ if self.dbapi and hasattr(self.dbapi, '__version__'):
+ m = re.match(r'(\d+)\.(\d+)\.(\d+)?',
+ self.dbapi.__version__)
+ if m:
+ self.psycopg2_version = tuple(map(int, m.group(1, 2, 3)))
@classmethod
def dbapi(cls):
@@ -295,7 +301,7 @@ class PGDialect_psycopg2(PGDialect):
opts.update(url.query)
return ([], opts)
- def is_disconnect(self, e):
+ def is_disconnect(self, e, connection, cursor):
if isinstance(e, self.dbapi.OperationalError):
# these error messages from libpq: interfaces/libpq/fe-misc.c.
# TODO: these are sent through gettext in libpq and we can't
diff --git a/lib/sqlalchemy/dialects/postgresql/pypostgresql.py b/lib/sqlalchemy/dialects/postgresql/pypostgresql.py
index dd22fcb33..a137a6240 100644
--- a/lib/sqlalchemy/dialects/postgresql/pypostgresql.py
+++ b/lib/sqlalchemy/dialects/postgresql/pypostgresql.py
@@ -67,7 +67,7 @@ class PGDialect_pypostgresql(PGDialect):
opts.update(url.query)
return ([], opts)
- def is_disconnect(self, e):
+ def is_disconnect(self, e, connection, cursor):
return "connection is closed" in str(e)
dialect = PGDialect_pypostgresql
diff --git a/lib/sqlalchemy/dialects/sqlite/pysqlite.py b/lib/sqlalchemy/dialects/sqlite/pysqlite.py
index 14cfa93d9..646c5b86f 100644
--- a/lib/sqlalchemy/dialects/sqlite/pysqlite.py
+++ b/lib/sqlalchemy/dialects/sqlite/pysqlite.py
@@ -238,7 +238,7 @@ class SQLiteDialect_pysqlite(SQLiteDialect):
return ([filename], opts)
- def is_disconnect(self, e):
+ def is_disconnect(self, e, connection, cursor):
return isinstance(e, self.dbapi.ProgrammingError) and "Cannot operate on a closed database." in str(e)
dialect = SQLiteDialect_pysqlite
diff --git a/lib/sqlalchemy/dialects/sybase/pysybase.py b/lib/sqlalchemy/dialects/sybase/pysybase.py
index fed792817..e12cf07dd 100644
--- a/lib/sqlalchemy/dialects/sybase/pysybase.py
+++ b/lib/sqlalchemy/dialects/sybase/pysybase.py
@@ -87,7 +87,7 @@ class SybaseDialect_pysybase(SybaseDialect):
# (12, 5, 0, 0)
return (vers / 1000, vers % 1000 / 100, vers % 100 / 10, vers % 10)
- def is_disconnect(self, e):
+ def is_disconnect(self, e, connection, cursor):
if isinstance(e, (self.dbapi.OperationalError,
self.dbapi.ProgrammingError)):
msg = str(e)
diff --git a/lib/sqlalchemy/engine/base.py b/lib/sqlalchemy/engine/base.py
index b78a30537..f6c974136 100644
--- a/lib/sqlalchemy/engine/base.py
+++ b/lib/sqlalchemy/engine/base.py
@@ -502,7 +502,7 @@ class Dialect(object):
raise NotImplementedError()
- def is_disconnect(self, e):
+ def is_disconnect(self, e, connection, cursor):
"""Return True if the given DB-API error indicates an invalid
connection"""
@@ -1518,7 +1518,7 @@ class Connection(Connectable):
if context:
context.handle_dbapi_exception(e)
- is_disconnect = self.dialect.is_disconnect(e)
+ is_disconnect = self.dialect.is_disconnect(e, self.__connection, cursor)
if is_disconnect:
self.invalidate(e)
self.engine.dispose()
diff --git a/lib/sqlalchemy/engine/default.py b/lib/sqlalchemy/engine/default.py
index aa75a2853..e669b305e 100644
--- a/lib/sqlalchemy/engine/default.py
+++ b/lib/sqlalchemy/engine/default.py
@@ -324,7 +324,7 @@ class DefaultDialect(base.Dialect):
def do_execute(self, cursor, statement, parameters, context=None):
cursor.execute(statement, parameters)
- def is_disconnect(self, e):
+ def is_disconnect(self, e, connection, cursor):
return False
def reset_isolation_level(self, dbapi_conn):
diff --git a/lib/sqlalchemy/pool.py b/lib/sqlalchemy/pool.py
index 5150d282c..7201bccf3 100644
--- a/lib/sqlalchemy/pool.py
+++ b/lib/sqlalchemy/pool.py
@@ -425,11 +425,7 @@ class _ConnectionFairy(object):
self._close()
def cursor(self, *args, **kwargs):
- try:
- return self.connection.cursor(*args, **kwargs)
- except Exception, e:
- self.invalidate(e=e)
- raise
+ return self.connection.cursor(*args, **kwargs)
def __getattr__(self, key):
return getattr(self.connection, key)
diff --git a/test/engine/test_reconnect.py b/test/engine/test_reconnect.py
index 31a2b705a..9b3a1b4db 100644
--- a/test/engine/test_reconnect.py
+++ b/test/engine/test_reconnect.py
@@ -58,7 +58,7 @@ class MockReconnectTest(TestBase):
module=dbapi, _initialize=False)
# monkeypatch disconnect checker
- db.dialect.is_disconnect = lambda e: isinstance(e, MockDisconnect)
+ db.dialect.is_disconnect = lambda e, conn, cursor: isinstance(e, MockDisconnect)
def test_reconnect(self):
"""test that an 'is_disconnect' condition will invalidate the
@@ -259,6 +259,22 @@ class RealReconnectTest(TestBase):
conn.close()
+ def test_ensure_is_disconnect_gets_connection(self):
+ def is_disconnect(e, conn, cursor):
+ # connection is still present
+ assert conn.connection is not None
+ # the error usually occurs on connection.cursor(),
+ # though MySQLdb we get a non-working cursor.
+ # assert cursor is None
+
+ engine.dialect.is_disconnect = is_disconnect
+ conn = engine.connect()
+ engine.test_shutdown()
+ assert_raises(
+ tsa.exc.DBAPIError,
+ conn.execute, select([1])
+ )
+
def test_invalidate_twice(self):
conn = engine.connect()
conn.invalidate()
@@ -280,7 +296,7 @@ class RealReconnectTest(TestBase):
p1 = engine.pool
- def is_disconnect(e):
+ def is_disconnect(e, conn, cursor):
return True
engine.dialect.is_disconnect = is_disconnect
@@ -374,13 +390,28 @@ class RecycleTest(TestBase):
def test_basic(self):
for threadlocal in False, True:
- engine = engines.reconnecting_engine(options={'pool_recycle'
- : 1, 'pool_threadlocal': threadlocal})
+ engine = engines.reconnecting_engine(
+ options={'pool_threadlocal': threadlocal})
+
conn = engine.contextual_connect()
eq_(conn.execute(select([1])).scalar(), 1)
conn.close()
+
+ # set the pool recycle down to 1.
+ # we aren't doing this inline with the
+ # engine create since cx_oracle takes way
+ # too long to create the 1st connection and don't
+ # want to build a huge delay into this test.
+
+ engine.pool._recycle = 1
+
+ # kill the DB connection
engine.test_shutdown()
+
+ # wait until past the recycle period
time.sleep(2)
+
+ # can connect, no exception
conn = engine.contextual_connect()
eq_(conn.execute(select([1])).scalar(), 1)
conn.close()