binary.right = percent.concat(binary.right).concat(percent)
return self.visit_not_like_op_binary(binary, operator, **kw)
+ def visit_icontains_op_binary(self, binary, operator, **kw):
+ binary = binary._clone()
+ percent = self._like_percent_literal
+ binary.left = functions.func.lower(binary.left)
+ binary.right = percent.concat(functions.func.lower(binary.right)).concat(percent)
+ return self.visit_like_op_binary(binary, operator, **kw)
+
+ def visit_not_icontains_op_binary(self, binary, operator, **kw):
+ binary = binary._clone()
+ percent = self._like_percent_literal
+ binary.left = functions.func.lower(binary.left)
+ binary.right = percent.concat(functions.func.lower(binary.right)).concat(percent)
+ return self.visit_not_like_op_binary(binary, operator, **kw)
+
def visit_startswith_op_binary(self, binary, operator, **kw):
binary = binary._clone()
percent = self._like_percent_literal
binary.right = percent._rconcat(binary.right)
return self.visit_not_like_op_binary(binary, operator, **kw)
+ def visit_istartswith_op_binary(self, binary, operator, **kw):
+ binary = binary._clone()
+ percent = self._like_percent_literal
+ binary.left = functions.func.lower(binary.left)
+ binary.right = percent._rconcat(functions.func.lower(binary.right))
+ return self.visit_like_op_binary(binary, operator, **kw)
+
+ def visit_not_istartswith_op_binary(self, binary, operator, **kw):
+ binary = binary._clone()
+ percent = self._like_percent_literal
+ binary.left = functions.func.lower(binary.left)
+ binary.right = percent._rconcat(functions.func.lower(binary.right))
+ return self.visit_not_like_op_binary(binary, operator, **kw)
+
def visit_endswith_op_binary(self, binary, operator, **kw):
binary = binary._clone()
percent = self._like_percent_literal
binary.right = percent.concat(binary.right)
return self.visit_not_like_op_binary(binary, operator, **kw)
+ def visit_iendswith_op_binary(self, binary, operator, **kw):
+ binary = binary._clone()
+ percent = self._like_percent_literal
+ binary.left = functions.func.lower(binary.left)
+ binary.right = percent.concat(functions.func.lower(binary.right))
+ return self.visit_like_op_binary(binary, operator, **kw)
+
+ def visit_not_iendswith_op_binary(self, binary, operator, **kw):
+ binary = binary._clone()
+ percent = self._like_percent_literal
+ binary.left = functions.func.lower(binary.left)
+ binary.right = percent.concat(functions.func.lower(binary.right))
+ return self.visit_not_like_op_binary(binary, operator, **kw)
+
def visit_like_op_binary(self, binary, operator, **kw):
escape = binary.modifiers.get("escape", None)
_boolean_compare,
util.immutabledict({"negate_op": operators.not_contains_op}),
),
+ "icontains_op": (
+ _boolean_compare,
+ util.immutabledict({"negate_op": operators.not_icontains_op}),
+ ),
"startswith_op": (
_boolean_compare,
util.immutabledict({"negate_op": operators.not_startswith_op}),
),
+ "istartswith_op": (
+ _boolean_compare,
+ util.immutabledict({"negate_op": operators.not_istartswith_op}),
+ ),
"endswith_op": (
_boolean_compare,
util.immutabledict({"negate_op": operators.not_endswith_op}),
),
+ "iendswith_op": (
+ _boolean_compare,
+ util.immutabledict({"negate_op": operators.not_iendswith_op}),
+ ),
"desc_op": (
_scalar,
util.immutabledict({"fn": UnaryExpression._create_desc}),
__name__: str
def __call__(
- self,
- left: "Operators",
- right: Optional[Any] = None,
- *other: Any,
- **kwargs: Any,
+ self,
+ left: "Operators",
+ right: Optional[Any] = None,
+ *other: Any,
+ **kwargs: Any,
) -> "Operators":
...
return self.operate(inv)
def op(
- self,
- opstring: str,
- precedence: int = 0,
- is_comparison: bool = False,
- return_type: Optional[
- Union[Type["TypeEngine[Any]"], "TypeEngine[Any]"]
- ] = None,
- python_impl: Optional[Callable[..., Any]] = None,
+ self,
+ opstring: str,
+ precedence: int = 0,
+ is_comparison: bool = False,
+ return_type: Optional[
+ Union[Type["TypeEngine[Any]"], "TypeEngine[Any]"]
+ ] = None,
+ python_impl: Optional[Callable[..., Any]] = None,
) -> Callable[[Any], Operators]:
"""Produce a generic operator function.
return against
def bool_op(
- self,
- opstring: str,
- precedence: int = 0,
- python_impl: Optional[Callable[..., Any]] = None,
+ self,
+ opstring: str,
+ precedence: int = 0,
+ python_impl: Optional[Callable[..., Any]] = None,
) -> Callable[[Any], Operators]:
"""Return a custom boolean operator.
)
def operate(
- self, op: OperatorType, *other: Any, **kwargs: Any
+ self, op: OperatorType, *other: Any, **kwargs: Any
) -> Operators:
r"""Operate on an argument.
__sa_operate__ = operate
def reverse_operate(
- self, op: OperatorType, other: Any, **kwargs: Any
+ self, op: OperatorType, other: Any, **kwargs: Any
) -> Operators:
"""Reverse operate on an argument.
)
def __init__(
- self,
- opstring: str,
- precedence: int = 0,
- is_comparison: bool = False,
- return_type: Optional[
- Union[Type["TypeEngine[_T]"], "TypeEngine[_T]"]
- ] = None,
- natural_self_precedent: bool = False,
- eager_grouping: bool = False,
- python_impl: Optional[Callable[..., Any]] = None,
+ self,
+ opstring: str,
+ precedence: int = 0,
+ is_comparison: bool = False,
+ return_type: Optional[
+ Union[Type["TypeEngine[_T]"], "TypeEngine[_T]"]
+ ] = None,
+ natural_self_precedent: bool = False,
+ eager_grouping: bool = False,
+ python_impl: Optional[Callable[..., Any]] = None,
):
self.opstring = opstring
self.precedence = precedence
return id(self)
def __call__(
- self,
- left: Operators,
- right: Optional[Any] = None,
- *other: Any,
- **kwargs: Any,
+ self,
+ left: Operators,
+ right: Optional[Any] = None,
+ *other: Any,
+ **kwargs: Any,
) -> Operators:
if hasattr(left, "__sa_operate__"):
return left.operate(self, right, *other, **kwargs)
"""Hack, allows datetime objects to be compared on the LHS."""
if typing.TYPE_CHECKING:
-
def operate(
- self, op: OperatorType, *other: Any, **kwargs: Any
+ self, op: OperatorType, *other: Any, **kwargs: Any
) -> ColumnOperators:
...
def reverse_operate(
- self, op: OperatorType, other: Any, **kwargs: Any
+ self, op: OperatorType, other: Any, **kwargs: Any
) -> ColumnOperators:
...
return self.reverse_operate(concat_op, other)
def like(
- self, other: Any, escape: Optional[str] = None
+ self, other: Any, escape: Optional[str] = None
) -> ColumnOperators:
r"""Implement the ``like`` operator.
return self.operate(like_op, other, escape=escape)
def ilike(
- self, other: Any, escape: Optional[str] = None
+ self, other: Any, escape: Optional[str] = None
) -> ColumnOperators:
r"""Implement the ``ilike`` operator, e.g. case insensitive LIKE.
notin_ = not_in
def not_like(
- self, other: Any, escape: Optional[str] = None
+ self, other: Any, escape: Optional[str] = None
) -> ColumnOperators:
"""implement the ``NOT LIKE`` operator.
notlike = not_like
def not_ilike(
- self, other: Any, escape: Optional[str] = None
+ self, other: Any, escape: Optional[str] = None
) -> ColumnOperators:
"""implement the ``NOT ILIKE`` operator.
isnot = is_not
def startswith(
- self,
- other: Any,
- escape: Optional[str] = None,
- autoescape: bool = False,
+ self,
+ other: Any,
+ escape: Optional[str] = None,
+ autoescape: bool = False,
) -> ColumnOperators:
r"""Implement the ``startswith`` operator.
startswith_op, other, escape=escape, autoescape=autoescape
)
+ def istartswith(
+ self,
+ other: Any,
+ escape: Optional[str] = None,
+ autoescape: bool = False,
+ ) -> ColumnOperators:
+ r"""Implement the ``istartswith`` operator, e.g. case insensitive
+ version of :meth:`.ColumnOperators.startswith`.
+
+ Produces a LIKE expression that tests against an insensitive match for the start
+ of a string value::
+
+ lower(column) LIKE lower(<other>) || '%'
+
+ E.g.::
+
+ stmt = select(sometable).\
+ where(sometable.c.column.istartswith("foobar"))
+
+ Since the operator uses ``LIKE``, wildcard characters
+ ``"%"`` and ``"_"`` that are present inside the <other> expression
+ will behave like wildcards as well. For literal string
+ values, the :paramref:`.ColumnOperators.istartswith.autoescape` flag
+ may be set to ``True`` to apply escaping to occurrences of these
+ characters within the string value so that they match as themselves
+ and not as wildcard characters. Alternatively, the
+ :paramref:`.ColumnOperators.istartswith.escape` parameter will establish
+ a given character as an escape character which can be of use when
+ the target expression is not a literal string.
+
+ :param other: expression to be compared. This is usually a plain
+ string value, but can also be an arbitrary SQL expression. LIKE
+ wildcard characters ``%`` and ``_`` are not escaped by default unless
+ the :paramref:`.ColumnOperators.istartswith.autoescape` flag is
+ set to True.
+
+ :param autoescape: boolean; when True, establishes an escape character
+ within the LIKE expression, then applies it to all occurrences of
+ ``"%"``, ``"_"`` and the escape character itself within the
+ comparison value, which is assumed to be a literal string and not a
+ SQL expression.
+
+ An expression such as::
+
+ somecolumn.istartswith("foo%bar", autoescape=True)
+
+ Will render as::
+
+ lower(somecolumn) LIKE lower(:param) || '%' ESCAPE '/'
+
+ With the value of ``:param`` as ``"foo/%bar"``.
+
+ :param escape: a character which when given will render with the
+ ``ESCAPE`` keyword to establish that character as the escape
+ character. This character can then be placed preceding occurrences
+ of ``%`` and ``_`` to allow them to act as themselves and not
+ wildcard characters.
+
+ An expression such as::
+
+ somecolumn.istartswith("foo/%bar", escape="^")
+
+ Will render as::
+
+ lower(somecolumn) LIKE lower(:param) || '%' ESCAPE '^'
+
+ The parameter may also be combined with
+ :paramref:`.ColumnOperators.istartswith.autoescape`::
+
+ somecolumn.istartswith("foo%bar^bat", escape="^", autoescape=True)
+
+ Where above, the given literal parameter will be converted to
+ ``"foo^%bar^^bat"`` before being passed to the database.
+
+ .. seealso::
+
+ :meth:`.ColumnOperators.startswith`
+ """
+ return self.operate(
+ istartswith_op, other, escape=escape, autoescape=autoescape
+ )
+
def endswith(
- self,
- other: Any,
- escape: Optional[str] = None,
- autoescape: bool = False,
+ self,
+ other: Any,
+ escape: Optional[str] = None,
+ autoescape: bool = False,
) -> ColumnOperators:
r"""Implement the 'endswith' operator.
endswith_op, other, escape=escape, autoescape=autoescape
)
+ def iendswith(
+ self,
+ other: Any,
+ escape: Optional[str] = None,
+ autoescape: bool = False,
+ ) -> ColumnOperators:
+ r"""Implement the ``iendswith`` operator, e.g. case insensitive
+ version of :meth:`.ColumnOperators.endswith`.
+
+ Produces a LIKE expression that tests against an insensitive match for the end
+ of a string value::
+
+ lower(column) LIKE '%' || lower(<other>)
+
+ E.g.::
+
+ stmt = select(sometable).\
+ where(sometable.c.column.iendswith("foobar"))
+
+ Since the operator uses ``LIKE``, wildcard characters
+ ``"%"`` and ``"_"`` that are present inside the <other> expression
+ will behave like wildcards as well. For literal string
+ values, the :paramref:`.ColumnOperators.iendswith.autoescape` flag
+ may be set to ``True`` to apply escaping to occurrences of these
+ characters within the string value so that they match as themselves
+ and not as wildcard characters. Alternatively, the
+ :paramref:`.ColumnOperators.iendswith.escape` parameter will establish
+ a given character as an escape character which can be of use when
+ the target expression is not a literal string.
+
+ :param other: expression to be compared. This is usually a plain
+ string value, but can also be an arbitrary SQL expression. LIKE
+ wildcard characters ``%`` and ``_`` are not escaped by default unless
+ the :paramref:`.ColumnOperators.iendswith.autoescape` flag is
+ set to True.
+
+ :param autoescape: boolean; when True, establishes an escape character
+ within the LIKE expression, then applies it to all occurrences of
+ ``"%"``, ``"_"`` and the escape character itself within the
+ comparison value, which is assumed to be a literal string and not a
+ SQL expression.
+
+ An expression such as::
+
+ somecolumn.iendswith("foo%bar", autoescape=True)
+
+ Will render as::
+
+ lower(somecolumn) LIKE '%' || lower(:param) ESCAPE '/'
+
+ With the value of ``:param`` as ``"foo/%bar"``.
+
+ :param escape: a character which when given will render with the
+ ``ESCAPE`` keyword to establish that character as the escape
+ character. This character can then be placed preceding occurrences
+ of ``%`` and ``_`` to allow them to act as themselves and not
+ wildcard characters.
+
+ An expression such as::
+
+ somecolumn.iendswith("foo/%bar", escape="^")
+
+ Will render as::
+
+ lower(somecolumn) LIKE '%' || lower(:param) ESCAPE '^'
+
+ The parameter may also be combined with
+ :paramref:`.ColumnOperators.iendswith.autoescape`::
+
+ somecolumn.endswith("foo%bar^bat", escape="^", autoescape=True)
+
+ Where above, the given literal parameter will be converted to
+ ``"foo^%bar^^bat"`` before being passed to the database.
+
+ .. seealso::
+
+ :meth:`.ColumnOperators.endswith`
+ """
+ return self.operate(
+ iendswith_op, other, escape=escape, autoescape=autoescape
+ )
+
def contains(self, other: Any, **kw: Any) -> ColumnOperators:
r"""Implement the 'contains' operator.
"""
return self.operate(contains_op, other, **kw)
+ def icontains(self, other: Any, **kw: Any) -> ColumnOperators:
+ r"""Implement the ``icontains`` operator, e.g. case insensitive
+ version of :meth:`.ColumnOperators.contains`.
+
+ Produces a LIKE expression that tests against an insensitive match for the middle
+ of a string value::
+
+ lower(column) LIKE '%' || lower(<other>) || '%'
+
+ E.g.::
+
+ stmt = select(sometable).\
+ where(sometable.c.column.icontains("foobar"))
+
+ Since the operator uses ``LIKE``, wildcard characters
+ ``"%"`` and ``"_"`` that are present inside the <other> expression
+ will behave like wildcards as well. For literal string
+ values, the :paramref:`.ColumnOperators.icontains.autoescape` flag
+ may be set to ``True`` to apply escaping to occurrences of these
+ characters within the string value so that they match as themselves
+ and not as wildcard characters. Alternatively, the
+ :paramref:`.ColumnOperators.icontains.escape` parameter will establish
+ a given character as an escape character which can be of use when
+ the target expression is not a literal string.
+
+ :param other: expression to be compared. This is usually a plain
+ string value, but can also be an arbitrary SQL expression. LIKE
+ wildcard characters ``%`` and ``_`` are not escaped by default unless
+ the :paramref:`.ColumnOperators.icontains.autoescape` flag is
+ set to True.
+
+ :param autoescape: boolean; when True, establishes an escape character
+ within the LIKE expression, then applies it to all occurrences of
+ ``"%"``, ``"_"`` and the escape character itself within the
+ comparison value, which is assumed to be a literal string and not a
+ SQL expression.
+
+ An expression such as::
+
+ somecolumn.icontains("foo%bar", autoescape=True)
+
+ Will render as::
+
+ lower(somecolumn) LIKE '%' || lower(:param) || '%' ESCAPE '/'
+
+ With the value of ``:param`` as ``"foo/%bar"``.
+
+ :param escape: a character which when given will render with the
+ ``ESCAPE`` keyword to establish that character as the escape
+ character. This character can then be placed preceding occurrences
+ of ``%`` and ``_`` to allow them to act as themselves and not
+ wildcard characters.
+
+ An expression such as::
+
+ somecolumn.icontains("foo/%bar", escape="^")
+
+ Will render as::
+
+ lower(somecolumn) LIKE '%' || lower(:param) || '%' ESCAPE '^'
+
+ The parameter may also be combined with
+ :paramref:`.ColumnOperators.contains.autoescape`::
+
+ somecolumn.icontains("foo%bar^bat", escape="^", autoescape=True)
+
+ Where above, the given literal parameter will be converted to
+ ``"foo^%bar^^bat"`` before being passed to the database.
+
+ .. seealso::
+
+ :meth:`.ColumnOperators.contains`
+
+ """
+ return self.operate(icontains_op, other, **kw)
+
def match(self, other: Any, **kwargs: Any) -> ColumnOperators:
"""Implements a database-specific 'match' operator.
return self.operate(match_op, other, **kwargs)
def regexp_match(
- self, pattern: Any, flags: Optional[str] = None
+ self, pattern: Any, flags: Optional[str] = None
) -> ColumnOperators:
"""Implements a database-specific 'regexp match' operator.
return self.operate(regexp_match_op, pattern, flags=flags)
def regexp_replace(
- self, pattern: Any, replacement: Any, flags: Optional[str] = None
+ self, pattern: Any, replacement: Any, flags: Optional[str] = None
) -> ColumnOperators:
"""Implements a database-specific 'regexp replace' operator.
return self.reverse_operate(mod, other)
def between(
- self, cleft: Any, cright: Any, symmetric: bool = False
+ self, cleft: Any, cright: Any, symmetric: bool = False
) -> ColumnOperators:
"""Produce a :func:`_expression.between` clause against
the parent object, given the lower and upper range.
def _escaped_like_impl(
- fn: Callable[..., Any], other: Any, escape: Optional[str], autoescape: bool
+ fn: Callable[..., Any], other: Any, escape: Optional[str], autoescape: bool
) -> Any:
if autoescape:
if autoescape is not True:
@comparison_op
@_operator_fn
def startswith_op(
- a: Any, b: Any, escape: Optional[str] = None, autoescape: bool = False
+ a: Any, b: Any, escape: Optional[str] = None, autoescape: bool = False
) -> Any:
return _escaped_like_impl(a.startswith, b, escape, autoescape)
@comparison_op
@_operator_fn
def not_startswith_op(
- a: Any, b: Any, escape: Optional[str] = None, autoescape: bool = False
+ a: Any, b: Any, escape: Optional[str] = None, autoescape: bool = False
) -> Any:
return ~_escaped_like_impl(a.startswith, b, escape, autoescape)
notstartswith_op = not_startswith_op
+@comparison_op
+@_operator_fn
+def istartswith_op(
+ a: Any, b: Any, escape: Optional[str] = None, autoescape: bool = False
+) -> Any:
+ return _escaped_like_impl(a.istartswith, b, escape, autoescape)
+
+
+@comparison_op
+@_operator_fn
+def not_istartswith_op(
+ a: Any, b: Any, escape: Optional[str] = None, autoescape: bool = False
+) -> Any:
+ return ~_escaped_like_impl(a.istartswith, b, escape, autoescape)
+
+
@comparison_op
@_operator_fn
def endswith_op(
- a: Any, b: Any, escape: Optional[str] = None, autoescape: bool = False
+ a: Any, b: Any, escape: Optional[str] = None, autoescape: bool = False
) -> Any:
return _escaped_like_impl(a.endswith, b, escape, autoescape)
@comparison_op
@_operator_fn
def not_endswith_op(
- a: Any, b: Any, escape: Optional[str] = None, autoescape: bool = False
+ a: Any, b: Any, escape: Optional[str] = None, autoescape: bool = False
) -> Any:
return ~_escaped_like_impl(a.endswith, b, escape, autoescape)
notendswith_op = not_endswith_op
+@comparison_op
+@_operator_fn
+def iendswith_op(
+ a: Any, b: Any, escape: Optional[str] = None, autoescape: bool = False
+) -> Any:
+ return _escaped_like_impl(a.iendswith, b, escape, autoescape)
+
+
+@comparison_op
+@_operator_fn
+def not_iendswith_op(
+ a: Any, b: Any, escape: Optional[str] = None, autoescape: bool = False
+) -> Any:
+ return ~_escaped_like_impl(a.iendswith, b, escape, autoescape)
+
+
@comparison_op
@_operator_fn
def contains_op(
- a: Any, b: Any, escape: Optional[str] = None, autoescape: bool = False
+ a: Any, b: Any, escape: Optional[str] = None, autoescape: bool = False
) -> Any:
return _escaped_like_impl(a.contains, b, escape, autoescape)
@comparison_op
@_operator_fn
def not_contains_op(
- a: Any, b: Any, escape: Optional[str] = None, autoescape: bool = False
+ a: Any, b: Any, escape: Optional[str] = None, autoescape: bool = False
) -> Any:
return ~_escaped_like_impl(a.contains, b, escape, autoescape)
notcontains_op = not_contains_op
+@comparison_op
+@_operator_fn
+def icontains_op(
+ a: Any, b: Any, escape: Optional[str] = None, autoescape: bool = False
+) -> Any:
+ return _escaped_like_impl(a.icontains, b, escape, autoescape)
+
+
+@comparison_op
+@_operator_fn
+def not_icontains_op(
+ a: Any, b: Any, escape: Optional[str] = None, autoescape: bool = False
+) -> Any:
+ return ~_escaped_like_impl(a.icontains, b, escape, autoescape)
+
+
@comparison_op
@_operator_fn
def match_op(a: Any, b: Any, **kw: Any) -> Any:
@_operator_fn
def regexp_replace_op(
- a: Any, b: Any, replacement: Any, flags: Optional[str] = None
+ a: Any, b: Any, replacement: Any, flags: Optional[str] = None
) -> Any:
return a.regexp_replace(b, replacement=replacement, flags=flags)
def is_natural_self_precedent(op: OperatorType) -> bool:
return (
- op in _natural_self_precedent
- or isinstance(op, custom_op)
- and op.natural_self_precedent
+ op in _natural_self_precedent
+ or isinstance(op, custom_op)
+ and op.natural_self_precedent
)
dialect=mysql.dialect(),
)
+ def test_icontains(self):
+ """
+ case insensitive contains method
+ """
+ self.assert_compile(
+ column("x").icontains("y"),
+ "lower(x) LIKE '%' || lower(:x_1) || '%'",
+ checkparams={"x_1": "y"},
+ )
+
+ def test_icontains_encoded(self):
+ self.assert_compile(
+ column("x").icontains(b"y"),
+ "lower(x) LIKE '%' || lower(:x_1) || '%'",
+ checkparams={"x_1": b"y"},
+ )
+
+ def test_not_icontains_encoded(self):
+ self.assert_compile(
+ ~column("x").icontains(b"y"),
+ "lower(x) NOT LIKE '%' || lower(:x_1) || '%'",
+ checkparams={"x_1": b"y"},
+ )
+
+ def test_icontains_encoded_mysql(self):
+ self.assert_compile(
+ column("x").icontains(b"y"),
+ "lower(x) LIKE concat('%%', lower(%s), '%%')",
+ checkparams={"x_1": b"y"},
+ dialect="mysql",
+ )
+
+ def test_not_icontains_encoded_mysql(self):
+ self.assert_compile(
+ ~column("x").icontains(b"y"),
+ "lower(x) NOT LIKE concat('%%', lower(%s), '%%')",
+ checkparams={"x_1": b"y"},
+ dialect="mysql",
+ )
+
+ def test_not_icontains(self):
+ """
+ same as test_icontains but negate the statement
+ """
+ self.assert_compile(
+ ~column("x").icontains("y"),
+ "lower(x) NOT LIKE '%' || lower(:x_1) || '%'",
+ checkparams={"x_1": "y"},
+ )
+
+ def test_icontains_escape(self):
+ """
+ render same SQL statement as icontains but include the ESCAPE
+ keyword to establish that character as the escape character
+ """
+ self.assert_compile(
+ column("x").icontains("a%b_c", escape="\\"),
+ "lower(x) LIKE '%' || lower(:x_1) || '%' ESCAPE '\\'",
+ checkparams={"x_1": "a%b_c"},
+ )
+
+ def test_not_icontains_escape(self):
+ """
+ same as test_icontains_escape but negate the statement
+ """
+ self.assert_compile(
+ ~column("x").icontains("a%b_c", escape="\\"),
+ "lower(x) NOT LIKE '%' || lower(:x_1) || '%' ESCAPE '\\'",
+ checkparams={"x_1": "a%b_c"},
+ )
+
+ def test_icontains_autoescape(self):
+ """
+ apply the escape character to all occurrences of "%", "_" and
+ the escape character itself
+ """
+ self.assert_compile(
+ column("x").icontains("a%b_c/d", autoescape=True),
+ "lower(x) LIKE '%' || lower(:x_1) || '%' ESCAPE '/'",
+ checkparams={"x_1": "a/%b/_c//d"},
+ )
+
+ def test_icontains_autoescape_custom_escape(self):
+ """
+ set an escape character to all occurrences of "%", "_" and
+ the escape character itself
+ """
+ self.assert_compile(
+ column("x").icontains("foo%bar^bat", escape="^", autoescape=True),
+ "lower(x) LIKE '%' || lower(:x_1) || '%' ESCAPE '^'",
+ checkparams={"x_1": "foo^%bar^^bat"},
+ )
+
+ def test_not_icontains_autoescape(self):
+ """
+ same as test_icontains_autoescape but negate the statement
+ """
+ self.assert_compile(
+ ~column("x").icontains("a%b_c/d", autoescape=True),
+ "lower(x) NOT LIKE '%' || lower(:x_1) || '%' ESCAPE '/'",
+ checkparams={"x_1": "a/%b/_c//d"},
+ )
+
+ def test_icontains_literal(self):
+ self.assert_compile(
+ column("x").icontains(literal_column("y")),
+ "lower(x) LIKE '%' || lower(y) || '%'",
+ checkparams={},
+ )
+
+ def test_icontains_text(self):
+ self.assert_compile(
+ column("x").icontains(text("y")),
+ "lower(x) LIKE '%' || lower(y) || '%'",
+ checkparams={},
+ )
+
+ def test_icontains_concat(self):
+ self.assert_compile(
+ column("x").icontains("y"),
+ "lower(x) LIKE concat('%%', lower(%s), '%%')",
+ checkparams={"x_1": "y"},
+ dialect=mysql.dialect(),
+ )
+
+ def test_not_icontains_concat(self):
+ self.assert_compile(
+ ~column("x").icontains("y"),
+ "lower(x) NOT LIKE concat('%%', lower(%s), '%%')",
+ checkparams={"x_1": "y"},
+ dialect=mysql.dialect(),
+ )
+
+ def test_icontains_literal_concat(self):
+ self.assert_compile(
+ column("x").icontains(literal_column("y")),
+ "lower(x) LIKE concat('%%', lower(y), '%%')",
+ checkparams={},
+ dialect=mysql.dialect(),
+ )
+
+ def test_icontains_text_concat(self):
+ self.assert_compile(
+ column("x").icontains(text("y")),
+ "lower(x) LIKE concat('%%', lower(y), '%%')",
+ checkparams={},
+ dialect=mysql.dialect(),
+ )
+
def test_like(self):
self.assert_compile(
column("x").like("y"), "x LIKE :x_1", checkparams={"x_1": "y"}
dialect=mysql.dialect(),
)
+ def test_istartswith(self):
+ """
+ case insensitive startswith method
+ """
+ self.assert_compile(
+ column("x").istartswith("y"),
+ "lower(x) LIKE lower(:x_1) || '%'",
+ checkparams={"x_1": "y"},
+ )
+
+ def test_not_istartswith(self):
+ """
+ same as test_istartswith but negate the statement
+ """
+ self.assert_compile(
+ ~column("x").istartswith("y"),
+ "lower(x) NOT LIKE lower(:x_1) || '%'",
+ checkparams={"x_1": "y"},
+ )
+
+ def test_istartswith_escape(self):
+ """
+ render same SQL statement as istartswith but include the ESCAPE
+ keyword to establish that character as the escape character
+ """
+ self.assert_compile(
+ column("x").istartswith("a%b_c", escape="\\"),
+ "lower(x) LIKE lower(:x_1) || '%' ESCAPE '\\'",
+ checkparams={"x_1": "a%b_c"},
+ )
+
+ def test_not_istartswith_escape(self):
+ self.assert_compile(
+ ~column("x").istartswith("a%b_c", escape="\\"),
+ "lower(x) NOT LIKE lower(:x_1) || '%' ESCAPE '\\'",
+ checkparams={"x_1": "a%b_c"},
+ )
+
+ def test_istartswith_autoescape(self):
+ """
+ apply the escape character to all occurrences of "%", "_" and
+ the escape character itself
+ """
+ self.assert_compile(
+ column("x").istartswith("a%b_c/d", autoescape=True),
+ "lower(x) LIKE lower(:x_1) || '%' ESCAPE '/'",
+ checkparams={"x_1": "a/%b/_c//d"},
+ )
+
+ def test_not_istartswith_autoescape(self):
+ self.assert_compile(
+ ~column("x").istartswith("a%b_c/d", autoescape=True),
+ "lower(x) NOT LIKE lower(:x_1) || '%' ESCAPE '/'",
+ checkparams={"x_1": "a/%b/_c//d"},
+ )
+
+ def test_istartswith_autoescape_custom_escape(self):
+ """
+ set an escape character to all occurrences of "%", "_" and
+ the escape character itself
+ """
+ self.assert_compile(
+ column("x").istartswith("a%b_c/d^e", autoescape=True, escape="^"),
+ "lower(x) LIKE lower(:x_1) || '%' ESCAPE '^'",
+ checkparams={"x_1": "a^%b^_c/d^^e"},
+ )
+
+ def test_istartswith_encoded(self):
+ self.assert_compile(
+ column("x").istartswith(b"y"),
+ "lower(x) LIKE lower(:x_1) || '%'",
+ checkparams={"x_1": b"y"},
+ )
+
+ def test_not_istartswith_encoded(self):
+ self.assert_compile(
+ ~column("x").istartswith(b"y"),
+ "lower(x) NOT LIKE lower(:x_1) || '%'",
+ checkparams={"x_1": b"y"},
+ )
+
+ def test_istartswith_encoded_mysql(self):
+ self.assert_compile(
+ column("x").istartswith(b"y"),
+ "lower(x) LIKE concat(lower(%s), '%%')",
+ checkparams={"x_1": b"y"},
+ dialect="mysql",
+ )
+
+ def test_not_istartswith_encoded_mysql(self):
+ self.assert_compile(
+ ~column("x").istartswith(b"y"),
+ "lower(x) NOT LIKE concat(lower(%s), '%%')",
+ checkparams={"x_1": b"y"},
+ dialect="mysql",
+ )
+
+ def test_istartswith_literal(self):
+ self.assert_compile(
+ column("x").istartswith(literal_column("y")),
+ "lower(x) LIKE lower(y) || '%'",
+ checkparams={},
+ )
+
+ def test_istartswith_text(self):
+ self.assert_compile(
+ column("x").istartswith(text("y")),
+ "lower(x) LIKE lower(y) || '%'",
+ checkparams={},
+ )
+
+ def test_istartswith_concat(self):
+ self.assert_compile(
+ column("x").istartswith("y"),
+ "lower(x) LIKE concat(lower(%s), '%%')",
+ checkparams={"x_1": "y"},
+ dialect=mysql.dialect(),
+ )
+
+ def test_not_istartswith_concat(self):
+ self.assert_compile(
+ ~column("x").istartswith("y"),
+ "lower(x) NOT LIKE concat(lower(%s), '%%')",
+ checkparams={"x_1": "y"},
+ dialect=mysql.dialect(),
+ )
+
+ def test_istartswith_literal_mysql(self):
+ self.assert_compile(
+ column("x").istartswith(literal_column("y")),
+ "lower(x) LIKE concat(lower(y), '%%')",
+ checkparams={},
+ dialect=mysql.dialect(),
+ )
+
+ def test_istartswith_text_mysql(self):
+ self.assert_compile(
+ column("x").istartswith(text("y")),
+ "lower(x) LIKE concat(lower(y), '%%')",
+ checkparams={},
+ dialect=mysql.dialect(),
+ )
+
def test_endswith(self):
self.assert_compile(
column("x").endswith("y"),
dialect=mysql.dialect(),
)
+ def test_iendswith(self):
+ """
+ case insensitive endswith method
+ """
+ self.assert_compile(
+ column("x").iendswith("y"),
+ "lower(x) LIKE '%' || lower(:x_1)",
+ checkparams={"x_1": "y"},
+ )
+
+ def test_not_iendswith(self):
+ """
+ same as test_iendswith but negate the statement
+ """
+ self.assert_compile(
+ ~column("x").iendswith("y"),
+ "lower(x) NOT LIKE '%' || lower(:x_1)",
+ checkparams={"x_1": "y"},
+ )
+
+ def test_iendswith_encoded(self):
+ self.assert_compile(
+ column("x").iendswith(b"y"),
+ "lower(x) LIKE '%' || lower(:x_1)",
+ checkparams={"x_1": b"y"},
+ )
+
+ def test_not_iendswith_encoded(self):
+ self.assert_compile(
+ ~column("x").iendswith(b"y"),
+ "lower(x) NOT LIKE '%' || lower(:x_1)",
+ checkparams={"x_1": b"y"},
+ )
+
+ def test_iendswith_encoded_mysql(self):
+ self.assert_compile(
+ column("x").iendswith(b"y"),
+ "lower(x) LIKE concat('%%', lower(%s))",
+ checkparams={"x_1": b"y"},
+ dialect="mysql",
+ )
+
+ def test_iendswith_escape(self):
+ """
+ render same SQL statement as iendswith but include the ESCAPE
+ keyword to establish that character as the escape character
+ """
+ self.assert_compile(
+ column("x").iendswith("a%b_c", escape="\\"),
+ "lower(x) LIKE '%' || lower(:x_1) ESCAPE '\\'",
+ checkparams={"x_1": "a%b_c"},
+ )
+
+ def test_not_iendswith_escape(self):
+ self.assert_compile(
+ ~column("x").iendswith("a%b_c", escape="\\"),
+ "lower(x) NOT LIKE '%' || lower(:x_1) ESCAPE '\\'",
+ checkparams={"x_1": "a%b_c"},
+ )
+
+ def test_iendswith_autoescape(self):
+ """
+ apply the escape character to all occurrences of "%", "_" and
+ the escape character itself
+ """
+ self.assert_compile(
+ column("x").iendswith("a%b_c/d", autoescape=True),
+ "lower(x) LIKE '%' || lower(:x_1) ESCAPE '/'",
+ checkparams={"x_1": "a/%b/_c//d"},
+ )
+
+ def test_not_iendswith_autoescape(self):
+ self.assert_compile(
+ ~column("x").iendswith("a%b_c/d", autoescape=True),
+ "lower(x) NOT LIKE '%' || lower(:x_1) ESCAPE '/'",
+ checkparams={"x_1": "a/%b/_c//d"},
+ )
+
+ def test_iendswith_autoescape_custom_escape(self):
+ """
+ set an escape character to all occurrences of "%", "_" and
+ the escape character itself
+ """
+ self.assert_compile(
+ column("x").iendswith("a%b_c/d^e", autoescape=True, escape="^"),
+ "lower(x) LIKE '%' || lower(:x_1) ESCAPE '^'",
+ checkparams={"x_1": "a^%b^_c/d^^e"},
+ )
+
+ def test_iendswith_autoescape_warning(self):
+ with expect_warnings("The autoescape parameter is now a simple"):
+ self.assert_compile(
+ column("x").iendswith("a%b_c/d", autoescape="P"),
+ "lower(x) LIKE '%' || lower(:x_1) ESCAPE '/'",
+ checkparams={"x_1": "a/%b/_c//d"},
+ )
+
+ def test_iendswith_autoescape_nosqlexpr(self):
+ assert_raises_message(
+ TypeError,
+ "String value expected when autoescape=True",
+ column("x").iendswith,
+ literal_column("'a%b_c/d'"),
+ autoescape=True,
+ )
+
+ def test_iendswith_literal(self):
+ self.assert_compile(
+ column("x").iendswith(literal_column("y")),
+ "lower(x) LIKE '%' || lower(y)",
+ checkparams={},
+ )
+
+ def test_iendswith_text(self):
+ self.assert_compile(
+ column("x").iendswith(text("y")),
+ "lower(x) LIKE '%' || lower(y)", checkparams={}
+ )
+
+ def test_iendswith_mysql(self):
+ self.assert_compile(
+ column("x").iendswith("y"),
+ "lower(x) LIKE concat('%%', lower(%s))",
+ checkparams={"x_1": "y"},
+ dialect=mysql.dialect(),
+ )
+
+ def test_not_iendswith_mysql(self):
+ self.assert_compile(
+ ~column("x").iendswith("y"),
+ "lower(x) NOT LIKE concat('%%', lower(%s))",
+ checkparams={"x_1": "y"},
+ dialect=mysql.dialect(),
+ )
+
+ def test_iendswith_literal_mysql(self):
+ self.assert_compile(
+ column("x").iendswith(literal_column("y")),
+ "lower(x) LIKE concat('%%', lower(y))",
+ checkparams={},
+ dialect=mysql.dialect(),
+ )
+
+ def test_iendswith_text_mysql(self):
+ self.assert_compile(
+ column("x").iendswith(text("y")),
+ "lower(x) LIKE concat('%%', lower(y))",
+ checkparams={},
+ dialect=mysql.dialect(),
+ )
class CustomOpTest(fixtures.TestBase):
def test_is_comparison_legacy(self):