pyodbc.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. # mssql/pyodbc.py
  2. # Copyright (C) 2005-2017 the SQLAlchemy authors and contributors
  3. # <see AUTHORS file>
  4. #
  5. # This module is part of SQLAlchemy and is released under
  6. # the MIT License: http://www.opensource.org/licenses/mit-license.php
  7. r"""
  8. .. dialect:: mssql+pyodbc
  9. :name: PyODBC
  10. :dbapi: pyodbc
  11. :connectstring: mssql+pyodbc://<username>:<password>@<dsnname>
  12. :url: http://pypi.python.org/pypi/pyodbc/
  13. Connecting to PyODBC
  14. --------------------
  15. The URL here is to be translated to PyODBC connection strings, as
  16. detailed in `ConnectionStrings <https://code.google.com/p/pyodbc/wiki/ConnectionStrings>`_.
  17. DSN Connections
  18. ^^^^^^^^^^^^^^^
  19. A DSN-based connection is **preferred** overall when using ODBC. A
  20. basic DSN-based connection looks like::
  21. engine = create_engine("mssql+pyodbc://scott:tiger@some_dsn")
  22. Which above, will pass the following connection string to PyODBC::
  23. dsn=mydsn;UID=user;PWD=pass
  24. If the username and password are omitted, the DSN form will also add
  25. the ``Trusted_Connection=yes`` directive to the ODBC string.
  26. Hostname Connections
  27. ^^^^^^^^^^^^^^^^^^^^
  28. Hostname-based connections are **not preferred**, however are supported.
  29. The ODBC driver name must be explicitly specified::
  30. engine = create_engine("mssql+pyodbc://scott:tiger@myhost:port/databasename?driver=SQL+Server+Native+Client+10.0")
  31. .. versionchanged:: 1.0.0 Hostname-based PyODBC connections now require the
  32. SQL Server driver name specified explicitly. SQLAlchemy cannot
  33. choose an optimal default here as it varies based on platform
  34. and installed drivers.
  35. Other keywords interpreted by the Pyodbc dialect to be passed to
  36. ``pyodbc.connect()`` in both the DSN and hostname cases include:
  37. ``odbc_autotranslate``, ``ansi``, ``unicode_results``, ``autocommit``.
  38. Pass through exact Pyodbc string
  39. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  40. A PyODBC connection string can also be sent exactly as specified in
  41. `ConnectionStrings <https://code.google.com/p/pyodbc/wiki/ConnectionStrings>`_
  42. into the driver using the parameter ``odbc_connect``. The delimeters must be URL escaped, however,
  43. as illustrated below using ``urllib.quote_plus``::
  44. import urllib
  45. params = urllib.quote_plus("DRIVER={SQL Server Native Client 10.0};SERVER=dagger;DATABASE=test;UID=user;PWD=password")
  46. engine = create_engine("mssql+pyodbc:///?odbc_connect=%s" % params)
  47. Unicode Binds
  48. -------------
  49. The current state of PyODBC on a unix backend with FreeTDS and/or
  50. EasySoft is poor regarding unicode; different OS platforms and versions of
  51. UnixODBC versus IODBC versus FreeTDS/EasySoft versus PyODBC itself
  52. dramatically alter how strings are received. The PyODBC dialect attempts to
  53. use all the information it knows to determine whether or not a Python unicode
  54. literal can be passed directly to the PyODBC driver or not; while SQLAlchemy
  55. can encode these to bytestrings first, some users have reported that PyODBC
  56. mis-handles bytestrings for certain encodings and requires a Python unicode
  57. object, while the author has observed widespread cases where a Python unicode
  58. is completely misinterpreted by PyODBC, particularly when dealing with
  59. the information schema tables used in table reflection, and the value
  60. must first be encoded to a bytestring.
  61. It is for this reason that whether or not unicode literals for bound
  62. parameters be sent to PyODBC can be controlled using the
  63. ``supports_unicode_binds`` parameter to ``create_engine()``. When
  64. left at its default of ``None``, the PyODBC dialect will use its
  65. best guess as to whether or not the driver deals with unicode literals
  66. well. When ``False``, unicode literals will be encoded first, and when
  67. ``True`` unicode literals will be passed straight through. This is an interim
  68. flag that hopefully should not be needed when the unicode situation stabilizes
  69. for unix + PyODBC.
  70. .. versionadded:: 0.7.7
  71. ``supports_unicode_binds`` parameter to ``create_engine()``\ .
  72. Rowcount Support
  73. ----------------
  74. Pyodbc only has partial support for rowcount. See the notes at
  75. :ref:`mssql_rowcount_versioning` for important notes when using ORM
  76. versioning.
  77. """
  78. from .base import MSExecutionContext, MSDialect, VARBINARY
  79. from ...connectors.pyodbc import PyODBCConnector
  80. from ... import types as sqltypes, util, exc
  81. import decimal
  82. import re
  83. class _ms_numeric_pyodbc(object):
  84. """Turns Decimals with adjusted() < 0 or > 7 into strings.
  85. The routines here are needed for older pyodbc versions
  86. as well as current mxODBC versions.
  87. """
  88. def bind_processor(self, dialect):
  89. super_process = super(_ms_numeric_pyodbc, self).\
  90. bind_processor(dialect)
  91. if not dialect._need_decimal_fix:
  92. return super_process
  93. def process(value):
  94. if self.asdecimal and \
  95. isinstance(value, decimal.Decimal):
  96. adjusted = value.adjusted()
  97. if adjusted < 0:
  98. return self._small_dec_to_string(value)
  99. elif adjusted > 7:
  100. return self._large_dec_to_string(value)
  101. if super_process:
  102. return super_process(value)
  103. else:
  104. return value
  105. return process
  106. # these routines needed for older versions of pyodbc.
  107. # as of 2.1.8 this logic is integrated.
  108. def _small_dec_to_string(self, value):
  109. return "%s0.%s%s" % (
  110. (value < 0 and '-' or ''),
  111. '0' * (abs(value.adjusted()) - 1),
  112. "".join([str(nint) for nint in value.as_tuple()[1]]))
  113. def _large_dec_to_string(self, value):
  114. _int = value.as_tuple()[1]
  115. if 'E' in str(value):
  116. result = "%s%s%s" % (
  117. (value < 0 and '-' or ''),
  118. "".join([str(s) for s in _int]),
  119. "0" * (value.adjusted() - (len(_int) - 1)))
  120. else:
  121. if (len(_int) - 1) > value.adjusted():
  122. result = "%s%s.%s" % (
  123. (value < 0 and '-' or ''),
  124. "".join(
  125. [str(s) for s in _int][0:value.adjusted() + 1]),
  126. "".join(
  127. [str(s) for s in _int][value.adjusted() + 1:]))
  128. else:
  129. result = "%s%s" % (
  130. (value < 0 and '-' or ''),
  131. "".join(
  132. [str(s) for s in _int][0:value.adjusted() + 1]))
  133. return result
  134. class _MSNumeric_pyodbc(_ms_numeric_pyodbc, sqltypes.Numeric):
  135. pass
  136. class _MSFloat_pyodbc(_ms_numeric_pyodbc, sqltypes.Float):
  137. pass
  138. class _VARBINARY_pyodbc(VARBINARY):
  139. def bind_processor(self, dialect):
  140. if dialect.dbapi is None:
  141. return None
  142. DBAPIBinary = dialect.dbapi.Binary
  143. def process(value):
  144. if value is not None:
  145. return DBAPIBinary(value)
  146. else:
  147. # pyodbc-specific
  148. return dialect.dbapi.BinaryNull
  149. return process
  150. class MSExecutionContext_pyodbc(MSExecutionContext):
  151. _embedded_scope_identity = False
  152. def pre_exec(self):
  153. """where appropriate, issue "select scope_identity()" in the same
  154. statement.
  155. Background on why "scope_identity()" is preferable to "@@identity":
  156. http://msdn.microsoft.com/en-us/library/ms190315.aspx
  157. Background on why we attempt to embed "scope_identity()" into the same
  158. statement as the INSERT:
  159. http://code.google.com/p/pyodbc/wiki/FAQs#How_do_I_retrieve_autogenerated/identity_values?
  160. """
  161. super(MSExecutionContext_pyodbc, self).pre_exec()
  162. # don't embed the scope_identity select into an
  163. # "INSERT .. DEFAULT VALUES"
  164. if self._select_lastrowid and \
  165. self.dialect.use_scope_identity and \
  166. len(self.parameters[0]):
  167. self._embedded_scope_identity = True
  168. self.statement += "; select scope_identity()"
  169. def post_exec(self):
  170. if self._embedded_scope_identity:
  171. # Fetch the last inserted id from the manipulated statement
  172. # We may have to skip over a number of result sets with
  173. # no data (due to triggers, etc.)
  174. while True:
  175. try:
  176. # fetchall() ensures the cursor is consumed
  177. # without closing it (FreeTDS particularly)
  178. row = self.cursor.fetchall()[0]
  179. break
  180. except self.dialect.dbapi.Error as e:
  181. # no way around this - nextset() consumes the previous set
  182. # so we need to just keep flipping
  183. self.cursor.nextset()
  184. self._lastrowid = int(row[0])
  185. else:
  186. super(MSExecutionContext_pyodbc, self).post_exec()
  187. class MSDialect_pyodbc(PyODBCConnector, MSDialect):
  188. execution_ctx_cls = MSExecutionContext_pyodbc
  189. colspecs = util.update_copy(
  190. MSDialect.colspecs,
  191. {
  192. sqltypes.Numeric: _MSNumeric_pyodbc,
  193. sqltypes.Float: _MSFloat_pyodbc,
  194. VARBINARY: _VARBINARY_pyodbc,
  195. sqltypes.LargeBinary: _VARBINARY_pyodbc,
  196. }
  197. )
  198. def __init__(self, description_encoding=None, **params):
  199. if 'description_encoding' in params:
  200. self.description_encoding = params.pop('description_encoding')
  201. super(MSDialect_pyodbc, self).__init__(**params)
  202. self.use_scope_identity = self.use_scope_identity and \
  203. self.dbapi and \
  204. hasattr(self.dbapi.Cursor, 'nextset')
  205. self._need_decimal_fix = self.dbapi and \
  206. self._dbapi_version() < (2, 1, 8)
  207. def _get_server_version_info(self, connection):
  208. try:
  209. raw = connection.scalar("SELECT SERVERPROPERTY('ProductVersion')")
  210. except exc.DBAPIError:
  211. # SQL Server docs indicate this function isn't present prior to
  212. # 2008; additionally, unknown combinations of pyodbc aren't
  213. # able to run this query.
  214. return super(MSDialect_pyodbc, self).\
  215. _get_server_version_info(connection)
  216. else:
  217. version = []
  218. r = re.compile(r'[.\-]')
  219. for n in r.split(raw):
  220. try:
  221. version.append(int(n))
  222. except ValueError:
  223. version.append(n)
  224. return tuple(version)
  225. dialect = MSDialect_pyodbc