Database Guide : GLORP Tutorial : Inheritance and Polymorphic Queries
Inheritance and Polymorphic Queries
For our examples we will use the class Parent and two subclasses of Parent, LeftChild and RightChild. Each of the classes has one instance variable.
A polymorphic query is one were we can query on the Parent class and as a result get instances of Parent, LeftChild and RightChild. If we set up the descriptor to support polymorphic queries then the following query will return instances of all Parent, LeftChild and RightChild objects stored in the database.
result := session readManyOf: Parent.
There are three common ways to support polymorphic queries with a relational database: table for each concrete class, table for each class and one table for all classes. GLORP currently supports the first and last.
Table for each Concrete Class
In this method each concrete class1 in the class hierarchy has its own table. That table holds all the instance variables for the class. For an example of this method we will make the Parent class an abstract class. So we need two tables, which we will call LEFTCHILDTABLE and RIGHTCHILDTABLE. The columns in each table are illustrated below.
LEFTCHILDTABLE
id
top
left_value
 
 
 
RIGHTCHILDTABLE
id
top
right_value
 
 
 
The class definitions are straightforward.
Object subclass: #Parent
instanceVariableNames: 'top id'
classVariableNames: ''
poolDictionaries: ''
 
Parent subclass: # LeftChild
instanceVariableNames: 'left '
classVariableNames: ''
poolDictionaries: ''
 
Parent subclass: #RightChild
instanceVariableNames: 'right'
classVariableNames: ''
poolDictionaries: ''
 
The descriptor uses one new feature a resolver.
DescriptorSystem subclass: #GlorpTutorialDescriptor
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
 
allTableNames
^#( 'LEFTCHILDTABLE' 'RIGHTCHILDTABLE' )
 
constructAllClasses
^(super constructAllClasses)
add: Parent;
add: LeftChild;
add: RightChild;
yourself
Note that even though Parent is an abstract class it is still listed in the constructAllClasses method. We do not add a class model for the class.
classModelForLeftChild: aClassModel
aClassModel newAttributeNamed: #left.
aClassModel newAttributeNamed: #top.
aClassModel newAttributeNamed: #id.
 
classModelForRightChild: aClassModel
aClassModel newAttributeNamed: #right.
aClassModel newAttributeNamed: #top.
aClassModel newAttributeNamed: #id.
Each of the class descriptor methods registers a resolver. The method typeResolverFor: results in a call to typeResolverForParent.
descriptorForParent: aDescriptor
(self typeResolverFor: Parent) register: aDescriptor abstract: true.
 
descriptorForLeftChild: aDescriptor
| leftTable |
leftTable := self tableNamed: 'LEFTCHILDTABLE'.
aDescriptor table: leftTable.
(aDescriptor newMapping: DirectMapping) from: #id
to: (leftTable fieldNamed: 'id').
(aDescriptor newMapping: DirectMapping) from: #top
to: (leftTable fieldNamed: 'top').
(aDescriptor newMapping: DirectMapping) from: #left
to: (leftTable fieldNamed: 'left_value').
(self typeResolverFor: Parent) register: aDescriptor.
 
descriptorForRightChild: aDescriptor
| rightTable |
rightTable := self tableNamed: 'RIGHTCHILDTABLE'.
aDescriptor table: rightTable.
(aDescriptor newMapping: DirectMapping) from: #id
to: (rightTable fieldNamed: 'id').
(aDescriptor newMapping: DirectMapping) from: #top
to: (rightTable fieldNamed: 'top').
(aDescriptor newMapping: DirectMapping) from: #right
to: (rightTable fieldNamed: 'right_value').
(self typeResolverFor: Parent) register: aDescriptor.
 
tableForLEFTCHILDTABLE: aTable
(aTable createFieldNamed: 'left_value' type: (platform varChar: 50)).
(aTable createFieldNamed: 'top' type: (platform varChar: 50)).
(aTable createFieldNamed: 'id' type: (platform sequence)) bePrimaryKey.
 
tableForRIGHTCHILDTABLE: aTable
(aTable createFieldNamed: 'right_value' type: (platform varChar: 50)).
(aTable createFieldNamed: 'top' type: (platform varChar: 50)).
(aTable createFieldNamed: 'id' type: (platform sequence)) bePrimaryKey.
 
typeResolverForParent
^HorizontalTypeResolver forRootClass: Parent.
The typeResolverForParent method returns the actual resolver, which is used by GLORP to determine which tables to examine for polymorphic queries. With the above setup the following query will return all Parent, LeftChild and RightChild objects with left as the value of the instance variable top. Note that for the descriptors for LeftChild and RightChild the typeResolverFor: registers the parent class (Parent), not the LeftChild or RightChild. This is what permits the polymorphic query.
result := session readManyOf: Parent where: [:each | each top = 'left'].
We can still write queries directly on the subclasses if appropriate.
result := session readManyOf: LeftChild where: [:each | each top = 'left'].
If the inheritance hierarchy is deeper, we may have multiple levels of polymorphic queries. Say we add a Bottom class to LeftChild as shown below. It may occur that we need to write polymorphic queries on Parent’s hierarchy and polymorphic queries on LeftChild’s hierarchy. If we wish to do this then in the Bottom class descriptor we need the following two type resolvers.
(self typeResolverFor: Parent) register: aDescriptor.
(self typeResolverFor: LeftChild) register: aDescriptor.
 
