pymssql.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. # dialects/mssql/pymssql.py
  2. # Copyright (C) 2005-2025 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: https://www.opensource.org/licenses/mit-license.php
  7. # mypy: ignore-errors
  8. """
  9. .. dialect:: mssql+pymssql
  10. :name: pymssql
  11. :dbapi: pymssql
  12. :connectstring: mssql+pymssql://<username>:<password>@<freetds_name>/?charset=utf8
  13. pymssql is a Python module that provides a Python DBAPI interface around
  14. `FreeTDS <https://www.freetds.org/>`_.
  15. .. versionchanged:: 2.0.5
  16. pymssql was restored to SQLAlchemy's continuous integration testing
  17. """ # noqa
  18. import re
  19. from .base import MSDialect
  20. from .base import MSIdentifierPreparer
  21. from ... import types as sqltypes
  22. from ... import util
  23. from ...engine import processors
  24. class _MSNumeric_pymssql(sqltypes.Numeric):
  25. def result_processor(self, dialect, type_):
  26. if not self.asdecimal:
  27. return processors.to_float
  28. else:
  29. return sqltypes.Numeric.result_processor(self, dialect, type_)
  30. class MSIdentifierPreparer_pymssql(MSIdentifierPreparer):
  31. def __init__(self, dialect):
  32. super().__init__(dialect)
  33. # pymssql has the very unusual behavior that it uses pyformat
  34. # yet does not require that percent signs be doubled
  35. self._double_percents = False
  36. class MSDialect_pymssql(MSDialect):
  37. supports_statement_cache = True
  38. supports_native_decimal = True
  39. supports_native_uuid = True
  40. driver = "pymssql"
  41. preparer = MSIdentifierPreparer_pymssql
  42. colspecs = util.update_copy(
  43. MSDialect.colspecs,
  44. {sqltypes.Numeric: _MSNumeric_pymssql, sqltypes.Float: sqltypes.Float},
  45. )
  46. @classmethod
  47. def import_dbapi(cls):
  48. module = __import__("pymssql")
  49. # pymmsql < 2.1.1 doesn't have a Binary method. we use string
  50. client_ver = tuple(int(x) for x in module.__version__.split("."))
  51. if client_ver < (2, 1, 1):
  52. # TODO: monkeypatching here is less than ideal
  53. module.Binary = lambda x: x if hasattr(x, "decode") else str(x)
  54. if client_ver < (1,):
  55. util.warn(
  56. "The pymssql dialect expects at least "
  57. "the 1.0 series of the pymssql DBAPI."
  58. )
  59. return module
  60. def _get_server_version_info(self, connection):
  61. vers = connection.exec_driver_sql("select @@version").scalar()
  62. m = re.match(r"Microsoft .*? - (\d+)\.(\d+)\.(\d+)\.(\d+)", vers)
  63. if m:
  64. return tuple(int(x) for x in m.group(1, 2, 3, 4))
  65. else:
  66. return None
  67. def create_connect_args(self, url):
  68. opts = url.translate_connect_args(username="user")
  69. opts.update(url.query)
  70. port = opts.pop("port", None)
  71. if port and "host" in opts:
  72. opts["host"] = "%s:%s" % (opts["host"], port)
  73. return ([], opts)
  74. def is_disconnect(self, e, connection, cursor):
  75. for msg in (
  76. "Adaptive Server connection timed out",
  77. "Net-Lib error during Connection reset by peer",
  78. "message 20003", # connection timeout
  79. "Error 10054",
  80. "Not connected to any MS SQL server",
  81. "Connection is closed",
  82. "message 20006", # Write to the server failed
  83. "message 20017", # Unexpected EOF from the server
  84. "message 20047", # DBPROCESS is dead or not enabled
  85. "The server failed to resume the transaction",
  86. ):
  87. if msg in str(e):
  88. return True
  89. else:
  90. return False
  91. def get_isolation_level_values(self, dbapi_connection):
  92. return super().get_isolation_level_values(dbapi_connection) + [
  93. "AUTOCOMMIT"
  94. ]
  95. def set_isolation_level(self, dbapi_connection, level):
  96. if level == "AUTOCOMMIT":
  97. dbapi_connection.autocommit(True)
  98. else:
  99. dbapi_connection.autocommit(False)
  100. super().set_isolation_level(dbapi_connection, level)
  101. dialect = MSDialect_pymssql