/* | |
* The SimpleUML to RDB Sample demonstrates how to use QVT transformations for | |
* transforming platform independent model to platform specific model. | |
* | |
* It also demonstrates the following basic features of QVT language: | |
* helper queries, mapping guards, and resolution operations. | |
* | |
* Sample model pim.simpleuml is included to be used as an input for the transformation. | |
*/ | |
/* | |
* Two modeltypes are declared. The http NS URIs correspond to those used to register the | |
* Ecore models in the environment. Alternatively, a workspace metamodel may be used | |
* in conjunction with mappings defined in the project properties. | |
*/ | |
modeltype UML uses 'http://www.eclipse.org/qvt/1.0.0/Operational/examples/simpleuml'; | |
modeltype RDB uses 'http://www.eclipse.org/qvt/1.0.0/Operational/examples/rdb'; | |
/* | |
* The transformation signature declares that a UML modeltype is required as input, while an RDB | |
* modeltype is produced as an output. The UML modeltype is referenced as 'uml' throughout the | |
* transformation definition, while no name is needed for the output RDB modeltype. Note that OCL | |
* type and namespace notation are used in operational QVT (: and :: respectively). | |
*/ | |
transformation Simpleuml_To_Rdb(in uml : UML, out RDB); | |
/* | |
* The main entry point of the transformation. The 'uml' reference to the input UML modeltype | |
* instance is used to collect all rootObjects() of type Model. The rootObjects() operation | |
* is available on all QVT Model objects (extents) and returns those objects found at the | |
* root of the input model. The [UML::Model] statement following the call to rootObjects() | |
* is shorthand notation for the imperative select (xselect) construct where the condition is | |
* a type expression, which effectively performs a oclIsKindOf(UML::Model) with type recasting | |
* as a sequence. | |
* | |
* The invocation of the model2RDBModel() mapping is done using an -> operator, which is a | |
* shorthand notation for the imperative collect (xcollect) construct. Alternatively, it could | |
* be written as uml.rootObjects()[UML::Model]->xcollect(a | a.map model2RDBModel()); | |
*/ | |
main() { | |
uml.rootObjects()[UML::Model]->map model2RDBModel(); | |
} | |
/* | |
* This mapping returns an RDB::Model instance from the UML::Model passed from main(). The name | |
* attributes map directly using the OCL assignment operator. The RDB Model has a collection | |
* of schemas populated by the package2schemas() mapping. The Sequence of RDB::Schema objects | |
* that is returned by the mapping is converted into the required OrderedSet using the OCL operation | |
* asOrderedSet(). | |
* | |
* This mapping has no init or end section, leaving the body as an implicit population section. | |
*/ | |
mapping UML::Model::model2RDBModel() : RDB::Model { | |
name := self.name; | |
schemas := self.map package2schemas()->asOrderedSet(); | |
} | |
/* | |
* This mapping recursively invokes the package2schema() mapping to produce a Sequence of | |
* RDB Schema objects from a UML Package and its subpackages. Note the use of OCL union() | |
* and flatten() operations to produce a single flattened Sequence of Schema objects. | |
* | |
* There is no population section in this mapping, but an init section that assigns the | |
* Sequence of returned objects to the result. Alternatively, the statement below could | |
* have been used in the schemas assignment of the mapping above. | |
*/ | |
mapping UML::Package::package2schemas() : Sequence(RDB::Schema) { | |
init { | |
result := self.map package2schema()->asSequence()-> | |
union(self.getSubpackages()->map package2schemas()->flatten()); | |
} | |
} | |
/* | |
* This mapping creates an RDB Schema object from a UML Package. It includes a when clause to | |
* verify the passed Package contains persistent classes. Look below to see the logic employed | |
* within the hasPersistentClasses() query. | |
* | |
* The name attributes map directly, while the elements reference is populated with RDB Table | |
* objects mapped from persistent UML Class objects using a map invocation and similar | |
* shorthand notation used above. | |
*/ | |
mapping UML::Package::package2schema() : RDB::Schema | |
when { self.hasPersistentClasses() } | |
{ | |
name := self.name; | |
elements := self.ownedElements[UML::Class]->map persistentClass2table()->asOrderedSet() | |
} | |
/* | |
* This mapping produces an RDB Table object from a UML persistent Class object. The when clause | |
* uses the isPersistent() query below to see if the Class has a 'persistent' string as one of | |
* its stereotype strings. | |
* | |
* Again, the name attributes map directly. The Class is mapped to a set of TableColumn objects | |
* using the class2columns() mapping, the results of which are sorted by name using the OCL | |
* sortedBy() operation. The primaryKey is set using the class2primaryKey() mapping, while | |
* foreignKeys are set using a resolveIn function. This will allow us to resolve RDB ForeignKey | |
* objects created using the relationshipAttribute2foreignKey() mapping for each of the Class | |
* attributes. | |
*/ | |
mapping UML::Class::persistentClass2table() : RDB::Table | |
when { self.isPersistent() } | |
{ | |
name := self.name; | |
columns := self.map class2columns(self)->sortedBy(name); | |
primaryKey := self.map class2primaryKey(); | |
foreignKeys := self.attributes.resolveIn( | |
UML::Property::relationshipAttribute2foreignKey, | |
RDB::constraints::ForeignKey)->asOrderedSet(); | |
} | |
/* | |
* A PrimaryKey object is created from a Class by prefixing the name with 'PK' and resolving | |
* one (the first) Table created from the Class in order to obtain its primary key columns | |
* using a query. | |
*/ | |
mapping UML::Class::class2primaryKey() : RDB::constraints::PrimaryKey { | |
name := 'PK' + self.name; | |
includedColumns := self.resolveoneIn(UML::Class::persistentClass2table, RDB::Table).getPrimaryKeyColumns() | |
} | |
/* | |
* This mapping will create an OrderedSet of RDB TableColumn objects from a UML Class object. | |
* Similar to package2schemas(), this mapping has no population section, but just an init | |
* that assigns the result based on the union of type mappings from the Class and its | |
* generalizations (Class extends DataType). | |
* | |
* Note that this mapping is defined for type UML::Class and also takes a UML::Class named | |
* targetClass as a parameter. This pattern is used in several places within this | |
* transformation definition to account for how generalization in the UML model is | |
* mapped to columns in the RDB model. As properties and inherited properties are | |
* flattened into columns, the combined use of 'self' and 'target' parameter represent a | |
* way to allow multiple copies of columns for a given property source, as subsequent | |
* mapping invocations retrieve the same resulting columns from the trace model. | |
*/ | |
mapping UML::Class::class2columns(targetClass: UML::Class) : OrderedSet(RDB::TableColumn) { | |
init { | |
result := self.map dataType2columns(targetClass)-> | |
union(self.map generalizations2columns(targetClass))->asOrderedSet() | |
} | |
} | |
/* | |
* For the passed DataType, an OrderedSet of TableColumn objects is created. Again, the result | |
* is assigned within the init block to the union of several attribute to column mappings. | |
*/ | |
mapping UML::DataType::dataType2columns(in targetType : UML::DataType) : OrderedSet(RDB::TableColumn) { | |
init { | |
result := self.map primitiveAttributes2columns(targetType)-> | |
union(self.map enumerationAttributes2columns(targetType))-> | |
union(self.map relationshipAttributes2columns(targetType))-> | |
union(self.map associationAttributes2columns(targetType))->asOrderedSet() | |
} | |
} | |
/* | |
* This mapping creates an OrderedSet of TableColumn objects from a DataType object. | |
* The mapping declares three input parameters, including a prefix string and primary key | |
* boolean. | |
* | |
* The init section uses the result keyword with mapping invocation, as we've seen before. | |
* What's new in this mapping is the use of an object definition within a collect operation. | |
* Here, the OrderedSet of TableColumn objects returned from the dataType2columns() | |
* mapping is filtered to select only those marked as primary keys, which in turn are | |
* used within the context of the collect where those matching the TableColumn created | |
* using object are returned. | |
*/ | |
mapping UML::DataType::dataType2primaryKeyColumns(in prefix : String, in leaveIsPrimaryKey : Boolean, in targetType : UML::DataType) : OrderedSet(RDB::TableColumn) { | |
init { | |
result := self.map dataType2columns(self)->select(isPrimaryKey)-> | |
collect(c | object RDB::TableColumn { | |
name := prefix + '_' + c.name; | |
domain := c.domain; | |
type := object RDB::datatypes::PrimitiveDataType { | |
name := c.type.name; | |
}; | |
isPrimaryKey := leaveIsPrimaryKey | |
})->asOrderedSet(); | |
} | |
} | |
/* | |
* This mapping returns an OrderedSet of TableColumn objects by invoking the | |
* primitiveAttribute2column() mapping for each attribute of the DataType. | |
*/ | |
mapping UML::DataType::primitiveAttributes2columns(in targetType: UML::DataType) : OrderedSet(RDB::TableColumn) { | |
init { | |
result := self.attributes->map primitiveAttribute2column(targetType)->asOrderedSet() | |
} | |
} | |
/* | |
* This mapping creates a TableColumn from a Property when the isPrimitive() query | |
* returns true. The isPrimaryKey and name mappings are straightforward, while | |
* the type reference is created using the object keyword to create a new | |
* PrimitiveDataType with name initialized to the result of the query | |
* umlPrimitive2rdbPrimitive() with the type name passed as a parameter. | |
*/ | |
mapping UML::Property::primitiveAttribute2column(in targetType: UML::DataType) : RDB::TableColumn | |
when { self.isPrimitive() } | |
{ | |
isPrimaryKey := self.isPrimaryKey(); | |
name := self.name; | |
type := object RDB::datatypes::PrimitiveDataType { name := umlPrimitive2rdbPrimitive(self.type.name); }; | |
} | |
/* | |
* This mapping returns an OrderedSet of TableColumn objects by invoking the | |
* enumerationAttribute2column() mapping for each attribute of the DataType. | |
*/ | |
mapping UML::DataType::enumerationAttributes2columns(in targetType: UML::DataType) : OrderedSet(RDB::TableColumn) { | |
init { | |
result := self.attributes->map enumerationAttribute2column(targetType)->asOrderedSet() | |
} | |
} | |
/* | |
* This mapping creates a TableColumn from a Property when the isEnumeration() query | |
* returns true. The isPrimaryKey and name mappings are straightforward, while | |
* the type reference is created using the object keyword to create a new | |
* PrimitiveDataType with name initialized to 'int'. | |
*/ | |
mapping UML::Property::enumerationAttribute2column(in targetType: UML::DataType) : RDB::TableColumn | |
when { self.isEnumeration() } | |
{ | |
isPrimaryKey := self.isPrimaryKey(); | |
name := self.name; | |
type := object RDB::datatypes::PrimitiveDataType { name := 'int'; }; | |
} | |
/* | |
* This mapping creates an OrderedSet of TableColumn objects from relationship | |
* attributes. The check for if the DataType is a relationship is performed | |
* in the when clause of the invoked relationshipAttribute2foreignKey mapping. | |
*/ | |
mapping UML::DataType::relationshipAttributes2columns(in targetType: UML::DataType) : OrderedSet(RDB::TableColumn) { | |
init { | |
result := self.attributes->map relationshipAttribute2foreignKey(targetType)-> | |
collect(includedColumns)->asOrderedSet(); | |
} | |
} | |
/* | |
* This mapping creates a ForeignKey object from a DataType that returns true from | |
* the isRelationship() query in the when clause. The name is prefixed with 'FK'. | |
* The includedColumns collection is populated using the dataType2primaryKeyColumns() | |
* mapping on the Property type reference cast to DataType. | |
* | |
* The referredUC reference uses resolveoneIn, but with the late modifier. This | |
* causes the resolution to happen at the end of the transformation, thereby avoiding | |
* a second pass to resolve objects that may not have been created during execution | |
* at this point. | |
*/ | |
mapping UML::Property::relationshipAttribute2foreignKey(in targetType: UML::DataType) : RDB::constraints::ForeignKey | |
when { self.isRelationship() } | |
{ | |
name := 'FK' + self.name; | |
includedColumns := self.type.asDataType().map dataType2primaryKeyColumns(self.name, self.isIdentifying(), targetType); | |
referredUC := self.type.late resolveoneIn(UML::Class::class2primaryKey, RDB::constraints::PrimaryKey); | |
} | |
/* | |
* This mapping produces an OrderedSet of TableColumn objects from DataType attributes | |
* that return true from the isAssociation() query. The TableColumn objects are created | |
* using a call to the dataType2columns() mapping. | |
*/ | |
mapping UML::DataType::associationAttributes2columns(targetType : UML::DataType) : OrderedSet(RDB::TableColumn) { | |
init { | |
result := self.attributes[isAssociation()]-> | |
collect(type.asDataType()->map dataType2columns(targetType))->asOrderedSet() | |
} | |
} | |
/* | |
* This mapping returns an OrderedSet of TableColumn objects from a Class using the | |
* generalizations of the class and the class2columns() mapping. | |
*/ | |
mapping UML::Class::generalizations2columns(targetClass : UML::Class) : OrderedSet(RDB::TableColumn) { | |
init { | |
result := self.generalizations.general->map class2columns(targetClass)->flatten()->asOrderedSet(); | |
} | |
} | |
/* | |
* This query returns an OrderedSet of Package objects from a Package's ownedElements | |
* collection that are of type UML::Package using shorthand xselect notation. | |
*/ | |
query UML::Package::getSubpackages() : OrderedSet(UML::Package) { | |
return self.ownedElements[UML::Package]->asOrderedSet() | |
} | |
/* | |
* This query performs a type cast from a UML Type to a UML DataType. | |
*/ | |
query UML::Type::asDataType() : UML::DataType { | |
return self.oclAsType(UML::DataType) | |
} | |
/* | |
* This query returns true if the list of string stereotypes includes one | |
* equal to 'primaryKey'. | |
*/ | |
query UML::Property::isPrimaryKey() : Boolean { | |
return self.stereotype->includes('primaryKey') | |
} | |
/* | |
* This query returns true if the list of string stereotypes includes one | |
* equal to 'identifying'. | |
*/ | |
query UML::Property::isIdentifying() : Boolean { | |
return self.stereotype->includes('identifying') | |
} | |
/* | |
* This query returns true if the type attribute of the Property conforms | |
* to the UML PrimitiveType. | |
*/ | |
query UML::Property::isPrimitive() : Boolean { | |
return self.type.oclIsKindOf(UML::PrimitiveType) | |
} | |
/* | |
* This query returns true if the type attribute of the Property conforms | |
* to the UML Enumeration. | |
*/ | |
query UML::Property::isEnumeration() : Boolean { | |
return self.type.oclIsKindOf(UML::Enumeration) | |
} | |
/* | |
* This query returns true if the type attribute of the Property conforms | |
* to the UML DataType and returns true from the isPersistent() query. | |
*/ | |
query UML::Property::isRelationship() : Boolean { | |
return self.type.oclIsKindOf(UML::DataType) and self.type.isPersistent() | |
} | |
/* | |
* This query returns true if the type attribute of the Property conforms | |
* to the UML DataType and returns false from the isPersistent() query. | |
*/ | |
query UML::Property::isAssociation() : Boolean { | |
return self.type.oclIsKindOf(UML::DataType) and not self.type.isPersistent() | |
} | |
/* | |
* This query returns an OrderedSet of TableColumn objects from those columns | |
* where isPrimaryKey returns true. | |
*/ | |
query RDB::Table::getPrimaryKeyColumns() : OrderedSet(RDB::TableColumn) { | |
return self.columns->select(isPrimaryKey) | |
} | |
/* | |
* This query returns true if the list of string stereotypes includes one | |
* equal to 'persistent'. | |
*/ | |
query UML::ModelElement::isPersistent() : Boolean { | |
return self.stereotype->includes('persistent') | |
} | |
/* | |
* This query examines the contents of a Package to determine if there exists | |
* at least one Class that returns true for the isPersistent() query. | |
*/ | |
query UML::Package::hasPersistentClasses() : Boolean { | |
return self.ownedElements->exists( | |
let c : UML::Class = oclAsType(UML::Class) in | |
c.oclIsUndefined() implies c.isPersistent()) | |
} | |
/* | |
* This helper returns the RDB primitive string corresponding to the passed | |
* UML primitive string. This helper produces no side effects and could be | |
* written as a query, alternatively. | |
*/ | |
helper umlPrimitive2rdbPrimitive(in name : String) : String { | |
return if name = 'String' then 'varchar' else | |
if name = 'Boolean' then 'int' else | |
if name = 'Integer' then 'int' else | |
name | |
endif | |
endif | |
endif | |
} |