enumerated.py 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. # mysql/enumerated.py
  2. # Copyright (C) 2005-2022 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. import re
  8. from .types import _StringType
  9. from ... import exc
  10. from ... import sql
  11. from ... import util
  12. from ...sql import sqltypes
  13. from ...sql.base import NO_ARG
  14. class ENUM(sqltypes.NativeForEmulated, sqltypes.Enum, _StringType):
  15. """MySQL ENUM type."""
  16. __visit_name__ = "ENUM"
  17. native_enum = True
  18. def __init__(self, *enums, **kw):
  19. """Construct an ENUM.
  20. E.g.::
  21. Column('myenum', ENUM("foo", "bar", "baz"))
  22. :param enums: The range of valid values for this ENUM. Values in
  23. enums are not quoted, they will be escaped and surrounded by single
  24. quotes when generating the schema. This object may also be a
  25. PEP-435-compliant enumerated type.
  26. .. versionadded: 1.1 added support for PEP-435-compliant enumerated
  27. types.
  28. :param strict: This flag has no effect.
  29. .. versionchanged:: The MySQL ENUM type as well as the base Enum
  30. type now validates all Python data values.
  31. :param charset: Optional, a column-level character set for this string
  32. value. Takes precedence to 'ascii' or 'unicode' short-hand.
  33. :param collation: Optional, a column-level collation for this string
  34. value. Takes precedence to 'binary' short-hand.
  35. :param ascii: Defaults to False: short-hand for the ``latin1``
  36. character set, generates ASCII in schema.
  37. :param unicode: Defaults to False: short-hand for the ``ucs2``
  38. character set, generates UNICODE in schema.
  39. :param binary: Defaults to False: short-hand, pick the binary
  40. collation type that matches the column's character set. Generates
  41. BINARY in schema. This does not affect the type of data stored,
  42. only the collation of character data.
  43. :param quoting: Not used. A warning will be raised if provided.
  44. """
  45. if kw.pop("quoting", NO_ARG) is not NO_ARG:
  46. util.warn_deprecated_20(
  47. "The 'quoting' parameter to :class:`.mysql.ENUM` is deprecated"
  48. " and will be removed in a future release. "
  49. "This parameter now has no effect."
  50. )
  51. kw.pop("strict", None)
  52. self._enum_init(enums, kw)
  53. _StringType.__init__(self, length=self.length, **kw)
  54. @classmethod
  55. def adapt_emulated_to_native(cls, impl, **kw):
  56. """Produce a MySQL native :class:`.mysql.ENUM` from plain
  57. :class:`.Enum`.
  58. """
  59. kw.setdefault("validate_strings", impl.validate_strings)
  60. kw.setdefault("values_callable", impl.values_callable)
  61. kw.setdefault("omit_aliases", impl._omit_aliases)
  62. return cls(**kw)
  63. def _object_value_for_elem(self, elem):
  64. # mysql sends back a blank string for any value that
  65. # was persisted that was not in the enums; that is, it does no
  66. # validation on the incoming data, it "truncates" it to be
  67. # the blank string. Return it straight.
  68. if elem == "":
  69. return elem
  70. else:
  71. return super(ENUM, self)._object_value_for_elem(elem)
  72. def __repr__(self):
  73. return util.generic_repr(
  74. self, to_inspect=[ENUM, _StringType, sqltypes.Enum]
  75. )
  76. class SET(_StringType):
  77. """MySQL SET type."""
  78. __visit_name__ = "SET"
  79. def __init__(self, *values, **kw):
  80. """Construct a SET.
  81. E.g.::
  82. Column('myset', SET("foo", "bar", "baz"))
  83. The list of potential values is required in the case that this
  84. set will be used to generate DDL for a table, or if the
  85. :paramref:`.SET.retrieve_as_bitwise` flag is set to True.
  86. :param values: The range of valid values for this SET. The values
  87. are not quoted, they will be escaped and surrounded by single
  88. quotes when generating the schema.
  89. :param convert_unicode: Same flag as that of
  90. :paramref:`.String.convert_unicode`.
  91. :param collation: same as that of :paramref:`.String.collation`
  92. :param charset: same as that of :paramref:`.VARCHAR.charset`.
  93. :param ascii: same as that of :paramref:`.VARCHAR.ascii`.
  94. :param unicode: same as that of :paramref:`.VARCHAR.unicode`.
  95. :param binary: same as that of :paramref:`.VARCHAR.binary`.
  96. :param retrieve_as_bitwise: if True, the data for the set type will be
  97. persisted and selected using an integer value, where a set is coerced
  98. into a bitwise mask for persistence. MySQL allows this mode which
  99. has the advantage of being able to store values unambiguously,
  100. such as the blank string ``''``. The datatype will appear
  101. as the expression ``col + 0`` in a SELECT statement, so that the
  102. value is coerced into an integer value in result sets.
  103. This flag is required if one wishes
  104. to persist a set that can store the blank string ``''`` as a value.
  105. .. warning::
  106. When using :paramref:`.mysql.SET.retrieve_as_bitwise`, it is
  107. essential that the list of set values is expressed in the
  108. **exact same order** as exists on the MySQL database.
  109. .. versionadded:: 1.0.0
  110. :param quoting: Not used. A warning will be raised if passed.
  111. """
  112. if kw.pop("quoting", NO_ARG) is not NO_ARG:
  113. util.warn_deprecated_20(
  114. "The 'quoting' parameter to :class:`.mysql.SET` is deprecated"
  115. " and will be removed in a future release. "
  116. "This parameter now has no effect."
  117. )
  118. self.retrieve_as_bitwise = kw.pop("retrieve_as_bitwise", False)
  119. self.values = tuple(values)
  120. if not self.retrieve_as_bitwise and "" in values:
  121. raise exc.ArgumentError(
  122. "Can't use the blank value '' in a SET without "
  123. "setting retrieve_as_bitwise=True"
  124. )
  125. if self.retrieve_as_bitwise:
  126. self._bitmap = dict(
  127. (value, 2 ** idx) for idx, value in enumerate(self.values)
  128. )
  129. self._bitmap.update(
  130. (2 ** idx, value) for idx, value in enumerate(self.values)
  131. )
  132. length = max([len(v) for v in values] + [0])
  133. kw.setdefault("length", length)
  134. super(SET, self).__init__(**kw)
  135. def column_expression(self, colexpr):
  136. if self.retrieve_as_bitwise:
  137. return sql.type_coerce(
  138. sql.type_coerce(colexpr, sqltypes.Integer) + 0, self
  139. )
  140. else:
  141. return colexpr
  142. def result_processor(self, dialect, coltype):
  143. if self.retrieve_as_bitwise:
  144. def process(value):
  145. if value is not None:
  146. value = int(value)
  147. return set(util.map_bits(self._bitmap.__getitem__, value))
  148. else:
  149. return None
  150. else:
  151. super_convert = super(SET, self).result_processor(dialect, coltype)
  152. def process(value):
  153. if isinstance(value, util.string_types):
  154. # MySQLdb returns a string, let's parse
  155. if super_convert:
  156. value = super_convert(value)
  157. return set(re.findall(r"[^,]+", value))
  158. else:
  159. # mysql-connector-python does a naive
  160. # split(",") which throws in an empty string
  161. if value is not None:
  162. value.discard("")
  163. return value
  164. return process
  165. def bind_processor(self, dialect):
  166. super_convert = super(SET, self).bind_processor(dialect)
  167. if self.retrieve_as_bitwise:
  168. def process(value):
  169. if value is None:
  170. return None
  171. elif isinstance(value, util.int_types + util.string_types):
  172. if super_convert:
  173. return super_convert(value)
  174. else:
  175. return value
  176. else:
  177. int_value = 0
  178. for v in value:
  179. int_value |= self._bitmap[v]
  180. return int_value
  181. else:
  182. def process(value):
  183. # accept strings and int (actually bitflag) values directly
  184. if value is not None and not isinstance(
  185. value, util.int_types + util.string_types
  186. ):
  187. value = ",".join(value)
  188. if super_convert:
  189. return super_convert(value)
  190. else:
  191. return value
  192. return process
  193. def adapt(self, impltype, **kw):
  194. kw["retrieve_as_bitwise"] = self.retrieve_as_bitwise
  195. return util.constructor_copy(self, impltype, *self.values, **kw)