]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
apply asbool reduction to the onclause in join()
authorMike Bayer <mike_mp@zzzcomputing.com>
Fri, 17 Jan 2020 19:31:10 +0000 (14:31 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Fri, 17 Jan 2020 21:29:27 +0000 (16:29 -0500)
The :func:`.true` and :func:`.false` operators may now be applied as the
"onclause" of a :func:`.sql.join` on a backend that does not support
"native boolean" expressions, e.g. Oracle or SQL Server, and the expression
will render as "1=1" for true and "1=0" false.  This is the behavior that
was introduced many years ago in :ticket:`2804` for and/or expressions.

Change-Id: I85311c31c22d6e226c618f8840f6b95eca611153

doc/build/changelog/unreleased_14/asbool_join.rst [new file with mode: 0644]
lib/sqlalchemy/sql/selectable.py
lib/sqlalchemy/testing/suite/test_select.py
test/profiles.txt
test/sql/test_operators.py

diff --git a/doc/build/changelog/unreleased_14/asbool_join.rst b/doc/build/changelog/unreleased_14/asbool_join.rst
new file mode 100644 (file)
index 0000000..154f056
--- /dev/null
@@ -0,0 +1,8 @@
+.. change::
+    :tags: usecase, sql
+
+    The :func:`.true` and :func:`.false` operators may now be applied as the
+    "onclause" of a :func:`.sql.join` on a backend that does not support
+    "native boolean" expressions, e.g. Oracle or SQL Server, and the expression
+    will render as "1=1" for true and "1=0" false.  This is the behavior that
+    was introduced many years ago in :ticket:`2804` for and/or expressions.
index 136c9f868c9ff7c81cc667d341783c405929a333..db743f408dabbd371498521c4e53b14242006d56 100644 (file)
@@ -698,7 +698,7 @@ class Join(FromClause):
         if onclause is None:
             self.onclause = self._match_primaries(self.left, self.right)
         else:
-            self.onclause = onclause
+            self.onclause = onclause.self_group(against=operators._asbool)
 
         self.isouter = isouter
         self.full = full
index 9db2daf7ac30bbea6cceb520d8c07aa69034c5b5..3b64b0f291bd68157c522c812dea4201148ae292 100644 (file)
@@ -1,3 +1,6 @@
+import itertools
+
+from sqlalchemy import ForeignKey
 from .. import AssertsCompiledSQL
 from .. import AssertsExecutionResults
 from .. import config
@@ -292,6 +295,105 @@ class LimitOffsetTest(fixtures.TablesTest):
         )
 
 
+class JoinTest(fixtures.TablesTest):
+    __backend__ = True
+
+    def _assert_result(self, select, result, params=()):
+        eq_(config.db.execute(select, params).fetchall(), result)
+
+    @classmethod
+    def define_tables(cls, metadata):
+        Table("a", metadata, Column("id", Integer, primary_key=True))
+        Table(
+            "b",
+            metadata,
+            Column("id", Integer, primary_key=True),
+            Column("a_id", ForeignKey("a.id"), nullable=False),
+        )
+
+    @classmethod
+    def insert_data(cls):
+        config.db.execute(
+            cls.tables.a.insert(),
+            [{"id": 1}, {"id": 2}, {"id": 3}, {"id": 4}, {"id": 5}],
+        )
+
+        config.db.execute(
+            cls.tables.b.insert(),
+            [
+                {"id": 1, "a_id": 1},
+                {"id": 2, "a_id": 1},
+                {"id": 4, "a_id": 2},
+                {"id": 5, "a_id": 3},
+            ],
+        )
+
+    def test_inner_join_fk(self):
+        a, b = self.tables("a", "b")
+
+        stmt = select([a, b]).select_from(a.join(b)).order_by(a.c.id, b.c.id)
+
+        self._assert_result(stmt, [(1, 1, 1), (1, 2, 1), (2, 4, 2), (3, 5, 3)])
+
+    def test_inner_join_true(self):
+        a, b = self.tables("a", "b")
+
+        stmt = (
+            select([a, b])
+            .select_from(a.join(b, true()))
+            .order_by(a.c.id, b.c.id)
+        )
+
+        self._assert_result(
+            stmt,
+            [
+                (a, b, c)
+                for (a,), (b, c) in itertools.product(
+                    [(1,), (2,), (3,), (4,), (5,)],
+                    [(1, 1), (2, 1), (4, 2), (5, 3)],
+                )
+            ],
+        )
+
+    def test_inner_join_false(self):
+        a, b = self.tables("a", "b")
+
+        stmt = (
+            select([a, b])
+            .select_from(a.join(b, false()))
+            .order_by(a.c.id, b.c.id)
+        )
+
+        self._assert_result(stmt, [])
+
+    def test_outer_join_false(self):
+        a, b = self.tables("a", "b")
+
+        stmt = (
+            select([a, b])
+            .select_from(a.outerjoin(b, false()))
+            .order_by(a.c.id, b.c.id)
+        )
+
+        self._assert_result(
+            stmt,
+            [
+                (1, None, None),
+                (2, None, None),
+                (3, None, None),
+                (4, None, None),
+                (5, None, None),
+            ],
+        )
+
+    def test_outer_join_fk(self):
+        a, b = self.tables("a", "b")
+
+        stmt = select([a, b]).select_from(a.join(b)).order_by(a.c.id, b.c.id)
+
+        self._assert_result(stmt, [(1, 1, 1), (1, 2, 1), (2, 4, 2), (3, 5, 3)])
+
+
 class CompoundSelectTest(fixtures.TablesTest):
     __backend__ = True
 
