]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
favor bool_op over op in comparison
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 18 May 2022 20:21:49 +0000 (16:21 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 18 May 2022 20:21:49 +0000 (16:21 -0400)
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

doc/build/orm/join_conditions.rst
lib/sqlalchemy/sql/operators.py
test/orm/test_relationships.py

index 51385efed840757e181527c9d9f7c3673a2f76e0..f694f105466191444318b26945c749fcff4c68c6 100644 (file)
@@ -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:
 
index 98d763e98de672cf2a8da09bda07d63dff482a71..0a85277611a56752d0965daffc20f70392bb1fd4 100644 (file)
@@ -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::
 
index 22bc549085ecd454ec0b6963e90ad715f8c9dbc6..b483bf6953d1fc0167df248aba786f839bef4222 100644 (file)
@@ -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