You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					646 lines
				
				22 KiB
			
		
		
			
		
	
	
					646 lines
				
				22 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								# mssql/pyodbc.py
							 | 
						||
| 
								 | 
							
								# Copyright (C) 2005-2022 the SQLAlchemy authors and contributors
							 | 
						||
| 
								 | 
							
								# <see AUTHORS file>
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# This module is part of SQLAlchemy and is released under
							 | 
						||
| 
								 | 
							
								# the MIT License: https://www.opensource.org/licenses/mit-license.php
							 | 
						||
| 
								 | 
							
								r"""
							 | 
						||
| 
								 | 
							
								.. dialect:: mssql+pyodbc
							 | 
						||
| 
								 | 
							
								    :name: PyODBC
							 | 
						||
| 
								 | 
							
								    :dbapi: pyodbc
							 | 
						||
| 
								 | 
							
								    :connectstring: mssql+pyodbc://<username>:<password>@<dsnname>
							 | 
						||
| 
								 | 
							
								    :url: https://pypi.org/project/pyodbc/
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Connecting to PyODBC
							 | 
						||
| 
								 | 
							
								--------------------
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The URL here is to be translated to PyODBC connection strings, as
							 | 
						||
| 
								 | 
							
								detailed in `ConnectionStrings <https://code.google.com/p/pyodbc/wiki/ConnectionStrings>`_.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								DSN Connections
							 | 
						||
| 
								 | 
							
								^^^^^^^^^^^^^^^
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								A DSN connection in ODBC means that a pre-existing ODBC datasource is
							 | 
						||
| 
								 | 
							
								configured on the client machine.   The application then specifies the name
							 | 
						||
| 
								 | 
							
								of this datasource, which encompasses details such as the specific ODBC driver
							 | 
						||
| 
								 | 
							
								in use as well as the network address of the database.   Assuming a datasource
							 | 
						||
| 
								 | 
							
								is configured on the client, a basic DSN-based connection looks like::
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    engine = create_engine("mssql+pyodbc://scott:tiger@some_dsn")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Which above, will pass the following connection string to PyODBC::
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    DSN=some_dsn;UID=scott;PWD=tiger
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								If the username and password are omitted, the DSN form will also add
							 | 
						||
| 
								 | 
							
								the ``Trusted_Connection=yes`` directive to the ODBC string.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Hostname Connections
							 | 
						||
| 
								 | 
							
								^^^^^^^^^^^^^^^^^^^^
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Hostname-based connections are also supported by pyodbc.  These are often
							 | 
						||
| 
								 | 
							
								easier to use than a DSN and have the additional advantage that the specific
							 | 
						||
| 
								 | 
							
								database name to connect towards may be specified locally in the URL, rather
							 | 
						||
| 
								 | 
							
								than it being fixed as part of a datasource configuration.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								When using a hostname connection, the driver name must also be specified in the
							 | 
						||
| 
								 | 
							
								query parameters of the URL.  As these names usually have spaces in them, the
							 | 
						||
| 
								 | 
							
								name must be URL encoded which means using plus signs for spaces::
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    engine = create_engine("mssql+pyodbc://scott:tiger@myhost:port/databasename?driver=ODBC+Driver+17+for+SQL+Server")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Other keywords interpreted by the Pyodbc dialect to be passed to
							 | 
						||
| 
								 | 
							
								``pyodbc.connect()`` in both the DSN and hostname cases include:
							 | 
						||
| 
								 | 
							
								``odbc_autotranslate``, ``ansi``, ``unicode_results``, ``autocommit``,
							 | 
						||
| 
								 | 
							
								``authentication``.
							 | 
						||
| 
								 | 
							
								Note that in order for the dialect to recognize these keywords
							 | 
						||
| 
								 | 
							
								(including the ``driver`` keyword above) they must be all lowercase.
							 | 
						||
| 
								 | 
							
								Multiple additional keyword arguments must be separated by an
							 | 
						||
| 
								 | 
							
								ampersand (``&``), not a semicolon::
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    engine = create_engine(
							 | 
						||
| 
								 | 
							
								        "mssql+pyodbc://scott:tiger@myhost:49242/databasename"
							 | 
						||
| 
								 | 
							
								        "?driver=ODBC+Driver+17+for+SQL+Server"
							 | 
						||
| 
								 | 
							
								        "&authentication=ActiveDirectoryIntegrated"
							 | 
						||
| 
								 | 
							
								    )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The equivalent URL can be constructed using :class:`_sa.engine.URL`::
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    from sqlalchemy.engine import URL
							 | 
						||
| 
								 | 
							
								    connection_url = URL.create(
							 | 
						||
| 
								 | 
							
								        "mssql+pyodbc",
							 | 
						||
| 
								 | 
							
								        username="scott",
							 | 
						||
| 
								 | 
							
								        password="tiger",
							 | 
						||
| 
								 | 
							
								        host="myhost",
							 | 
						||
| 
								 | 
							
								        port=49242,
							 | 
						||
| 
								 | 
							
								        database="databasename",
							 | 
						||
| 
								 | 
							
								        query={
							 | 
						||
| 
								 | 
							
								            "driver": "ODBC Driver 17 for SQL Server",
							 | 
						||
| 
								 | 
							
								            "authentication": "ActiveDirectoryIntegrated",
							 | 
						||
| 
								 | 
							
								        },
							 | 
						||
| 
								 | 
							
								    )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Pass through exact Pyodbc string
							 | 
						||
| 
								 | 
							
								^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								A PyODBC connection string can also be sent in pyodbc's format directly, as
							 | 
						||
| 
								 | 
							
								specified in `the PyODBC documentation
							 | 
						||
| 
								 | 
							
								<https://github.com/mkleehammer/pyodbc/wiki/Connecting-to-databases>`_,
							 | 
						||
| 
								 | 
							
								using the parameter ``odbc_connect``.  A :class:`_sa.engine.URL` object
							 | 
						||
| 
								 | 
							
								can help make this easier::
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    from sqlalchemy.engine import URL
							 | 
						||
| 
								 | 
							
								    connection_string = "DRIVER={SQL Server Native Client 10.0};SERVER=dagger;DATABASE=test;UID=user;PWD=password"
							 | 
						||
| 
								 | 
							
								    connection_url = URL.create("mssql+pyodbc", query={"odbc_connect": connection_string})
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    engine = create_engine(connection_url)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								.. _mssql_pyodbc_access_tokens:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Connecting to databases with access tokens
							 | 
						||
| 
								 | 
							
								^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Some database servers are set up to only accept access tokens for login. For
							 | 
						||
| 
								 | 
							
								example, SQL Server allows the use of Azure Active Directory tokens to connect
							 | 
						||
| 
								 | 
							
								to databases. This requires creating a credential object using the
							 | 
						||
| 
								 | 
							
								``azure-identity`` library. More information about the authentication step can be
							 | 
						||
| 
								 | 
							
								found in `Microsoft's documentation
							 | 
						||
| 
								 | 
							
								<https://docs.microsoft.com/en-us/azure/developer/python/azure-sdk-authenticate?tabs=bash>`_.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								After getting an engine, the credentials need to be sent to ``pyodbc.connect``
							 | 
						||
| 
								 | 
							
								each time a connection is requested. One way to do this is to set up an event
							 | 
						||
| 
								 | 
							
								listener on the engine that adds the credential token to the dialect's connect
							 | 
						||
| 
								 | 
							
								call. This is discussed more generally in :ref:`engines_dynamic_tokens`. For
							 | 
						||
| 
								 | 
							
								SQL Server in particular, this is passed as an ODBC connection attribute with
							 | 
						||
| 
								 | 
							
								a data structure `described by Microsoft
							 | 
						||
| 
								 | 
							
								<https://docs.microsoft.com/en-us/sql/connect/odbc/using-azure-active-directory#authenticating-with-an-access-token>`_.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The following code snippet will create an engine that connects to an Azure SQL
							 | 
						||
| 
								 | 
							
								database using Azure credentials::
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    import struct
							 | 
						||
| 
								 | 
							
								    from sqlalchemy import create_engine, event
							 | 
						||
| 
								 | 
							
								    from sqlalchemy.engine.url import URL
							 | 
						||
| 
								 | 
							
								    from azure import identity
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    SQL_COPT_SS_ACCESS_TOKEN = 1256  # Connection option for access tokens, as defined in msodbcsql.h
							 | 
						||
| 
								 | 
							
								    TOKEN_URL = "https://database.windows.net/"  # The token URL for any Azure SQL database
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    connection_string = "mssql+pyodbc://@my-server.database.windows.net/myDb?driver=ODBC+Driver+17+for+SQL+Server"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    engine = create_engine(connection_string)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    azure_credentials = identity.DefaultAzureCredential()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @event.listens_for(engine, "do_connect")
							 | 
						||
| 
								 | 
							
								    def provide_token(dialect, conn_rec, cargs, cparams):
							 | 
						||
| 
								 | 
							
								        # remove the "Trusted_Connection" parameter that SQLAlchemy adds
							 | 
						||
| 
								 | 
							
								        cargs[0] = cargs[0].replace(";Trusted_Connection=Yes", "")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # create token credential
							 | 
						||
| 
								 | 
							
								        raw_token = azure_credentials.get_token(TOKEN_URL).token.encode("utf-16-le")
							 | 
						||
| 
								 | 
							
								        token_struct = struct.pack(f"<I{len(raw_token)}s", len(raw_token), raw_token)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # apply it to keyword arguments
							 | 
						||
| 
								 | 
							
								        cparams["attrs_before"] = {SQL_COPT_SS_ACCESS_TOKEN: token_struct}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								.. tip::
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    The ``Trusted_Connection`` token is currently added by the SQLAlchemy
							 | 
						||
| 
								 | 
							
								    pyodbc dialect when no username or password is present.  This needs
							 | 
						||
| 
								 | 
							
								    to be removed per Microsoft's
							 | 
						||
| 
								 | 
							
								    `documentation for Azure access tokens
							 | 
						||
| 
								 | 
							
								    <https://docs.microsoft.com/en-us/sql/connect/odbc/using-azure-active-directory#authenticating-with-an-access-token>`_,
							 | 
						||
| 
								 | 
							
								    stating that a connection string when using an access token must not contain
							 | 
						||
| 
								 | 
							
								    ``UID``, ``PWD``, ``Authentication`` or ``Trusted_Connection`` parameters.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Enable autocommit for Azure SQL Data Warehouse (DW) connections
							 | 
						||
| 
								 | 
							
								^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Azure SQL Data Warehouse does not support transactions,
							 | 
						||
| 
								 | 
							
								and that can cause problems with SQLAlchemy's "autobegin" (and implicit
							 | 
						||
| 
								 | 
							
								commit/rollback) behavior. We can avoid these problems by enabling autocommit
							 | 
						||
| 
								 | 
							
								at both the pyodbc and engine levels::
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    connection_url = sa.engine.URL.create(
							 | 
						||
| 
								 | 
							
								        "mssql+pyodbc",
							 | 
						||
| 
								 | 
							
								        username="scott",
							 | 
						||
| 
								 | 
							
								        password="tiger",
							 | 
						||
| 
								 | 
							
								        host="dw.azure.example.com",
							 | 
						||
| 
								 | 
							
								        database="mydb",
							 | 
						||
| 
								 | 
							
								        query={
							 | 
						||
| 
								 | 
							
								            "driver": "ODBC Driver 17 for SQL Server",
							 | 
						||
| 
								 | 
							
								            "autocommit": "True",
							 | 
						||
| 
								 | 
							
								        },
							 | 
						||
| 
								 | 
							
								    )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    engine = create_engine(connection_url).execution_options(
							 | 
						||
| 
								 | 
							
								        isolation_level="AUTOCOMMIT"
							 | 
						||
| 
								 | 
							
								    )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Avoiding sending large string parameters as TEXT/NTEXT
							 | 
						||
| 
								 | 
							
								^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								By default, for historical reasons, Microsoft's ODBC drivers for SQL Server
							 | 
						||
| 
								 | 
							
								send long string parameters (greater than 4000 SBCS characters or 2000 Unicode
							 | 
						||
| 
								 | 
							
								characters) as TEXT/NTEXT values. TEXT and NTEXT have been deprecated for many
							 | 
						||
| 
								 | 
							
								years and are starting to cause compatibility issues with newer versions of
							 | 
						||
| 
								 | 
							
								SQL_Server/Azure. For example, see `this
							 | 
						||
| 
								 | 
							
								issue <https://github.com/mkleehammer/pyodbc/issues/835>`_.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Starting with ODBC Driver 18 for SQL Server we can override the legacy
							 | 
						||
| 
								 | 
							
								behavior and pass long strings as varchar(max)/nvarchar(max) using the
							 | 
						||
| 
								 | 
							
								``LongAsMax=Yes`` connection string parameter::
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    connection_url = sa.engine.URL.create(
							 | 
						||
| 
								 | 
							
								        "mssql+pyodbc",
							 | 
						||
| 
								 | 
							
								        username="scott",
							 | 
						||
| 
								 | 
							
								        password="tiger",
							 | 
						||
| 
								 | 
							
								        host="mssqlserver.example.com",
							 | 
						||
| 
								 | 
							
								        database="mydb",
							 | 
						||
| 
								 | 
							
								        query={
							 | 
						||
| 
								 | 
							
								            "driver": "ODBC Driver 18 for SQL Server",
							 | 
						||
| 
								 | 
							
								            "LongAsMax": "Yes",
							 | 
						||
| 
								 | 
							
								        },
							 | 
						||
| 
								 | 
							
								    )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Pyodbc Pooling / connection close behavior
							 | 
						||
| 
								 | 
							
								------------------------------------------
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								PyODBC uses internal `pooling
							 | 
						||
| 
								 | 
							
								<https://github.com/mkleehammer/pyodbc/wiki/The-pyodbc-Module#pooling>`_ by
							 | 
						||
| 
								 | 
							
								default, which means connections will be longer lived than they are within
							 | 
						||
| 
								 | 
							
								SQLAlchemy itself.  As SQLAlchemy has its own pooling behavior, it is often
							 | 
						||
| 
								 | 
							
								preferable to disable this behavior.  This behavior can only be disabled
							 | 
						||
| 
								 | 
							
								globally at the PyODBC module level, **before** any connections are made::
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    import pyodbc
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    pyodbc.pooling = False
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # don't use the engine before pooling is set to False
							 | 
						||
| 
								 | 
							
								    engine = create_engine("mssql+pyodbc://user:pass@dsn")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								If this variable is left at its default value of ``True``, **the application
							 | 
						||
| 
								 | 
							
								will continue to maintain active database connections**, even when the
							 | 
						||
| 
								 | 
							
								SQLAlchemy engine itself fully discards a connection or if the engine is
							 | 
						||
| 
								 | 
							
								disposed.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								.. seealso::
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    `pooling <https://github.com/mkleehammer/pyodbc/wiki/The-pyodbc-Module#pooling>`_ -
							 | 
						||
| 
								 | 
							
								    in the PyODBC documentation.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Driver / Unicode Support
							 | 
						||
| 
								 | 
							
								-------------------------
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								PyODBC works best with Microsoft ODBC drivers, particularly in the area
							 | 
						||
| 
								 | 
							
								of Unicode support on both Python 2 and Python 3.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Using the FreeTDS ODBC drivers on Linux or OSX with PyODBC is **not**
							 | 
						||
| 
								 | 
							
								recommended; there have been historically many Unicode-related issues
							 | 
						||
| 
								 | 
							
								in this area, including before Microsoft offered ODBC drivers for Linux
							 | 
						||
| 
								 | 
							
								and OSX.   Now that Microsoft offers drivers for all platforms, for
							 | 
						||
| 
								 | 
							
								PyODBC support these are recommended.  FreeTDS remains relevant for
							 | 
						||
| 
								 | 
							
								non-ODBC drivers such as pymssql where it works very well.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Rowcount Support
							 | 
						||
| 
								 | 
							
								----------------
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								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, for **limited size batches that fit in memory**.  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:`_sa.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)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								.. warning:: The pyodbc fast_executemany mode **buffers all rows in memory** and is
							 | 
						||
| 
								 | 
							
								   not compatible with very large batches of data.    A future version of SQLAlchemy
							 | 
						||
| 
								 | 
							
								   may support this flag as a per-execution option instead.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								.. versionadded:: 1.3
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								.. seealso::
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    `fast executemany <https://github.com/mkleehammer/pyodbc/wiki/Features-beyond-the-DB-API#fast_executemany>`_
							 | 
						||
| 
								 | 
							
								    - on github
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								.. _mssql_pyodbc_setinputsizes:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Setinputsizes Support
							 | 
						||
| 
								 | 
							
								-----------------------
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The pyodbc ``cursor.setinputsizes()`` method can be used if necessary.  To
							 | 
						||
| 
								 | 
							
								enable this hook, pass ``use_setinputsizes=True`` to :func:`_sa.create_engine`::
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    engine = create_engine("mssql+pyodbc://...", use_setinputsizes=True)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The behavior of the hook can then be customized, as may be necessary
							 | 
						||
| 
								 | 
							
								particularly if fast_executemany is in use, via the
							 | 
						||
| 
								 | 
							
								:meth:`.DialectEvents.do_setinputsizes` hook. See that method for usage
							 | 
						||
| 
								 | 
							
								examples.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								.. versionchanged:: 1.4.1  The pyodbc dialects will not use setinputsizes
							 | 
						||
| 
								 | 
							
								   unless ``use_setinputsizes=True`` is passed.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								"""  # noqa
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import datetime
							 | 
						||
