default_comparator.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. # sql/default_comparator.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. """Default implementation of SQL comparison operations.
  8. """
  9. from .. import exc, util
  10. from . import type_api
  11. from . import operators
  12. from .elements import BindParameter, True_, False_, BinaryExpression, \
  13. Null, _const_expr, _clause_element_as_expr, \
  14. ClauseList, ColumnElement, TextClause, UnaryExpression, \
  15. collate, _is_literal, _literal_as_text, ClauseElement, and_, or_, \
  16. Slice, Visitable, _literal_as_binds
  17. from .selectable import SelectBase, Alias, Selectable, ScalarSelect
  18. def _boolean_compare(expr, op, obj, negate=None, reverse=False,
  19. _python_is_types=(util.NoneType, bool),
  20. result_type = None,
  21. **kwargs):
  22. if result_type is None:
  23. result_type = type_api.BOOLEANTYPE
  24. if isinstance(obj, _python_is_types + (Null, True_, False_)):
  25. # allow x ==/!= True/False to be treated as a literal.
  26. # this comes out to "== / != true/false" or "1/0" if those
  27. # constants aren't supported and works on all platforms
  28. if op in (operators.eq, operators.ne) and \
  29. isinstance(obj, (bool, True_, False_)):
  30. return BinaryExpression(expr,
  31. _literal_as_text(obj),
  32. op,
  33. type_=result_type,
  34. negate=negate, modifiers=kwargs)
  35. elif op in (operators.is_distinct_from, operators.isnot_distinct_from):
  36. return BinaryExpression(expr,
  37. _literal_as_text(obj),
  38. op,
  39. type_=result_type,
  40. negate=negate, modifiers=kwargs)
  41. else:
  42. # all other None/True/False uses IS, IS NOT
  43. if op in (operators.eq, operators.is_):
  44. return BinaryExpression(expr, _const_expr(obj),
  45. operators.is_,
  46. negate=operators.isnot)
  47. elif op in (operators.ne, operators.isnot):
  48. return BinaryExpression(expr, _const_expr(obj),
  49. operators.isnot,
  50. negate=operators.is_)
  51. else:
  52. raise exc.ArgumentError(
  53. "Only '=', '!=', 'is_()', 'isnot()', "
  54. "'is_distinct_from()', 'isnot_distinct_from()' "
  55. "operators can be used with None/True/False")
  56. else:
  57. obj = _check_literal(expr, op, obj)
  58. if reverse:
  59. return BinaryExpression(obj,
  60. expr,
  61. op,
  62. type_=result_type,
  63. negate=negate, modifiers=kwargs)
  64. else:
  65. return BinaryExpression(expr,
  66. obj,
  67. op,
  68. type_=result_type,
  69. negate=negate, modifiers=kwargs)
  70. def _binary_operate(expr, op, obj, reverse=False, result_type=None,
  71. **kw):
  72. obj = _check_literal(expr, op, obj)
  73. if reverse:
  74. left, right = obj, expr
  75. else:
  76. left, right = expr, obj
  77. if result_type is None:
  78. op, result_type = left.comparator._adapt_expression(
  79. op, right.comparator)
  80. return BinaryExpression(
  81. left, right, op, type_=result_type, modifiers=kw)
  82. def _conjunction_operate(expr, op, other, **kw):
  83. if op is operators.and_:
  84. return and_(expr, other)
  85. elif op is operators.or_:
  86. return or_(expr, other)
  87. else:
  88. raise NotImplementedError()
  89. def _scalar(expr, op, fn, **kw):
  90. return fn(expr)
  91. def _in_impl(expr, op, seq_or_selectable, negate_op, **kw):
  92. seq_or_selectable = _clause_element_as_expr(seq_or_selectable)
  93. if isinstance(seq_or_selectable, ScalarSelect):
  94. return _boolean_compare(expr, op, seq_or_selectable,
  95. negate=negate_op)
  96. elif isinstance(seq_or_selectable, SelectBase):
  97. # TODO: if we ever want to support (x, y, z) IN (select x,
  98. # y, z from table), we would need a multi-column version of
  99. # as_scalar() to produce a multi- column selectable that
  100. # does not export itself as a FROM clause
  101. return _boolean_compare(
  102. expr, op, seq_or_selectable.as_scalar(),
  103. negate=negate_op, **kw)
  104. elif isinstance(seq_or_selectable, (Selectable, TextClause)):
  105. return _boolean_compare(expr, op, seq_or_selectable,
  106. negate=negate_op, **kw)
  107. elif isinstance(seq_or_selectable, ClauseElement):
  108. raise exc.InvalidRequestError(
  109. 'in_() accepts'
  110. ' either a list of expressions '
  111. 'or a selectable: %r' % seq_or_selectable)
  112. # Handle non selectable arguments as sequences
  113. args = []
  114. for o in seq_or_selectable:
  115. if not _is_literal(o):
  116. if not isinstance(o, operators.ColumnOperators):
  117. raise exc.InvalidRequestError(
  118. 'in_() accepts'
  119. ' either a list of expressions '
  120. 'or a selectable: %r' % o)
  121. elif o is None:
  122. o = Null()
  123. else:
  124. o = expr._bind_param(op, o)
  125. args.append(o)
  126. if len(args) == 0:
  127. # Special case handling for empty IN's, behave like
  128. # comparison against zero row selectable. We use != to
  129. # build the contradiction as it handles NULL values
  130. # appropriately, i.e. "not (x IN ())" should not return NULL
  131. # values for x.
  132. util.warn('The IN-predicate on "%s" was invoked with an '
  133. 'empty sequence. This results in a '
  134. 'contradiction, which nonetheless can be '
  135. 'expensive to evaluate. Consider alternative '
  136. 'strategies for improved performance.' % expr)
  137. if op is operators.in_op:
  138. return expr != expr
  139. else:
  140. return expr == expr
  141. return _boolean_compare(expr, op,
  142. ClauseList(*args).self_group(against=op),
  143. negate=negate_op)
  144. def _getitem_impl(expr, op, other, **kw):
  145. if isinstance(expr.type, type_api.INDEXABLE):
  146. other = _check_literal(expr, op, other)
  147. return _binary_operate(expr, op, other, **kw)
  148. else:
  149. _unsupported_impl(expr, op, other, **kw)
  150. def _unsupported_impl(expr, op, *arg, **kw):
  151. raise NotImplementedError("Operator '%s' is not supported on "
  152. "this expression" % op.__name__)
  153. def _inv_impl(expr, op, **kw):
  154. """See :meth:`.ColumnOperators.__inv__`."""
  155. if hasattr(expr, 'negation_clause'):
  156. return expr.negation_clause
  157. else:
  158. return expr._negate()
  159. def _neg_impl(expr, op, **kw):
  160. """See :meth:`.ColumnOperators.__neg__`."""
  161. return UnaryExpression(expr, operator=operators.neg, type_=expr.type)
  162. def _match_impl(expr, op, other, **kw):
  163. """See :meth:`.ColumnOperators.match`."""
  164. return _boolean_compare(
  165. expr, operators.match_op,
  166. _check_literal(
  167. expr, operators.match_op, other),
  168. result_type=type_api.MATCHTYPE,
  169. negate=operators.notmatch_op
  170. if op is operators.match_op else operators.match_op,
  171. **kw
  172. )
  173. def _distinct_impl(expr, op, **kw):
  174. """See :meth:`.ColumnOperators.distinct`."""
  175. return UnaryExpression(expr, operator=operators.distinct_op,
  176. type_=expr.type)
  177. def _between_impl(expr, op, cleft, cright, **kw):
  178. """See :meth:`.ColumnOperators.between`."""
  179. return BinaryExpression(
  180. expr,
  181. ClauseList(
  182. _check_literal(expr, operators.and_, cleft),
  183. _check_literal(expr, operators.and_, cright),
  184. operator=operators.and_,
  185. group=False, group_contents=False),
  186. op,
  187. negate=operators.notbetween_op
  188. if op is operators.between_op
  189. else operators.between_op,
  190. modifiers=kw)
  191. def _collate_impl(expr, op, other, **kw):
  192. return collate(expr, other)
  193. # a mapping of operators with the method they use, along with
  194. # their negated operator for comparison operators
  195. operator_lookup = {
  196. "and_": (_conjunction_operate,),
  197. "or_": (_conjunction_operate,),
  198. "inv": (_inv_impl,),
  199. "add": (_binary_operate,),
  200. "mul": (_binary_operate,),
  201. "sub": (_binary_operate,),
  202. "div": (_binary_operate,),
  203. "mod": (_binary_operate,),
  204. "truediv": (_binary_operate,),
  205. "custom_op": (_binary_operate,),
  206. "json_path_getitem_op": (_binary_operate, ),
  207. "json_getitem_op": (_binary_operate, ),
  208. "concat_op": (_binary_operate,),
  209. "lt": (_boolean_compare, operators.ge),
  210. "le": (_boolean_compare, operators.gt),
  211. "ne": (_boolean_compare, operators.eq),
  212. "gt": (_boolean_compare, operators.le),
  213. "ge": (_boolean_compare, operators.lt),
  214. "eq": (_boolean_compare, operators.ne),
  215. "is_distinct_from": (_boolean_compare, operators.isnot_distinct_from),
  216. "isnot_distinct_from": (_boolean_compare, operators.is_distinct_from),
  217. "like_op": (_boolean_compare, operators.notlike_op),
  218. "ilike_op": (_boolean_compare, operators.notilike_op),
  219. "notlike_op": (_boolean_compare, operators.like_op),
  220. "notilike_op": (_boolean_compare, operators.ilike_op),
  221. "contains_op": (_boolean_compare, operators.notcontains_op),
  222. "startswith_op": (_boolean_compare, operators.notstartswith_op),
  223. "endswith_op": (_boolean_compare, operators.notendswith_op),
  224. "desc_op": (_scalar, UnaryExpression._create_desc),
  225. "asc_op": (_scalar, UnaryExpression._create_asc),
  226. "nullsfirst_op": (_scalar, UnaryExpression._create_nullsfirst),
  227. "nullslast_op": (_scalar, UnaryExpression._create_nullslast),
  228. "in_op": (_in_impl, operators.notin_op),
  229. "notin_op": (_in_impl, operators.in_op),
  230. "is_": (_boolean_compare, operators.is_),
  231. "isnot": (_boolean_compare, operators.isnot),
  232. "collate": (_collate_impl,),
  233. "match_op": (_match_impl,),
  234. "notmatch_op": (_match_impl,),
  235. "distinct_op": (_distinct_impl,),
  236. "between_op": (_between_impl, ),
  237. "notbetween_op": (_between_impl, ),
  238. "neg": (_neg_impl,),
  239. "getitem": (_getitem_impl,),
  240. "lshift": (_unsupported_impl,),
  241. "rshift": (_unsupported_impl,),
  242. "contains": (_unsupported_impl,),
  243. }
  244. def _check_literal(expr, operator, other, bindparam_type=None):
  245. if isinstance(other, (ColumnElement, TextClause)):
  246. if isinstance(other, BindParameter) and \
  247. other.type._isnull:
  248. other = other._clone()
  249. other.type = expr.type
  250. return other
  251. elif hasattr(other, '__clause_element__'):
  252. other = other.__clause_element__()
  253. elif isinstance(other, type_api.TypeEngine.Comparator):
  254. other = other.expr
  255. if isinstance(other, (SelectBase, Alias)):
  256. return other.as_scalar()
  257. elif not isinstance(other, Visitable):
  258. return expr._bind_param(operator, other, type_=bindparam_type)
  259. else:
  260. return other