Example: Transform an OO model to a DB model with ETL

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:

  • go to the SVN repository
  • navigate to trunk/examples
  • check out the org.eclipse.epsilon.examples.metamodels project
  • check out the org.eclipse.epsilon.examples.oo2db project

Once you have checked out/imported the code, to run the example you need to go through the following steps:

  1. register all .ecore metamodels in the org.eclipse.epsilon.examples.metamodels project (select all of them and then right click and select Register EPackages)
  2. register any .ecore metamodels in the org.eclipse.epsilon.examples.oo2db project
  3. right click the .launch file in the org.eclipse.epsilon.examples.oo2db project
  4. select Run as... and click the first item in the menu that pops up

What's this?

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.

What are .emf files?

.emf files are Ecore metamodels expressed using the Emfatic textual syntax.

More examples...

Epsilon Object Language
Epsilon Transformation Language
Epsilon Generation Language
Epsilon Validation Language
Epsilon Merging Language
Epsilon Flock
Combining the Epsilon Languages
EuGENia
EUnit

Even more examples...

More examples are available in the examples folder of the SVN repository.