--- /dev/null
+.. 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.
+
.. 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:
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::
"""
- __slots__ = "__clause_element__", "info"
+ __slots__ = "__clause_element__", "info", "expressions"
def _memoized_method___clause_element__(self):
if self.adapter:
)
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.
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"