diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2017-10-24 18:08:05 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2017-10-24 23:11:13 -0400 |
commit | 654ca5463d2045d8dc74d7d790081f58554d796d (patch) | |
tree | f3202afc7f7345e9fbee321c80660a112d262cfb /lib/sqlalchemy/sql/operators.py | |
parent | f34b180ca9059a74c3bf1db1b79e187c3f4b81c9 (diff) | |
download | sqlalchemy-654ca5463d2045d8dc74d7d790081f58554d796d.tar.gz |
Rework autoescape to be a simple boolean; escape the escape character
Reworked the new "autoescape" feature introduced in
:ref:`change_2694` in 1.2.0b2 to be fully automatic; the escape
character now defaults to a forwards slash ``"/"`` and
is applied to percent, underscore, as well as the escape
character itself, for fully automatic escaping. The
character can also be changed using the "escape" parameter.
Change-Id: I74894a2576983c0f6eb89480c9e5727f49fa9c25
Fixes: #2694
Diffstat (limited to 'lib/sqlalchemy/sql/operators.py')
-rw-r--r-- | lib/sqlalchemy/sql/operators.py | 328 |
1 files changed, 247 insertions, 81 deletions
diff --git a/lib/sqlalchemy/sql/operators.py b/lib/sqlalchemy/sql/operators.py index ef6f78929..0e8dec2a0 100644 --- a/lib/sqlalchemy/sql/operators.py +++ b/lib/sqlalchemy/sql/operators.py @@ -597,101 +597,267 @@ class ColumnOperators(Operators): return self.operate(isnot, other) def startswith(self, other, **kwargs): - """Implement the ``startwith`` operator. + r"""Implement the ``startswith`` operator. - In a column context, produces the clause ``LIKE '<other>%'`` + Produces a LIKE expression that tests against a match for the start + of a string value:: + + column LIKE <other> || '%' E.g.:: - select([sometable]).where(sometable.c.column.startswith("foobar")) + stmt = select([sometable]).\ + where(sometable.c.column.startswith("foobar")) - :param other: expression to be compared, with SQL wildcard - matching (``%`` and ``_``) enabled, e.g.:: + 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.startswith.autoescape` flag + may be set to ``True`` to apply escaping to occurences of these + characters within the string value so that they match as themselves + and not as wildcard characters. Alternatively, the + :paramref:`.ColumnOperators.startswith.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. - somecolumn.startswith("foo%bar") + :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.startswith.autoescape` flag is + set to True. - :param escape: optional escape character, renders the ``ESCAPE`` - keyword allowing that escape character to be used to manually - disable SQL wildcard matching (``%`` and ``_``) in the expression, - e.g.:: + :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. - somecolumn.startswith("foo/%bar", escape="/") + An expression such as:: - :param autoescape: optional escape character, renders the ``ESCAPE`` - keyword and uses that escape character to auto escape the - expression, disabling all SQL wildcard matching (``%`` and ``_``), - e.g.:: + somecolumn.startswith("foo%bar", autoescape=True) - somecolumn.startswith("foo%bar", autoescape="/") + Will render as:: + + somecolumn LIKE :param || '%' ESCAPE '/' + + With the value of :param as ``"foo/%bar"``. .. versionadded:: 1.2 + .. versionchanged:: 1.2.0b4 The + :paramref:`.ColumnOperators.startswith.autoescape` parameter is + now a simple boolean rather than a character; the escape + character itself is also escaped, and defaults to a forwards + slash, which itself can be customized using the + :paramref:`.ColumnOperators.startswith.escape` parameter. + + :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.startswith("foo/%bar", escape="^") + + Will render as:: + + somecolumn LIKE :param || '%' ESCAPE '^' + + The parameter may also be combined with + :paramref:`.ColumnOperators.startswith.autoescape`:: + + somecolumn.startswith("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` + + :meth:`.ColumnOperators.contains` + + :meth:`.ColumnOperators.like` + """ return self.operate(startswith_op, other, **kwargs) def endswith(self, other, **kwargs): - """Implement the 'endswith' operator. + r"""Implement the 'endswith' operator. + + Produces a LIKE expression that tests against a match for the end + of a string value:: - In a column context, produces the clause ``LIKE '%<other>'`` + column LIKE '%' || <other> E.g.:: - select([sometable]).where(sometable.c.column.endswith("foobar")) + stmt = select([sometable]).\ + where(sometable.c.column.endswith("foobar")) - :param other: expression to be compared, with SQL wildcard - matching (``%`` and ``_``) enabled, e.g.:: + 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.endswith.autoescape` flag + may be set to ``True`` to apply escaping to occurences of these + characters within the string value so that they match as themselves + and not as wildcard characters. Alternatively, the + :paramref:`.ColumnOperators.endswith.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. - somecolumn.endswith("foo%bar") + :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.endswith.autoescape` flag is + set to True. - :param escape: optional escape character, renders the ``ESCAPE`` - keyword allowing that escape character to be used to manually - disable SQL wildcard matching (``%`` and ``_``) in the expression, - e.g.:: + :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.endswith("foo/%bar", escape="/") + somecolumn.endswith("foo%bar", autoescape=True) - :param autoescape: optional escape character, renders the ``ESCAPE`` - keyword and uses that escape character to auto escape the - expression, disabling all SQL wildcard matching (``%`` and ``_``), - e.g.:: + Will render as:: - somecolumn.endswith("foo%bar", autoescape="/") + somecolumn LIKE '%' || :param ESCAPE '/' + + With the value of :param as ``"foo/%bar"``. .. versionadded:: 1.2 + .. versionchanged:: 1.2.0b4 The + :paramref:`.ColumnOperators.endswith.autoescape` parameter is + now a simple boolean rather than a character; the escape + character itself is also escaped, and defaults to a forwards + slash, which itself can be customized using the + :paramref:`.ColumnOperators.endswith.escape` parameter. + + :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.endswith("foo/%bar", escape="^") + + Will render as:: + + somecolumn LIKE '%' || :param ESCAPE '^' + + The parameter may also be combined with + :paramref:`.ColumnOperators.endswith.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.startswith` + + :meth:`.ColumnOperators.contains` + + :meth:`.ColumnOperators.like` + """ return self.operate(endswith_op, other, **kwargs) def contains(self, other, **kwargs): - """Implement the 'contains' operator. + r"""Implement the 'contains' operator. + + Produces a LIKE expression that tests against a match for the middle + of a string value:: - In a column context, produces the clause ``LIKE '%<other>%'`` + column LIKE '%' || <other> || '%' E.g.:: - select([sometable]).where(sometable.c.column.contains("foobar")) + stmt = select([sometable]).\ + where(sometable.c.column.contains("foobar")) - :param other: expression to compare, with SQL wildcard - matching (``%`` and ``_``) enabled, e.g.:: + 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.contains.autoescape` flag + may be set to ``True`` to apply escaping to occurences of these + characters within the string value so that they match as themselves + and not as wildcard characters. Alternatively, the + :paramref:`.ColumnOperators.contains.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. - somecolumn.contains("foo%bar") + :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.contains.autoescape` flag is + set to True. - :param escape: optional escape character, renders the ``ESCAPE`` - keyword allowing that escape character to be used to manually - disable SQL wildcard matching (``%`` and ``_``) in the expression, - e.g.:: + :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.contains("foo/%bar", escape="/") + somecolumn.contains("foo%bar", autoescape=True) - :param autoescape: optional escape character, renders the ``ESCAPE`` - keyword and uses that escape character to auto escape the - expression, disabling all SQL wildcard matching (``%`` and ``_``), - e.g.:: + Will render as:: - somecolumn.contains("foo%bar", autoescape="/") + somecolumn LIKE '%' || :param || '%' ESCAPE '/' + + With the value of :param as ``"foo/%bar"``. .. versionadded:: 1.2 + .. versionchanged:: 1.2.0b4 The + :paramref:`.ColumnOperators.contains.autoescape` parameter is + now a simple boolean rather than a character; the escape + character itself is also escaped, and defaults to a forwards + slash, which itself can be customized using the + :paramref:`.ColumnOperators.contains.escape` parameter. + + :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.contains("foo/%bar", escape="^") + + Will render as:: + + somecolumn LIKE '%' || :param || '%' ESCAPE '^' + + The parameter may also be combined with + :paramref:`.ColumnOperators.contains.autoescape`:: + + somecolumn.contains("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` + + :meth:`.ColumnOperators.endswith` + + :meth:`.ColumnOperators.like` + + """ return self.operate(contains_op, other, **kwargs) @@ -909,10 +1075,6 @@ class ColumnOperators(Operators): return self.reverse_operate(truediv, other) -def _escaped(value, escape): - return value.replace('%', escape + '%').replace('_', escape + '_') - - def from_(): raise NotImplementedError() @@ -1001,46 +1163,50 @@ def all_op(a): return a.all_() -def startswith_op(a, b, escape=None, autoescape=None): +def _escaped_like_impl(fn, other, escape, autoescape): if autoescape: - return a.startswith(_escaped(b, autoescape), escape=autoescape) - else: - return a.startswith(b, escape=escape) + if autoescape is not True: + util.warn( + "The autoescape parameter is now a simple boolean True/False") + if escape is None: + escape = '/' + if not isinstance(other, util.compat.string_types): + raise TypeError("String value expected when autoescape=True") -def notstartswith_op(a, b, escape=None, autoescape=None): - if autoescape: - return ~a.startswith(_escaped(b, autoescape), escape=autoescape) - else: - return ~a.startswith(b, escape=escape) + if escape not in ('%', '_'): + other = other.replace(escape, escape + escape) + other = ( + other.replace('%', escape + '%'). + replace('_', escape + '_') + ) -def endswith_op(a, b, escape=None, autoescape=None): - if autoescape: - return a.endswith(_escaped(b, autoescape), escape=autoescape) - else: - return a.endswith(b, escape=escape) + return fn(other, escape=escape) -def notendswith_op(a, b, escape=None, autoescape=None): - if autoescape: - return ~a.endswith(_escaped(b, autoescape), escape=autoescape) - else: - return ~a.endswith(b, escape=escape) +def startswith_op(a, b, escape=None, autoescape=False): + return _escaped_like_impl(a.startswith, b, escape, autoescape) -def contains_op(a, b, escape=None, autoescape=None): - if autoescape: - return a.contains(_escaped(b, autoescape), escape=autoescape) - else: - return a.contains(b, escape=escape) +def notstartswith_op(a, b, escape=None, autoescape=False): + return ~_escaped_like_impl(a.startswith, b, escape, autoescape) -def notcontains_op(a, b, escape=None, autoescape=None): - if autoescape: - return ~a.contains(_escaped(b, autoescape), escape=autoescape) - else: - return ~a.contains(b, escape=escape) +def endswith_op(a, b, escape=None, autoescape=False): + return _escaped_like_impl(a.endswith, b, escape, autoescape) + + +def notendswith_op(a, b, escape=None, autoescape=False): + return ~_escaped_like_impl(a.endswith, b, escape, autoescape) + + +def contains_op(a, b, escape=None, autoescape=False): + return _escaped_like_impl(a.contains, b, escape, autoescape) + + +def notcontains_op(a, b, escape=None, autoescape=False): + return ~_escaped_like_impl(a.contains, b, escape, autoescape) def match_op(a, b, **kw): |