Skip to content

2016_m2m2c

Mócsai Krisztián edited this page May 1, 2018 · 8 revisions

Model to model and model to code transformations

The task

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.

Workflow

Using EMF-IncQuery (VIATRA Query) from Java

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;
}

Set up

  1. 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
    
  2. Create a plug-in project called hu.bme.mit.mdsd.m2m2c

  3. Add dependencies for the imported projects, incquery.runtime and viatra.runtime

Note that we will mainly use Xtend.

Relational Database Schema:

Relational Database Schema

VIATRA Model transformations

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 creating BatchTransformationRules.
  • 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);
		
	}
}

Code generation with Xtend templating

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 keycolumn.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();

References

Lab material

MDSD 2021

MDSD 2020

MDSD 2019

(Gradually replaced with updated content)

MDSD 2018

MDSD 2017

MDSD 2016

MDSD 2015

MDSD 2014

System Integration 2014

Clone this wiki locally