diff options
author | Matias Martinez Rebori <matias.martinez@dinapi.gov.py> | 2022-09-07 12:36:06 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2022-09-08 12:15:23 -0400 |
commit | 93aaf16727f1750d74df1f37b86fcbc7f4a8b139 (patch) | |
tree | f9c7f122e7851ea7be00d52f4de5ef7575f0d4c2 /lib/sqlalchemy/sql/operators.py | |
parent | 06fe424256a80b91e9ff87b3bbe12ea93bc59453 (diff) | |
download | sqlalchemy-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.py | 288 |
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) |