--- /dev/null
+.. change::
+ :tags: change, sql
+ :tickets: 5435
+
+ Several operators are renamed to achieve more consistent naming across
+ SQLAlchemy.
+
+ The operator changes are:
+
+ * `isfalse` is now `is_false`
+ * `isnot_distinct_from` is now `is_not_distinct_from`
+ * `istrue` is now `is_true`
+ * `notbetween` is now `not_between`
+ * `notcontains` is now `not_contains`
+ * `notendswith` is now `not_endswith`
+ * `notilike` is now `not_ilike`
+ * `notlike` is now `not_like`
+ * `notmatch` is now `not_match`
+ * `notstartswith` is now `not_startswith`
+ * `nullsfirst` is now `nulls_first`
+ * `nullslast` is now `nulls_last`
+
+ Because these are core operators, the internal migration strategy for this
+ change is to support legacy terms for an extended period of time -- if not
+ indefinitely -- but update all documentation, tutorials, and internal usage
+ to the new terms. The new terms are used to define the functions, and
+ the legacy terms have been deprecated into aliases of the new terms.
impl = VARCHAR
def coerce_compared_value(self, op, value):
- if op in (operators.like_op, operators.notlike_op):
+ if op in (operators.like_op, operators.not_like_op):
return String()
else:
return self
.. autofunction:: label
-.. autofunction:: nullsfirst
+.. autofunction:: nulls_first
-.. autofunction:: nullslast
+.. autofunction:: nulls_last
.. autofunction:: over
from .sql import modifier # noqa
from .sql import not_ # noqa
from .sql import null # noqa
-from .sql import nullsfirst # noqa
-from .sql import nullslast # noqa
+from .sql import nullsfirst # noqa; deprecated 1.4; see #5435
+from .sql import nullslast # noqa; deprecated 1.4; see #5435
+from .sql import nulls_first # noqa
+from .sql import nulls_last # noqa
from .sql import or_ # noqa
from .sql import outerjoin # noqa
from .sql import outparam # noqa
# def visit_contains_op_binary(self, binary, operator, **kw):
# cant use CONTAINING b.c. it's case insensitive.
- # def visit_notcontains_op_binary(self, binary, operator, **kw):
+ # def visit_not_contains_op_binary(self, binary, operator, **kw):
# cant use NOT CONTAINING b.c. it's case insensitive.
def visit_now_func(self, fn, **kw):
binary.right._compiler_dispatch(self, **kw),
)
- def visit_notstartswith_op_binary(self, binary, operator, **kw):
+ def visit_not_startswith_op_binary(self, binary, operator, **kw):
return "%s NOT STARTING WITH %s" % (
binary.left._compiler_dispatch(self, **kw),
binary.right._compiler_dispatch(self, **kw),
self.process(binary.right),
)
- def visit_isnot_distinct_from_binary(self, binary, operator, **kw):
+ def visit_is_not_distinct_from_binary(self, binary, operator, **kw):
return "EXISTS (SELECT %s INTERSECT SELECT %s)" % (
self.process(binary.left),
self.process(binary.right),
self.process(binary.right, **kw),
)
- def visit_notin_op_binary(self, binary, operator, **kw):
+ def visit_not_in_op_binary(self, binary, operator, **kw):
kw["literal_execute"] = True
return "%s NOT IN %s" % (
self.process(binary.left, **kw),
self.process(binary.right),
)
- def visit_isnot_distinct_from_binary(self, binary, operator, **kw):
+ def visit_is_not_distinct_from_binary(self, binary, operator, **kw):
return "%s <=> %s" % (
self.process(binary.left),
self.process(binary.right),
self.process(binary.right),
)
- def visit_isnot_distinct_from_binary(self, binary, operator, **kw):
+ def visit_is_not_distinct_from_binary(self, binary, operator, **kw):
return "DECODE(%s, %s, 0, 1) = 0" % (
self.process(binary.left),
self.process(binary.right),
else ""
)
- def visit_notilike_op_binary(self, binary, operator, **kw):
+ def visit_not_ilike_op_binary(self, binary, operator, **kw):
escape = binary.modifiers.get("escape", None)
return "%s NOT ILIKE %s" % (
self.process(binary.left, **kw),
if col_flags & 0x01:
col_sorting += ("desc",)
if not (col_flags & 0x02):
- col_sorting += ("nullslast",)
+ col_sorting += ("nulls_last",)
else:
if col_flags & 0x02:
- col_sorting += ("nullsfirst",)
+ col_sorting += ("nulls_first",)
if col_sorting:
sorting[col_idx] = col_sorting
if sorting:
self.process(binary.right),
)
- def visit_isnot_distinct_from_binary(self, binary, operator, **kw):
+ def visit_is_not_distinct_from_binary(self, binary, operator, **kw):
return "%s IS %s" % (
self.process(binary.left),
self.process(binary.right),
* ``column_sorting`` -
optional dict mapping column names to tuple of sort keywords,
- which may include ``asc``, ``desc``, ``nullsfirst``, ``nullslast``.
+ which may include ``asc``, ``desc``, ``nulls_first``, ``nulls_last``.
.. versionadded:: 1.3.5
_index_sort_exprs = [
("asc", operators.asc_op),
("desc", operators.desc_op),
- ("nullsfirst", operators.nullsfirst_op),
- ("nullslast", operators.nullslast_op),
+ ("nulls_first", operators.nulls_first_op),
+ ("nulls_last", operators.nulls_last_op),
]
def _reflect_indexes(
getattr(operators, op)
for op in (
"like_op",
- "notlike_op",
+ "not_like_op",
"ilike_op",
- "notilike_op",
+ "not_ilike_op",
"startswith_op",
"between_op",
"endswith_op",
from .expression import modifier # noqa
from .expression import not_ # noqa
from .expression import null # noqa
-from .expression import nullsfirst # noqa
-from .expression import nullslast # noqa
+from .expression import nullsfirst # noqa; deprecated 1.4; see #5435
+from .expression import nullslast # noqa; deprecated 1.4; see #5435
+from .expression import nulls_first # noqa
+from .expression import nulls_last # noqa
from .expression import or_ # noqa
from .expression import outerjoin # noqa
from .expression import outparam # noqa
operators.ge: " >= ",
operators.eq: " = ",
operators.is_distinct_from: " IS DISTINCT FROM ",
- operators.isnot_distinct_from: " IS NOT DISTINCT FROM ",
+ operators.is_not_distinct_from: " IS NOT DISTINCT FROM ",
operators.concat_op: " || ",
operators.match_op: " MATCH ",
- operators.notmatch_op: " NOT MATCH ",
+ operators.not_match_op: " NOT MATCH ",
operators.in_op: " IN ",
operators.not_in_op: " NOT IN ",
operators.comma_op: ", ",
# modifiers
operators.desc_op: " DESC",
operators.asc_op: " ASC",
- operators.nullsfirst_op: " NULLS FIRST",
- operators.nullslast_op: " NULLS LAST",
+ operators.nulls_first_op: " NULLS FIRST",
+ operators.nulls_last_op: " NULLS LAST",
}
FUNCTIONS = {
"Unary expression has no operator or modifier"
)
- def visit_istrue_unary_operator(self, element, operator, **kw):
+ def visit_is_true_unary_operator(self, element, operator, **kw):
if (
element._is_implicitly_boolean
or self.dialect.supports_native_boolean
else:
return "%s = 1" % self.process(element.element, **kw)
- def visit_isfalse_unary_operator(self, element, operator, **kw):
+ def visit_is_false_unary_operator(self, element, operator, **kw):
if (
element._is_implicitly_boolean
or self.dialect.supports_native_boolean
else:
return "%s = 0" % self.process(element.element, **kw)
- def visit_notmatch_op_binary(self, binary, operator, **kw):
+ def visit_not_match_op_binary(self, binary, operator, **kw):
return "NOT %s" % self.visit_binary(
binary, override_operator=operators.match_op
)
binary.right = percent.__add__(binary.right).__add__(percent)
return self.visit_like_op_binary(binary, operator, **kw)
- def visit_notcontains_op_binary(self, binary, operator, **kw):
+ def visit_not_contains_op_binary(self, binary, operator, **kw):
binary = binary._clone()
percent = self._like_percent_literal
binary.right = percent.__add__(binary.right).__add__(percent)
- return self.visit_notlike_op_binary(binary, operator, **kw)
+ return self.visit_not_like_op_binary(binary, operator, **kw)
def visit_startswith_op_binary(self, binary, operator, **kw):
binary = binary._clone()
binary.right = percent.__radd__(binary.right)
return self.visit_like_op_binary(binary, operator, **kw)
- def visit_notstartswith_op_binary(self, binary, operator, **kw):
+ def visit_not_startswith_op_binary(self, binary, operator, **kw):
binary = binary._clone()
percent = self._like_percent_literal
binary.right = percent.__radd__(binary.right)
- return self.visit_notlike_op_binary(binary, operator, **kw)
+ return self.visit_not_like_op_binary(binary, operator, **kw)
def visit_endswith_op_binary(self, binary, operator, **kw):
binary = binary._clone()
binary.right = percent.__add__(binary.right)
return self.visit_like_op_binary(binary, operator, **kw)
- def visit_notendswith_op_binary(self, binary, operator, **kw):
+ def visit_not_endswith_op_binary(self, binary, operator, **kw):
binary = binary._clone()
percent = self._like_percent_literal
binary.right = percent.__add__(binary.right)
- return self.visit_notlike_op_binary(binary, operator, **kw)
+ 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)
else ""
)
- def visit_notlike_op_binary(self, binary, operator, **kw):
+ def visit_not_like_op_binary(self, binary, operator, **kw):
escape = binary.modifiers.get("escape", None)
return "%s NOT LIKE %s" % (
binary.left._compiler_dispatch(self, **kw),
else ""
)
- def visit_notilike_op_binary(self, binary, operator, **kw):
+ def visit_not_ilike_op_binary(self, binary, operator, **kw):
escape = binary.modifiers.get("escape", None)
return "lower(%s) NOT LIKE lower(%s)" % (
binary.left._compiler_dispatch(self, **kw),
binary, " BETWEEN SYMMETRIC " if symmetric else " BETWEEN ", **kw
)
- def visit_notbetween_op_binary(self, binary, operator, **kw):
+ def visit_not_between_op_binary(self, binary, operator, **kw):
symmetric = binary.modifiers.get("symmetric", False)
return self._generate_generic_binary(
binary,
negate=negate,
modifiers=kwargs,
)
- elif op in (operators.is_distinct_from, operators.isnot_distinct_from):
+ elif op in (
+ operators.is_distinct_from,
+ operators.is_not_distinct_from,
+ ):
return BinaryExpression(
expr,
coercions.expect(roles.ConstExprRole, obj),
else:
raise exc.ArgumentError(
"Only '=', '!=', 'is_()', 'is_not()', "
- "'is_distinct_from()', 'isnot_distinct_from()' "
+ "'is_distinct_from()', 'is_not_distinct_from()' "
"operators can be used with None/True/False"
)
else:
operator=operators.match_op,
),
result_type=type_api.MATCHTYPE,
- negate=operators.notmatch_op
+ negate=operators.not_match_op
if op is operators.match_op
else operators.match_op,
**kw
group_contents=False,
),
op,
- negate=operators.notbetween_op
+ negate=operators.not_between_op
if op is operators.between_op
else operators.between_op,
modifiers=kw,
"gt": (_boolean_compare, operators.le),
"ge": (_boolean_compare, operators.lt),
"eq": (_boolean_compare, operators.ne),
- "is_distinct_from": (_boolean_compare, operators.isnot_distinct_from),
- "isnot_distinct_from": (_boolean_compare, operators.is_distinct_from),
- "like_op": (_boolean_compare, operators.notlike_op),
- "ilike_op": (_boolean_compare, operators.notilike_op),
- "notlike_op": (_boolean_compare, operators.like_op),
- "notilike_op": (_boolean_compare, operators.ilike_op),
- "contains_op": (_boolean_compare, operators.notcontains_op),
- "startswith_op": (_boolean_compare, operators.notstartswith_op),
- "endswith_op": (_boolean_compare, operators.notendswith_op),
+ "is_distinct_from": (_boolean_compare, operators.is_not_distinct_from),
+ "is_not_distinct_from": (_boolean_compare, operators.is_distinct_from),
+ "like_op": (_boolean_compare, operators.not_like_op),
+ "ilike_op": (_boolean_compare, operators.not_ilike_op),
+ "not_like_op": (_boolean_compare, operators.like_op),
+ "not_ilike_op": (_boolean_compare, operators.ilike_op),
+ "contains_op": (_boolean_compare, operators.not_contains_op),
+ "startswith_op": (_boolean_compare, operators.not_startswith_op),
+ "endswith_op": (_boolean_compare, operators.not_endswith_op),
"desc_op": (_scalar, UnaryExpression._create_desc),
"asc_op": (_scalar, UnaryExpression._create_asc),
- "nullsfirst_op": (_scalar, UnaryExpression._create_nullsfirst),
- "nullslast_op": (_scalar, UnaryExpression._create_nullslast),
+ "nulls_first_op": (_scalar, UnaryExpression._create_nulls_first),
+ "nulls_last_op": (_scalar, UnaryExpression._create_nulls_last),
"in_op": (_in_impl, operators.not_in_op),
"not_in_op": (_in_impl, operators.in_op),
"is_": (_boolean_compare, operators.is_),
"is_not": (_boolean_compare, operators.is_not),
"collate": (_collate_impl,),
"match_op": (_match_impl,),
- "notmatch_op": (_match_impl,),
+ "not_match_op": (_match_impl,),
"distinct_op": (_distinct_impl,),
"between_op": (_between_impl,),
- "notbetween_op": (_between_impl,),
+ "not_between_op": (_between_impl,),
"neg": (_neg_impl,),
"getitem": (_getitem_impl,),
"lshift": (_unsupported_impl,),
against in (operators.and_, operators.or_, operators._asbool)
and self.type._type_affinity is type_api.BOOLEANTYPE._type_affinity
):
- return AsBoolean(self, operators.istrue, operators.isfalse)
+ return AsBoolean(self, operators.is_true, operators.is_false)
elif against in (operators.any_op, operators.all_op):
return Grouping(self)
else:
def _negate(self):
if self.type._type_affinity is type_api.BOOLEANTYPE._type_affinity:
- return AsBoolean(self, operators.isfalse, operators.istrue)
+ return AsBoolean(self, operators.is_false, operators.is_true)
else:
return super(ColumnElement, self)._negate()
:class:`.UnaryExpression` is the basis for several unary operators
including those used by :func:`.desc`, :func:`.asc`, :func:`.distinct`,
- :func:`.nullsfirst` and :func:`.nullslast`.
+ :func:`.nulls_first` and :func:`.nulls_last`.
"""
self.wraps_column_expression = wraps_column_expression
@classmethod
- def _create_nullsfirst(cls, column):
+ def _create_nulls_first(cls, column):
"""Produce the ``NULLS FIRST`` modifier for an ``ORDER BY`` expression.
- :func:`.nullsfirst` is intended to modify the expression produced
+ :func:`.nulls_first` is intended to modify the expression produced
by :func:`.asc` or :func:`.desc`, and indicates how NULL values
should be handled when they are encountered during ordering::
- from sqlalchemy import desc, nullsfirst
+ from sqlalchemy import desc, nulls_first
stmt = select(users_table).order_by(
- nullsfirst(desc(users_table.c.name)))
+ nulls_first(desc(users_table.c.name)))
The SQL expression from the above would resemble::
SELECT id, name FROM user ORDER BY name DESC NULLS FIRST
- Like :func:`.asc` and :func:`.desc`, :func:`.nullsfirst` is typically
+ Like :func:`.asc` and :func:`.desc`, :func:`.nulls_first` is typically
invoked from the column expression itself using
- :meth:`_expression.ColumnElement.nullsfirst`,
+ :meth:`_expression.ColumnElement.nulls_first`,
rather than as its standalone
function version, as in::
stmt = select(users_table).order_by(
- users_table.c.name.desc().nullsfirst())
+ users_table.c.name.desc().nulls_first())
+
+ .. versionchanged:: 1.4 :func:`.nulls_first` is renamed from
+ :func:`.nullsfirst` in previous releases.
+ The previous name remains available for backwards compatibility.
.. seealso::
:func:`.desc`
- :func:`.nullslast`
+ :func:`.nulls_last`
:meth:`_expression.Select.order_by`
"""
return UnaryExpression(
coercions.expect(roles.ByOfRole, column),
- modifier=operators.nullsfirst_op,
+ modifier=operators.nulls_first_op,
wraps_column_expression=False,
)
@classmethod
- def _create_nullslast(cls, column):
+ def _create_nulls_last(cls, column):
"""Produce the ``NULLS LAST`` modifier for an ``ORDER BY`` expression.
- :func:`.nullslast` is intended to modify the expression produced
+ :func:`.nulls_last` is intended to modify the expression produced
by :func:`.asc` or :func:`.desc`, and indicates how NULL values
should be handled when they are encountered during ordering::
- from sqlalchemy import desc, nullslast
+ from sqlalchemy import desc, nulls_last
stmt = select(users_table).order_by(
- nullslast(desc(users_table.c.name)))
+ nulls_last(desc(users_table.c.name)))
The SQL expression from the above would resemble::
SELECT id, name FROM user ORDER BY name DESC NULLS LAST
- Like :func:`.asc` and :func:`.desc`, :func:`.nullslast` is typically
+ Like :func:`.asc` and :func:`.desc`, :func:`.nulls_last` is typically
invoked from the column expression itself using
- :meth:`_expression.ColumnElement.nullslast`,
+ :meth:`_expression.ColumnElement.nulls_last`,
rather than as its standalone
function version, as in::
stmt = select(users_table).order_by(
- users_table.c.name.desc().nullslast())
+ users_table.c.name.desc().nulls_last())
+
+ .. versionchanged:: 1.4 :func:`.nulls_last` is renamed from
+ :func:`.nullslast` in previous releases.
+ The previous name remains available for backwards compatibility.
.. seealso::
:func:`.desc`
- :func:`.nullsfirst`
+ :func:`.nulls_first`
:meth:`_expression.Select.order_by`
"""
return UnaryExpression(
coercions.expect(roles.ByOfRole, column),
- modifier=operators.nullslast_op,
+ modifier=operators.nulls_last_op,
wraps_column_expression=False,
)
:func:`.asc`
- :func:`.nullsfirst`
+ :func:`.nulls_first`
- :func:`.nullslast`
+ :func:`.nulls_last`
:meth:`_expression.Select.order_by`
:func:`.desc`
- :func:`.nullsfirst`
+ :func:`.nulls_first`
- :func:`.nullslast`
+ :func:`.nulls_last`
:meth:`_expression.Select.order_by`
"literal_column",
"not_",
"null",
- "nullsfirst",
- "nullslast",
+ "nulls_first",
+ "nulls_last",
"or_",
"outparam",
"outerjoin",
CompoundSelect._create_union_all, ".sql.expression.union_all"
)
exists = public_factory(Exists, ".sql.expression.exists")
-nullsfirst = public_factory(
- UnaryExpression._create_nullsfirst, ".sql.expression.nullsfirst"
+nulls_first = public_factory(
+ UnaryExpression._create_nulls_first, ".sql.expression.nulls_first"
)
-nullslast = public_factory(
- UnaryExpression._create_nullslast, ".sql.expression.nullslast"
+nullsfirst = nulls_first # deprecated 1.4; see #5435
+nulls_last = public_factory(
+ UnaryExpression._create_nulls_last, ".sql.expression.nulls_last"
)
+nullslast = nulls_last # deprecated 1.4; see #5435
asc = public_factory(UnaryExpression._create_asc, ".sql.expression.asc")
desc = public_factory(UnaryExpression._create_desc, ".sql.expression.desc")
distinct = public_factory(
"""
return self.operate(is_distinct_from, other)
- def isnot_distinct_from(self, other):
+ def is_not_distinct_from(self, other):
"""Implement the ``IS NOT DISTINCT FROM`` operator.
Renders "a IS NOT DISTINCT FROM b" on most platforms;
on some such as SQLite may render "a IS b".
+ .. versionchanged:: 1.4 The ``is_not_distinct_from()`` operator is
+ renamed from ``isnot_distinct_from()`` in previous releases.
+ The previous name remains available for backwards compatibility.
+
.. versionadded:: 1.1
"""
- return self.operate(isnot_distinct_from, other)
+ return self.operate(is_not_distinct_from, other)
+
+ # deprecated 1.4; see #5435
+ isnot_distinct_from = is_not_distinct_from
def __gt__(self, other):
"""Implement the ``>`` operator.
# deprecated 1.4; see #5429
notin_ = not_in
- def notlike(self, other, escape=None):
+ def not_like(self, other, escape=None):
"""implement the ``NOT LIKE`` operator.
This is equivalent to using negation with
:meth:`.ColumnOperators.like`, i.e. ``~x.like(y)``.
+ .. versionchanged:: 1.4 The ``not_like()`` operator is renamed from
+ ``notlike()`` in previous releases. The previous name remains
+ available for backwards compatibility.
+
.. seealso::
:meth:`.ColumnOperators.like`
"""
return self.operate(notlike_op, other, escape=escape)
- def notilike(self, other, escape=None):
+ # deprecated 1.4; see #5435
+ notlike = not_like
+
+ def not_ilike(self, other, escape=None):
"""implement the ``NOT ILIKE`` operator.
This is equivalent to using negation with
:meth:`.ColumnOperators.ilike`, i.e. ``~x.ilike(y)``.
+ .. versionchanged:: 1.4 The ``not_ilike()`` operator is renamed from
+ ``notilike()`` in previous releases. The previous name remains
+ available for backwards compatibility.
+
.. seealso::
:meth:`.ColumnOperators.ilike`
"""
return self.operate(notilike_op, other, escape=escape)
+ # deprecated 1.4; see #5435
+ notilike = not_ilike
+
def is_(self, other):
"""Implement the ``IS`` operator.
``isnot()`` in previous releases. The previous name remains
available for backwards compatibility.
-
.. seealso:: :meth:`.ColumnOperators.is_`
"""
parent object."""
return self.operate(asc_op)
- def nullsfirst(self):
- """Produce a :func:`_expression.nullsfirst` clause against the
- parent object."""
- return self.operate(nullsfirst_op)
+ def nulls_first(self):
+ """Produce a :func:`_expression.nulls_first` clause against the
+ parent object.
- def nullslast(self):
- """Produce a :func:`_expression.nullslast` clause against the
- parent object."""
- return self.operate(nullslast_op)
+ .. versionchanged:: 1.4 The ``nulls_first()`` operator is
+ renamed from ``nullsfirst()`` in previous releases.
+ The previous name remains available for backwards compatibility.
+ """
+ return self.operate(nulls_first_op)
+
+ # deprecated 1.4; see #5435
+ nullsfirst = nulls_first
+
+ def nulls_last(self):
+ """Produce a :func:`_expression.nulls_last` clause against the
+ parent object.
+
+ .. versionchanged:: 1.4 The ``nulls_last()`` operator is
+ renamed from ``nullslast()`` in previous releases.
+ The previous name remains available for backwards compatibility.
+ """
+ return self.operate(nulls_last_op)
+
+ # deprecated 1.4; see #5429
+ nullslast = nulls_last
def collate(self, collation):
"""Produce a :func:`_expression.collate` clause against
raise NotImplementedError()
-def istrue(a):
+def is_true(a):
raise NotImplementedError()
-def isfalse(a):
+# 1.4 deprecated; see #5435
+istrue = is_true
+
+
+def is_false(a):
raise NotImplementedError()
+# 1.4 deprecated; see #5435
+isfalse = is_false
+
+
@comparison_op
def is_distinct_from(a, b):
return a.is_distinct_from(b)
@comparison_op
-def isnot_distinct_from(a, b):
- return a.isnot_distinct_from(b)
+def is_not_distinct_from(a, b):
+ return a.is_not_distinct_from(b)
+
+
+# deprecated 1.4; see #5435
+isnot_distinct_from = is_not_distinct_from
@comparison_op
@comparison_op
-def notlike_op(a, b, escape=None):
+def not_like_op(a, b, escape=None):
return a.notlike(b, escape=escape)
+# 1.4 deprecated; see #5435
+notlike_op = not_like_op
+
+
@comparison_op
def ilike_op(a, b, escape=None):
return a.ilike(b, escape=escape)
@comparison_op
-def notilike_op(a, b, escape=None):
- return a.notilike(b, escape=escape)
+def not_ilike_op(a, b, escape=None):
+ return a.not_ilike(b, escape=escape)
+
+
+# 1.4 deprecated; see #5435
+notilike_op = not_ilike_op
@comparison_op
@comparison_op
-def notbetween_op(a, b, c, symmetric=False):
- return a.notbetween(b, c, symmetric=symmetric)
+def not_between_op(a, b, c, symmetric=False):
+ return ~a.between(b, c, symmetric=symmetric)
+
+
+# 1.4 deprecated; see #5435
+notbetween_op = not_between_op
@comparison_op
@comparison_op
-def notstartswith_op(a, b, escape=None, autoescape=False):
+def not_startswith_op(a, b, escape=None, autoescape=False):
return ~_escaped_like_impl(a.startswith, b, escape, autoescape)
+# 1.4 deprecated; see #5435
+notstartswith_op = not_startswith_op
+
+
@comparison_op
def endswith_op(a, b, escape=None, autoescape=False):
return _escaped_like_impl(a.endswith, b, escape, autoescape)
@comparison_op
-def notendswith_op(a, b, escape=None, autoescape=False):
+def not_endswith_op(a, b, escape=None, autoescape=False):
return ~_escaped_like_impl(a.endswith, b, escape, autoescape)
+# 1.4 deprecated; see #5435
+notendswith_op = not_endswith_op
+
+
@comparison_op
def contains_op(a, b, escape=None, autoescape=False):
return _escaped_like_impl(a.contains, b, escape, autoescape)
@comparison_op
-def notcontains_op(a, b, escape=None, autoescape=False):
+def not_contains_op(a, b, escape=None, autoescape=False):
return ~_escaped_like_impl(a.contains, b, escape, autoescape)
+# 1.4 deprecated; see #5435
+notcontains_op = not_contains_op
+
+
@comparison_op
def match_op(a, b, **kw):
return a.match(b, **kw)
@comparison_op
-def notmatch_op(a, b, **kw):
- return a.notmatch(b, **kw)
+def not_match_op(a, b, **kw):
+ return ~a.match(b, **kw)
+
+
+# 1.4 deprecated; see #5429
+notmatch_op = not_match_op
def comma_op(a, b):
return a.asc()
-def nullsfirst_op(a):
- return a.nullsfirst()
+def nulls_first_op(a):
+ return a.nulls_first()
+
+
+# 1.4 deprecated; see #5435
+nullsfirst_op = nulls_first_op
+
+
+def nulls_last_op(a):
+ return a.nulls_last()
-def nullslast_op(a):
- return a.nullslast()
+# 1.4 deprecated; see #5435
+nullslast_op = nulls_last_op
def json_getitem_op(a, b):
def is_ordering_modifier(op):
- return op in (asc_op, desc_op, nullsfirst_op, nullslast_op)
+ return op in (asc_op, desc_op, nulls_first_op, nulls_last_op)
def is_natural_self_precedent(op):
)
-_booleans = (inv, istrue, isfalse, and_, or_)
+_booleans = (inv, is_true, is_false, and_, or_)
def is_boolean(op):
concat_op: 6,
filter_op: 6,
match_op: 5,
- notmatch_op: 5,
+ not_match_op: 5,
regexp_match_op: 5,
not_regexp_match_op: 5,
regexp_replace_op: 5,
ilike_op: 5,
- notilike_op: 5,
+ not_ilike_op: 5,
like_op: 5,
- notlike_op: 5,
+ not_like_op: 5,
in_op: 5,
not_in_op: 5,
is_: 5,
eq: 5,
ne: 5,
is_distinct_from: 5,
- isnot_distinct_from: 5,
+ is_not_distinct_from: 5,
gt: 5,
lt: 5,
ge: 5,
le: 5,
between_op: 5,
- notbetween_op: 5,
+ not_between_op: 5,
distinct_op: 5,
inv: 5,
- istrue: 5,
- isfalse: 5,
+ is_true: 5,
+ is_false: 5,
and_: 3,
or_: 2,
comma_op: -1,
id_="iaaa",
argnames="col_a_value, col_b_value, expected_row_count_for_is",
)
- def test_is_or_isnot_distinct_from(
+ def test_is_or_is_not_distinct_from(
self, col_a_value, col_b_value, expected_row_count_for_is, connection
):
tbl = self.tables.is_distinct_test
1 if expected_row_count_for_is == 0 else 0
)
result = connection.execute(
- tbl.select(tbl.c.col_a.isnot_distinct_from(tbl.c.col_b))
+ tbl.select(tbl.c.col_a.is_not_distinct_from(tbl.c.col_b))
).fetchall()
eq_(
len(result),
)
eq_(
- compile_exprs([t2.c.name.desc(), t2.c.aname.desc().nullslast()]),
+ compile_exprs([t2.c.name.desc(), t2.c.aname.desc().nulls_last()]),
compile_exprs(r2.expressions),
)
eq_(
- compile_exprs([t2.c.name.nullsfirst(), t2.c.aname]),
+ compile_exprs([t2.c.name.nulls_first(), t2.c.aname]),
compile_exprs(r3.expressions),
)
)
self.assert_compile(
- sql.column("x").isnot_distinct_from(False), "x IS 0"
+ sql.column("x").is_not_distinct_from(False), "x IS 0"
)
def test_localtime(self):
self.assert_compile(
table2.select().order_by(
table2.c.otherid,
- table2.c.othername.desc().nullsfirst(),
+ table2.c.othername.desc().nulls_first(),
),
"SELECT myothertable.otherid, myothertable.othername FROM "
"myothertable ORDER BY myothertable.otherid, "
self.assert_compile(
table2.select().order_by(
table2.c.otherid,
- table2.c.othername.desc().nullslast(),
+ table2.c.othername.desc().nulls_last(),
),
"SELECT myothertable.otherid, myothertable.othername FROM "
"myothertable ORDER BY myothertable.otherid, "
self.assert_compile(
table2.select().order_by(
- table2.c.otherid.nullslast(),
- table2.c.othername.desc().nullsfirst(),
+ table2.c.otherid.nulls_last(),
+ table2.c.othername.desc().nulls_first(),
),
"SELECT myothertable.otherid, myothertable.othername FROM "
"myothertable ORDER BY myothertable.otherid NULLS LAST, "
self.assert_compile(
table2.select().order_by(
- table2.c.otherid.nullsfirst(),
+ table2.c.otherid.nulls_first(),
table2.c.othername.desc(),
),
"SELECT myothertable.otherid, myothertable.othername FROM "
self.assert_compile(
table2.select().order_by(
- table2.c.otherid.nullsfirst(),
- table2.c.othername.desc().nullslast(),
+ table2.c.otherid.nulls_first(),
+ table2.c.othername.desc().nulls_last(),
),
"SELECT myothertable.otherid, myothertable.othername FROM "
"myothertable ORDER BY myothertable.otherid NULLS FIRST, "
from sqlalchemy.testing import eq_
from sqlalchemy.testing import fixtures
from sqlalchemy.testing import in_
+from sqlalchemy.testing import is_
from sqlalchemy.testing import is_true
from sqlalchemy.testing import mock
from sqlalchemy.testing import not_in
class LegacyOperatorTest(AssertsCompiledSQL, fixtures.TestBase):
+ """
+ Several operators were renamed for SqlAlchemy 2.0 in #5429 and #5435
+
+ This test class is designed to ensure the deprecated legacy operators
+ are still available and equivalent to their modern replacements.
+
+ These tests should be removed when the legacy operators are removed.
+
+ Note: Although several of these tests simply check to see if two functions
+ are the same, some platforms in the test matrix require an `==` comparison
+ and will fail on an `is` comparison.
+
+ .. seealso::
+
+ :ref:`change_5429`
+ :ref:`change_5435`
+ """
+
__dialect__ = "default"
def test_issue_5429_compile(self):
# is_not
assert hasattr(operators, "is_not") # modern
assert hasattr(operators, "isnot") # legacy
- assert operators.is_not is operators.isnot
+ is_(operators.is_not, operators.isnot)
# not_in
assert hasattr(operators, "not_in_op") # modern
assert hasattr(operators, "notin_op") # legacy
- assert operators.not_in_op is operators.notin_op
+ is_(operators.not_in_op, operators.notin_op)
# precedence mapping
+ # since they are the same item, only 1 precedence check needed
# is_not
- assert operators.is_not in operators._PRECEDENCE # modern
assert operators.isnot in operators._PRECEDENCE # legacy
- assert (
- operators._PRECEDENCE[operators.is_not]
- == operators._PRECEDENCE[operators.isnot]
- )
+
# not_in_op
- assert operators.not_in_op in operators._PRECEDENCE # modern
assert operators.notin_op in operators._PRECEDENCE # legacy
- assert (
- operators._PRECEDENCE[operators.not_in_op]
- == operators._PRECEDENCE[operators.notin_op]
- )
# ColumnOperators
# is_not
# is_not
assert hasattr(assertions, "is_not") # modern
assert hasattr(assertions, "is_not_") # legacy
- assert assertions.is_not is assertions.is_not_
+ assert assertions.is_not == assertions.is_not_
# not_in
assert hasattr(assertions, "not_in") # modern
assert hasattr(assertions, "not_in_") # legacy
- assert assertions.not_in is assertions.not_in_
+ assert assertions.not_in == assertions.not_in_
+
+ @testing.combinations(
+ (
+ "is_not_distinct_from",
+ "isnot_distinct_from",
+ "a IS NOT DISTINCT FROM b",
+ ),
+ ("not_contains_op", "notcontains_op", "a NOT LIKE '%' || b || '%'"),
+ ("not_endswith_op", "notendswith_op", "a NOT LIKE '%' || b"),
+ ("not_ilike_op", "notilike_op", "lower(a) NOT LIKE lower(b)"),
+ ("not_like_op", "notlike_op", "a NOT LIKE b"),
+ ("not_match_op", "notmatch_op", "NOT a MATCH b"),
+ ("not_startswith_op", "notstartswith_op", "a NOT LIKE b || '%'"),
+ )
+ def test_issue_5435_binary_operators(self, modern, legacy, txt):
+ a, b = column("a"), column("b")
+ _op_modern = getattr(operators, modern)
+ _op_legacy = getattr(operators, legacy)
+
+ eq_(str(_op_modern(a, b)), txt)
+
+ eq_(str(_op_modern(a, b)), str(_op_legacy(a, b)))
+
+ @testing.combinations(
+ ("nulls_first_op", "nullsfirst_op", "a NULLS FIRST"),
+ ("nulls_last_op", "nullslast_op", "a NULLS LAST"),
+ )
+ def test_issue_5435_unary_operators(self, modern, legacy, txt):
+ a = column("a")
+ _op_modern = getattr(operators, modern)
+ _op_legacy = getattr(operators, legacy)
+
+ eq_(str(_op_modern(a)), txt)
+
+ eq_(str(_op_modern(a)), str(_op_legacy(a)))
+
+ @testing.combinations(
+ ("not_between_op", "notbetween_op", "a NOT BETWEEN b AND c")
+ )
+ def test_issue_5435_between_operators(self, modern, legacy, txt):
+ a, b, c = column("a"), column("b"), column("c")
+ _op_modern = getattr(operators, modern)
+ _op_legacy = getattr(operators, legacy)
+
+ eq_(str(_op_modern(a, b, c)), txt)
+
+ eq_(str(_op_modern(a, b, c)), str(_op_legacy(a, b, c)))
+
+ @testing.combinations(
+ ("is_false", "isfalse", True),
+ ("is_true", "istrue", True),
+ ("is_not_distinct_from", "isnot_distinct_from", True),
+ ("not_between_op", "notbetween_op", True),
+ ("not_contains_op", "notcontains_op", False),
+ ("not_endswith_op", "notendswith_op", False),
+ ("not_ilike_op", "notilike_op", True),
+ ("not_like_op", "notlike_op", True),
+ ("not_match_op", "notmatch_op", True),
+ ("not_startswith_op", "notstartswith_op", False),
+ ("nulls_first_op", "nullsfirst_op", False),
+ ("nulls_last_op", "nullslast_op", False),
+ )
+ def test_issue_5435_operators_precedence(
+ self, _modern, _legacy, _in_precedence
+ ):
+ # (modern, legacy, in_precendence)
+ # core operators
+ assert hasattr(operators, _modern)
+ assert hasattr(operators, _legacy)
+ _op_modern = getattr(operators, _modern)
+ _op_legacy = getattr(operators, _legacy)
+ assert _op_modern == _op_legacy
+ # since they are the same item, only 1 precedence check needed
+ if _in_precedence:
+ assert _op_legacy in operators._PRECEDENCE
+ else:
+ assert _op_legacy not in operators._PRECEDENCE
+
+ @testing.combinations(
+ ("is_not_distinct_from", "isnot_distinct_from"),
+ ("not_ilike", "notilike"),
+ ("not_like", "notlike"),
+ ("nulls_first", "nullsfirst"),
+ ("nulls_last", "nullslast"),
+ )
+ def test_issue_5435_operators_column(self, _modern, _legacy):
+ # (modern, legacy)
+ # Column operators
+ assert hasattr(operators.ColumnOperators, _modern)
+ assert hasattr(operators.ColumnOperators, _legacy)
+ _op_modern = getattr(operators.ColumnOperators, _modern)
+ _op_legacy = getattr(operators.ColumnOperators, _legacy)
+ assert _op_modern == _op_legacy
(operators.is_distinct_from, True),
(operators.is_distinct_from, False),
(operators.is_distinct_from, None),
- (operators.isnot_distinct_from, True),
+ (operators.is_not_distinct_from, True),
+ (operators.isnot_distinct_from, True), # deprecated 1.4; See #5429
(operators.is_, True),
(operators.is_not, True),
(operators.isnot, True), # deprecated 1.4; See #5429
(operators.is_not, False),
(operators.isnot, False), # deprecated 1.4; See #5429
(operators.like_op, right_column),
- (operators.notlike_op, right_column),
+ (operators.not_like_op, right_column),
+ (operators.notlike_op, right_column), # deprecated 1.4; See #5435
(operators.ilike_op, right_column),
- (operators.notilike_op, right_column),
+ (operators.not_ilike_op, right_column),
+ (operators.notilike_op, right_column), # deprecated 1.4; See #5435
(operators.is_, right_column),
(operators.is_not, right_column),
(operators.isnot, right_column), # deprecated 1.4; See #5429
def test_operator_precedence_collate_6(self):
self.assert_compile(
select(self.table1.c.name).order_by(
- self.table1.c.name.collate("utf-8").desc().nullslast()
+ self.table1.c.name.collate("utf-8").desc().nulls_last()
),
"SELECT mytable.name FROM mytable "
'ORDER BY mytable.name COLLATE "utf-8" DESC NULLS LAST',
dialect=postgresql.dialect(),
)
- def test_isnot_distinct_from(self):
+ def test_is_not_distinct_from(self):
self.assert_compile(
- self.table1.c.myid.isnot_distinct_from(1),
+ self.table1.c.myid.is_not_distinct_from(1),
"mytable.myid IS NOT DISTINCT FROM :myid_1",
)
- def test_isnot_distinct_from_sqlite(self):
+ def test_is_not_distinct_from_sqlite(self):
self.assert_compile(
- self.table1.c.myid.isnot_distinct_from(1),
+ self.table1.c.myid.is_not_distinct_from(1),
"mytable.myid IS ?",
dialect=sqlite.dialect(),
)
- def test_isnot_distinct_from_postgresql(self):
+ def test_is_not_distinct_from_postgresql(self):
self.assert_compile(
- self.table1.c.myid.isnot_distinct_from(1),
+ self.table1.c.myid.is_not_distinct_from(1),
"mytable.myid IS NOT DISTINCT FROM %(myid_1)s",
dialect=postgresql.dialect(),
)
- def test_not_isnot_distinct_from(self):
+ def test_not_is_not_distinct_from(self):
self.assert_compile(
- ~self.table1.c.myid.isnot_distinct_from(1),
+ ~self.table1.c.myid.is_not_distinct_from(1),
"mytable.myid IS DISTINCT FROM :myid_1",
)
- def test_not_isnot_distinct_from_postgresql(self):
+ def test_not_is_not_distinct_from_postgresql(self):
self.assert_compile(
- ~self.table1.c.myid.isnot_distinct_from(1),
+ ~self.table1.c.myid.is_not_distinct_from(1),
"mytable.myid IS DISTINCT FROM %(myid_1)s",
dialect=postgresql.dialect(),
)
checkparams={"x_1": "a%b_c"},
)
- def test_notlike(self):
+ def test_not_like(self):
self.assert_compile(
- column("x").notlike("y"),
+ column("x").not_like("y"),
"x NOT LIKE :x_1",
checkparams={"x_1": "y"},
)
- def test_notlike_escape(self):
+ def test_not_like_escape(self):
self.assert_compile(
- column("x").notlike("a%b_c", escape="\\"),
+ column("x").not_like("a%b_c", escape="\\"),
"x NOT LIKE :x_1 ESCAPE '\\'",
checkparams={"x_1": "a%b_c"},
)
- def test_notilike(self):
+ def test_not_ilike(self):
self.assert_compile(
- column("x").notilike("y"),
+ column("x").not_ilike("y"),
"lower(x) NOT LIKE lower(:x_1)",
checkparams={"x_1": "y"},
)
- def test_notilike_escape(self):
+ def test_not_ilike_escape(self):
self.assert_compile(
- column("x").notilike("a%b_c", escape="\\"),
+ column("x").not_ilike("a%b_c", escape="\\"),
"lower(x) NOT LIKE lower(:x_1) ESCAPE '\\'",
checkparams={"x_1": "a%b_c"},
)
for labels in False, True:
a_eq(
users.select(
- order_by=[users.c.user_name.nullsfirst()],
+ order_by=[users.c.user_name.nulls_first()],
use_labels=labels,
),
[(1, None), (3, "a"), (2, "b")],
a_eq(
users.select(
- order_by=[users.c.user_name.nullslast()], use_labels=labels
+ order_by=[users.c.user_name.nulls_last()],
+ use_labels=labels,
),
[(3, "a"), (2, "b"), (1, None)],
)
a_eq(
users.select(
- order_by=[asc(users.c.user_name).nullsfirst()],
+ order_by=[asc(users.c.user_name).nulls_first()],
use_labels=labels,
),
[(1, None), (3, "a"), (2, "b")],
a_eq(
users.select(
- order_by=[asc(users.c.user_name).nullslast()],
+ order_by=[asc(users.c.user_name).nulls_last()],
use_labels=labels,
),
[(3, "a"), (2, "b"), (1, None)],
a_eq(
users.select(
- order_by=[users.c.user_name.desc().nullsfirst()],
+ order_by=[users.c.user_name.desc().nulls_first()],
use_labels=labels,
),
[(1, None), (2, "b"), (3, "a")],
a_eq(
users.select(
- order_by=[users.c.user_name.desc().nullslast()],
+ order_by=[users.c.user_name.desc().nulls_last()],
use_labels=labels,
),
[(2, "b"), (3, "a"), (1, None)],
a_eq(
users.select(
- order_by=[desc(users.c.user_name).nullsfirst()],
+ order_by=[desc(users.c.user_name).nulls_first()],
use_labels=labels,
),
[(1, None), (2, "b"), (3, "a")],
a_eq(
users.select(
- order_by=[desc(users.c.user_name).nullslast()],
+ order_by=[desc(users.c.user_name).nulls_last()],
use_labels=labels,
),
[(2, "b"), (3, "a"), (1, None)],
a_eq(
users.select(
- order_by=[users.c.user_name.nullsfirst(), users.c.user_id],
+ order_by=[
+ users.c.user_name.nulls_first(),
+ users.c.user_id,
+ ],
use_labels=labels,
),
[(1, None), (3, "a"), (2, "b")],
a_eq(
users.select(
- order_by=[users.c.user_name.nullslast(), users.c.user_id],
+ order_by=[users.c.user_name.nulls_last(), users.c.user_id],
use_labels=labels,
),
[(3, "a"), (2, "b"), (1, None)],