Add pyodbc fast_executemany

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.

Change-Id: I743fa7280e8f709addd330cfc7682623701cbb2e
Fixes: #4158
This commit is contained in:
Mike Bayer
2018-07-10 23:47:05 -04:00
parent 5fedaa0eb8
commit aaa60cd17e
5 changed files with 116 additions and 3 deletions
+20
View File
@@ -200,3 +200,23 @@ Dialect Improvements and Changes - Oracle
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`
+11
View File
@@ -0,0 +1,11 @@
.. 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`
+36 -1
View File
@@ -85,6 +85,33 @@ Pyodbc only has partial support for rowcount. See the notes at
: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
@@ -264,7 +291,8 @@ class MSDialect_pyodbc(PyODBCConnector, MSDialect):
}
)
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)
@@ -273,6 +301,7 @@ class MSDialect_pyodbc(PyODBCConnector, MSDialect):
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:
@@ -296,6 +325,12 @@ class MSDialect_pyodbc(PyODBCConnector, MSDialect):
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 (
+36
View File
@@ -10,6 +10,8 @@ from sqlalchemy.testing import assert_raises_message, \
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):
@@ -257,6 +259,40 @@ class EngineFromConfigTest(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()
+13 -2
View File
@@ -1066,12 +1066,23 @@ class DefaultRequirements(SuiteRequirements):
"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(