From: Mike Bayer Date: Mon, 20 Apr 2020 16:24:40 +0000 (-0400) Subject: Add ColumnProperty.Comparator.expressions X-Git-Tag: rel_1_4_0b1~368^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6abbda34ebb2c154ccae12d749968fe72f27f372;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Add ColumnProperty.Comparator.expressions 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 --- diff --git a/doc/build/changelog/unreleased_13/5262.rst b/doc/build/changelog/unreleased_13/5262.rst new file mode 100644 index 0000000000..32d3405b47 --- /dev/null +++ b/doc/build/changelog/unreleased_13/5262.rst @@ -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. + diff --git a/doc/build/orm/internals.rst b/doc/build/orm/internals.rst index d0e7c1ce46..e9f1b431ea 100644 --- a/doc/build/orm/internals.rst +++ b/doc/build/orm/internals.rst @@ -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: diff --git a/doc/build/orm/nonstandard_mappings.rst b/doc/build/orm/nonstandard_mappings.rst index 01a615ae23..81679dd014 100644 --- a/doc/build/orm/nonstandard_mappings.rst +++ b/doc/build/orm/nonstandard_mappings.rst @@ -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:: diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py index 1022f4ef69..4cf316ac72 100644 --- a/lib/sqlalchemy/orm/properties.py +++ b/lib/sqlalchemy/orm/properties.py @@ -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. diff --git a/test/orm/inheritance/test_basic.py b/test/orm/inheritance/test_basic.py index cb4e70ecb2..02addc6f58 100644 --- a/test/orm/inheritance/test_basic.py +++ b/test/orm/inheritance/test_basic.py @@ -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"