| /* |
| * 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 |
| } |