Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# mysql/enumerated.py 

2# Copyright (C) 2005-2020 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 

8import re 

9 

10from .types import _StringType 

11from ... import exc 

12from ... import sql 

13from ... import util 

14from ...sql import sqltypes 

15 

16 

17class _EnumeratedValues(_StringType): 

18 def _init_values(self, values, kw): 

19 self.quoting = kw.pop("quoting", "auto") 

20 

21 if self.quoting == "auto" and len(values): 

22 # What quoting character are we using? 

23 q = None 

24 for e in values: 

25 if len(e) == 0: 

26 self.quoting = "unquoted" 

27 break 

28 elif q is None: 

29 q = e[0] 

30 

31 if len(e) == 1 or e[0] != q or e[-1] != q: 

32 self.quoting = "unquoted" 

33 break 

34 else: 

35 self.quoting = "quoted" 

36 

37 if self.quoting == "quoted": 

38 util.warn_deprecated( 

39 "Manually quoting %s value literals is deprecated. Supply " 

40 "unquoted values and use the quoting= option in cases of " 

41 "ambiguity." % self.__class__.__name__ 

42 ) 

43 

44 values = self._strip_values(values) 

45 

46 self._enumerated_values = values 

47 length = max([len(v) for v in values] + [0]) 

48 return values, length 

49 

50 @classmethod 

51 def _strip_values(cls, values): 

52 strip_values = [] 

53 for a in values: 

54 if a[0:1] == '"' or a[0:1] == "'": 

55 # strip enclosing quotes and unquote interior 

56 a = a[1:-1].replace(a[0] * 2, a[0]) 

57 strip_values.append(a) 

58 return strip_values 

59 

60 

61class ENUM(sqltypes.NativeForEmulated, sqltypes.Enum, _EnumeratedValues): 

62 """MySQL ENUM type.""" 

63 

64 __visit_name__ = "ENUM" 

65 

66 native_enum = True 

67 

68 def __init__(self, *enums, **kw): 

69 """Construct an ENUM. 

70 

71 E.g.:: 

72 

73 Column('myenum', ENUM("foo", "bar", "baz")) 

74 

75 :param enums: The range of valid values for this ENUM. Values will be 

76 quoted when generating the schema according to the quoting flag (see 

77 below). This object may also be a PEP-435-compliant enumerated 

78 type. 

79 

80 .. versionadded: 1.1 added support for PEP-435-compliant enumerated 

81 types. 

82 

83 :param strict: This flag has no effect. 

84 

85 .. versionchanged:: The MySQL ENUM type as well as the base Enum 

86 type now validates all Python data values. 

87 

88 :param charset: Optional, a column-level character set for this string 

89 value. Takes precedence to 'ascii' or 'unicode' short-hand. 

90 

91 :param collation: Optional, a column-level collation for this string 

92 value. Takes precedence to 'binary' short-hand. 

93 

94 :param ascii: Defaults to False: short-hand for the ``latin1`` 

95 character set, generates ASCII in schema. 

96 

97 :param unicode: Defaults to False: short-hand for the ``ucs2`` 

98 character set, generates UNICODE in schema. 

99 

100 :param binary: Defaults to False: short-hand, pick the binary 

101 collation type that matches the column's character set. Generates 

102 BINARY in schema. This does not affect the type of data stored, 

103 only the collation of character data. 

104 

105 :param quoting: Defaults to 'auto': automatically determine enum value 

106 quoting. If all enum values are surrounded by the same quoting 

107 character, then use 'quoted' mode. Otherwise, use 'unquoted' mode. 

108 

109 'quoted': values in enums are already quoted, they will be used 

110 directly when generating the schema - this usage is deprecated. 

111 

112 'unquoted': values in enums are not quoted, they will be escaped and 

113 surrounded by single quotes when generating the schema. 

114 

115 Previous versions of this type always required manually quoted 

116 values to be supplied; future versions will always quote the string 

117 literals for you. This is a transitional option. 

118 

119 """ 

120 

121 kw.pop("strict", None) 

122 self._enum_init(enums, kw) 

123 _StringType.__init__(self, length=self.length, **kw) 

124 

125 @classmethod 

126 def adapt_emulated_to_native(cls, impl, **kw): 

127 """Produce a MySQL native :class:`.mysql.ENUM` from plain 

128 :class:`.Enum`. 

129 

130 """ 

131 kw.setdefault("validate_strings", impl.validate_strings) 

132 kw.setdefault("values_callable", impl.values_callable) 

133 return cls(**kw) 

134 

135 def _setup_for_values(self, values, objects, kw): 

136 values, length = self._init_values(values, kw) 

137 return super(ENUM, self)._setup_for_values(values, objects, kw) 

138 

139 def _object_value_for_elem(self, elem): 

140 # mysql sends back a blank string for any value that 

141 # was persisted that was not in the enums; that is, it does no 

142 # validation on the incoming data, it "truncates" it to be 

143 # the blank string. Return it straight. 

144 if elem == "": 

145 return elem 

146 else: 

147 return super(ENUM, self)._object_value_for_elem(elem) 

148 

149 def __repr__(self): 

150 return util.generic_repr( 

151 self, to_inspect=[ENUM, _StringType, sqltypes.Enum] 

152 ) 

153 

154 

155class SET(_EnumeratedValues): 

156 """MySQL SET type.""" 

157 

158 __visit_name__ = "SET" 

159 

