Dialect Improvements and Changes - SQL Server
=============================================
+
+.. _change_4158:
+
+Support for pyodbc fast_executemany
+-----------------------------------
+
+Pyodbc's recently added "fast_executemany" mode, available when using the
+Microsoft ODBC driver, is now an option for the pyodbc / mssql dialect.
+Pass it via :func:`.create_engine`::
+
+ engine = create_engine(
+ "mssql+pyodbc://scott:tiger@mssql2017:1433/test?driver=ODBC+Driver+13+for+SQL+Server",
+ fast_executemany=True)
+
+.. seealso::
+
+ :ref:`mssql_pyodbc_fastexecutemany`
+
+
+:ticket:`4158`
--- /dev/null
+.. change::
+ :tags: feature, mssql
+ :tickets: 4158
+
+ Added ``fast_executemany=True`` parameter to the SQL Server pyodbc dialect,
+ which enables use of pyodbc's new performance feature of the same name
+ when using Microsoft ODBC drivers.
+
+ .. seealso::
+
+ :ref:`change_4158`
:ref:`mssql_rowcount_versioning` for important notes when using ORM
versioning.
+.. _mssql_pyodbc_fastexecutemany:
+
+Fast Executemany Mode
+---------------------
+
+The Pyodbc driver has added support for a "fast executemany" mode of execution
+which greatly reduces round trips for a DBAPI ``executemany()`` call when using
+Microsoft ODBC drivers. The feature is enabled by setting the flag
+``.fast_executemany`` on the DBAPI cursor when an executemany call is to be
+used. The SQLAlchemy pyodbc SQL Server dialect supports setting this flag
+automatically when the ``.fast_executemany`` flag is passed to
+:func:`.create_engine`; note that the ODBC driver must be the Microsoft driver
+in order to use this flag::
+
+ engine = create_engine(
+ "mssql+pyodbc://scott:tiger@mssql2017:1433/test?driver=ODBC+Driver+13+for+SQL+Server",
+ fast_executemany=True)
+
+.. versionadded:: 1.3
+
+.. seealso::
+
+ `fast executemany
+ <https://github.com/mkleehammer/pyodbc/wiki/Features-beyond-the-DB-API#fast_executemany>`_
+ - on github
+
+
"""
from .base import MSExecutionContext, MSDialect, BINARY, VARBINARY
}
)
- def __init__(self, description_encoding=None, **params):
+ def __init__(self, description_encoding=None, fast_executemany=False,
+ **params):
if 'description_encoding' in params:
self.description_encoding = params.pop('description_encoding')
super(MSDialect_pyodbc, self).__init__(**params)
hasattr(self.dbapi.Cursor, 'nextset')
self._need_decimal_fix = self.dbapi and \
self._dbapi_version() < (2, 1, 8)
+ self.fast_executemany = fast_executemany
def _get_server_version_info(self, connection):
try:
pass
return tuple(version)
+ def do_executemany(self, cursor, statement, parameters, context=None):
+ if self.fast_executemany:
+ cursor.fast_executemany = True
+ super(MSDialect_pyodbc, self).do_executemany(
+ cursor, statement, parameters, context=context)
+
def is_disconnect(self, e, connection, cursor):
if isinstance(e, self.dbapi.Error):
for code in (
assert_warnings, expect_warnings
from sqlalchemy.testing.mock import Mock
from sqlalchemy.dialects.mssql import base
+from sqlalchemy import Integer, String, Table, Column
+from sqlalchemy import event
class ParseConnectTest(fixtures.TestBase):
eq_(e.dialect.legacy_schema_aliasing, False)
+class FastExecutemanyTest(fixtures.TestBase):
+ __only_on__ = 'mssql'
+ __backend__ = True
+ __requires__ = ('pyodbc_fast_executemany', )
+
+ @testing.provide_metadata
+ def test_flag_on(self):
+ t = Table(
+ 't', self.metadata,
+ Column('id', Integer, primary_key=True),
+ Column('data', String(50))
+ )
+ t.create()
+
+ eng = engines.testing_engine(options={"fast_executemany": True})
+
+ @event.listens_for(eng, "after_cursor_execute")
+ def after_cursor_execute(
+ conn, cursor, statement, parameters, context, executemany):
+ if executemany:
+ assert cursor.fast_executemany
+
+ with eng.connect() as conn:
+ conn.execute(
+ t.insert(),
+ [{"id": i, "data": "data_%d" % i} for i in range(100)]
+ )
+
+ conn.execute(
+ t.insert(),
+ {"id": 200, "data": "data_200"}
+ )
+
+
class VersionDetectionTest(fixtures.TestBase):
def test_pymssql_version(self):
dialect = pymssql.MSDialect_pymssql()
"works, but Oracle just gets tired with "
"this much connection activity")
-
-
@property
def no_mssql_freetds(self):
return self.mssql_freetds.not_()
+ @property
+ def pyodbc_fast_executemany(self):
+ def has_fastexecutemany(config):
+ if not against(config, "mssql+pyodbc"):
+ return False
+ with config.db.connect() as conn:
+ drivername = conn.connection.connection.getinfo(
+ config.db.dialect.dbapi.SQL_DRIVER_NAME)
+ # on linux this is 'libmsodbcsql-13.1.so.9.2'.
+ # don't know what it is on windows
+ return "msodbc" in drivername
+ return only_if(has_fastexecutemany)
+
@property
def python_fixed_issue_8743(self):
return exclusions.skip_if(