mirror of
https://github.com/sqlalchemy/sqlalchemy.git
synced 2026-05-17 06:07:27 -04:00
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:
Vendored
+20
@@ -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
@@ -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`
|
||||
@@ -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 (
|
||||
|
||||
@@ -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
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user