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.
-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
--- /dev/null
+"""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
# 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):
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):
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):
# 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()