pyodbc.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. # connectors/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. from . import Connector
  8. from .. import util
  9. import sys
  10. import re
  11. class PyODBCConnector(Connector):
  12. driver = 'pyodbc'
  13. supports_sane_multi_rowcount = False
  14. if util.py2k:
  15. # PyODBC unicode is broken on UCS-4 builds
  16. supports_unicode = sys.maxunicode == 65535
  17. supports_unicode_statements = supports_unicode
  18. supports_native_decimal = True
  19. default_paramstyle = 'named'
  20. # for non-DSN connections, this *may* be used to
  21. # hold the desired driver name
  22. pyodbc_driver_name = None
  23. # will be set to True after initialize()
  24. # if the freetds.so is detected
  25. freetds = False
  26. # will be set to the string version of
  27. # the FreeTDS driver if freetds is detected
  28. freetds_driver_version = None
  29. # will be set to True after initialize()
  30. # if the libessqlsrv.so is detected
  31. easysoft = False
  32. def __init__(self, supports_unicode_binds=None, **kw):
  33. super(PyODBCConnector, self).__init__(**kw)
  34. self._user_supports_unicode_binds = supports_unicode_binds
  35. @classmethod
  36. def dbapi(cls):
  37. return __import__('pyodbc')
  38. def create_connect_args(self, url):
  39. opts = url.translate_connect_args(username='user')
  40. opts.update(url.query)
  41. keys = opts
  42. query = url.query
  43. connect_args = {}
  44. for param in ('ansi', 'unicode_results', 'autocommit'):
  45. if param in keys:
  46. connect_args[param] = util.asbool(keys.pop(param))
  47. if 'odbc_connect' in keys:
  48. connectors = [util.unquote_plus(keys.pop('odbc_connect'))]
  49. else:
  50. def check_quote(token):
  51. if ";" in str(token):
  52. token = "'%s'" % token
  53. return token
  54. keys = dict(
  55. (k, check_quote(v)) for k, v in keys.items()
  56. )
  57. dsn_connection = 'dsn' in keys or \
  58. ('host' in keys and 'database' not in keys)
  59. if dsn_connection:
  60. connectors = ['dsn=%s' % (keys.pop('host', '') or
  61. keys.pop('dsn', ''))]
  62. else:
  63. port = ''
  64. if 'port' in keys and 'port' not in query:
  65. port = ',%d' % int(keys.pop('port'))
  66. connectors = []
  67. driver = keys.pop('driver', self.pyodbc_driver_name)
  68. if driver is None:
  69. util.warn(
  70. "No driver name specified; "
  71. "this is expected by PyODBC when using "
  72. "DSN-less connections")
  73. else:
  74. connectors.append("DRIVER={%s}" % driver)
  75. connectors.extend(
  76. [
  77. 'Server=%s%s' % (keys.pop('host', ''), port),
  78. 'Database=%s' % keys.pop('database', '')
  79. ])
  80. user = keys.pop("user", None)
  81. if user:
  82. connectors.append("UID=%s" % user)
  83. connectors.append("PWD=%s" % keys.pop('password', ''))
  84. else:
  85. connectors.append("Trusted_Connection=Yes")
  86. # if set to 'Yes', the ODBC layer will try to automagically
  87. # convert textual data from your database encoding to your
  88. # client encoding. This should obviously be set to 'No' if
  89. # you query a cp1253 encoded database from a latin1 client...
  90. if 'odbc_autotranslate' in keys:
  91. connectors.append("AutoTranslate=%s" %
  92. keys.pop("odbc_autotranslate"))
  93. connectors.extend(['%s=%s' % (k, v) for k, v in keys.items()])
  94. return [[";".join(connectors)], connect_args]
  95. def is_disconnect(self, e, connection, cursor):
  96. if isinstance(e, self.dbapi.ProgrammingError):
  97. return "The cursor's connection has been closed." in str(e) or \
  98. 'Attempt to use a closed connection.' in str(e)
  99. elif isinstance(e, self.dbapi.Error):
  100. return '[08S01]' in str(e)
  101. else:
  102. return False
  103. def initialize(self, connection):
  104. # determine FreeTDS first. can't issue SQL easily
  105. # without getting unicode_statements/binds set up.
  106. pyodbc = self.dbapi
  107. dbapi_con = connection.connection
  108. _sql_driver_name = dbapi_con.getinfo(pyodbc.SQL_DRIVER_NAME)
  109. self.freetds = bool(re.match(r".*libtdsodbc.*\.so", _sql_driver_name
  110. ))
  111. self.easysoft = bool(re.match(r".*libessqlsrv.*\.so", _sql_driver_name
  112. ))
  113. if self.freetds:
  114. self.freetds_driver_version = dbapi_con.getinfo(
  115. pyodbc.SQL_DRIVER_VER)
  116. self.supports_unicode_statements = (
  117. not util.py2k or
  118. (not self.freetds and not self.easysoft)
  119. )
  120. if self._user_supports_unicode_binds is not None:
  121. self.supports_unicode_binds = self._user_supports_unicode_binds
  122. elif util.py2k:
  123. self.supports_unicode_binds = (
  124. not self.freetds or self.freetds_driver_version >= '0.91'
  125. ) and not self.easysoft
  126. else:
  127. self.supports_unicode_binds = True
  128. # run other initialization which asks for user name, etc.
  129. super(PyODBCConnector, self).initialize(connection)
  130. def _dbapi_version(self):
  131. if not self.dbapi:
  132. return ()
  133. return self._parse_dbapi_version(self.dbapi.version)
  134. def _parse_dbapi_version(self, vers):
  135. m = re.match(
  136. r'(?:py.*-)?([\d\.]+)(?:-(\w+))?',
  137. vers
  138. )
  139. if not m:
  140. return ()
  141. vers = tuple([int(x) for x in m.group(1).split(".")])
  142. if m.group(2):
  143. vers += (m.group(2),)
  144. return vers
  145. def _get_server_version_info(self, connection):
  146. # NOTE: this function is not reliable, particularly when
  147. # freetds is in use. Implement database-specific server version
  148. # queries.
  149. dbapi_con = connection.connection
  150. version = []
  151. r = re.compile(r'[.\-]')
  152. for n in r.split(dbapi_con.getinfo(self.dbapi.SQL_DBMS_VER)):
  153. try:
  154. version.append(int(n))
  155. except ValueError:
  156. version.append(n)
  157. return tuple(version)