| 
								 | 
							
								import decimal
							 | 
						||
| 
								 | 
							
								import re
							 | 
						||
| 
								 | 
							
								import struct
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								from .base import BINARY
							 | 
						||
| 
								 | 
							
								from .base import DATETIMEOFFSET
							 | 
						||
| 
								 | 
							
								from .base import MSDialect
							 | 
						||
| 
								 | 
							
								from .base import MSExecutionContext
							 | 
						||
| 
								 | 
							
								from .base import VARBINARY
							 | 
						||
| 
								 | 
							
								from ... import exc
							 | 
						||
| 
								 | 
							
								from ... import types as sqltypes
							 | 
						||
| 
								 | 
							
								from ... import util
							 | 
						||
| 
								 | 
							
								from ...connectors.pyodbc import PyODBCConnector
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class _ms_numeric_pyodbc(object):
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    """Turns Decimals with adjusted() < 0 or > 7 into strings.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    The routines here are needed for older pyodbc versions
							 | 
						||
| 
								 | 
							
								    as well as current mxODBC versions.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def bind_processor(self, dialect):
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        super_process = super(_ms_numeric_pyodbc, self).bind_processor(dialect)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if not dialect._need_decimal_fix:
							 | 
						||
| 
								 | 
							
								            return super_process
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        def process(value):
							 | 
						||
| 
								 | 
							
								            if self.asdecimal and isinstance(value, decimal.Decimal):
							 | 
						||
| 
								 | 
							
								                adjusted = value.adjusted()
							 | 
						||
| 
								 | 
							
								                if adjusted < 0:
							 | 
						||
| 
								 | 
							
								                    return self._small_dec_to_string(value)
							 | 
						||
| 
								 | 
							
								                elif adjusted > 7:
							 | 
						||
| 
								 | 
							
								                    return self._large_dec_to_string(value)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            if super_process:
							 | 
						||
| 
								 | 
							
								                return super_process(value)
							 | 
						||
| 
								 | 
							
								            else:
							 | 
						||
| 
								 | 
							
								                return value
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return process
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # these routines needed for older versions of pyodbc.
							 | 
						||
| 
								 | 
							
								    # as of 2.1.8 this logic is integrated.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _small_dec_to_string(self, value):
							 | 
						||
| 
								 | 
							
								        return "%s0.%s%s" % (
							 | 
						||
| 
								 | 
							
								            (value < 0 and "-" or ""),
							 | 
						||
| 
								 | 
							
								            "0" * (abs(value.adjusted()) - 1),
							 | 
						||
| 
								 | 
							
								            "".join([str(nint) for nint in value.as_tuple()[1]]),
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _large_dec_to_string(self, value):
							 | 
						||
| 
								 | 
							
								        _int = value.as_tuple()[1]
							 | 
						||
| 
								 | 
							
								        if "E" in str(value):
							 | 
						||
| 
								 | 
							
								            result = "%s%s%s" % (
							 | 
						||
| 
								 | 
							
								                (value < 0 and "-" or ""),
							 | 
						||
| 
								 | 
							
								                "".join([str(s) for s in _int]),
							 | 
						||
| 
								 | 
							
								                "0" * (value.adjusted() - (len(_int) - 1)),
							 | 
						||
| 
								 | 
							
								            )
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            if (len(_int) - 1) > value.adjusted():
							 | 
						||
| 
								 | 
							
								                result = "%s%s.%s" % (
							 | 
						||
| 
								 | 
							
								                    (value < 0 and "-" or ""),
							 | 
						||
| 
								 | 
							
								                    "".join([str(s) for s in _int][0 : value.adjusted() + 1]),
							 | 
						||
| 
								 | 
							
								                    "".join([str(s) for s in _int][value.adjusted() + 1 :]),
							 | 
						||
| 
								 | 
							
								                )
							 | 
						||
| 
								 | 
							
								            else:
							 | 
						||
| 
								 | 
							
								                result = "%s%s" % (
							 | 
						||
| 
								 | 
							
								                    (value < 0 and "-" or ""),
							 | 
						||
| 
								 | 
							
								                    "".join([str(s) for s in _int][0 : value.adjusted() + 1]),
							 | 
						||
| 
								 | 
							
								                )
							 | 
						||
| 
								 | 
							
								        return result
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class _MSNumeric_pyodbc(_ms_numeric_pyodbc, sqltypes.Numeric):
							 | 
						||
| 
								 | 
							
								    pass
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class _MSFloat_pyodbc(_ms_numeric_pyodbc, sqltypes.Float):
							 | 
						||
| 
								 | 
							
								    pass
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class _ms_binary_pyodbc(object):
							 | 
						||
| 
								 | 
							
								    """Wraps binary values in dialect-specific Binary wrapper.
							 | 
						||
