-
Notifications
You must be signed in to change notification settings - Fork 1
2016_m2m2c
In this laboratory, first we write a model to model transformation, which creates a relational database schema from any entity relation diagram. Secondly, we write a code generator generating a .sql file from the schema that can create all the tables and columns with appropriate types and constraints.
We will use EMF-IncQuery from Java in the model to model transformation, thus it is important to understand how to ude it beforehand.
For each query four Java classes are generated into the src-gen folder
-
[PatternName]QuerySpecification.java
- Represents a pattern, containing its name, parameters, body, annotations, etc. -
[PatternName]Match.java
- Represents a match of a pattern (parameters) on an instance model, containing references to the EObjects. -
[PatternName]Matcher.java
- Basically a query specification initialized on a concrete instance model. The match set can obtained from it. -
[PatternName]Processor.java
- Abstract class for defining an action on match. -
[FileName].java
- IncQuery also generates a class for every .eiq file containing the QuerySpecifications in a list.
For example, we can check a well-formedness constraint on a model in the following way:
public static boolean checkConstraints(EntityRelationDiagram model) throws IncQueryException{
// Create a query enginge on the model
EMFScope scope = new EMFScope(model);
IncQueryEngine iqe = IncQueryEngine.on(scope);
// Initialize a query on the model
RelationWithoutEndingsMatcher matcher = iqe.getMatcher(RelationWithoutEndingsQuerySpecification.instance());
// Write on the console for each well-formedness constrain violation
matcher.forEachMatch(new RelationWithoutEndingsProcessor() {
@Override
public void process(Relation pRelation) {
System.out.println("Relation without relation ending " + pRelation);
}
});
if(matcher.countMatches() > 0) {
return false;
}
return true;
}
-
Clone m2m2c-start branch and import the following projects from this git repository: https://github.com/FTSRG/mdsd-examples
hu.bme.mit.mdsd.erdiagram hu.bme.mit.mdsd.erdiagram.examplediagrams hu.bme.mit.mdsd.erdiagram.queries hu.bme.mit.mdsd.erdiagram.queries.validation hu.bme.mit.mdsd.erdiagram.rdb
-
Create a plug-in project called
hu.bme.mit.mdsd.m2m2c
-
Add dependencies for the imported projects, incquery.runtime and viatra.runtime
Note that we will mainly use Xtend.
Relational Database Schema:
Model transformation can be done in many different ways, the most simple one is to write many for loops traversing the source model to create the target model. However, it is more maintainable to use graph transformation rules for such a task.
These are the most important classes in VIATRA-MT
-
BatchTransformationRule
- represents a graph transformation rule: an IncQuery pattern as the condition (or left hand side) and simple Java code as for the operation (right hand side). -
BatchTransformationRuleFactory
- helper class for creatingBatchTransformationRule
s. -
BatchTransformation
- encapsulates a concrete model (initialized with an IncQuery engine) and a set of rules. -
BatchTransformationStatements
- contains helper methods for the transformation itself. E.g.fireOne(rule)
to execute an arbitrary activation of the rule.
In this demo, we will use two transformation rules:
- A rule that creates a table with its columns for one entity. The table will inherit all the attributes (columns) from its ancestors.
- A rule that creates a table with its columns for one relation (join table). The table will contain an integer primary key and two foreign keys (we assume all the entities or super entities have exactly one key).
Corresponding Xtend code:
class ErdToRdbBatchTransformation {
private extension BatchTransformationRuleFactory ruleFactory = new BatchTransformationRuleFactory
private BatchTransformationRule<? extends IPatternMatch, ? extends IncQueryMatcher<?>> entityRule
private BatchTransformationRule<? extends IPatternMatch, ? extends IncQueryMatcher<?>> relationRule
// target model:
private RelationalDataBase rdb
private extension RdbFactory rdbFactory = RdbFactory.eINSTANCE
private AllSuperEntityMatcher allSuperEntitiesMatcher
private Map<Entity, Key> entityToKeyMap = new HashMap
// Helper method for creating a column from an attribute
private def createColumnFromAttribute(Attribute attr) {
var Column column = null;
if (attr.isIsKey) {
column = createKey
} else {
column = createColumn
}
column.name = attr.name
column.type = attr.type.columnType
column
}
// Helper method to transform an AttrbiuteType to a ColumnType
private static def getColumnType(AttributeType type) {
ColumnType.get(type.getName)
}
private def createEntityRule() {
entityRule = createRule
.name("EntityRule")
// left hand side - queries a single entity
.precondition(EntityMatcher.querySpecification)
// right hand side
.action() [
// create table
val table = createTable
table.name = it.e.name
rdb.tables.add(table)
// create columns
for (attr : it.e.attributes) {
var column = attr.createColumnFromAttribute
table.columns += column
}
// create columns from super entities' attributes
allSuperEntitiesMatcher.forEachMatch(it.e,null)[
for (attr : it.superEntity.attributes) {
var column = attr.createColumnFromAttribute
table.columns += column
}
]
// store the generated key for the entities, which we use for the join tables
for (column : table.columns) {
if (column instanceof Key) {
entityToKeyMap.put(it.e, column)
}
}
]
.build
}
private def createRelationRule() {
relationRule = createRule
.name("RelationRule")
// left hand side - queries a single relation
.precondition(RelationMatcher.querySpecification)
// right hand side
.action() [
val leftEndind = it.r.leftEnding
val rightEndind = it.r.rightEnding
val leftEntity = leftEndind.target
val rightEntity = rightEndind.target
val joinTable = createTable
joinTable.name = it.r.name.toFirstUpper + "Relation"
val columnKey = createKey
columnKey.name = "id"
columnKey.type = ColumnType::INT
val columnLeft = createForeignKey
columnLeft.referencedKey = entityToKeyMap.get(leftEntity)
columnLeft.name = columnLeft.referencedKey.name
columnLeft.type = columnLeft.referencedKey.type
val columnRight = createForeignKey
columnRight.referencedKey = entityToKeyMap.get(rightEntity)
columnRight.name = columnRight.referencedKey.name
columnRight.type = columnRight.referencedKey.type
joinTable.columns += columnKey
joinTable.columns += columnLeft
joinTable.columns += columnRight
rdb.tables += joinTable
]
.build
}
}
Initialize the transformation in the constructor and create a method to execute it:
private BatchTransformationStatements statements
public new(EntityRelationDiagram erd) {
rdb = createRelationalDataBase
val iqEngine = IncQueryEngine.on(new EMFScope(erd))
allSuperEntitiesMatcher = AllSuperEntityMatcher.on(iqEngine)
createEntityRule
createRelationRule
val transformation = BatchTransformation.forEngine(iqEngine)
transformation.addRule(createEntityRule)
transformation.addRule(createRelationRule)
statements = new BatchTransformationStatements(transformation)
}
public def doTransformation() {
statements.fireAllCurrent(entityRule)
statements.fireAllCurrent(relationRule)
}
public def getRdb() {
rdb
}
Copy the example model to the project folder and run the transformation with a Junit plug-in test (protipp: use headless mode):
public class M2M2CExecutor {
@Test
public void execute() throws IOException {
ResourceSetImpl resSet = new ResourceSetImpl();
Resource resource = resSet.getResource(URI.createURI("My.erdiagram"), true);
EntityRelationDiagram erd = (EntityRelationDiagram) resource.getContents().get(0);
ErdToRdbBatchTransformation m2m = new ErdToRdbBatchTransformation(erd);
m2m.doTransformation();
resSet = new ResourceSetImpl();
Resource rdbResource = resSet.createResource(URI.createURI("drivenVehiclesRegistry.generated.rdb"));
rdbResource.getContents().add(m2m.getRdb());
rdbResource.save(null);
}
}
Create an xtend class RdbToSqlGenerator
and add a constructor, which waits for a relational database schema.
class RdbToSqlGenerator {
private RelationalDataBase rdb
new(RelationalDataBase rdb) {
this.rdb = rdb
}
}
Add a method that generates sql code for a table:
def generateCreateTable(Table table)'''
create table «table.name» (
«FOR column : table.columns»
«column.name» «column.type.columnTypeToDataType» «IF column instanceof Key»primary key«ENDIF»«IF column instanceof ForeignKey»foreign key («column.referencedKey.name»)«ENDIF»
«ENDFOR»
)
'''
// helper method for creating database type
def columnTypeToDataType(ColumnType type) {
switch (type) {
case BOOLEAN: {
"boolean"
}
case DATETIME: {
"timestamp"
}
case DOUBLE: {
"float"
}
case INT: {
"integer"
}
case STRING: {
"varchar(50)"
}
default: {
}
}
}
The '''
will simply return a character sequence, but it will hold the formatting and you can write code inside it (just press ctrl+space to generate the « »
characters). This is why it is called a template.
Next, create a method that goes through all the tables and generates the sql commands. And a method that writes it to a file.
def generateAll()'''
«FOR table : rdb.tables»
«table.generateCreateTable»
«ENDFOR»
'''
def generateSql() {
Files.write(generateAll,new File("createRdb.sql"),Charsets.UTF_8)
}
It is important that the generated code is structured and looks good. After the «table.generateCreateTable»
there is an enpty new line so that there will be a new line after each create table
command.
Add these two lines of code to M2M2CExecutor.java
to generate the .sql file:
RdbToSqlGenerator m2c = new RdbToSqlGenerator(m2m.getRdb());
m2c.generateSql();
- Xtend language elements (including templating) - https://eclipse.org/xtend/documentation/203_xtend_expressions.html
- IncQuery (VIATRA Query) API usage - https://wiki.eclipse.org/VIATRA/Query/UserDocumentation/API
- VIATRA-MT API
- https://wiki.eclipse.org/VIATRA/Transformation/Transformation_API
- http://static.incquerylabs.com/projects/viatra/viatra-docs/ViatraDocs.html#_batch_transformations
- There is a m2m2c-end branch to get the final code: https://github.com/FTSRG/mdsd-examples
- Eclipse basics
- EMF (incl. advanced topics)
- VIATRA Query
- Sirius
- Xtext+Xtend
- M2M
- Eclipse basics
- EMF (incl. advanced topics)
- VIATRA Query
- Sirius
- Xtext
- M2M
(Gradually replaced with updated content)
- Eclipse basics
- EGit
- EMF (incl. advanced topics)
- VIATRA Query
- Sirius
- Xtext
- M2M (VIATRA)
- Eclipse basics
- EGit
- EMF (incl. advanced topics)
- VIATRA Query
- Sirius
- Xtext
- M2M (VIATRA)