text.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. from django.db import NotSupportedError
  2. from django.db.models.expressions import Func, Value
  3. from django.db.models.fields import CharField, IntegerField
  4. from django.db.models.functions import Coalesce
  5. from django.db.models.lookups import Transform
  6. class MySQLSHA2Mixin:
  7. def as_mysql(self, compiler, connection, **extra_content):
  8. return super().as_sql(
  9. compiler,
  10. connection,
  11. template='SHA2(%%(expressions)s, %s)' % self.function[3:],
  12. **extra_content,
  13. )
  14. class OracleHashMixin:
  15. def as_oracle(self, compiler, connection, **extra_context):
  16. return super().as_sql(
  17. compiler,
  18. connection,
  19. template=(
  20. "LOWER(RAWTOHEX(STANDARD_HASH(UTL_I18N.STRING_TO_RAW("
  21. "%(expressions)s, 'AL32UTF8'), '%(function)s')))"
  22. ),
  23. **extra_context,
  24. )
  25. class PostgreSQLSHAMixin:
  26. def as_postgresql(self, compiler, connection, **extra_content):
  27. return super().as_sql(
  28. compiler,
  29. connection,
  30. template="ENCODE(DIGEST(%(expressions)s, '%(function)s'), 'hex')",
  31. function=self.function.lower(),
  32. **extra_content,
  33. )
  34. class Chr(Transform):
  35. function = 'CHR'
  36. lookup_name = 'chr'
  37. def as_mysql(self, compiler, connection, **extra_context):
  38. return super().as_sql(
  39. compiler, connection, function='CHAR',
  40. template='%(function)s(%(expressions)s USING utf16)',
  41. **extra_context
  42. )
  43. def as_oracle(self, compiler, connection, **extra_context):
  44. return super().as_sql(
  45. compiler, connection,
  46. template='%(function)s(%(expressions)s USING NCHAR_CS)',
  47. **extra_context
  48. )
  49. def as_sqlite(self, compiler, connection, **extra_context):
  50. return super().as_sql(compiler, connection, function='CHAR', **extra_context)
  51. class ConcatPair(Func):
  52. """
  53. Concatenate two arguments together. This is used by `Concat` because not
  54. all backend databases support more than two arguments.
  55. """
  56. function = 'CONCAT'
  57. def as_sqlite(self, compiler, connection, **extra_context):
  58. coalesced = self.coalesce()
  59. return super(ConcatPair, coalesced).as_sql(
  60. compiler, connection, template='%(expressions)s', arg_joiner=' || ',
  61. **extra_context
  62. )
  63. def as_mysql(self, compiler, connection, **extra_context):
  64. # Use CONCAT_WS with an empty separator so that NULLs are ignored.
  65. return super().as_sql(
  66. compiler, connection, function='CONCAT_WS',
  67. template="%(function)s('', %(expressions)s)",
  68. **extra_context
  69. )
  70. def coalesce(self):
  71. # null on either side results in null for expression, wrap with coalesce
  72. c = self.copy()
  73. c.set_source_expressions([
  74. Coalesce(expression, Value('')) for expression in c.get_source_expressions()
  75. ])
  76. return c
  77. class Concat(Func):
  78. """
  79. Concatenate text fields together. Backends that result in an entire
  80. null expression when any arguments are null will wrap each argument in
  81. coalesce functions to ensure a non-null result.
  82. """
  83. function = None
  84. template = "%(expressions)s"
  85. def __init__(self, *expressions, **extra):
  86. if len(expressions) < 2:
  87. raise ValueError('Concat must take at least two expressions')
  88. paired = self._paired(expressions)
  89. super().__init__(paired, **extra)
  90. def _paired(self, expressions):
  91. # wrap pairs of expressions in successive concat functions
  92. # exp = [a, b, c, d]
  93. # -> ConcatPair(a, ConcatPair(b, ConcatPair(c, d))))
  94. if len(expressions) == 2:
  95. return ConcatPair(*expressions)
  96. return ConcatPair(expressions[0], self._paired(expressions[1:]))
  97. class Left(Func):
  98. function = 'LEFT'
  99. arity = 2
  100. output_field = CharField()
  101. def __init__(self, expression, length, **extra):
  102. """
  103. expression: the name of a field, or an expression returning a string
  104. length: the number of characters to return from the start of the string
  105. """
  106. if not hasattr(length, 'resolve_expression'):
  107. if length < 1:
  108. raise ValueError("'length' must be greater than 0.")
  109. super().__init__(expression, length, **extra)
  110. def get_substr(self):
  111. return Substr(self.source_expressions[0], Value(1), self.source_expressions[1])
  112. def as_oracle(self, compiler, connection, **extra_context):
  113. return self.get_substr().as_oracle(compiler, connection, **extra_context)
  114. def as_sqlite(self, compiler, connection, **extra_context):
  115. return self.get_substr().as_sqlite(compiler, connection, **extra_context)
  116. class Length(Transform):
  117. """Return the number of characters in the expression."""
  118. function = 'LENGTH'
  119. lookup_name = 'length'
  120. output_field = IntegerField()
  121. def as_mysql(self, compiler, connection, **extra_context):
  122. return super().as_sql(compiler, connection, function='CHAR_LENGTH', **extra_context)
  123. class Lower(Transform):
  124. function = 'LOWER'
  125. lookup_name = 'lower'
  126. class LPad(Func):
  127. function = 'LPAD'
  128. output_field = CharField()
  129. def __init__(self, expression, length, fill_text=Value(' '), **extra):
  130. if not hasattr(length, 'resolve_expression') and length is not None and length < 0:
  131. raise ValueError("'length' must be greater or equal to 0.")
  132. super().__init__(expression, length, fill_text, **extra)
  133. class LTrim(Transform):
  134. function = 'LTRIM'
  135. lookup_name = 'ltrim'
  136. class MD5(OracleHashMixin, Transform):
  137. function = 'MD5'
  138. lookup_name = 'md5'
  139. class Ord(Transform):
  140. function = 'ASCII'
  141. lookup_name = 'ord'
  142. output_field = IntegerField()
  143. def as_mysql(self, compiler, connection, **extra_context):
  144. return super().as_sql(compiler, connection, function='ORD', **extra_context)
  145. def as_sqlite(self, compiler, connection, **extra_context):
  146. return super().as_sql(compiler, connection, function='UNICODE', **extra_context)
  147. class Repeat(Func):
  148. function = 'REPEAT'
  149. output_field = CharField()
  150. def __init__(self, expression, number, **extra):
  151. if not hasattr(number, 'resolve_expression') and number is not None and number < 0:
  152. raise ValueError("'number' must be greater or equal to 0.")
  153. super().__init__(expression, number, **extra)
  154. def as_oracle(self, compiler, connection, **extra_context):
  155. expression, number = self.source_expressions
  156. length = None if number is None else Length(expression) * number
  157. rpad = RPad(expression, length, expression)
  158. return rpad.as_sql(compiler, connection, **extra_context)
  159. class Replace(Func):
  160. function = 'REPLACE'
  161. def __init__(self, expression, text, replacement=Value(''), **extra):
  162. super().__init__(expression, text, replacement, **extra)
  163. class Reverse(Transform):
  164. function = 'REVERSE'
  165. lookup_name = 'reverse'
  166. def as_oracle(self, compiler, connection, **extra_context):
  167. # REVERSE in Oracle is undocumented and doesn't support multi-byte
  168. # strings. Use a special subquery instead.
  169. return super().as_sql(
  170. compiler, connection,
  171. template=(
  172. '(SELECT LISTAGG(s) WITHIN GROUP (ORDER BY n DESC) FROM '
  173. '(SELECT LEVEL n, SUBSTR(%(expressions)s, LEVEL, 1) s '
  174. 'FROM DUAL CONNECT BY LEVEL <= LENGTH(%(expressions)s)) '
  175. 'GROUP BY %(expressions)s)'
  176. ),
  177. **extra_context
  178. )
  179. class Right(Left):
  180. function = 'RIGHT'
  181. def get_substr(self):
  182. return Substr(self.source_expressions[0], self.source_expressions[1] * Value(-1))
  183. class RPad(LPad):
  184. function = 'RPAD'
  185. class RTrim(Transform):
  186. function = 'RTRIM'
  187. lookup_name = 'rtrim'
  188. class SHA1(OracleHashMixin, PostgreSQLSHAMixin, Transform):
  189. function = 'SHA1'
  190. lookup_name = 'sha1'
  191. class SHA224(MySQLSHA2Mixin, PostgreSQLSHAMixin, Transform):
  192. function = 'SHA224'
  193. lookup_name = 'sha224'
  194. def as_oracle(self, compiler, connection, **extra_context):
  195. raise NotSupportedError('SHA224 is not supported on Oracle.')
  196. class SHA256(MySQLSHA2Mixin, OracleHashMixin, PostgreSQLSHAMixin, Transform):
  197. function = 'SHA256'
  198. lookup_name = 'sha256'
  199. class SHA384(MySQLSHA2Mixin, OracleHashMixin, PostgreSQLSHAMixin, Transform):
  200. function = 'SHA384'
  201. lookup_name = 'sha384'
  202. class SHA512(MySQLSHA2Mixin, OracleHashMixin, PostgreSQLSHAMixin, Transform):
  203. function = 'SHA512'
  204. lookup_name = 'sha512'
  205. class StrIndex(Func):
  206. """
  207. Return a positive integer corresponding to the 1-indexed position of the
  208. first occurrence of a substring inside another string, or 0 if the
  209. substring is not found.
  210. """
  211. function = 'INSTR'
  212. arity = 2
  213. output_field = IntegerField()
  214. def as_postgresql(self, compiler, connection, **extra_context):
  215. return super().as_sql(compiler, connection, function='STRPOS', **extra_context)
  216. class Substr(Func):
  217. function = 'SUBSTRING'
  218. output_field = CharField()
  219. def __init__(self, expression, pos, length=None, **extra):
  220. """
  221. expression: the name of a field, or an expression returning a string
  222. pos: an integer > 0, or an expression returning an integer
  223. length: an optional number of characters to return
  224. """
  225. if not hasattr(pos, 'resolve_expression'):
  226. if pos < 1:
  227. raise ValueError("'pos' must be greater than 0")
  228. expressions = [expression, pos]
  229. if length is not None:
  230. expressions.append(length)
  231. super().__init__(*expressions, **extra)
  232. def as_sqlite(self, compiler, connection, **extra_context):
  233. return super().as_sql(compiler, connection, function='SUBSTR', **extra_context)
  234. def as_oracle(self, compiler, connection, **extra_context):
  235. return super().as_sql(compiler, connection, function='SUBSTR', **extra_context)
  236. class Trim(Transform):
  237. function = 'TRIM'
  238. lookup_name = 'trim'
  239. class Upper(Transform):
  240. function = 'UPPER'
  241. lookup_name = 'upper'