]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Allow flat for join with name
authorEric Atkin <eatkin@certusllc.us>
Wed, 3 Jul 2024 20:05:04 +0000 (16:05 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 4 Jul 2024 17:53:41 +0000 (13:53 -0400)
The :paramref:`_orm.aliased.name` parameter to :func:`_orm.aliased` may now
be combined with the :paramref:`_orm.aliased.flat` parameter, producing
per-table names based on a name-prefixed naming convention.  Pull request
courtesy Eric Atkin.

Fixes: #11575
Closes: #11531
Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/11531
Pull-request-sha: f85535464be7b04d5f9745848d28f87dcd248b86

Change-Id: If79679c7a9598fffe99c033894b7dffecef13939

doc/build/changelog/unreleased_20/11575.rst [new file with mode: 0644]
lib/sqlalchemy/orm/_orm_constructors.py
lib/sqlalchemy/sql/selectable.py
test/orm/test_core_compilation.py
test/sql/test_selectable.py

diff --git a/doc/build/changelog/unreleased_20/11575.rst b/doc/build/changelog/unreleased_20/11575.rst
new file mode 100644 (file)
index 0000000..4eb5665
--- /dev/null
@@ -0,0 +1,8 @@
+.. change::
+    :tags: usecase, orm
+    :tickets: 11575
+
+    The :paramref:`_orm.aliased.name` parameter to :func:`_orm.aliased` may now
+    be combined with the :paramref:`_orm.aliased.flat` parameter, producing
+    per-table names based on a name-prefixed naming convention.  Pull request
+    courtesy Eric Atkin.
index 7d215059af0e0715c55e67cc62468db96604b866..74a0d316e7447879c870a1c610720b403af1d9c5 100644 (file)
@@ -2301,6 +2301,16 @@ def aliased(
      supported by all modern databases with regards to right-nested joins
      and generally produces more efficient queries.
 
+     When :paramref:`_orm.aliased.flat` is combined with
+     :paramref:`_orm.aliased.name`, the resulting joins will alias individual
+     tables using a naming scheme similar to ``<prefix>_<tablename>``.  This
+     naming scheme is for visibility / debugging purposes only and the
+     specific scheme is subject to change without notice.
+
+     .. versionadded:: 2.0.32 added support for combining
+        :paramref:`_orm.aliased.name` with :paramref:`_orm.aliased.flat`.
+        Previously, this would raise ``NotImplementedError``.
+
     :param adapt_on_names: if True, more liberal "matching" will be used when
      mapping the mapped columns of the ORM entity to those of the
      given selectable - a name-based match will be performed if the
index a9ef7fd0301b71ca29345bcc779b66144dc20825..6fa29fd767fb6a18a3a462d08a12a0ed5420a8af 100644 (file)
@@ -1521,11 +1521,23 @@ class Join(roles.DMLTableRole, FromClause):
     ) -> TODO_Any:
         sqlutil = util.preloaded.sql_util
         if flat:
-            if name is not None:
-                raise exc.ArgumentError("Can't send name argument with flat")
+            if isinstance(self.left, (FromGrouping, Join)):
+                left_name = name  # will recurse
+            else:
+                if name and isinstance(self.left, NamedFromClause):
+                    left_name = f"{name}_{self.left.name}"
+                else:
+                    left_name = name
+            if isinstance(self.right, (FromGrouping, Join)):
+                right_name = name  # will recurse
+            else:
+                if name and isinstance(self.right, NamedFromClause):
+                    right_name = f"{name}_{self.right.name}"
+                else:
+                    right_name = name
             left_a, right_a = (
-                self.left._anonymous_fromclause(flat=True),
-                self.right._anonymous_fromclause(flat=True),
+                self.left._anonymous_fromclause(name=left_name, flat=flat),
+                self.right._anonymous_fromclause(name=right_name, flat=flat),
             )
             adapter = sqlutil.ClauseAdapter(left_a).chain(
                 sqlutil.ClauseAdapter(right_a)
index 915c9747f8f87ed5916c0deca60204494dbd4a0e..81aa760d9b2fffac00611f5b2134256f363b3b86 100644 (file)
@@ -2604,6 +2604,61 @@ class JoinedInhTest(
             "anon_1.primary_language FROM anon_1",
         )
 
+    @testing.variation("named", [True, False])
+    @testing.variation("flat", [True, False])
+    def test_aliased_joined_entities(self, named, flat):
+        Company = self.classes.Company
+        Engineer = self.classes.Engineer
+
+        if named:
+            e1 = aliased(Engineer, flat=flat, name="myengineer")
+        else:
+            e1 = aliased(Engineer, flat=flat)
+
+        q = select(Company.name, e1.primary_language).join(
+            Company.employees.of_type(e1)
+        )
+
+        if not flat:
+            name = "anon_1" if not named else "myengineer"
+
+            self.assert_compile(
+                q,
+                "SELECT companies.name, "
+                f"{name}.engineers_primary_language FROM companies "
+                "JOIN (SELECT people.person_id AS people_person_id, "
+                "people.company_id AS people_company_id, "
+                "people.name AS people_name, people.type AS people_type, "
+                "engineers.person_id AS engineers_person_id, "
+                "engineers.status AS engineers_status, "
+                "engineers.engineer_name AS engineers_engineer_name, "
+                "engineers.primary_language AS engineers_primary_language "
+                "FROM people JOIN engineers "
+                "ON people.person_id = engineers.person_id) AS "
+                f"{name} "
+                f"ON companies.company_id = {name}.people_company_id",
+            )
+        elif named:
+            self.assert_compile(
+                q,
+                "SELECT companies.name, "
+                "myengineer_engineers.primary_language "
+                "FROM companies JOIN (people AS myengineer_people "
+                "JOIN engineers AS myengineer_engineers "
+                "ON myengineer_people.person_id = "
+                "myengineer_engineers.person_id) "
+                "ON companies.company_id = myengineer_people.company_id",
+            )
+        else:
+            self.assert_compile(
+                q,
+                "SELECT companies.name, engineers_1.primary_language "
+                "FROM companies JOIN (people AS people_1 "
+                "JOIN engineers AS engineers_1 "
+                "ON people_1.person_id = engineers_1.person_id) "
+                "ON companies.company_id = people_1.company_id",
+            )
+
 
 class RawSelectTest(QueryTest, AssertsCompiledSQL):
     """older tests from test_query.   Here, they are converted to use
index 0c0c23b870056747a9b6f977b897f8059ff504a0..4a252930a38d9cf62d231dd28acc0cd907aa7eb6 100644 (file)
@@ -2045,6 +2045,16 @@ class JoinAnonymizingTest(fixtures.TestBase, AssertsCompiledSQL):
             "a AS a_1 JOIN b AS b_1 ON a_1.a = b_1.b",
         )
 
+    def test_join_alias_name_flat(self):
+        a = table("a", column("a"))
+        b = table("b", column("b"))
+        self.assert_compile(
+            a.join(b, a.c.a == b.c.b)._anonymous_fromclause(
+                name="foo", flat=True
+            ),
+            "a AS foo_a JOIN b AS foo_b ON foo_a.a = foo_b.b",
+        )
+
     def test_composed_join_alias_flat(self):
         a = table("a", column("a"))
         b = table("b", column("b"))
@@ -2063,6 +2073,24 @@ class JoinAnonymizingTest(fixtures.TestBase, AssertsCompiledSQL):
             "ON b_1.b = c_1.c",
         )
 
+    def test_composed_join_alias_name_flat(self):
+        a = table("a", column("a"))
+        b = table("b", column("b"))
+        c = table("c", column("c"))
+        d = table("d", column("d"))
+
+        j1 = a.join(b, a.c.a == b.c.b)
+        j2 = c.join(d, c.c.c == d.c.d)
+
+        self.assert_compile(
+            j1.join(j2, b.c.b == c.c.c)._anonymous_fromclause(
+                name="foo", flat=True
+            ),
+            "a AS foo_a JOIN b AS foo_b ON foo_a.a = foo_b.b JOIN "
+            "(c AS foo_c JOIN d AS foo_d ON foo_c.c = foo_d.d) "
+            "ON foo_b.b = foo_c.c",
+        )
+
     def test_composed_join_alias(self):
         a = table("a", column("a"))
         b = table("b", column("b"))