pre {
"Running ETL".println();
var db : new DB!Database;
}
post {
// Store traceability links in custom model
var trace : new Trace!Trace;
for (t in transTrace.transformations) {
var link : new Trace!TraceLink;
link.sources.add(t.source);
link.targets = t.targets;
link.description = "Transformed by " + t.getRule().name;
trace.links.add(link);
}
}
// Transforms a class into a table and
// a primary key column
rule Class2Table
transform c : OO!Class
to t : DB!Table, pk : DB!Column {
t.name = c.name;
t.database = db;
// Fill the details of the primary key
// of the table
pk.name = t.primaryKeyName();
pk.type = "INT";
t.columns.add(pk);
t.primaryKeys.add(pk);
// If the class extends some other class
// create a foreign key pointing towards
// the primary key of the parent class
if (c.`extends`.isDefined()){
var fk : new DB!ForeignKey;
var childFkCol : new DB!Column;
var parentFkCol : DB!Column;
var parentTable : DB!Table;
parentTable ::= c.`extends`;
parentFkCol = parentTable.primaryKeys.first();
childFkCol.name = parentFkCol.name;
childFkCol.type = "INT";
childFkCol.table = t;
fk.database = db;
fk.parent = parentFkCol;
fk.child = childFkCol;
fk.name = c.name + "Extends" + c.`extends`.name;
}
}
// Transforms a single-valued attribute
// to a column
rule SingleValuedAttribute2Column
transform a : OO!Attribute
to c : DB!Column {
guard : not a.isMany
c.name = a.name;
c.table ::= a.owner;
c.type = a.type.name.toDbType();
}
// Transforms a multi-valued attribute
// to a table where its values are stored
// and a foreign key
rule MultiValuedAttribute2Table
transform a : OO!Attribute
to t : DB!Table, pkCol : DB!Column, valueCol : DB!Column,
fkCol : DB!Column, fk : DB!ForeignKey {
guard : a.isMany
// The table that stores the values
// has an "id" column and a "value" column
t.name = a.valuesTableName();
t.database = db;
pkCol.name = "id";
pkCol.table = t;
pkCol.type = "INT";
valueCol.name = "value";
valueCol.table = t;
valueCol.type = a.type.name.toDbType();
// Another column is added into the table
// to link with the "id" column of the
// values table
fkCol.name = a.name + "Id";
fkCol.table ::= a.owner;
fkCol.type = "INT";
// The foreign key that connects
// the two columns is defined
fk.parent = pkCol;
fk.child = fkCol;
fk.database = db;
}
// Transforms a referecne into a foreign key
rule Reference2ForeignKey
transform r : OO!Reference
to fk : DB!ForeignKey, fkCol : DB!Column {
fkCol.table ::= r.type;
fkCol.name = r.name + "Id";
fkCol.type = "INT";
fk.database = db;
fk.parent = r.owner.equivalent().primaryKeys.first();
fk.child = fkCol;
fk.name = r.name;
}
operation DB!Table primaryKeyName() : String {
return self.name.firstToLowerCase() + "Id";
}
operation OO!Attribute valuesTableName() : String {
return self.owner.name + "_" + self.name.firstToUpperCase() + "Values";
}
operation Any toDbType() : String {
var mapping : OO2DB!TypeMapping;
mapping = OO2DB!TypeMapping.allInstances().
select(tm|tm.source = self).first;
if (not mapping.isDefined()){
("Cannot find DB type for OO type " + self +
". Setting the default.").println();
return OO2DB!TypeMap.allInstances().first().`default`.target;
}
else {
return mapping.target;
}
}
@namespace(uri="OO", prefix="OO")
package OO;
class Model extends Package {
}
abstract class PackageableElement extends NamedElement {
ref Package#contents ~package;
}
abstract class AnnotatedElement {
val Annotation[*] annotations;
}
class Annotation {
attr String key;
attr String value;
}
abstract class NamedElement extends AnnotatedElement {
attr String name;
}
class Package extends PackageableElement {
val PackageableElement[*]#~package contents;
}
abstract class ~Classifier extends PackageableElement {
}
class ExternalClass extends ~Class {
}
class ~Class extends ~Classifier {
ref ~Class#extendedBy ~extends;
ref ~Class[*]#~extends extendedBy;
val Feature[*]#owner features;
attr Boolean isAbstract;
}
class Datatype extends ~Classifier {
}
abstract class Feature extends NamedElement {
ref ~Class#features owner;
ref ~Classifier type;
attr VisibilityEnum visibility;
}
abstract class StructuralFeature extends Feature {
attr Boolean isMany;
}
class Operation extends Feature {
val Parameter[*]#owner parameters;
}
class Parameter extends NamedElement {
ref ~Classifier type;
ref Operation#parameters owner;
}
class Reference extends StructuralFeature {
}
class Attribute extends StructuralFeature {
}
enum VisibilityEnum {
public = 1;
private = 2;
}
@namespace(uri="DB", prefix="DB")
package DB;
abstract class NamedElement {
attr String name;
}
class Database {
val DatabaseElement[*]#database contents;
}
abstract class DatabaseElement extends NamedElement {
ref Database#contents database;
}
class Table extends DatabaseElement {
val Column[*]#table columns;
ref Column[*] primaryKeys;
}
class Column extends DatabaseElement {
ref Table#columns table;
attr String type;
}
class ForeignKey extends DatabaseElement {
ref Column parent;
ref Column child;
attr Boolean isMany;
}
Check out the code from the SVN:
Once you have checked out/imported the code, to run the example you need to go through the following steps:
In this example, we use ETL to transform a model that conforms to an Object-Oriented metamodel to a model that conforms to the Database metamodel.
.emf files are Ecore metamodels expressed using the Emfatic textual syntax.
More examples are available in the examples folder of the SVN repository.