| 
								 | 
							
								    If the value is null, return a pyodbc-specific BinaryNull
							 | 
						||
| 
								 | 
							
								    object to prevent pyODBC [and FreeTDS] from defaulting binary
							 | 
						||
| 
								 | 
							
								    NULL types to SQLWCHAR and causing implicit conversion errors.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def bind_processor(self, dialect):
							 | 
						||
| 
								 | 
							
								        if dialect.dbapi is None:
							 | 
						||
| 
								 | 
							
								            return None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        DBAPIBinary = dialect.dbapi.Binary
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        def process(value):
							 | 
						||
| 
								 | 
							
								            if value is not None:
							 | 
						||
| 
								 | 
							
								                return DBAPIBinary(value)
							 | 
						||
| 
								 | 
							
								            else:
							 | 
						||
| 
								 | 
							
								                # pyodbc-specific
							 | 
						||
| 
								 | 
							
								                return dialect.dbapi.BinaryNull
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return process
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class _ODBCDateTimeBindProcessor(object):
							 | 
						||
| 
								 | 
							
								    """Add bind processors to handle datetimeoffset behaviors"""
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    has_tz = False
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def bind_processor(self, dialect):
							 | 
						||
| 
								 | 
							
								        def process(value):
							 | 
						||
| 
								 | 
							
								            if value is None:
							 | 
						||
