summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/sql/operators.py
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2017-10-24 18:08:05 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2017-10-24 23:11:13 -0400
commit654ca5463d2045d8dc74d7d790081f58554d796d (patch)
treef3202afc7f7345e9fbee321c80660a112d262cfb /lib/sqlalchemy/sql/operators.py
parentf34b180ca9059a74c3bf1db1b79e187c3f4b81c9 (diff)
downloadsqlalchemy-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.py328
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):