From c28bccd8964958792e129d82e14630990809689f Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Wed, 18 May 2022 16:21:49 -0400 Subject: [PATCH] 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 (cherry picked from commit deb9bcc0d97dd8b38dfccb340a5fc1f880202ff6) --- doc/build/orm/join_conditions.rst | 24 ++++++------------------ lib/sqlalchemy/sql/operators.py | 18 ++++++++++++------ test/orm/test_relationships.py | 29 ++++++++++++++++++++++++++++- 3 files changed, 46 insertions(+), 25 deletions(-) diff --git a/doc/build/orm/join_conditions.rst b/doc/build/orm/join_conditions.rst index af314f221e..509ccc98f3 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,10 +298,6 @@ 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. - .. _relationship_custom_operator_sql_function: Custom operators based on SQL functions diff --git a/lib/sqlalchemy/sql/operators.py b/lib/sqlalchemy/sql/operators.py index 31a2a01a73..4ab0c4f29e 100644 --- a/lib/sqlalchemy/sql/operators.py +++ b/lib/sqlalchemy/sql/operators.py @@ -153,14 +153,16 @@ class Operators(object): 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. In SQLAlchemy 2.0 it + will also provide for improved typing support. :param return_type: a :class:`.TypeEngine` class or object that will force the return type of an expression produced by this operator @@ -171,6 +173,8 @@ class Operators(object): .. seealso:: + :meth:`.Operators.bool_op` + :ref:`types_operators` :ref:`relationship_custom_operator` @@ -189,7 +193,9 @@ class Operators(object): 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 acb22ce0f8..1dc5b37fd2 100644 --- a/test/orm/test_relationships.py +++ b/test/orm/test_relationships.py @@ -2906,7 +2906,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 @@ -2933,6 +2933,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 -- 2.47.2