| 
								 | 
							
								                return None
							 | 
						||
| 
								 | 
							
								            elif isinstance(value, util.string_types):
							 | 
						||
| 
								 | 
							
								                # if a string was passed directly, allow it through
							 | 
						||
| 
								 | 
							
								                return value
							 | 
						||
| 
								 | 
							
								            elif not value.tzinfo or (not self.timezone and not self.has_tz):
							 | 
						||
| 
								 | 
							
								                # for DateTime(timezone=False)
							 | 
						||
| 
								 | 
							
								                return value
							 | 
						||
| 
								 | 
							
								            else:
							 | 
						||
| 
								 | 
							
								                # for DATETIMEOFFSET or DateTime(timezone=True)
							 | 
						||
| 
								 | 
							
								                #
							 | 
						||
| 
								 | 
							
								                # Convert to string format required by T-SQL
							 | 
						||
| 
								 | 
							
								                dto_string = value.strftime("%Y-%m-%d %H:%M:%S.%f %z")
							 | 
						||
| 
								 | 
							
								                # offset needs a colon, e.g., -0700 -> -07:00
							 | 
						||
| 
								 | 
							
								                # "UTC offset in the form (+-)HHMM[SS[.ffffff]]"
							 | 
						||
| 
								 | 
							
								                # backend currently rejects seconds / fractional seconds
							 | 
						||
