]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Add ColumnProperty.Comparator.expressions
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 20 Apr 2020 16:24:40 +0000 (12:24 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 20 Apr 2020 19:59:37 +0000 (15:59 -0400)
Added an accessor :attr:`.ColumnProperty.Comparator.expressions` which
provides access to the group of columns mapped under a multi-column
:class:`.ColumnProperty` attribute.

Fixes: #5262
Change-Id: I44cf53ff0e6cf76a0c90eee4638ca96da3df8088

doc/build/changelog/unreleased_13/5262.rst [new file with mode: 0644]
doc/build/orm/internals.rst
doc/build/orm/nonstandard_mappings.rst
lib/sqlalchemy/orm/properties.py
test/orm/inheritance/test_basic.py

diff --git a/doc/build/changelog/unreleased_13/5262.rst b/doc/build/changelog/unreleased_13/5262.rst
new file mode 100644 (file)
index 0000000..32d3405
--- /dev/null
@@ -0,0 +1,8 @@
+.. change::
+    :tags: usecase, orm
+    :tickets: 5262
+
+    Added an accessor :attr:`.ColumnProperty.Comparator.expressions` which
+    provides access to the group of columns mapped under a multi-column
+    :class:`.ColumnProperty` attribute.
+
index d0e7c1ce4646421b7fa51221203bebbba6ad035a..e9f1b431eaf952fc34948452a17f69a4e9128aea 100644 (file)
@@ -21,6 +21,17 @@ sections, are listed here.
 .. autoclass:: sqlalchemy.orm.properties.ColumnProperty
     :members:
 
+    .. attribute:: Comparator.expressions
+
+         The full sequence of columns referenced by this
+         attribute, adjusted for any aliasing in progress.
+
+         .. versionadded:: 1.3.17
+
+         .. seealso::
+
+            :ref:`maptojoin` - usage example
+
 .. autoclass:: sqlalchemy.orm.descriptor_props.CompositeProperty
     :members:
 
index 01a615ae23241f65903080f7532974fcf33acf5f..81679dd014da351aa0055ace8f0d46c7b9f7e9e7 100644 (file)
@@ -68,6 +68,19 @@ The natural primary key of the above mapping is the composite of
 is represented from an ``AddressUser`` object as
 ``(AddressUser.id, AddressUser.address_id)``.
 
+When referring to the ``AddressUser.id`` column, most SQL expressions will
+make use of only the first column in the list of columns mapped, as the
+two columns are synonymous.  However, for the special use case such as
+a GROUP BY expression where both columns must be referenced at the same
+time while making use of the proper context, that is, accommodating for
+aliases and similar, the accessor :attr:`.ColumnProperty.Comparator.expressions`
+may be used::
+
+    q = session.query(AddressUser).group_by(*AddressUser.id.expressions)
+
+.. versionadded:: 1.3.17 Added the
+   :attr:`.ColumnProperty.Comparator.expressions` accessor.
+
 
 .. note::
 
index 1022f4ef69b6e7423713da5cafb43f4b0fafb69d..4cf316ac72d2fad164330190f5e0d7a03f810602 100644 (file)
@@ -337,7 +337,7 @@ class ColumnProperty(StrategizedProperty):
 
         """
 
-        __slots__ = "__clause_element__", "info"
+        __slots__ = "__clause_element__", "info", "expressions"
 
         def _memoized_method___clause_element__(self):
             if self.adapter:
@@ -354,12 +354,40 @@ class ColumnProperty(StrategizedProperty):
                 )
 
         def _memoized_attr_info(self):
+            """The .info dictionary for this attribute."""
+
             ce = self.__clause_element__()
             try:
                 return ce.info
             except AttributeError:
                 return self.prop.info
 
+        def _memoized_attr_expressions(self):
+            """The full sequence of columns referenced by this
+            attribute, adjusted for any aliasing in progress.
+
+            .. versionadded:: 1.3.17
+
+            """
+            if self.adapter:
+                return [
+                    self.adapter(col, self.prop.key)
+                    for col in self.prop.columns
+                ]
+            else:
+                # no adapter, so we aren't aliased
+                # assert self._parententity is self._parentmapper
+                return [
+                    col._annotate(
+                        {
+                            "parententity": self._parententity,
+                            "parentmapper": self._parententity,
+                            "orm_key": self.prop.key,
+                        }
+                    )
+                    for col in self.prop.columns
+                ]
+
         def _fallback_getattr(self, key):
             """proxy attribute access down to the mapped column.
 
index cb4e70ecb2e3cb7e0f42f594cabe5ed3feb5718a..02addc6f58e5d8d8436de1feac5ae702064900b8 100644 (file)
@@ -131,6 +131,50 @@ class O2MTest(fixtures.MappedTest):
         eq_(result[1].parent_foo.data, "foo #1")
 
 
+class ColExpressionsTest(fixtures.DeclarativeMappedTest):
+    __backend__ = True
+
+    @classmethod
+    def setup_classes(cls):
+        Base = cls.DeclarativeBasic
+
+        class A(Base):
+            __tablename__ = "a"
+            id = Column(
+                Integer, primary_key=True, test_needs_autoincrement=True
+            )
+            type = Column(String(10))
+            __mapper_args__ = {
+                "polymorphic_on": type,
+                "polymorphic_identity": "a",
+            }
+
+        class B(A):
+            __tablename__ = "b"
+            id = Column(ForeignKey("a.id"), primary_key=True)
+            data = Column(Integer)
+            __mapper_args__ = {"polymorphic_identity": "b"}
+
+    @classmethod
+    def insert_data(cls, connection):
+        A, B = cls.classes("A", "B")
+        s = Session(connection)
+
+        s.add_all([B(data=5), B(data=7)])
+        s.commit()
+
+    def test_group_by(self):
+        B = self.classes.B
+        s = Session()
+
+        rows = (
+            s.query(B.id.expressions[0], B.id.expressions[1], func.sum(B.data))
+            .group_by(*B.id.expressions)
+            .all()
+        )
+        eq_(rows, [(1, 1, 5), (2, 2, 7)])
+
+
 class PolyExpressionEagerLoad(fixtures.DeclarativeMappedTest):
     run_setup_mappers = "once"
     __dialect__ = "default"