From: Jesse Bakker Date: Mon, 17 Aug 2020 14:35:35 +0000 (-0400) Subject: Make discriminator column used by ConcreteBase configurable X-Git-Tag: rel_1_3_19~1 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=584bcfa6c2c0de82f9d55532fdc5a4890cb3ac5e;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Make discriminator column used by ConcreteBase configurable The name of the virtual column used when using the :class:`_declarative.AbstractConcreteBase` and :class:`_declarative.ConcreteBase` classes can now be customized, to allow for models that have a column that is actually named ``type``. Pull request courtesy Jesse-Bakker. Fixes: #5513 Closes: #5514 Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/5514 Pull-request-sha: 5e7429f3531e2e22fffe996c9760905578d16ef9 Change-Id: I733737844d4f4e1f52dd2475a66c7044ff7292f5 (cherry picked from commit db2b2e21d6458d29a156a8532a52425333e0317a) --- diff --git a/doc/build/changelog/unreleased_13/5513.rst b/doc/build/changelog/unreleased_13/5513.rst new file mode 100644 index 0000000000..50e74046be --- /dev/null +++ b/doc/build/changelog/unreleased_13/5513.rst @@ -0,0 +1,9 @@ +.. change:: + :tags: usecase, declarative, orm + :tickets: 5513 + + The name of the virtual column used when using the + :class:`_declarative.AbstractConcreteBase` and + :class:`_declarative.ConcreteBase` classes can now be customized, to allow + for models that have a column that is actually named ``type``. Pull + request courtesy Jesse-Bakker. diff --git a/lib/sqlalchemy/ext/declarative/api.py b/lib/sqlalchemy/ext/declarative/api.py index e900a3beab..a501330be9 100644 --- a/lib/sqlalchemy/ext/declarative/api.py +++ b/lib/sqlalchemy/ext/declarative/api.py @@ -15,6 +15,7 @@ from .base import _as_declarative from .base import _declarative_constructor from .base import _DeferredMapperConfig from .base import _del_attribute +from .base import _get_immediate_cls_attr from .clsregistry import _class_resolver from ... import exc from ... import inspection @@ -464,6 +465,20 @@ class ConcreteBase(object): 'polymorphic_identity':'manager', 'concrete':True} + + The name of the discriminator column used by :func:`.polymorphic_union` + defaults to the name ``type``. To suit the use case of a mapping where an + actual column in a mapped table is already named ``type``, the + discriminator name can be configured by setting the + ``_concrete_discriminator_name`` attribute:: + + class Employee(ConcreteBase, Base): + _concrete_discriminator_name = '_concrete_discriminator' + + .. versionadded:: 1.3.19 Added the ``_concrete_discriminator_name`` + attribute to :class:`_declarative.ConcreteBase` so that the + virtual discriminator column name can be customized. + .. seealso:: :class:`.AbstractConcreteBase` @@ -474,12 +489,12 @@ class ConcreteBase(object): """ @classmethod - def _create_polymorphic_union(cls, mappers): + def _create_polymorphic_union(cls, mappers, discriminator_name): return polymorphic_union( OrderedDict( (mp.polymorphic_identity, mp.local_table) for mp in mappers ), - "type", + discriminator_name, "pjoin", ) @@ -489,10 +504,15 @@ class ConcreteBase(object): if m.with_polymorphic: return + discriminator_name = ( + _get_immediate_cls_attr(cls, "_concrete_discriminator_name") + or "type" + ) + mappers = list(m.self_and_descendants) - pjoin = cls._create_polymorphic_union(mappers) + pjoin = cls._create_polymorphic_union(mappers, discriminator_name) m._set_with_polymorphic(("*", pjoin)) - m._set_polymorphic_on(pjoin.c.type) + m._set_polymorphic_on(pjoin.c[discriminator_name]) class AbstractConcreteBase(ConcreteBase): @@ -635,7 +655,12 @@ class AbstractConcreteBase(ConcreteBase): mn = _mapper_or_none(klass) if mn is not None: mappers.append(mn) - pjoin = cls._create_polymorphic_union(mappers) + + discriminator_name = ( + _get_immediate_cls_attr(cls, "_concrete_discriminator_name") + or "type" + ) + pjoin = cls._create_polymorphic_union(mappers, discriminator_name) # For columns that were declared on the class, these # are normally ignored with the "__no_table__" mapping, @@ -654,7 +679,7 @@ class AbstractConcreteBase(ConcreteBase): def mapper_args(): args = m_args() - args["polymorphic_on"] = pjoin.c.type + args["polymorphic_on"] = pjoin.c[discriminator_name] return args to_map.mapper_args_fn = mapper_args diff --git a/test/ext/declarative/test_inheritance.py b/test/ext/declarative/test_inheritance.py index 083fdb0dbb..aba6d41915 100644 --- a/test/ext/declarative/test_inheritance.py +++ b/test/ext/declarative/test_inheritance.py @@ -2038,3 +2038,38 @@ class ConcreteExtensionConfigTest( "(SELECT offers.documenttype AS documenttype, offers.id AS id, " "'offer' AS type FROM offers) AS pjoin", ) + + def test_configure_discriminator_col(self): + """test #5513""" + + class Employee(AbstractConcreteBase, Base): + _concrete_discriminator_name = "_alt_discriminator" + employee_id = Column(Integer, primary_key=True) + + class Manager(Employee): + __tablename__ = "manager" + + __mapper_args__ = { + "polymorphic_identity": "manager", + "concrete": True, + } + + class Engineer(Employee): + __tablename__ = "engineer" + + __mapper_args__ = { + "polymorphic_identity": "engineer", + "concrete": True, + } + + configure_mappers() + self.assert_compile( + Session().query(Employee), + "SELECT pjoin.employee_id AS pjoin_employee_id, " + "pjoin._alt_discriminator AS pjoin__alt_discriminator " + "FROM (SELECT engineer.employee_id AS employee_id, " + "'engineer' AS _alt_discriminator FROM engineer " + "UNION ALL SELECT manager.employee_id AS employee_id, " + "'manager' AS _alt_discriminator " + "FROM manager) AS pjoin", + )