| 
								 | 
							
								                dto_string = re.sub(
							 | 
						||
| 
								 | 
							
								                    r"([\+\-]\d{2})([\d\.]+)$", r"\1:\2", dto_string
							 | 
						||
| 
								 | 
							
								                )
							 | 
						||
| 
								 | 
							
								                return dto_string
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return process
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class _ODBCDateTime(_ODBCDateTimeBindProcessor, sqltypes.DateTime):
							 | 
						||
| 
								 | 
							
								    pass
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class _ODBCDATETIMEOFFSET(_ODBCDateTimeBindProcessor, DATETIMEOFFSET):
							 | 
						||
| 
								 | 
							
								    has_tz = True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class _VARBINARY_pyodbc(_ms_binary_pyodbc, VARBINARY):
							 | 
						||
| 
								 | 
							
								    pass
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class _BINARY_pyodbc(_ms_binary_pyodbc, BINARY):
							 | 
						||
| 
								 | 
							
								    pass
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class MSExecutionContext_pyodbc(MSExecutionContext):
							 | 
						||
| 
								 | 
							
								    _embedded_scope_identity = False
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def pre_exec(self):
							 | 
						||
| 
								 | 
							
								        """where appropriate, issue "select scope_identity()" in the same
							 | 
						||
| 
								 | 
							
								        statement.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        Background on why "scope_identity()" is preferable to "@@identity":
							 | 
						||
| 
								 | 
							
								        https://msdn.microsoft.com/en-us/library/ms190315.aspx
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        Background on why we attempt to embed "scope_identity()" into the same
							 | 
						||
| 
								 | 
							
								        statement as the INSERT:
							 | 
						||
| 
								 | 
							
								        https://code.google.com/p/pyodbc/wiki/FAQs#How_do_I_retrieve_autogenerated/identity_values?
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        super(MSExecutionContext_pyodbc, self).pre_exec()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # don't embed the scope_identity select into an
							 | 
						||
| 
								 | 
							
								        # "INSERT .. DEFAULT VALUES"
							 | 
						||
| 
								 | 
							
								        if (
							 | 
						||
| 
								 | 
							
								            self._select_lastrowid
							 | 
						||
| 
								 | 
							
								            and self.dialect.use_scope_identity
							 | 
						||
| 
								 | 
							
								            and len(self.parameters[0])
							 | 
						||
| 
								 | 
							
								        ):
							 | 
						||
| 
								 | 
							
								            self._embedded_scope_identity = True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            self.statement += "; select scope_identity()"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def post_exec(self):
							 | 
						||
| 
								 | 
							
								        if self._embedded_scope_identity:
							 | 
						||
| 
								 | 
							
								            # Fetch the last inserted id from the manipulated statement
							 | 
						||
| 
								 | 
							
								            # We may have to skip over a number of result sets with
							 | 
						||
| 
								 | 
							
								            # no data (due to triggers, etc.)
							 | 
						||
| 
								 | 
							
								            while True:
							 | 
						||
| 
								 | 
							
								                try:
							 | 
						||
| 
								 | 
							
								                    # fetchall() ensures the cursor is consumed
							 | 
						||
| 
								 | 
							
								                    # without closing it (FreeTDS particularly)
							 | 
						||
| 
								 | 
							
								                    row = self.cursor.fetchall()[0]
							 | 
						||
| 
								 | 
							
								                    break
							 | 
						||
| 
								 | 
							
								                except self.dialect.dbapi.Error:
							 | 
						||
| 
								 | 
							
								                    # no way around this - nextset() consumes the previous set
							 | 
						||
| 
								 | 
							
								                    # so we need to just keep flipping
							 | 
						||
| 
								 | 
							
								                    self.cursor.nextset()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            self._lastrowid = int(row[0])
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            super(MSExecutionContext_pyodbc, self).post_exec()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class MSDialect_pyodbc(PyODBCConnector, MSDialect):
							 | 
						||
| 
								 | 
							
								    supports_statement_cache = True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # mssql still has problems with this on Linux
							 | 
						||
| 
								 | 
							
								    supports_sane_rowcount_returning = False
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    execution_ctx_cls = MSExecutionContext_pyodbc
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    colspecs = util.update_copy(
							 | 
						||
| 
								 | 
							
								        MSDialect.colspecs,
							 | 
						||
| 
								 | 
							
								        {
							 | 
						||
| 
								 | 
							
								            sqltypes.Numeric: _MSNumeric_pyodbc,
							 | 
						||
| 
								 | 
							
								            sqltypes.Float: _MSFloat_pyodbc,
							 | 
						||
| 
								 | 
							
								            BINARY: _BINARY_pyodbc,
							 | 
						||
| 
								 | 
							
								            # support DateTime(timezone=True)
							 | 
						||
| 
								 | 
							
								            sqltypes.DateTime: _ODBCDateTime,
							 | 
						||
| 
								 | 
							
								            DATETIMEOFFSET: _ODBCDATETIMEOFFSET,
							 | 
						||
| 
								 | 
							
								            # SQL Server dialect has a VARBINARY that is just to support
							 | 
						||
| 
								 | 
							
								            # "deprecate_large_types" w/ VARBINARY(max), but also we must
							 | 
						||
| 
								 | 
							
								            # handle the usual SQL standard VARBINARY
							 | 
						||
| 
								 | 
							
								            VARBINARY: _VARBINARY_pyodbc,
							 | 
						||
| 
								 | 
							
								            sqltypes.VARBINARY: _VARBINARY_pyodbc,
							 | 
						||
| 
								 | 
							
								            sqltypes.LargeBinary: _VARBINARY_pyodbc,
							 | 
						||
| 
								 | 
							
								        },
							 | 
						||
| 
								 | 
							
								    )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    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)
							 | 
						||
| 
								 | 
							
								        self.use_scope_identity = (
							 | 
						||
| 
								 | 
							
								            self.use_scope_identity
							 | 
						||
| 
								 | 
							
								            and self.dbapi
							 | 
						||
| 
								 | 
							
								            and 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:
							 | 
						||
| 
								 | 
							
								            # "Version of the instance of SQL Server, in the form
							 | 
						||
| 
								 | 
							
								            # of 'major.minor.build.revision'"
							 | 
						||
| 
								 | 
							
								            raw = connection.exec_driver_sql(
							 | 
						||
| 
								 | 
							
								                "SELECT CAST(SERVERPROPERTY('ProductVersion') AS VARCHAR)"
							 | 
						||
| 
								 | 
							
								            ).scalar()
							 | 
						||
| 
								 | 
							
								        except exc.DBAPIError:
							 | 
						||
| 
								 | 
							
								            # SQL Server docs indicate this function isn't present prior to
							 | 
						||
| 
								 | 
							
								            # 2008.  Before we had the VARCHAR cast above, pyodbc would also
							 | 
						||
| 
								 | 
							
								            # fail on this query.
							 | 
						||
| 
								 | 
							
								            return super(MSDialect_pyodbc, self)._get_server_version_info(
							 | 
						||
| 
								 | 
							
								                connection, allow_chars=False
							 | 
						||
| 
								 | 
							
								            )
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            version = []
							 | 
						||
| 
								 | 
							
								            r = re.compile(r"[.\-]")
							 | 
						||
| 
								 | 
							
								            for n in r.split(raw):
							 | 
						||
| 
								 | 
							
								                try:
							 | 
						||
| 
								 | 
							
								                    version.append(int(n))
							 | 
						||
| 
								 | 
							
								                except ValueError:
							 | 
						||
| 
								 | 
							
								                    pass
							 | 
						||
| 
								 | 
							
								            return tuple(version)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def on_connect(self):
							 | 
						||
| 
								 | 
							
								        super_ = super(MSDialect_pyodbc, self).on_connect()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        def on_connect(conn):
							 | 
						||
| 
								 | 
							
								            if super_ is not None:
							 | 
						||
| 
								 | 
							
								                super_(conn)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            self._setup_timestampoffset_type(conn)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return on_connect
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _setup_timestampoffset_type(self, connection):
							 | 
						||
| 
								 | 
							
								        # output converter function for datetimeoffset
							 | 
						||
| 
								 | 
							
								        def _handle_datetimeoffset(dto_value):
							 | 
						||
| 
								 | 
							
								            tup = struct.unpack("<6hI2h", dto_value)
							 | 
						||
| 
								 | 
							
								            return datetime.datetime(
							 | 
						||
| 
								 | 
							
								                tup[0],
							 | 
						||
| 
								 | 
							
								                tup[1],
							 | 
						||
| 
								 | 
							
								                tup[2],
							 | 
						||
| 
								 | 
							
								                tup[3],
							 | 
						||
| 
								 | 
							
								                tup[4],
							 | 
						||
| 
								 | 
							
								                tup[5],
							 | 
						||
| 
								 | 
							
								                tup[6] // 1000,
							 | 
						||
| 
								 | 
							
								                util.timezone(
							 | 
						||
| 
								 | 
							
								                    datetime.timedelta(hours=tup[7], minutes=tup[8])
							 | 
						||
| 
								 | 
							
								                ),
							 | 
						||
| 
								 | 
							
								            )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        odbc_SQL_SS_TIMESTAMPOFFSET = -155  # as defined in SQLNCLI.h
							 | 
						||
| 
								 | 
							
								        connection.add_output_converter(
							 | 
						||
| 
								 | 
							
								            odbc_SQL_SS_TIMESTAMPOFFSET, _handle_datetimeoffset
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    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):
							 | 
						||
| 
								 | 
							
								            code = e.args[0]
							 | 
						||
| 
								 | 
							
								            if code in {
							 | 
						||
| 
								 | 
							
								                "08S01",
							 | 
						||
| 
								 | 
							
								                "01000",
							 | 
						||
| 
								 | 
							
								                "01002",
							 | 
						||
| 
								 | 
							
								                "08003",
							 | 
						||
| 
								 | 
							
								                "08007",
							 | 
						||
| 
								 | 
							
								                "08S02",
							 | 
						||
| 
								 | 
							
								                "08001",
							 | 
						||
| 
								 | 
							
								                "HYT00",
							 | 
						||
| 
								 | 
							
								                "HY010",
							 | 
						||
| 
								 | 
							
								                "10054",
							 | 
						||
| 
								 | 
							
								            }:
							 | 
						||
| 
								 | 
							
								                return True
							 | 
						||
| 
								 | 
							
								        return super(MSDialect_pyodbc, self).is_disconnect(
							 | 
						||
| 
								 | 
							
								            e, connection, cursor
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								dialect = MSDialect_pyodbc
							 |