From: Mike Bayer Date: Wed, 18 May 2022 20:21:49 +0000 (-0400) Subject: favor bool_op over op in comparison X-Git-Tag: rel_2_0_0b1~306^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=deb9bcc0d97dd8b38dfccb340a5fc1f880202ff6;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git favor bool_op over op in comparison there's no need to use the is_comparison parameter anymore as bool_op() works better and in 2.0 also does typing correctly. Change-Id: I9e92b665b112d40d90e539003b0efe00ed7b075f --- diff --git a/doc/build/orm/join_conditions.rst b/doc/build/orm/join_conditions.rst index 51385efed8..f694f10546 100644 --- a/doc/build/orm/join_conditions.rst +++ b/doc/build/orm/join_conditions.rst @@ -264,21 +264,13 @@ Using custom operators in join conditions Another use case for relationships is the use of custom operators, such as PostgreSQL's "is contained within" ``<<`` operator when joining with types such as :class:`_postgresql.INET` and :class:`_postgresql.CIDR`. -For custom operators we use the :meth:`.Operators.op` function:: +For custom boolean operators we use the :meth:`.Operators.bool_op` function:: - inet_column.op("<<")(cidr_column) + inet_column.bool_op("<<")(cidr_column) -However, if we construct a :paramref:`_orm.relationship.primaryjoin` using this -operator, :func:`_orm.relationship` will still need more information. This is because -when it examines our primaryjoin condition, it specifically looks for operators -used for **comparisons**, and this is typically a fixed list containing known -comparison operators such as ``==``, ``<``, etc. So for our custom operator -to participate in this system, we need it to register as a comparison operator -using the :paramref:`~.Operators.op.is_comparison` parameter:: - - inet_column.op("<<", is_comparison=True)(cidr_column) - -A complete example:: +A comparison like the above may be used directly with +:paramref:`_orm.relationship.primaryjoin` when constructing +a :func:`_orm.relationship`:: class IPA(Base): __tablename__ = 'ip_address' @@ -287,7 +279,7 @@ A complete example:: v4address = Column(INET) network = relationship("Network", - primaryjoin="IPA.v4address.op('<<', is_comparison=True)" + primaryjoin="IPA.v4address.bool_op('<<')" "(foreign(Network.v4representation))", viewonly=True ) @@ -306,9 +298,9 @@ Will render as:: SELECT ip_address.id AS ip_address_id, ip_address.v4address AS ip_address_v4address FROM ip_address JOIN network ON ip_address.v4address << network.v4representation -.. versionadded:: 0.9.2 - Added the :paramref:`.Operators.op.is_comparison` - flag to assist in the creation of :func:`_orm.relationship` constructs using - custom operators. +.. versionadded:: 2.0 Added :meth:`.Operators.bool_op` to generate operators + that are inherently "boolean" when used in expressions and ORM join + conditions. .. _relationship_custom_operator_sql_function: diff --git a/lib/sqlalchemy/sql/operators.py b/lib/sqlalchemy/sql/operators.py index 98d763e98d..0a85277611 100644 --- a/lib/sqlalchemy/sql/operators.py +++ b/lib/sqlalchemy/sql/operators.py @@ -217,14 +217,17 @@ class Operators: A value of 100 will be higher or equal to all operators, and -100 will be lower than or equal to all operators. - :param is_comparison: if True, the operator will be considered as a - "comparison" operator, that is which evaluates to a boolean - true/false value, like ``==``, ``>``, etc. This flag should be set + :param is_comparison: legacy; if True, the operator will be considered + as a "comparison" operator, that is which evaluates to a boolean + true/false value, like ``==``, ``>``, etc. This flag is provided so that ORM relationships can establish that the operator is a comparison operator when used in a custom join condition. - .. versionadded:: 0.9.2 - added the - :paramref:`.Operators.op.is_comparison` flag. + Using the ``is_comparison`` parameter is superseded by using the + :meth:`.Operators.bool_op` method instead; this more succinct + operator sets this parameter automatically, but also provides + correct :pep:`484` typing support as the returned object will + express a "boolean" datatype, i.e. ``BinaryExpression[bool]``. :param return_type: a :class:`.TypeEngine` class or object that will force the return type of an expression produced by this operator @@ -255,6 +258,8 @@ class Operators: .. seealso:: + :meth:`.Operators.bool_op` + :ref:`types_operators` :ref:`relationship_custom_operator` @@ -284,7 +289,9 @@ class Operators: This method is shorthand for calling :meth:`.Operators.op` and passing the :paramref:`.Operators.op.is_comparison` - flag with True. + flag with True. A key advantage to using :meth:`.Operators.bool_op` + is that when using column constructs, the "boolean" nature of the + returned expression will be present for :pep:`484` purposes. .. seealso:: diff --git a/test/orm/test_relationships.py b/test/orm/test_relationships.py index 22bc549085..b483bf6953 100644 --- a/test/orm/test_relationships.py +++ b/test/orm/test_relationships.py @@ -2907,7 +2907,7 @@ class CustomOperatorTest(fixtures.MappedTest, AssertsCompiledSQL): Column("foo", String(50)), ) - def test_join_on_custom_op(self): + def test_join_on_custom_op_legacy_is_comparison(self): class A(fixtures.BasicEntity): pass @@ -2934,6 +2934,33 @@ class CustomOperatorTest(fixtures.MappedTest, AssertsCompiledSQL): "FROM a JOIN b ON a.foo &* b.foo", ) + def test_join_on_custom_bool_op(self): + class A(fixtures.BasicEntity): + pass + + class B(fixtures.BasicEntity): + pass + + self.mapper_registry.map_imperatively( + A, + self.tables.a, + properties={ + "bs": relationship( + B, + primaryjoin=self.tables.a.c.foo.bool_op("&*")( + foreign(self.tables.b.c.foo) + ), + viewonly=True, + ) + }, + ) + self.mapper_registry.map_imperatively(B, self.tables.b) + self.assert_compile( + fixture_session().query(A).join(A.bs), + "SELECT a.id AS a_id, a.foo AS a_foo " + "FROM a JOIN b ON a.foo &* b.foo", + ) + class ViewOnlyHistoryTest(fixtures.MappedTest): @classmethod