index 4006fb94d059376d9bbf0ede55f08654b3baa6b9..5e2ca814ac2b18ac4b529e29afaad80b7432b214 100644 (file)
@@ -562,9 +562,9 @@ test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_aliased 3.7_sqlite_pys
 
 # TEST: test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_plain
 
-test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_plain 2.7_postgresql_psycopg2_dbapiunicode_cextensions 3255
-test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_plain 2.7_sqlite_pysqlite_dbapiunicode_cextensions 3255
-test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_plain 3.7_sqlite_pysqlite_dbapiunicode_nocextensions 3256
+test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_plain 2.7_postgresql_psycopg2_dbapiunicode_cextensions 3555
+test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_plain 2.7_sqlite_pysqlite_dbapiunicode_cextensions 3555
+test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_b_plain 3.7_sqlite_pysqlite_dbapiunicode_nocextensions 3556
 
 # TEST: test.aaa_profiling.test_orm.JoinConditionTest.test_a_to_d
 
index aa56d0b6b30ab81c7f6dbc7fcc498f6fef168b93..a90b03b387bf16014cad0253baef548a3c424aab 100644 (file)
@@ -5,6 +5,7 @@ from sqlalchemy import and_
 from sqlalchemy import between
 from sqlalchemy import exc
 from sqlalchemy import Integer
+from sqlalchemy import join
 from sqlalchemy import LargeBinary
 from sqlalchemy import literal_column
 from sqlalchemy import not_
@@ -988,6 +989,42 @@ class BooleanEvalTest(fixtures.TestBase, testing.AssertsCompiledSQL):
             or_(false(), true()), "1 = 1", dialect=self._dialect(False)
         )
 
+    def test_seven_a(self):
+        t1 = table("t1", column("a"))
+        t2 = table("t2", column("b"))
+        self.assert_compile(
+            join(t1, t2, onclause=true()),
+            "t1 JOIN t2 ON 1 = 1",
+            dialect=self._dialect(False),
+        )
+
+    def test_seven_b(self):
+        t1 = table("t1", column("a"))
+        t2 = table("t2", column("b"))
+        self.assert_compile(
+            join(t1, t2, onclause=false()),
+            "t1 JOIN t2 ON 0 = 1",
+            dialect=self._dialect(False),
+        )
+
+    def test_seven_c(self):
+        t1 = table("t1", column("a"))
+        t2 = table("t2", column("b"))
+        self.assert_compile(
+            join(t1, t2, onclause=true()),
+            "t1 JOIN t2 ON true",
+            dialect=self._dialect(True),
+        )
+
+    def test_seven_d(self):
+        t1 = table("t1", column("a"))
+        t2 = table("t2", column("b"))
+        self.assert_compile(
+            join(t1, t2, onclause=false()),
+            "t1 JOIN t2 ON false",
+            dialect=self._dialect(True),
+        )
+
     def test_eight(self):
         self.assert_compile(
             and_(false(), true()), "false", dialect=self._dialect(True)