From: Philip Jenvey Date: Fri, 11 Sep 2009 08:10:32 +0000 (+0000) Subject: mssql+zxjdbc support X-Git-Tag: rel_0_6beta1~294 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=f385260987da45ce140edba986b1ac0c2a6a9e35;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git mssql+zxjdbc support original patch from Victor Ng fixes #1505 --- diff --git a/README.unittests b/README.unittests index 92a7521d02..ac1b7a18e6 100644 --- a/README.unittests +++ b/README.unittests @@ -192,10 +192,15 @@ and an additional "owner" named "ed" is required: MSSQL: Tests that involve multiple connections require Snapshot Isolation ability implented on the test database in order to prevent deadlocks that will occur with record locking isolation. This feature is only available with -MSSQL 2005 and greater. For example:: +MSSQL 2005 and greater. You must enable snapshot isolation at the database level +and set the default cursor isolation with two SQL commands :: ALTER DATABASE MyDatabase SET ALLOW_SNAPSHOT_ISOLATION ON ALTER DATABASE MyDatabase SET READ_COMMITTED_SNAPSHOT ON + +MSSQL+zxJDBC: Trying to run the unit tests on Windows against SQL Server +requires using a test.cfg configuration file as the cmd.exe shell won't properly +pass the URL arguments into the nose test runner. diff --git a/lib/sqlalchemy/dialects/mssql/__init__.py b/lib/sqlalchemy/dialects/mssql/__init__.py index e3a829047c..292320568d 100644 --- a/lib/sqlalchemy/dialects/mssql/__init__.py +++ b/lib/sqlalchemy/dialects/mssql/__init__.py @@ -1,3 +1,3 @@ -from sqlalchemy.dialects.mssql import base, pyodbc, adodbapi, pymssql +from sqlalchemy.dialects.mssql import base, pyodbc, adodbapi, pymssql, zxjdbc -base.dialect = pyodbc.dialect \ No newline at end of file +base.dialect = pyodbc.dialect diff --git a/lib/sqlalchemy/dialects/mssql/zxjdbc.py b/lib/sqlalchemy/dialects/mssql/zxjdbc.py new file mode 100644 index 0000000000..28b9547d83 --- /dev/null +++ b/lib/sqlalchemy/dialects/mssql/zxjdbc.py @@ -0,0 +1,64 @@ +"""Support for the Microsoft SQL Server database via the zxjdbc JDBC +connector. + +JDBC Driver +----------- + +Requires the jTDS driver, available from: http://jtds.sourceforge.net/ + +Connecting +---------- + +URLs are of the standard form of +``mssql+zxjdbc://user:pass@host:port/dbname[?key=value&key=value...]``. + +Additional arguments which may be specified either as query string +arguments on the URL, or as keyword arguments to +:func:`~sqlalchemy.create_engine()` will be passed as Connection +properties to the underlying JDBC driver. + +""" +from sqlalchemy.connectors.zxJDBC import ZxJDBCConnector +from sqlalchemy.dialects.mssql.base import MSDialect, MSExecutionContext +from sqlalchemy.engine import base + +class MS_zxjdbcExecutionContext(MSExecutionContext): + + _embedded_scope_identity = False + + def pre_exec(self): + super(MS_zxjdbcExecutionContext, self).pre_exec() + # scope_identity after the fact returns null in jTDS so we must + # embed it + if self._select_lastrowid and self.dialect.use_scope_identity: + self._embedded_scope_identity = True + self.statement += "; SELECT scope_identity()" + + def post_exec(self): + if self._embedded_scope_identity: + while True: + try: + row = self.cursor.fetchall()[0] + break + except self.dialect.dbapi.Error, e: + self.cursor.nextset() + self._lastrowid = int(row[0]) + + if (self.isinsert or self.isupdate or self.isdelete) and self.compiled.returning: + self._result_proxy = base.FullyBufferedResultProxy(self) + + if self._enable_identity_insert: + table = self.dialect.identifier_preparer.format_table(self.compiled.statement.table) + self.cursor.execute("SET IDENTITY_INSERT %s OFF" % table) + + +class MS_zxjdbc(ZxJDBCConnector, MSDialect): + jdbc_db_name = 'jtds:sqlserver' + jdbc_driver_name = 'net.sourceforge.jtds.jdbc.Driver' + + execution_ctx_cls = MS_zxjdbcExecutionContext + + def _get_server_version_info(self, connection): + return tuple(int(x) for x in connection.connection.dbversion.split('.')) + +dialect = MS_zxjdbc diff --git a/lib/sqlalchemy/test/requires.py b/lib/sqlalchemy/test/requires.py index f3f4ec1911..be6ae9594b 100644 --- a/lib/sqlalchemy/test/requires.py +++ b/lib/sqlalchemy/test/requires.py @@ -70,7 +70,9 @@ def independent_connections(fn): # ODBC as well. return _chain_decorators_on( fn, - no_support('sqlite', 'no driver support') + no_support('sqlite', 'no driver support'), + exclude('mssql', '<', (9, 0, 0), + 'SQL Server 2005+ is required for independent connections'), ) def row_triggers(fn): diff --git a/test/orm/test_unitofwork.py b/test/orm/test_unitofwork.py index cdf6d39b94..b987291ca9 100644 --- a/test/orm/test_unitofwork.py +++ b/test/orm/test_unitofwork.py @@ -243,7 +243,8 @@ class UnicodeSchemaTest(engine_base.AltEngineTest, _base.MappedTest): def teardown_class(cls): super(UnicodeSchemaTest, cls).teardown_class() - @testing.fails_on('mssql', 'pyodbc returns a non unicode encoding of the results description.') + @testing.fails_on('mssql+pyodbc', + 'pyodbc returns a non unicode encoding of the results description.') @testing.resolve_artifact_names def test_mapping(self): class A(_base.ComparableEntity): @@ -280,7 +281,8 @@ class UnicodeSchemaTest(engine_base.AltEngineTest, _base.MappedTest): assert new_a1.t2s[0].d == b1.d session.expunge_all() - @testing.fails_on('mssql', 'pyodbc returns a non unicode encoding of the results description.') + @testing.fails_on('mssql+pyodbc', + 'pyodbc returns a non unicode encoding of the results description.') @testing.resolve_artifact_names def test_inheritance_mapping(self): class A(_base.ComparableEntity): diff --git a/test/sql/test_returning.py b/test/sql/test_returning.py index d76c76173a..8ba754c67f 100644 --- a/test/sql/test_returning.py +++ b/test/sql/test_returning.py @@ -99,13 +99,16 @@ class ReturningTest(TestBase, AssertsExecutionResults): # return value is documented as failing with psycopg2/executemany result2 = table.insert().returning(table).execute( [{'persons': 2, 'full': False}, {'persons': 3, 'full': True}]) - - if testing.against('firebird', 'mssql'): + + if testing.against('mssql+zxjdbc'): + # jtds apparently returns only the first row + eq_(result2.fetchall(), [(2, 2, False, None)]) + elif testing.against('firebird', 'mssql'): # Multiple inserts only return the last row - eq_(result2.fetchall(), [(3,3,True, None)]) + eq_(result2.fetchall(), [(3, 3, True, None)]) else: # nobody does this as far as we know (pg8000?) - eq_(result2.fetchall(), [(2, 2, False, None), (3,3,True, None)]) + eq_(result2.fetchall(), [(2, 2, False, None), (3, 3, True, None)]) test_executemany()