]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- redo the docs for concrete inheritance to more strongly
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 20 Jan 2016 18:34:57 +0000 (13:34 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 20 Jan 2016 18:34:57 +0000 (13:34 -0500)
favor declarative, fixes #2890

doc/build/orm/extensions/declarative/inheritance.rst
doc/build/orm/inheritance.rst

index 59d68545655ef54a1df691049fff378c95162c13..bf0f2a6e1e9ca8dfba49fbeedede38242d5e7ac3 100644 (file)
@@ -194,6 +194,8 @@ Because we're using single table inheritance, we're sure that in this case,
 and single-table inheritance, we might want our mixin to check more carefully
 if ``cls.__table__`` is really the :class:`.Table` we're looking for.
 
+.. _declarative_concrete_table:
+
 Concrete Table Inheritance
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -254,8 +256,6 @@ Helper classes provides a simpler pattern for concrete inheritance.
 With these objects, the ``__declare_first__`` helper is used to configure the
 "polymorphic" loader for the mapper after all subclasses have been declared.
 
-.. versionadded:: 0.7.3
-
 An abstract base can be declared using the
 :class:`.AbstractConcreteBase` class::
 
index 290d8099ec4fbcfb0c72b680a73b8275ccd4e80e..5645b55991a4335a71b76e351b36dc2ee4da9189 100644 (file)
@@ -579,53 +579,111 @@ their own.
 Concrete Table Inheritance
 --------------------------
 
-.. note::
-
-    this section is currently using classical mappings.  The
-    Declarative system fully supports concrete inheritance
-    however.   See the links below for more information on using
-    declarative with concrete table inheritance.
-
-This form of inheritance maps each class to a distinct table, as below:
+This form of inheritance maps each class to a distinct table.  As concrete
+inheritance has a bit more conceptual overhead, first we'll illustrate
+what these tables look like as Core table metadata:
 
 .. sourcecode:: python+sql
 
-    employees_table = Table('employees', metadata,
-        Column('employee_id', Integer, primary_key=True),
+    employees_table = Table(
+        'employee', metadata,
+        Column('id', Integer, primary_key=True),
         Column('name', String(50)),
     )
 
-    managers_table = Table('managers', metadata,
-        Column('employee_id', Integer, primary_key=True),
+    managers_table = Table(
+        'manager', metadata,
+        Column('id', Integer, primary_key=True),
         Column('name', String(50)),
         Column('manager_data', String(50)),
     )
 
-    engineers_table = Table('engineers', metadata,
-        Column('employee_id', Integer, primary_key=True),
+    engineers_table = Table(
+        'engineer', metadata,
+        Column('id', Integer, primary_key=True),
         Column('name', String(50)),
         Column('engineer_info', String(50)),
     )
 
-Notice in this case there is no ``type`` column. If polymorphic loading is not
-required, there's no advantage to using ``inherits`` here; you just define a
-separate mapper for each class.
+Notice in this case there is no ``type`` column; for polymorphic loading,
+additional steps will be needed in order to "manufacture" this information
+during a query.
 
-.. sourcecode:: python+sql
+Using classical mapping, we can map our three classes independently without
+any relationship between them; the fact that ``Engineer`` and ``Manager``
+inherit from ``Employee`` does not have any impact on a classical mapping::
+
+    class Employee(object):
+        pass
+
+    class Manager(Employee):
+        pass
+
+    class Engineer(Employee):
+        pass
 
     mapper(Employee, employees_table)
     mapper(Manager, managers_table)
     mapper(Engineer, engineers_table)
 
-To load polymorphically, the ``with_polymorphic`` argument is required, along
-with a selectable indicating how rows should be loaded. In this case we must
-construct a UNION of all three tables. SQLAlchemy includes a helper function
-to create these called :func:`~sqlalchemy.orm.util.polymorphic_union`, which
+However when using Declarative, Declarative assumes an inheritance mapping
+between the classes because they are already in an inheritance relationship.
+So to map our three classes declaratively, we must include the
+:paramref:`.orm.mapper.concrete` parameter within the ``__mapper_args__``::
+
+    class Employee(Base):
+        __tablename__ = 'employee'
+
+        id = Column(Integer, primary_key=True)
+        name = Column(String(50))
+
+    class Manager(Employee):
+        __tablename__ = 'manager'
+
+        id = Column(Integer, primary_key=True)
+        name = Column(String(50))
+        manager_data = Column(String(50))
+
+        __mapper_args__ = {
+            'concrete': True
+        }
+
+    class Engineer(Employee):
+        __tablename__ = 'engineer'
+
+        id = Column(Integer, primary_key=True)
+        name = Column(String(50))
+        engineer_info = Column(String(50))
+
+        __mapper_args__ = {
+            'concrete': True
+        }
+
+Two critical points should be noted:
+
+* We must **define all columns explicitly** on each subclass, even those of
+  the same name.  A column such as
+  ``Employee.name`` here is **not** copied out to the tables mapped
+  by ``Manager`` or ``Engineer`` for us.
+
+* while the ``Engineer`` and ``Manager`` classes are
+  mapped in an inheritance relationship with ``Employee``, they still **do not
+  include polymorphic loading**.
+
+To load polymorphically, the :paramref:`.orm.mapper.with_polymorphic` argument is required, along
+with a selectable indicating how rows should be loaded.   Polymorphic loading
+is most inefficient with concrete inheritance, so if we do seek this style of
+loading, while it is possible it's less recommended. In the case of concrete
+inheritance, it means we must construct a UNION of all three tables.
+
+First illustrating this with classical mapping, SQLAlchemy includes a helper
+function to create this UNION called :func:`~sqlalchemy.orm.util.polymorphic_union`, which
 will map all the different columns into a structure of selects with the same
 numbers and names of columns, and also generate a virtual ``type`` column for
-each subselect:
+each subselect.  The function is called **after** all three tables are declared,
+and is then combined with the mappers::
 
-.. sourcecode:: python+sql
+    from sqlalchemy.orm import polymorphic_union
 
     pjoin = polymorphic_union({
         'employee': employees_table,
@@ -652,34 +710,167 @@ Upon select, the polymorphic union produces a query like this:
 
     session.query(Employee).all()
     {opensql}
-    SELECT pjoin.type AS pjoin_type,
-            pjoin.manager_data AS pjoin_manager_data,
-            pjoin.employee_id AS pjoin_employee_id,
-    pjoin.name AS pjoin_name, pjoin.engineer_info AS pjoin_engineer_info
+    SELECT
+        pjoin.id AS pjoin_id,
+        pjoin.name AS pjoin_name,
+        pjoin.type AS pjoin_type,
+        pjoin.manager_data AS pjoin_manager_data,
+        pjoin.engineer_info AS pjoin_engineer_info
     FROM (
-        SELECT employees.employee_id AS employee_id,
-            CAST(NULL AS VARCHAR(50)) AS manager_data, employees.name AS name,
-            CAST(NULL AS VARCHAR(50)) AS engineer_info, 'employee' AS type
-        FROM employees
-    UNION ALL
-        SELECT managers.employee_id AS employee_id,
-            managers.manager_data AS manager_data, managers.name AS name,
-            CAST(NULL AS VARCHAR(50)) AS engineer_info, 'manager' AS type
-        FROM managers
-    UNION ALL
-        SELECT engineers.employee_id AS employee_id,
-            CAST(NULL AS VARCHAR(50)) AS manager_data, engineers.name AS name,
-        engineers.engineer_info AS engineer_info, 'engineer' AS type
-        FROM engineers
+        SELECT
+            employee.id AS id,
+            employee.name AS name,
+            CAST(NULL AS VARCHAR(50)) AS manager_data,
+            CAST(NULL AS VARCHAR(50)) AS engineer_info,
+            'employee' AS type
+        FROM employee
+        UNION ALL
+        SELECT
+            manager.id AS id,
+            manager.name AS name,
+            manager.manager_data AS manager_data,
+            CAST(NULL AS VARCHAR(50)) AS engineer_info,
+            'manager' AS type
+        FROM manager
+        UNION ALL
+        SELECT
+            engineer.id AS id,
+            engineer.name AS name,
+            CAST(NULL AS VARCHAR(50)) AS manager_data,
+            engineer.engineer_info AS engineer_info,
+            'engineer' AS type
+        FROM engineer
     ) AS pjoin
-    []
 
-Concrete Inheritance with Declarative
-++++++++++++++++++++++++++++++++++++++
+The above UNION query needs to manufacture "NULL" columns for each subtable
+in order to accommodate for those columns that aren't part of the mapping.
+
+In order to map with concrete inheritance and polymorphic loading using
+Declarative, the challenge is to have the polymorphic union ready to go
+when the mappings are created.  One way to achieve this is to continue to
+define the table metadata before the actual mapped classes, and specify
+them to each class using ``__table__``::
+
+    class Employee(Base):
+        __table__ = employee_table
+        __mapper_args__ = {
+            'polymorphic_on':pjoin.c.type
+            'with_polymorphic': ('*', pjoin),
+            'polymorphic_identity':'employee'
+        }
+
+    class Engineer(Employee):
+        __table__ = engineer_table
+        __mapper_args__ = {'polymorphic_identity':'engineer', 'concrete':True}
+
+    class Manager(Employee):
+        __table__ = manager_table
+        __mapper_args__ = {'polymorphic_identity':'manager', 'concrete':True}
+
+Another way is to use a special helper class that takes on the fairly
+complicated task of deferring the production of :class:`.Mapper` objects
+until all table metadata has been collected, and the polymorphic union to which
+the mappers will be associated will be available.  This is available via
+the :class:`.AbstractConcreteBase` and :class:`.ConcreteBase` classes.  For
+our example here, we're using a "concrete" base, e.g. an ``Employee`` row
+can exist by itself that is not an ``Engineer`` or a ``Manager``.   The
+mapping would look like::
+
+    from sqlalchemy.ext.declarative import ConcreteBase
+
+    class Employee(ConcreteBase, Base):
+        __tablename__ = 'employee'
+        id = Column(Integer, primary_key=True)
+        name = Column(String(50))
+
+        __mapper_args__ = {
+            'polymorphic_identity':'employee',
+            'concrete':True
+        }
+
+    class Manager(Employee):
+        __tablename__ = 'manager'
+        id = Column(Integer, primary_key=True)
+        name = Column(String(50))
+        manager_data = Column(String(40))
+
+        __mapper_args__ = {
+            'polymorphic_identity':'manager',
+            'concrete':True
+        }
+
+    class Engineer(Employee):
+        __tablename__ = 'engineer'
+        id = Column(Integer, primary_key=True)
+        name = Column(String(50))
+        engineer_info = Column(String(40))
+
+        __mapper_args__ = {
+            'polymorphic_identity':'engineer',
+            'concrete':True
+        }
+
+There is also the option to use a so-called "abstract" base; where we wont
+actually have an ``employee`` table at all, and instead will only have
+``manager`` and ``engineer`` tables.  The ``Employee`` class will never be
+instantiated directly.  The change here is that the base mapper is mapped
+directly to the "polymorphic union" selectable, which no longer includes
+the ``employee`` table.  In classical mapping, this is::
+
+    from sqlalchemy.orm import polymorphic_union
+
+    pjoin = polymorphic_union({
+        'manager': managers_table,
+        'engineer': engineers_table
+    }, 'type', 'pjoin')
+
+    employee_mapper = mapper(Employee, pjoin,
+                                        with_polymorphic=('*', pjoin),
+                                        polymorphic_on=pjoin.c.type)
+    manager_mapper = mapper(Manager, managers_table,
+                                        inherits=employee_mapper,
+                                        concrete=True,
+                                        polymorphic_identity='manager')
+    engineer_mapper = mapper(Engineer, engineers_table,
+                                        inherits=employee_mapper,
+                                        concrete=True,
+                                        polymorphic_identity='engineer')
+
+Using the Declarative helpers, the :class:`.AbstractConcreteBase` helper
+can produce this; the mapping would be::
+
+    from sqlalchemy.ext.declarative import AbstractConcreteBase
+
+    class Employee(AbstractConcreteBase, Base):
+        pass
+
+    class Manager(Employee):
+        __tablename__ = 'manager'
+        id = Column(Integer, primary_key=True)
+        name = Column(String(50))
+        manager_data = Column(String(40))
+
+        __mapper_args__ = {
+            'polymorphic_identity':'manager',
+            'concrete':True
+        }
+
+    class Engineer(Employee):
+        __tablename__ = 'engineer'
+        id = Column(Integer, primary_key=True)
+        name = Column(String(50))
+        engineer_info = Column(String(40))
+
+        __mapper_args__ = {
+            'polymorphic_identity':'engineer',
+            'concrete':True
+        }
+
+.. seealso::
+
+    :ref:`declarative_concrete_table` - in the Declarative reference documentation
 
-.. versionadded:: 0.7.3
-    The :ref:`declarative_toplevel` module includes helpers for concrete
-    inheritance. See :ref:`declarative_concrete_helpers` for more information.
+    :ref:`declarative_concrete_helpers` - in the Declarative reference documentation
 
 Using Relationships with Inheritance
 ------------------------------------