160 def __init__(self, *values, **kw): 

161 """Construct a SET. 

162 

163 E.g.:: 

164 

165 Column('myset', SET("foo", "bar", "baz")) 

166 

167 

168 The list of potential values is required in the case that this 

169 set will be used to generate DDL for a table, or if the 

170 :paramref:`.SET.retrieve_as_bitwise` flag is set to True. 

171 

172 :param values: The range of valid values for this SET. 

173 

174 :param convert_unicode: Same flag as that of 

175 :paramref:`.String.convert_unicode`. 

176 

177 :param collation: same as that of :paramref:`.String.collation` 

178 

179 :param charset: same as that of :paramref:`.VARCHAR.charset`. 

180 

181 :param ascii: same as that of :paramref:`.VARCHAR.ascii`. 

182 

183 :param unicode: same as that of :paramref:`.VARCHAR.unicode`. 

184 

185 :param binary: same as that of :paramref:`.VARCHAR.binary`. 

186 

187 :param quoting: Defaults to 'auto': automatically determine set value 

188 quoting. If all values are surrounded by the same quoting 

189 character, then use 'quoted' mode. Otherwise, use 'unquoted' mode. 

190 

191 'quoted': values in enums are already quoted, they will be used 

192 directly when generating the schema - this usage is deprecated. 

193 

194 'unquoted': values in enums are not quoted, they will be escaped and 

195 surrounded by single quotes when generating the schema. 

196 

197 Previous versions of this type always required manually quoted 

198 values to be supplied; future versions will always quote the string 

199 literals for you. This is a transitional option. 

200 

201 .. versionadded:: 0.9.0 

202 

203 :param retrieve_as_bitwise: if True, the data for the set type will be 

204 persisted and selected using an integer value, where a set is coerced 

205 into a bitwise mask for persistence. MySQL allows this mode which 

206 has the advantage of being able to store values unambiguously, 

207 such as the blank string ``''``. The datatype will appear 

208 as the expression ``col + 0`` in a SELECT statement, so that the 

209 value is coerced into an integer value in result sets. 

210 This flag is required if one wishes 

211 to persist a set that can store the blank string ``''`` as a value. 

212 

213 .. warning:: 

214 

215 When using :paramref:`.mysql.SET.retrieve_as_bitwise`, it is 

216 essential that the list of set values is expressed in the 

217 **exact same order** as exists on the MySQL database. 

218 

219 .. versionadded:: 1.0.0 

220 

221 

222 """ 

223 self.retrieve_as_bitwise = kw.pop("retrieve_as_bitwise", False) 

224 values, length = self._init_values(values, kw) 

225 self.values = tuple(values) 

226 if not self.retrieve_as_bitwise and "" in values: 

227 raise exc.ArgumentError( 

228 "Can't use the blank value '' in a SET without " 

229 "setting retrieve_as_bitwise=True" 

230 ) 

231 if self.retrieve_as_bitwise: 

232 self._bitmap = dict( 

233 (value, 2 ** idx) for idx, value in enumerate(self.values) 

234 ) 

235 self._bitmap.update( 

236 (2 ** idx, value) for idx, value in enumerate(self.values) 

237 ) 

238 kw.setdefault("length", length) 

239 super(SET, self).__init__(**kw) 

240 

241 def column_expression(self, colexpr): 

242 if self.retrieve_as_bitwise: 

243 return sql.type_coerce( 

244 sql.type_coerce(colexpr, sqltypes.Integer) + 0, self 

245 ) 

246 else: 

247 return colexpr 

248 

249 def result_processor(self, dialect, coltype): 

250 if self.retrieve_as_bitwise: 

251 

252 def process(value): 

253 if value is not None: 

254 value = int(value) 

255 

256 return set(util.map_bits(self._bitmap.__getitem__, value)) 

257 else: 

258 return None 

259 

260 else: 

261 super_convert = super(SET, self).result_processor(dialect, coltype) 

262 

263 def process(value): 

264 if isinstance(value, util.string_types): 

265 # MySQLdb returns a string, let's parse 

266 if super_convert: 

267 value = super_convert(value) 

268 return set(re.findall(r"[^,]+", value)) 

269 else: 

270 # mysql-connector-python does a naive 

271 # split(",") which throws in an empty string 

272 if value is not None: 

273 value.discard("") 

274 return value 

275 

276 return process 

277 

278 def bind_processor(self, dialect): 

279 super_convert = super(SET, self).bind_processor(dialect) 

280 if self.retrieve_as_bitwise: 

281 

282 def process(value): 

283 if value is None: 

284 return None 

285 elif isinstance(value, util.int_types + util.string_types): 

286 if super_convert: 

287 return super_convert(value) 

288 else: 

289 return value 

290 else: 

291 int_value = 0 

292 for v in value: 

293 int_value |= self._bitmap[v] 

294 return int_value 

295 

296 else: 

297 

298 def process(value): 

299 # accept strings and int (actually bitflag) values directly 

300 if value is not None and not isinstance( 

301 value, util.int_types + util.string_types 

302 ): 

303 value = ",".join(value) 

304 

305 if super_convert: 

306 return super_convert(value) 

307 else: 

308 return value 

309 

310 return process 

311 

312 def adapt(self, impltype, **kw): 

313 kw["retrieve_as_bitwise"] = self.retrieve_as_bitwise 

314 return util.constructor_copy(self, impltype, *self.values, **kw)