3.2. Relations

The following code excerpt adds some relations to the above transformation declaration.

transformation umlToRdbms(uml:SimpleUML, rdbms:SimpleRDBMS)
{
    top relation Package2Schema
    {
        theName: String;

        checkonly domain uml p:UMLPackage{
            name = theName
        };
        enforce domain rdbms s:Schema {
            name = theName
        };
    }

    top relation Class2Table
    {
        theName: String;
        prefix: String;

        checkonly domain uml c:UMLClass {
            name = theName,
            owningPackage = p:UMLPackage {},
            kind = UMLElementKind::Persistent
        };
        enforce domain rdbms t:Table {
            name = theName,
            schema = s:Schema {},
            columns = cl:Column {
                name = theName + '_PrimaryKeyColumn',
                type = 'NUMBER'
            },
            primaryKey = pk:PrimaryKey {
                name = prefix+ 'PrimaryKey'
                constitutingColumnc = cl
            }
        };
        when {
            Package2Schema(p, s);
        }
        where {
            prefix = theName + '_';
            Attribute2Column(c, t, prefix);
        }
    }

    relation Attribute2Column
    {
        checkonly domain uml c:UMLClass {};
        enforce domain rdbms t:Table {};
        primitive domain prefix:String;
        where {
            /* These relations are not shown in this example */
            ComplexAttribute2Column(c, t, prefix);
            PrimitiveAttribute2Column(c, t, prefix);
            SuperAttribute2Column(c, t, prefix);
        }
    }
}

The Package2Schema relation , defined by two complex domains "p" and "s" conforming to the uml and rdbms metamodels respectively, expresses the relationship that must hold between a UMLPackage and a Schema. Each complex domain specifies a pattern called an object template for finding, creating and modifying packages and schemas in the models passed to the transformation. "p" and "s" also denote template variables . The declarative form of this language is not familiar to imperative programmers and therefore demands a different thinking approach. The above simple Package2Schema relation is interpreted as follows:

Any package in a candidate model whose name attribute's value is set (including an empty string), is bound to the template variable "p" and transformed into a schema bound to "s", whereby the name of the schema is set to that of the package. Any existing schema that has no corresponding package will be deleted.

To pass the name of the package to the schema, the free variable theName is used to hold the matched value from the package.

[Note] Note

The deletion of schemas without corresponding packages in the above semantics shows that QVT was designed for strict synchronization between models. However, our experiences in projects show that this is sometimes not very practical if the schema did not originate from a previous transformation. Therefore the medini QVT engine will delete such a schema only if it originated from a previous transformation and its package has been deleted.

The type of a template variable can be either "complex type" or a collection of a "complex type" (i.e. a Set, List, etc.) known as collection template . Besides complex domains, a relation can also declare a primitive domain which has no object templates and is used to pass primitive values (see Attribute2Column). A domain can be specified as checkonly or enforce . Enforce means that if the above transformation is executed in the rdbms direction, schemas should be created and modified. In the uml direction however, violations of the relation should only be reported. Besides the execution direction of a transformation, the checkonly/enforce execution mode can also be specified at runtime. If the runtime mode is checkonly, it overrides any enforce specifications in the QVT script. This makes it possible to execute the above transformation in the rdbms direction in checkonly mode, even though its domain mode is enforce.

[Note] Note

The medini QVT engine does not yet support collection templates The engine also executes transformations only in enforce mode. To achieve the semantic of runtime checkonly to some extent, the engine can be used to evaluate Object Constraint Language (OCL) queries and constraints. QVT expressions are an extension of OCL expressions, but this guide doesn't explain how to write pure OCL expressions. See the OCL2.0 standard and the numerous literature on it for more details.

A relation can be either top- or non top-level . A top-level (e.g. Package2Schema) relation must always hold and is therefore always executed while a non top-level one (e.g. Attribute2Column) is only executed when invoked from another relation as we shall see later. As a result, a transformation must have at least one top relation.

When declaring a relation, an optional " when " and/or " where " clause can be specified as in the Class2Table relation. In these clauses, relation calls (e.g. Package2Schema(p,s)) and any valid OCL expression (e.g. prefix = theName + '_') can be specified. The variables passed in relation calls must correspond in type and order to the relation's domains.

A when-clause is a precondition under which the relation must hold. In our example, a class can be transformed to a table only if its package has been transformed to a schema. The evaluation of such a precondition results in the binding of values to unbound variables used in the precondition. In the "when" clause of the Class2Table relation, the corresponding packages and schemas fulfilling the Package2Schema relation are bound to the variables "p" and "s" respectively. These values are then used when evaluating the domains "c" and "t". The result is that the object template "c" only matches classes whose packages were bound to "p" in the when-clause. The schema of the resulting table is set to that bound to "s" in the when-clause.

Without this when-clause, every class which has a package (i.e. the value of the owningPackage attribute is set) will be transformed to a table for which a new schema with no name will be created. The Package2Schema top relation will be executed resulting in subsequent schemas containing no tables.

Although relation calls are allowed in "when" clauses, they don't result in the execution of a relation. If Package2Schema was not a top relation, it won't be executed in the above transformation. As a result, the Class2Table precondition would be false, thus the relation would fail. Relation calls in when-clauses can be treated as boolean expressions which can be used in complex OCL expressions.

A where-clause specifies the conditions that must be satisfied if the relation holds. In our example, the Attribute2Column relation creating columns for the class' attributes must be ensured if Class2Table holds. As mentioned above, a non top-level relation can be executed by invoking it explicitly. In fact, this is done in a where-clause. Therefore the call of the non top-level Attribute2Column relation causes it to be executed. Invoking a top relation in a where-clause is forbidden because it is always executed. Relation invocation allows complex relations to be composed from simpler ones. Values bound by an expression in a "where" clause can only be used to evaluate the target domain and subsequent expressions in the same where-clause. For example, in Class2Table's where-clause, the value of the prefix variable resulting from the first expression is used in the "pk" object template and Attribute2Column relation call. On the contrary, if a template variable like "pk" appears in a target domain and a relation call of a where-clause, the model element for the object template is first created before being passed to the relation call. This implies that the type of such a template variable must not be abstract since abstract classes cannot be instantiated.