summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES12
-rw-r--r--lib/sqlalchemy/connectors/mxodbc.py56
-rw-r--r--lib/sqlalchemy/connectors/pyodbc.py3
-rw-r--r--lib/sqlalchemy/dialects/mssql/__init__.py2
-rw-r--r--lib/sqlalchemy/dialects/mssql/mxodbc.py56
-rw-r--r--lib/sqlalchemy/engine/base.py14
-rw-r--r--lib/sqlalchemy/engine/threadlocal.py2
-rw-r--r--lib/sqlalchemy/types.py4
-rw-r--r--test/engine/test_transaction.py3
-rw-r--r--test/sql/test_query.py23
10 files changed, 147 insertions, 28 deletions
diff --git a/CHANGES b/CHANGES
index b335972de..df49116bc 100644
--- a/CHANGES
+++ b/CHANGES
@@ -194,6 +194,16 @@ CHANGES
- Bind parameters are sent as a tuple instead of a list. Some
backend drivers will not accept bind parameters as a list.
+
+ - threadlocal engine wasn't properly closing the connection
+ upon close() - fixed that.
+
+ - Transaction object doesn't rollback or commit if it isn't
+ "active", allows more accurate nesting of begin/rollback/commit.
+
+ - Python unicode objects as binds result in the Unicode type,
+ not string, thus eliminating a certain class of unicode errors
+ on drivers that don't support unicode binds.
- metadata
- Added the ability to strip schema information when using
@@ -245,6 +255,8 @@ CHANGES
- mssql
- Re-established initial support for pymssql.
+ - Added basic support for mxODBC [ticket:1710].
+
- Removed the text_as_varchar option.
- oracle
diff --git a/lib/sqlalchemy/connectors/mxodbc.py b/lib/sqlalchemy/connectors/mxodbc.py
index a0f3f0216..c5d055b5a 100644
--- a/lib/sqlalchemy/connectors/mxodbc.py
+++ b/lib/sqlalchemy/connectors/mxodbc.py
@@ -1,3 +1,5 @@
+
+import sys
from sqlalchemy.connectors import Connector
class MxODBCConnector(Connector):
@@ -8,17 +10,53 @@ class MxODBCConnector(Connector):
supports_unicode_binds = False
@classmethod
- def import_dbapi(cls):
- import mxODBC as module
+ def dbapi(cls):
+ if 'win32' in sys.platform:
+ from mx.ODBC import Windows as module
+ elif 'linux' in sys.platform:
+ from mx.ODBC import unixODBC as module
+ elif 'darwin' in sys.platform:
+ from mx.ODBC import iODBC as module
+ else:
+ raise ImportError, "Unrecognized platform for mxODBC import"
return module
+ def visit_pool(self, pool):
+ def connect(conn, rec):
+ conn.stringformat = self.dbapi.MIXED_STRINGFORMAT
+ conn.datetimeformat = self.dbapi.PYDATETIME_DATETIMEFORMAT
+ #conn.bindmethod = self.dbapi.BIND_USING_PYTHONTYPE
+ #conn.bindmethod = self.dbapi.BIND_USING_SQLTYPE
+
+ pool.add_listener({'connect':connect})
+
def create_connect_args(self, url):
- '''Return a tuple of *args,**kwargs'''
- # FIXME: handle mx.odbc.Windows proprietary args
+ """ Return a tuple of *args,**kwargs for creating a connection.
+
+ The mxODBC 3.x connection constructor looks like this:
+
+ connect(dsn, user='', password='',
+ clear_auto_commit=1, errorhandler=None)
+
+ This method translates the values in the provided uri
+ into args and kwargs needed to instantiate an mxODBC Connection.
+
+ The arg 'errorhandler' is not used by SQLAlchemy and will
+ not be populated.
+ """
opts = url.translate_connect_args(username='user')
opts.update(url.query)
- argsDict = {}
- argsDict['user'] = opts['user']
- argsDict['password'] = opts['password']
- connArgs = [[opts['dsn']], argsDict]
- return connArgs
+ args = opts['host'],
+ kwargs = {'user':opts['user'],
+ 'password': opts['password']}
+ return args, kwargs
+
+ def is_disconnect(self, e):
+ if isinstance(e, self.dbapi.ProgrammingError):
+ return "connection already closed" in str(e)
+ elif isinstance(e, self.dbapi.Error):
+ return '[08S01]' in str(e)
+ else:
+ return False
+
+
diff --git a/lib/sqlalchemy/connectors/pyodbc.py b/lib/sqlalchemy/connectors/pyodbc.py
index 30a7f98d0..46b0556d5 100644
--- a/lib/sqlalchemy/connectors/pyodbc.py
+++ b/lib/sqlalchemy/connectors/pyodbc.py
@@ -68,7 +68,8 @@ class PyODBCConnector(Connector):
def is_disconnect(self, e):
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)
+ return "The cursor's connection has been closed." in str(e) or \
+ 'Attempt to use a closed connection.' in str(e)
elif isinstance(e, self.dbapi.Error):
return '[08S01]' in str(e)
else:
diff --git a/lib/sqlalchemy/dialects/mssql/__init__.py b/lib/sqlalchemy/dialects/mssql/__init__.py
index 23ce3aec8..65ae3e39b 100644
--- a/lib/sqlalchemy/dialects/mssql/__init__.py
+++ b/lib/sqlalchemy/dialects/mssql/__init__.py
@@ -1,4 +1,4 @@
-from sqlalchemy.dialects.mssql import base, pyodbc, adodbapi, pymssql, zxjdbc
+from sqlalchemy.dialects.mssql import base, pyodbc, adodbapi, pymssql, zxjdbc, mxodbc
base.dialect = pyodbc.dialect
diff --git a/lib/sqlalchemy/dialects/mssql/mxodbc.py b/lib/sqlalchemy/dialects/mssql/mxodbc.py
new file mode 100644
index 000000000..0047a55fe
--- /dev/null
+++ b/lib/sqlalchemy/dialects/mssql/mxodbc.py
@@ -0,0 +1,56 @@
+from sqlalchemy.dialects.mssql.base import MSExecutionContext, MSDialect
+from sqlalchemy.connectors.mxodbc import MxODBCConnector
+from sqlalchemy import types as sqltypes
+import re
+import sys
+
+from sqlalchemy.dialects.mssql.pyodbc import MSExecutionContext_pyodbc
+
+# The pyodbc execution context seems to work for mxODBC; reuse it here
+MSExecutionContext_mxodbc = MSExecutionContext_pyodbc
+
+
+class MSDialect_mxodbc(MxODBCConnector, MSDialect):
+ supports_sane_rowcount = True
+ supports_sane_multi_rowcount = False
+
+ execution_ctx_cls = MSExecutionContext_mxodbc
+
+
+ def __init__(self, description_encoding='latin-1', **params):
+ super(MSDialect_mxodbc, self).__init__(**params)
+ self.description_encoding = description_encoding
+
+ def initialize(self, connection):
+ super(MSDialect_mxodbc, self).initialize(connection)
+ dbapi_con = connection.connection
+
+dialect = MSDialect_mxodbc
+from sqlalchemy.dialects.mssql.base import MSExecutionContext, MSDialect
+from sqlalchemy.connectors.mxodbc import MxODBCConnector
+from sqlalchemy import types as sqltypes
+import re
+import sys
+
+from sqlalchemy.dialects.mssql.pyodbc import MSExecutionContext_pyodbc
+
+# The pyodbc execution context seems to work for mxODBC; reuse it here
+MSExecutionContext_mxodbc = MSExecutionContext_pyodbc
+
+
+class MSDialect_mxodbc(MxODBCConnector, MSDialect):
+ supports_sane_rowcount = True
+ supports_sane_multi_rowcount = False
+
+ execution_ctx_cls = MSExecutionContext_mxodbc
+
+
+ def __init__(self, description_encoding='latin-1', **params):
+ super(MSDialect_mxodbc, self).__init__(**params)
+ self.description_encoding = description_encoding
+
+ def initialize(self, connection):
+ super(MSDialect_mxodbc, self).initialize(connection)
+ dbapi_con = connection.connection
+
+dialect = MSDialect_mxodbc
diff --git a/lib/sqlalchemy/engine/base.py b/lib/sqlalchemy/engine/base.py
index ff475ee3d..578bd58d7 100644
--- a/lib/sqlalchemy/engine/base.py
+++ b/lib/sqlalchemy/engine/base.py
@@ -1287,8 +1287,8 @@ class Transaction(object):
def rollback(self):
if not self._parent.is_active:
return
- self.is_active = False
self._do_rollback()
+ self.is_active = False
def _do_rollback(self):
self._parent.rollback()
@@ -1318,10 +1318,12 @@ class RootTransaction(Transaction):
self.connection._begin_impl()
def _do_rollback(self):
- self.connection._rollback_impl()
+ if self.is_active:
+ self.connection._rollback_impl()
def _do_commit(self):
- self.connection._commit_impl()
+ if self.is_active:
+ self.connection._commit_impl()
class NestedTransaction(Transaction):
@@ -1330,10 +1332,12 @@ class NestedTransaction(Transaction):
self._savepoint = self.connection._savepoint_impl()
def _do_rollback(self):
- self.connection._rollback_to_savepoint_impl(self._savepoint, self._parent)
+ if self.is_active:
+ self.connection._rollback_to_savepoint_impl(self._savepoint, self._parent)
def _do_commit(self):
- self.connection._release_savepoint_impl(self._savepoint, self._parent)
+ if self.is_active:
+ self.connection._release_savepoint_impl(self._savepoint, self._parent)
class TwoPhaseTransaction(Transaction):
diff --git a/lib/sqlalchemy/engine/threadlocal.py b/lib/sqlalchemy/engine/threadlocal.py
index a9892ae7e..001caee2a 100644
--- a/lib/sqlalchemy/engine/threadlocal.py
+++ b/lib/sqlalchemy/engine/threadlocal.py
@@ -94,6 +94,8 @@ class TLEngine(base.Engine):
def close(self):
if not self.closed:
self.contextual_connect().close()
+ connection = self._connections.conn()
+ connection._force_close()
del self._connections.conn
self._connections.trans = []
diff --git a/lib/sqlalchemy/types.py b/lib/sqlalchemy/types.py
index d7b8f9289..7f1e38003 100644
--- a/lib/sqlalchemy/types.py
+++ b/lib/sqlalchemy/types.py
@@ -1607,8 +1607,10 @@ BOOLEANTYPE = Boolean()
# type which usually resolves to TEXT/CLOB
type_map = {
str: String(),
+ # Py3K
+ #bytes : LargeBinary(),
# Py2K
- unicode : String(),
+ unicode : Unicode(),
# end Py2K
int : Integer(),
float : Numeric(),
diff --git a/test/engine/test_transaction.py b/test/engine/test_transaction.py
index de2cc46d3..84ffaf455 100644
--- a/test/engine/test_transaction.py
+++ b/test/engine/test_transaction.py
@@ -830,9 +830,6 @@ class TLTransactionTest(TestBase):
tlengine.execute(users.insert(), user_id=3, user_name='user3')
tlengine.rollback()
- # TODO: removing this line, the test still tends to pass in most
- # cases, except sporadically on PG. this should be nailed down
- # in TLEngine - removing this line should be guaranteed fail.
tlengine.rollback()
tlengine.execute(users.insert(), user_id=4, user_name='user4')
diff --git a/test/sql/test_query.py b/test/sql/test_query.py
index 5433cb92f..d7bca1af4 100644
--- a/test/sql/test_query.py
+++ b/test/sql/test_query.py
@@ -536,7 +536,18 @@ class QueryTest(TestBase):
use_labels=labels,
order_by=[users.c.user_id.desc()]),
[(3,), (2,), (1,)])
+
+ @testing.fails_on("+pyodbc", "pyodbc row doesn't seem to accept slices")
+ def test_column_slices(self):
+ users.insert().execute(user_id=1, user_name='john')
+ users.insert().execute(user_id=2, user_name='jack')
+ addresses.insert().execute(address_id=1, user_id=2, address='foo@bar.com')
+ r = text("select * from query_addresses", bind=testing.db).execute().first()
+ self.assert_(r[0:1] == (1,))
+ self.assert_(r[1:] == (2, 'foo@bar.com'))
+ self.assert_(r[:-1] == (1, 2))
+
def test_column_accessor(self):
users.insert().execute(user_id=1, user_name='john')
users.insert().execute(user_id=2, user_name='jack')
@@ -550,15 +561,11 @@ class QueryTest(TestBase):
self.assert_(r.user_id == r['user_id'] == r[users.c.user_id] == 2)
self.assert_(r.user_name == r['user_name'] == r[users.c.user_name] == 'jack')
- # test slices
- r = text("select * from query_addresses", bind=testing.db).execute().first()
- self.assert_(r[0:1] == (1,))
- self.assert_(r[1:] == (2, 'foo@bar.com'))
- self.assert_(r[:-1] == (1, 2))
-
- # test a little sqlite weirdness - with the UNION, cols come back as "query_users.user_id" in cursor.description
+ # test a little sqlite weirdness - with the UNION,
+ # cols come back as "query_users.user_id" in cursor.description
r = text("select query_users.user_id, query_users.user_name from query_users "
- "UNION select query_users.user_id, query_users.user_name from query_users", bind=testing.db).execute().first()
+ "UNION select query_users.user_id, query_users.user_name from query_users",
+ bind=testing.db).execute().first()
self.assert_(r['user_id']) == 1
self.assert_(r['user_name']) == "john"