default_comparator.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551
  1. # sql/default_comparator.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. """Default implementation of SQL comparison operations."""
  8. from __future__ import annotations
  9. import typing
  10. from typing import Any
  11. from typing import Callable
  12. from typing import Dict
  13. from typing import NoReturn
  14. from typing import Optional
  15. from typing import Tuple
  16. from typing import Type
  17. from typing import Union
  18. from . import coercions
  19. from . import operators
  20. from . import roles
  21. from . import type_api
  22. from .elements import and_
  23. from .elements import BinaryExpression
  24. from .elements import ClauseElement
  25. from .elements import CollationClause
  26. from .elements import CollectionAggregate
  27. from .elements import ExpressionClauseList
  28. from .elements import False_
  29. from .elements import Null
  30. from .elements import OperatorExpression
  31. from .elements import or_
  32. from .elements import True_
  33. from .elements import UnaryExpression
  34. from .operators import OperatorType
  35. from .. import exc
  36. from .. import util
  37. _T = typing.TypeVar("_T", bound=Any)
  38. if typing.TYPE_CHECKING:
  39. from .elements import ColumnElement
  40. from .operators import custom_op
  41. from .type_api import TypeEngine
  42. def _boolean_compare(
  43. expr: ColumnElement[Any],
  44. op: OperatorType,
  45. obj: Any,
  46. *,
  47. negate_op: Optional[OperatorType] = None,
  48. reverse: bool = False,
  49. _python_is_types: Tuple[Type[Any], ...] = (type(None), bool),
  50. result_type: Optional[TypeEngine[bool]] = None,
  51. **kwargs: Any,
  52. ) -> OperatorExpression[bool]:
  53. if result_type is None:
  54. result_type = type_api.BOOLEANTYPE
  55. if isinstance(obj, _python_is_types + (Null, True_, False_)):
  56. # allow x ==/!= True/False to be treated as a literal.
  57. # this comes out to "== / != true/false" or "1/0" if those
  58. # constants aren't supported and works on all platforms
  59. if op in (operators.eq, operators.ne) and isinstance(
  60. obj, (bool, True_, False_)
  61. ):
  62. return OperatorExpression._construct_for_op(
  63. expr,
  64. coercions.expect(roles.ConstExprRole, obj),
  65. op,
  66. type_=result_type,
  67. negate=negate_op,
  68. modifiers=kwargs,
  69. )
  70. elif op in (
  71. operators.is_distinct_from,
  72. operators.is_not_distinct_from,
  73. ):
  74. return OperatorExpression._construct_for_op(
  75. expr,
  76. coercions.expect(roles.ConstExprRole, obj),
  77. op,
  78. type_=result_type,
  79. negate=negate_op,
  80. modifiers=kwargs,
  81. )
  82. elif expr._is_collection_aggregate:
  83. obj = coercions.expect(
  84. roles.ConstExprRole, element=obj, operator=op, expr=expr
  85. )
  86. else:
  87. # all other None uses IS, IS NOT
  88. if op in (operators.eq, operators.is_):
  89. return OperatorExpression._construct_for_op(
  90. expr,
  91. coercions.expect(roles.ConstExprRole, obj),
  92. operators.is_,
  93. negate=operators.is_not,
  94. type_=result_type,
  95. )
  96. elif op in (operators.ne, operators.is_not):
  97. return OperatorExpression._construct_for_op(
  98. expr,
  99. coercions.expect(roles.ConstExprRole, obj),
  100. operators.is_not,
  101. negate=operators.is_,
  102. type_=result_type,
  103. )
  104. else:
  105. raise exc.ArgumentError(
  106. "Only '=', '!=', 'is_()', 'is_not()', "
  107. "'is_distinct_from()', 'is_not_distinct_from()' "
  108. "operators can be used with None/True/False"
  109. )
  110. else:
  111. obj = coercions.expect(
  112. roles.BinaryElementRole, element=obj, operator=op, expr=expr
  113. )
  114. if reverse:
  115. return OperatorExpression._construct_for_op(
  116. obj,
  117. expr,
  118. op,
  119. type_=result_type,
  120. negate=negate_op,
  121. modifiers=kwargs,
  122. )
  123. else:
  124. return OperatorExpression._construct_for_op(
  125. expr,
  126. obj,
  127. op,
  128. type_=result_type,
  129. negate=negate_op,
  130. modifiers=kwargs,
  131. )
  132. def _custom_op_operate(
  133. expr: ColumnElement[Any],
  134. op: custom_op[Any],
  135. obj: Any,
  136. reverse: bool = False,
  137. result_type: Optional[TypeEngine[Any]] = None,
  138. **kw: Any,
  139. ) -> ColumnElement[Any]:
  140. if result_type is None:
  141. if op.return_type:
  142. result_type = op.return_type
  143. elif op.is_comparison:
  144. result_type = type_api.BOOLEANTYPE
  145. return _binary_operate(
  146. expr, op, obj, reverse=reverse, result_type=result_type, **kw
  147. )
  148. def _binary_operate(
  149. expr: ColumnElement[Any],
  150. op: OperatorType,
  151. obj: roles.BinaryElementRole[Any],
  152. *,
  153. reverse: bool = False,
  154. result_type: Optional[TypeEngine[_T]] = None,
  155. **kw: Any,
  156. ) -> OperatorExpression[_T]:
  157. coerced_obj = coercions.expect(
  158. roles.BinaryElementRole, obj, expr=expr, operator=op
  159. )
  160. if reverse:
  161. left, right = coerced_obj, expr
  162. else:
  163. left, right = expr, coerced_obj
  164. if result_type is None:
  165. op, result_type = left.comparator._adapt_expression(
  166. op, right.comparator
  167. )
  168. return OperatorExpression._construct_for_op(
  169. left, right, op, type_=result_type, modifiers=kw
  170. )
  171. def _conjunction_operate(
  172. expr: ColumnElement[Any], op: OperatorType, other: Any, **kw: Any
  173. ) -> ColumnElement[Any]:
  174. if op is operators.and_:
  175. return and_(expr, other)
  176. elif op is operators.or_:
  177. return or_(expr, other)
  178. else:
  179. raise NotImplementedError()
  180. def _scalar(
  181. expr: ColumnElement[Any],
  182. op: OperatorType,
  183. fn: Callable[[ColumnElement[Any]], ColumnElement[Any]],
  184. **kw: Any,
  185. ) -> ColumnElement[Any]:
  186. return fn(expr)
  187. def _in_impl(
  188. expr: ColumnElement[Any],
  189. op: OperatorType,
  190. seq_or_selectable: ClauseElement,
  191. negate_op: OperatorType,
  192. **kw: Any,
  193. ) -> ColumnElement[Any]:
  194. seq_or_selectable = coercions.expect(
  195. roles.InElementRole, seq_or_selectable, expr=expr, operator=op
  196. )
  197. if "in_ops" in seq_or_selectable._annotations:
  198. op, negate_op = seq_or_selectable._annotations["in_ops"]
  199. return _boolean_compare(
  200. expr, op, seq_or_selectable, negate_op=negate_op, **kw
  201. )
  202. def _getitem_impl(
  203. expr: ColumnElement[Any], op: OperatorType, other: Any, **kw: Any
  204. ) -> ColumnElement[Any]:
  205. if (
  206. isinstance(expr.type, type_api.INDEXABLE)
  207. or isinstance(expr.type, type_api.TypeDecorator)
  208. and isinstance(expr.type.impl_instance, type_api.INDEXABLE)
  209. ):
  210. other = coercions.expect(
  211. roles.BinaryElementRole, other, expr=expr, operator=op
  212. )
  213. return _binary_operate(expr, op, other, **kw)
  214. else:
  215. _unsupported_impl(expr, op, other, **kw)
  216. def _unsupported_impl(
  217. expr: ColumnElement[Any], op: OperatorType, *arg: Any, **kw: Any
  218. ) -> NoReturn:
  219. raise NotImplementedError(
  220. "Operator '%s' is not supported on this expression" % op.__name__
  221. )
  222. def _inv_impl(
  223. expr: ColumnElement[Any], op: OperatorType, **kw: Any
  224. ) -> ColumnElement[Any]:
  225. """See :meth:`.ColumnOperators.__inv__`."""
  226. # undocumented element currently used by the ORM for
  227. # relationship.contains()
  228. if hasattr(expr, "negation_clause"):
  229. return expr.negation_clause
  230. else:
  231. return expr._negate()
  232. def _neg_impl(
  233. expr: ColumnElement[Any], op: OperatorType, **kw: Any
  234. ) -> ColumnElement[Any]:
  235. """See :meth:`.ColumnOperators.__neg__`."""
  236. return UnaryExpression(expr, operator=operators.neg, type_=expr.type)
  237. def _bitwise_not_impl(
  238. expr: ColumnElement[Any], op: OperatorType, **kw: Any
  239. ) -> ColumnElement[Any]:
  240. """See :meth:`.ColumnOperators.bitwise_not`."""
  241. return UnaryExpression(
  242. expr, operator=operators.bitwise_not_op, type_=expr.type
  243. )
  244. def _match_impl(
  245. expr: ColumnElement[Any], op: OperatorType, other: Any, **kw: Any
  246. ) -> ColumnElement[Any]:
  247. """See :meth:`.ColumnOperators.match`."""
  248. return _boolean_compare(
  249. expr,
  250. operators.match_op,
  251. coercions.expect(
  252. roles.BinaryElementRole,
  253. other,
  254. expr=expr,
  255. operator=operators.match_op,
  256. ),
  257. result_type=type_api.MATCHTYPE,
  258. negate_op=(
  259. operators.not_match_op
  260. if op is operators.match_op
  261. else operators.match_op
  262. ),
  263. **kw,
  264. )
  265. def _distinct_impl(
  266. expr: ColumnElement[Any], op: OperatorType, **kw: Any
  267. ) -> ColumnElement[Any]:
  268. """See :meth:`.ColumnOperators.distinct`."""
  269. return UnaryExpression(
  270. expr, operator=operators.distinct_op, type_=expr.type
  271. )
  272. def _between_impl(
  273. expr: ColumnElement[Any],
  274. op: OperatorType,
  275. cleft: Any,
  276. cright: Any,
  277. **kw: Any,
  278. ) -> ColumnElement[Any]:
  279. """See :meth:`.ColumnOperators.between`."""
  280. return BinaryExpression(
  281. expr,
  282. ExpressionClauseList._construct_for_list(
  283. operators.and_,
  284. type_api.NULLTYPE,
  285. coercions.expect(
  286. roles.BinaryElementRole,
  287. cleft,
  288. expr=expr,
  289. operator=operators.and_,
  290. ),
  291. coercions.expect(
  292. roles.BinaryElementRole,
  293. cright,
  294. expr=expr,
  295. operator=operators.and_,
  296. ),
  297. group=False,
  298. ),
  299. op,
  300. negate=(
  301. operators.not_between_op
  302. if op is operators.between_op
  303. else operators.between_op
  304. ),
  305. modifiers=kw,
  306. )
  307. def _collate_impl(
  308. expr: ColumnElement[str], op: OperatorType, collation: str, **kw: Any
  309. ) -> ColumnElement[str]:
  310. return CollationClause._create_collation_expression(expr, collation)
  311. def _regexp_match_impl(
  312. expr: ColumnElement[str],
  313. op: OperatorType,
  314. pattern: Any,
  315. flags: Optional[str],
  316. **kw: Any,
  317. ) -> ColumnElement[Any]:
  318. return BinaryExpression(
  319. expr,
  320. coercions.expect(
  321. roles.BinaryElementRole,
  322. pattern,
  323. expr=expr,
  324. operator=operators.comma_op,
  325. ),
  326. op,
  327. negate=operators.not_regexp_match_op,
  328. modifiers={"flags": flags},
  329. )
  330. def _regexp_replace_impl(
  331. expr: ColumnElement[Any],
  332. op: OperatorType,
  333. pattern: Any,
  334. replacement: Any,
  335. flags: Optional[str],
  336. **kw: Any,
  337. ) -> ColumnElement[Any]:
  338. return BinaryExpression(
  339. expr,
  340. ExpressionClauseList._construct_for_list(
  341. operators.comma_op,
  342. type_api.NULLTYPE,
  343. coercions.expect(
  344. roles.BinaryElementRole,
  345. pattern,
  346. expr=expr,
  347. operator=operators.comma_op,
  348. ),
  349. coercions.expect(
  350. roles.BinaryElementRole,
  351. replacement,
  352. expr=expr,
  353. operator=operators.comma_op,
  354. ),
  355. group=False,
  356. ),
  357. op,
  358. modifiers={"flags": flags},
  359. )
  360. # a mapping of operators with the method they use, along with
  361. # additional keyword arguments to be passed
  362. operator_lookup: Dict[
  363. str,
  364. Tuple[
  365. Callable[..., ColumnElement[Any]],
  366. util.immutabledict[
  367. str, Union[OperatorType, Callable[..., ColumnElement[Any]]]
  368. ],
  369. ],
  370. ] = {
  371. "and_": (_conjunction_operate, util.EMPTY_DICT),
  372. "or_": (_conjunction_operate, util.EMPTY_DICT),
  373. "inv": (_inv_impl, util.EMPTY_DICT),
  374. "add": (_binary_operate, util.EMPTY_DICT),
  375. "mul": (_binary_operate, util.EMPTY_DICT),
  376. "sub": (_binary_operate, util.EMPTY_DICT),
  377. "div": (_binary_operate, util.EMPTY_DICT),
  378. "mod": (_binary_operate, util.EMPTY_DICT),
  379. "bitwise_xor_op": (_binary_operate, util.EMPTY_DICT),
  380. "bitwise_or_op": (_binary_operate, util.EMPTY_DICT),
  381. "bitwise_and_op": (_binary_operate, util.EMPTY_DICT),
  382. "bitwise_not_op": (_bitwise_not_impl, util.EMPTY_DICT),
  383. "bitwise_lshift_op": (_binary_operate, util.EMPTY_DICT),
  384. "bitwise_rshift_op": (_binary_operate, util.EMPTY_DICT),
  385. "truediv": (_binary_operate, util.EMPTY_DICT),
  386. "floordiv": (_binary_operate, util.EMPTY_DICT),
  387. "custom_op": (_custom_op_operate, util.EMPTY_DICT),
  388. "json_path_getitem_op": (_binary_operate, util.EMPTY_DICT),
  389. "json_getitem_op": (_binary_operate, util.EMPTY_DICT),
  390. "concat_op": (_binary_operate, util.EMPTY_DICT),
  391. "any_op": (
  392. _scalar,
  393. util.immutabledict({"fn": CollectionAggregate._create_any}),
  394. ),
  395. "all_op": (
  396. _scalar,
  397. util.immutabledict({"fn": CollectionAggregate._create_all}),
  398. ),
  399. "lt": (_boolean_compare, util.immutabledict({"negate_op": operators.ge})),
  400. "le": (_boolean_compare, util.immutabledict({"negate_op": operators.gt})),
  401. "ne": (_boolean_compare, util.immutabledict({"negate_op": operators.eq})),
  402. "gt": (_boolean_compare, util.immutabledict({"negate_op": operators.le})),
  403. "ge": (_boolean_compare, util.immutabledict({"negate_op": operators.lt})),
  404. "eq": (_boolean_compare, util.immutabledict({"negate_op": operators.ne})),
  405. "is_distinct_from": (
  406. _boolean_compare,
  407. util.immutabledict({"negate_op": operators.is_not_distinct_from}),
  408. ),
  409. "is_not_distinct_from": (
  410. _boolean_compare,
  411. util.immutabledict({"negate_op": operators.is_distinct_from}),
  412. ),
  413. "like_op": (
  414. _boolean_compare,
  415. util.immutabledict({"negate_op": operators.not_like_op}),
  416. ),
  417. "ilike_op": (
  418. _boolean_compare,
  419. util.immutabledict({"negate_op": operators.not_ilike_op}),
  420. ),
  421. "not_like_op": (
  422. _boolean_compare,
  423. util.immutabledict({"negate_op": operators.like_op}),
  424. ),
  425. "not_ilike_op": (
  426. _boolean_compare,
  427. util.immutabledict({"negate_op": operators.ilike_op}),
  428. ),
  429. "contains_op": (
  430. _boolean_compare,
  431. util.immutabledict({"negate_op": operators.not_contains_op}),
  432. ),
  433. "icontains_op": (
  434. _boolean_compare,
  435. util.immutabledict({"negate_op": operators.not_icontains_op}),
  436. ),
  437. "startswith_op": (
  438. _boolean_compare,
  439. util.immutabledict({"negate_op": operators.not_startswith_op}),
  440. ),
  441. "istartswith_op": (
  442. _boolean_compare,
  443. util.immutabledict({"negate_op": operators.not_istartswith_op}),
  444. ),
  445. "endswith_op": (
  446. _boolean_compare,
  447. util.immutabledict({"negate_op": operators.not_endswith_op}),
  448. ),
  449. "iendswith_op": (
  450. _boolean_compare,
  451. util.immutabledict({"negate_op": operators.not_iendswith_op}),
  452. ),
  453. "desc_op": (
  454. _scalar,
  455. util.immutabledict({"fn": UnaryExpression._create_desc}),
  456. ),
  457. "asc_op": (
  458. _scalar,
  459. util.immutabledict({"fn": UnaryExpression._create_asc}),
  460. ),
  461. "nulls_first_op": (
  462. _scalar,
  463. util.immutabledict({"fn": UnaryExpression._create_nulls_first}),
  464. ),
  465. "nulls_last_op": (
  466. _scalar,
  467. util.immutabledict({"fn": UnaryExpression._create_nulls_last}),
  468. ),
  469. "in_op": (
  470. _in_impl,
  471. util.immutabledict({"negate_op": operators.not_in_op}),
  472. ),
  473. "not_in_op": (
  474. _in_impl,
  475. util.immutabledict({"negate_op": operators.in_op}),
  476. ),
  477. "is_": (
  478. _boolean_compare,
  479. util.immutabledict({"negate_op": operators.is_}),
  480. ),
  481. "is_not": (
  482. _boolean_compare,
  483. util.immutabledict({"negate_op": operators.is_not}),
  484. ),
  485. "collate": (_collate_impl, util.EMPTY_DICT),
  486. "match_op": (_match_impl, util.EMPTY_DICT),
  487. "not_match_op": (_match_impl, util.EMPTY_DICT),
  488. "distinct_op": (_distinct_impl, util.EMPTY_DICT),
  489. "between_op": (_between_impl, util.EMPTY_DICT),
  490. "not_between_op": (_between_impl, util.EMPTY_DICT),
  491. "neg": (_neg_impl, util.EMPTY_DICT),
  492. "getitem": (_getitem_impl, util.EMPTY_DICT),
  493. "lshift": (_unsupported_impl, util.EMPTY_DICT),
  494. "rshift": (_unsupported_impl, util.EMPTY_DICT),
  495. "contains": (_unsupported_impl, util.EMPTY_DICT),
  496. "regexp_match_op": (_regexp_match_impl, util.EMPTY_DICT),
  497. "not_regexp_match_op": (_regexp_match_impl, util.EMPTY_DICT),
  498. "regexp_replace_op": (_regexp_replace_impl, util.EMPTY_DICT),
  499. }