There is one restriction on polymorphic queries when using Table for each Concrete Class, which is one can not use orderBy:. The query makes multiple requests to the database as it has to read both tables, so we cannot have the database order the results for use. As a result the following request will raise an exception.
session readManyOf: Parent orderBy: #top
One Table for all Classes
In this method of organizing the tables there is one table2 that holds the data for all objects in the class hierarchy. In this example the one table is called ALL_DATA. The columns of the table are illustrated below. The object_type column is used to store the type of the object in the row. This allows GLORP to create an instance of the correct class when it reads a row in the table. Note that regardless of what type of object is in a row, one column, either left_value or right_value, will be null. While this method does waste space in the database, performing a polymorphic query will be faster as there is only one table to access.
ALL_DATA
id
top
left_value
right_value
object_type
 
 
 
 
 
The class definitions remain the same as in the Table for each Concrete Class example. The descriptor changes a bit. We use a different resolver, need to supply values for object_type column and need more information for the Parent class.
DescriptorSystem subclass: #GlorpTutorialDescriptor
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
 
allTableNames
^#( 'ALL_DATA')
 
constructAllClasses
^(super constructAllClasses)
add: Parent;
add: LeftChild;
add: RightChild;
yourself
 
classModelForLeftChild: aClassModel
aClassModel newAttributeNamed: #left.
aClassModel newAttributeNamed: #top.
aClassModel newAttributeNamed: #id.
 
classModelForRightChild: aClassModel
aClassModel newAttributeNamed: #right.
aClassModel newAttributeNamed: #top.
aClassModel newAttributeNamed: #id.
Note how to indicate the value of the object_type column for LeftChild objects with the resolver.
descriptorForLeftChild: aDescriptor
| leftTable |
leftTable := self tableNamed: 'ALL_DATA'.
aDescriptor table: leftTable.
(aDescriptor newMapping: DirectMapping) from: #id
to: (leftTable fieldNamed: 'id').
(aDescriptor newMapping: DirectMapping) from: #top
to: (leftTable fieldNamed: 'top').
(aDescriptor newMapping: DirectMapping) from: #left
to: (leftTable fieldNamed: 'left_value').
(self typeResolverFor: Parent) register: aDescriptor keyedBy: 'L' field: (leftTable fieldNamed: 'object_type').
 
descriptorForRightChild: aDescriptor
| rightTable |
rightTable := self tableNamed: 'ALL_DATA'.
aDescriptor table: rightTable.
(aDescriptor newMapping: DirectMapping) from: #id
to: (rightTable fieldNamed: 'id').
(aDescriptor newMapping: DirectMapping) from: #top
to: (rightTable fieldNamed: 'top').
(aDescriptor newMapping: DirectMapping) from: #right
to: (rightTable fieldNamed: 'right_value').
(self typeResolverFor: Parent) register: aDescriptor keyedBy: 'R' field: (rightTable fieldNamed: 'object_type').
Even though Parent is still an abstract class, GLORP requires us to give the mappings for the Parent instance variables. If Parent were not abstract we would have to provide a key value for the column object_type.
descriptorForParent: aDescriptor
| table |
table := self tableNamed: 'ALL_DATA'.
aDescriptor table: table.
(aDescriptor newMapping: DirectMapping) from: #id
to: (table fieldNamed: 'id').
(aDescriptor newMapping: DirectMapping) from: #top
to: (table fieldNamed: 'top').
(self typeResolverFor: Parent) register: aDescriptor abstract: true
 
tableForALL_DATA: aTable
(aTable createFieldNamed: 'top' type: (platform varChar: 50)).
(aTable createFieldNamed: 'left_value' type: (platform varChar: 50)).
(aTable createFieldNamed: 'right_value' type: (platform varChar: 50)).
(aTable createFieldNamed: 'object_type' type: (platform varChar: 2)).
(aTable createFieldNamed: 'id' type: (platform sequence)) bePrimaryKey.
 
typeResolverForParent
^FilteredTypeResolver forRootClass: Parent.
Defining the Resolver for a Class
In the above examples the resolver type of the class Parent was defined in a method in the GlorpTutorialDescriptor. If you prefer you can define in the Parent class itself. So you could delete the GlorpTutorialDescriptor>>typeResolverForParent method and add the following class method to Parent
Parent class>>glorpTypeResolver
^FilteredTypeResolver forRootClass: Parent
 

1 See Concrete Table Inheritance pattern pp 293-301 in Fowler [2003]

2 See Single Table Inheritance pattern pp 293-301 in Fowler [2003]

Last modified date: 05/20/2020