summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/sql/operators.py
diff options
context:
space:
mode:
authorMatias Martinez Rebori <matias.martinez@dinapi.gov.py>2022-09-07 12:36:06 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2022-09-08 12:15:23 -0400
commit93aaf16727f1750d74df1f37b86fcbc7f4a8b139 (patch)
treef9c7f122e7851ea7be00d52f4de5ef7575f0d4c2 /lib/sqlalchemy/sql/operators.py
parent06fe424256a80b91e9ff87b3bbe12ea93bc59453 (diff)
downloadsqlalchemy-93aaf16727f1750d74df1f37b86fcbc7f4a8b139.tar.gz
implement icontains, istartswith, iendswith operators
Added long-requested case-insensitive string operators :meth:`_sql.ColumnOperators.icontains`, :meth:`_sql.ColumnOperators.istartswith`, :meth:`_sql.ColumnOperators.iendswith`, which produce case-insensitive LIKE compositions (using ILIKE on PostgreSQL, and the LOWER() function on all other backends) to complement the existing LIKE composition operators :meth:`_sql.ColumnOperators.contains`, :meth:`_sql.ColumnOperators.startswith`, etc. Huge thanks to Matias Martinez Rebori for their meticulous and complete efforts in implementing these new methods. Fixes: #3482 Closes: #8496 Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/8496 Pull-request-sha: 7287e2c436959fac4fef022f359fcc73d1528211 Change-Id: I9fcdd603716218067547cc92a2b07bd02a2c366b
Diffstat (limited to 'lib/sqlalchemy/sql/operators.py')
-rw-r--r--lib/sqlalchemy/sql/operators.py288
1 files changed, 288 insertions, 0 deletions
diff --git a/lib/sqlalchemy/sql/operators.py b/lib/sqlalchemy/sql/operators.py
index 44d63b398..49cf05f8d 100644
--- a/lib/sqlalchemy/sql/operators.py
+++ b/lib/sqlalchemy/sql/operators.py
@@ -966,6 +966,88 @@ class ColumnOperators(Operators):
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,
@@ -1052,6 +1134,88 @@ class ColumnOperators(Operators):
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.
@@ -1132,6 +1296,82 @@ class ColumnOperators(Operators):
"""
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.
@@ -1689,6 +1929,22 @@ 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
) -> Any:
@@ -1709,6 +1965,22 @@ 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
) -> Any:
@@ -1729,6 +2001,22 @@ 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:
return a.match(b, **kw)