=Paper=
{{Paper
|id=Vol-3089/ttc21_paper_ocl2sql_Hinkel_solution
|storemode=property
|title=An NMF solution to the TTC 2021 OCL to SQL case
|pdfUrl=https://ceur-ws.org/Vol-3089/ttc21_paper13_ocl2sql_Hinkel_solution.pdf
|volume=Vol-3089
|authors=Georg Hinkel
|dblpUrl=https://dblp.org/rec/conf/ttc/Hinkel21c
}}
==An NMF solution to the TTC 2021 OCL to SQL case==
An NMF solution to the TTC 2021 OCL to SQL case
Georg Hinkel1
1
Am Rathaus 4b, 65207 Wiesbaden, Germany
Abstract
Recent advancements in modern general-purpose programming languages challenge the often stated assumption that dedi-
cated model transformation languages are required to express model transformations, especially when not only few of the
typical properties of model transformations are required. In particular, the OCL to SQL case at the Transformation Tool
Contest (TTC) 2021 asks for solutions to a transformation from OCL, a typical tree model. This paper presents a solution to
this case using dynamic C# code, but without any dedicated model transformation language, only using NMF for the model
representation. The transformation tools of NMF are not used because the case does not fall under NMFs definition of a
model transformation problem and we discuss the reasons for that.
Keywords
Model Queries, OCL, SQL
1. Introduction statements. For this purpose, metamodels are provided
to understand OCL and SQL as models.
In a frequently cited paper [1], Sendall and Kozaczyn- The .NET Modeling Framework [3] is a framework for
ski state that dedicated model transformation languages model-driven engineering on the .NET platform and pa-
should be used for most model transformation activities per presents a solution of the OCL to SQL case using NMF.
because general-purpose languages are less suited for this NMF even has multiple model transformation languages,
kind of tasks. Particularly in recent years, this assump- but these do not fit to the problem at hand. Instead, this
tion has become very popular [2]. However, modern paper presents a solution using plain C# with massive
general-purpose programming languages have signifi- usage of the Dynamic Language Runtime.
cantly evolved since the paper from Sendall and Koza- In the remainder of the paper, I first briefly introduce
czynski was published and it is fair to doubt whether that the Dynamic Language Runtime that is heavily used for
statement is still correct, in particular since surprisingly the solution in this paper in Section 2. Section 3 presents
few empirical evidence is given to support the assump- the solution. Finally, Section 4 discusses the solution.
tion [2].
One of the arguments in favor of model transformation
languages is the simple and usually type-safe access to a 2. Dynamic C#
trace model. However, the trace is only important when
model elements are referenced more than once and there The solution makes use of the dynamic language runtime
is an important category of models where model elements (DLR) that is part of the .NET Framework but perhaps
are typically only referenced only once, namely trees, for not so widely known. The idea of the DLR is to allow el-
instance expression trees. ements of dynamic programming languages in the scope
A particularly important expression tree model is the of the .NET runtime. These features are also available in
Object Constraint Language (OCL). OCL is an important C#, in particular the ability for late binding. That is, by
language to denote expressions based on models in order converting variables to dynamics, the compiler sees that
to formulate constraints, but it is also used to specify method calls are only resolved at runtime, based on the
queries. If the models are stored in a database, it is de- usual C# overload selection principles which the compiler
sirable to translate these queries to SQL statements such attaches to make them available at runtime. However,
that they can be processed directly by the database. especially when passing dynamic objects only as param-
Using models for OCL and SQL, the OCL to SQL case at eters, the compiler is able to calculate the set of methods
the Transformation Tool Contest (TTC) asks tool authors that are candidates for a certain call already, which makes
to transform models of OCL queries into models of SQL the actual call very efficient. Further, integrated editors
such as Visual Studio even show errors, if no suitable
TTC’21: Transformation Tool Contest, Part of the Software candidates could be found, the reference count counts all
Technologies: Applications and Foundations (STAF) federated possible methods that the call could be resolved to and
conferences, Eds. A. Boronat, A. García-Domínguez, and G. Hinkel, the "‘Go To Definition"’ feature lists all of them.
25 June 2021, Bergen, Norway (online).
" georg.hinkel@gmail.com (G. Hinkel)
© 2021 Copyright for this paper by its authors. Use permitted under Creative
Commons License Attribution 4.0 International (CC BY 4.0).
CEUR
Workshop
Proceedings
http://ceur-ws.org
ISSN 1613-0073
CEUR Workshop Proceedings (CEUR-WS.org)
3. Solution other OCL expressions require to modify the context in
which they are called.
To discuss the solution, I first give an overview in Section Listing 1 shows how this applies to boolean expres-
3.1 before Sections 3.2, 3.3 and 3.4 go into details for the sions where the literal is simply converted to an Equal-
actual translation process, pruning and printing the SQL sToExpression, either that 1 = 1 for true or 1 = 0 for
statement models to strings. false.
1 private IExpression GetExpression(SelectContext context,
3.1. Overview BooleanLiteralExp booleanLiteral) {
2 return new EqualsToExpression {
3 LeftExp = new LongValue { Value = 1 },
NMF does have a model transformation language (NTL, 4 RightExp = new LongValue {
[4, 5])1 but I decided not to use it for this case. Why? Ac- 5 Value = booleanLiteral.BooleanValue.GetValueOrDefault()
? 1 : 0
cording to the philosophy of NTL, the biggest challenge 6 }
of a model transformation is to establish an isomorphism 7 };
}
between source and target models that provides a trac- 8
ing functionality and that is used to ensure that certain Listing 1: Translating simple boolean expressions
input model elements are only transformed once and not
once for every reference. This is because maintaining Calls to GetExpression can be nested as denoted in
such a trace is difficult in general-purpose programming Listing 2 that depicts how to translate And call expres-
languages because it requires a lot of bookkeeping – one sions.
essentially requires a dedicated hashtable for each type
1 return new AndExpression {
and as soon as inheritance is in place, things start to 2 LeftExp = GetExpression(context, (dynamic)callExpression.
become messy. Source),
3 RightExp = GetExpression(context, (dynamic)callExpression.
However, both the OCL and the SQL metamodels are Argument[0])
essentially expression models that have a tree structure 4 };
with very few cross-references, even none in the case of
Listing 2: Nesting translation calls to translate an And
SQL. Because NMF takes containments very seriously
call expression
and model elements must always have exactly one parent,
trying to add an existing model element to a containment More interesting is the handling of the AllInstances
reference of another model element removes it from its method as depicted in Listing 3. Because it does not
old container. Therefore, not only that a trace is not directly have an impact on the result, we return a null
needed, it is even counter-productive. reference, but this time change the context and set it to
Since the availability of a trace is not an argument the table with the name of the referred type.
in favor of NTL, the question is whether NTL still adds
value against a pure general-purpose code solution and I 1 privateIEntity IExpression GetAllInstances(SelectContext context,
referredType) {
believe the answer is plainly no. Especially using features 2 var table = new Table { Name = referredType.Name };
if (context.Body.FromItem == null) {
like the DLR, the late binding can be implemented directly 34 context.Body.FromItem = table;
in C# with concepts known by a lot more developers and 5 } else {
6 context.Body.Joins.Add(new Join { RightItem = table });
therefore easier to understand and better supported by 7 }
tools. 8 return null;
Therefore, I decided to create a solution to the case 9 }
using plain C# code making use of DLR features. Listing 3: Handling the AllInstances method
3.2. Translator To handle iterators, we need to determine how to bind
the variable. For this, the considered subset of the OCL
The general idea of the solution is to translate the OCL language knows to collections that can be iterated: A
expressions in a (mutable) context to SQL expressions. collection returned by the AllInstances method or an as-
This context includes a notion of open variables and their sociation of a different variable. In both cases, we add an
types as well as the body of the enclosing SQL statement open variable to the select context while calculating the
and a counter of temporary tables created for a statement expression for the iterator body and remove it afterwards.
in order that they do not get confused. While simple With the iterators in place, we can implement the Prop-
expressions can be mapped to simple SQL expressions, ertyCallExp expressions as depicted in Listing 4.
The (syntactically allowed) case that a property of a
1
In fact, NMF even has two model transformation languages property is queried would require adding more joins,
where NTL is the rather imperative approach. NMF Synchroniza-
which is ignored in the current solution, particularly
tions [6] is more declarative and targets incremental and/or bidirec-
tional model transformations. given that this was not required for the reference inputs.
1 private IExpression GetExpression(SelectContext context, this, we create a temporary sub-select model with the
PropertyCallExp propertyCall) {
2 switch (propertyCall.Source) {
current context query inside, group that query by all
3 case VariableExp variableRef: context variables and return a column of the temporary
4 var table = context.Variables[variableRef.
ReferredVariable.Name];
table. However, because this eliminates the open vari-
5 return new Column { ables that might be needed elsewhere, we group the result
6 Table = new Table {
7 Name = table,
by all open variables and add these variables to the result.
8 Alias = new Alias { To make them available in the sub-select, which is the
9 Name = variableRef.ReferredVariable.Name
10 }
new context select statement, we add joins for each open
11 }, variable from their original table.
12 Name = propertyCall.ReferredProperty.Name
13 };
To see this, consider an extension of stage 8 where we
14 default: reuse the open variable c as depicted in Listing 6. We
15 throw new NotSupportedException();
16 }
refer to this query later on as stage 9.
17 }
1 Car.allInstances()->exists(c|c.owners->exists(p|p.name = ’
Peter’) and c.color=’black’)
Listing 4: Transformation of a PropertyCallExp
Listing 6: Slight extension of the stage 8 query that reuses
the open variable c
1 private IExpression GetExpression(SelectContext context,
AssociationClassCallExp association) { Note, the exists method is treated as a filter condi-
2 switch (association.Source) {
3 case VariableExp variableRef:
tion and an additional size aggregate. We need to keep
4 var variable = variableRef.ReferredVariable.Name; the variable c in order to be able to check whether the
5 var associationEnd = association.
ReferredAssociationEnds;
color is black.
6 var alias = variable + "_" + associationEnd.Association
;
7 context.Body.Joins.Add(new Join { 3.3. Pruning
8 Left = false,
9 RightItem = new Table { The resulting SQL statement may join tables that are not
10 Name = associationEnd.Association,
11 Alias = new Alias { Name = alias } actually needed, e.g. when joined tables are not actually
12 }, needed. This gets apparent in challenge 8, where the
13 OnExp = new EqualsToExpression {
14 LeftExp = new Column { open variable c is only used to calculate the size, but
15 Table = new Table { given that we are not interested in any of its properties,
16 Name = context.Variables[variable],
17 Alias = new Alias { Name = variable } we do not actually need to join the Car table once again
18 }, after the initial context is gone.
19 Name = associationEnd.Name,
20 },
1 if (selectBody.SelItems.Select(s => s.Exp).OfType<
21 RightExp = new Column {
CountAllFunction>().Any()) {
22 Table = new Table {
2 return;
23 Name = associationEnd.Association,
3 }
24 Alias = new Alias { Name = alias }
4 var expressionsToCheck = selectBody.SelItems.Select(s => s.
25 },
Exp).ToList();
26 Name = context.Variables[variable] + "_id",
5 if (selectBody.WhereExp != null) {
27 }
6 expressionsToCheck.Add(selectBody.WhereExp);
28 }
7 }
29 });
8 var usedAliases = (from selectExp in expressionsToCheck
30 context.LastJoin = Tuple.Create(variable,
9 from column in selectExp.Descendants().OfType<
associationEnd);
Column>()
31 return null;
10 select column.Table.Alias.Name).Distinct();
32 default:
11 for (int i = selectBody.Joins.Count - 1; i >= 0; i--) {
33 throw new NotSupportedException();
12 var join = selectBody.Joins[i];
34 }
13 if (join.RightItem is Table table && !usedAliases.Contains
35 }
(table.Alias.Name)) {
14 selectBody.Joins.RemoveAt(i);
Listing 5: Transformation of an AssociationCallExp 15 }
16 }
17 if (selectBody.FromItem is SubSelect subSelect) {
18 Prune(subSelect.SelectBody);
19 }
In case of an AssociationCallExp, we register the
Listing 7: Pruning the joins of the resulting SQL
join as last join in the context and add the join to the
statement
current select context as depicted in Listing 5.
Perhaps the most interesting expression is the method The implementation of the pruning is depicted in List-
to return the sizes. This is because the aggregate dras- ing 7. Aggregate (sub-)queries are not pruned because
tically changes the execution of the query and we need removing joins changes the number of result elements
to return rows for actually empty combinations. To do and thus the result get incorrect. Otherwise, we select all
table aliases that appear either in the selection or in the is tested with larger databases. First, the transformation
where clause and remove all joins that join tables that scheme used inside this paper differs from the original
are not actually needed. Lastly, we recurse in case the OCL2PSQL transformation scheme [7] and a correctness
source is a sub-query. proof for the transformation scheme presented used in
this paper is correct is out of scope for this paper. The
3.4. Printer reason that I did not use the OCL2PSQL mapping is that
I am generally not satisfied with the verbosity of the SQL
The solution to print the SQL statement models to strings statements generated by it, whereas the SQL statements
works similar by using the DLR to dispatch the different generated by the transformation scheme presented here
object types and then print them to strings. are much easier to comprehend in my opinion. The val-
1 public static string Print(IPlainSelect selectBody) {
idation of the mapping scheme presented here will be
2 var resultBuilder = new StringBuilder(); subject of future work. However, this different mapping
3 resultBuilder.Append($"SELECT {string.Join(", ",
selectBody.SelItems.Select(Print))}");
scheme also makes a comparison with alternative im-
4 if (selectBody.FromItem != null) { plementations that stick more closely to the OCL2PSQL
5 resultBuilder.Append($" FROM {PrintFrom((dynamic)
selectBody.FromItem)}");
mapping more difficult.
6 } In my opinion, the solution shows well how to use the
7 foreach (var join in selectBody.Joins) {
8 resultBuilder.Append($" {(join.Left.GetValueOrDefault() ?
Dynamic Language Runtime available in C# to perform
"LEFT" : "INNER")} JOIN {PrintFrom((dynamic)join. dispatch on parameters, a frequent selling argument of
RightItem)} ON {PrintExpression((dynamic)join.
OnExp)}");
model transformation languages apart from access to
9 } trace, incrementality and bidirectionality. The latter two
10 if (selectBody.WhereExp != null) {
11 resultBuilder.Append($" WHERE {PrintExpression((dynamic)
properties require a very declarative way of specifying
selectBody.WhereExp)}"); model transformations such as exemplified e.g. by NMF
12 }
13 if (selectBody.GroupBy != null) {
Synchronizations [6], but the transformation at hand is
14 resultBuilder.Append($" GROUP BY {string.Join(", ", written in a very imperative style. Anyways, incremental
selectBody.GroupBy.GroupByExps.Select(exp =>
PrintExpression((dynamic)exp)))}");
change propagation is not relevant for the case at hand,
15 } since changes the main purpose of the transformation is
16 return resultBuilder.ToString();
17 }
to execute the resulting SQL statement and analyze the
result data. Bidirectionality would be very interesting to
Listing 8: Printing the resulting SQL statement using the reverse-engineer SQL statements in order to make them
DLR more understandable, but it is unclear to what extend this
is possible at all. Given that the trace is not important in
As an example, the method to print the actual SQL this case, there is just no reason not to use the Dynamic
statement is depicted in Listing 8. The query printer Language Runtime, especially taking into account the
makes intensive use of the string interpolation available very good performance results.
in C#. A further advantage of a solution in plain C# is that
it can be easily integrated into model transformations
4. Evaluation and discussion written in internal DSLs using C# as a host language, in
particular NTL, since usually, not the entire model forms
The solution has been integrated into the benchmark a tree structure and hence, access to the trace is required.
framework. In order to get an insight on the generated This integration, however, would be much more dif-
SQL queries, the resulting queries are depicted in Listing ficult when incrementality was important despite incre-
9. mentalization systems like NMF Expressions [8] that op-
Notably, to reduce the influence of just-in-time com- erate on C# code (or models thereof). It will be subject of
pilation, I actually run the solution 100 times on a Intel future work how transformations like the mapping from
Core i7-8550U CPU clocked at 1.99 Ghz in a system with OCL to SQL can be supported when incremental change
8GB RAM running Windows 10 and divide the result by propagation is required.
1002 . The resulting transformation times then are in the
range of up to 1.4ms for the stage 8 query and in the
sub-millisecond area for most of the other queries and
References
thus is negligible. The time for the test lies around 20ms [1] S. Sendall, W. Kozaczynski, Model transformation
but that certainly gets more interesting once the solution the heart and soul of model-driven software devel-
opment, Technical Report, 2003.
2
Actually, I do not because the smallest time unit in .NET hap- [2] S. Götz, M. Tichy, R. Groner, Claimed advantages and
pens to be 100ns, so we merge the division by 100 with the multipli- disadvantages of (dedicated) model transformation
cation by 100.
1 ***** Stage#0 ***
2 +++ challenge#0: SQL: SELECT 2 res
3 +++ challenge#1: SQL: SELECT ’Peter’ res
4 +++ challenge#2: SQL: SELECT 1 = 1 res
5 ***** Stage#1 ***
6 +++ challenge#0: SQL: SELECT 2 = 3 res
7 +++ challenge#1: SQL: SELECT ’Peter’ = ’Peter’ res
8 +++ challenge#2: SQL: SELECT 1 = 1 and 1 = 1 res
9 ***** Stage#2 ***
10 +++ challenge#0: SQL: SELECT Car_id res FROM Car
11 ***** Stage#3 ***
12 +++ challenge#0: SQL: SELECT tmp1.res res FROM (SELECT COUNT(*) res FROM Car) AS tmp1
13 +++ challenge#1: SQL: SELECT tmp1.res = 1 res FROM (SELECT COUNT(*) res FROM Car) AS tmp1
14 ***** Stage#4 ***
15 +++ challenge#0: SQL: SELECT 5 res FROM Car AS c
16 +++ challenge#1: SQL: SELECT c.Car_id res FROM Car AS c
17 +++ challenge#2: SQL: SELECT 1 = 0 res FROM Car AS c
18 ***** Stage#5 ***
19 +++ challenge#0: SQL: SELECT c.color res FROM Car AS c
20 +++ challenge#1: SQL: SELECT c.color = ’black’ res FROM Car AS c
21 ***** Stage#6 ***
22 +++ challenge#0: SQL: SELECT tmp1.res res FROM (SELECT c.Car_id, COUNT(c_Ownership.ownedCars) res FROM Car AS c LEFT JOIN
Ownership AS c_Ownership ON c.Car_id = c_Ownership.ownedCars GROUP BY c.Car_id) AS tmp1
23 +++ challenge#1: SQL: SELECT tmp1.res = 0 res FROM (SELECT c.Car_id, COUNT(c_Ownership.ownedCars) res FROM Car AS c LEFT JOIN
Ownership AS c_Ownership ON c.Car_id = c_Ownership.ownedCars GROUP BY c.Car_id) AS tmp1
24 ***** Stage#7 ***
25 +++ challenge#0: SQL: SELECT tmp1.res > 0 res FROM (SELECT COUNT(*) res FROM Car AS c WHERE 1 = 1) AS tmp1
26 +++ challenge#1: SELECT tmp1.res > 0 res FROM (SELECT COUNT(*) res FROM Car AS c WHERE 1 = 0) AS tmp1
27 +++ challenge#2: SQL: SELECT tmp1.res > 0 res FROM (SELECT COUNT(*) res FROM Car AS c WHERE c.color = ’black’) AS tmp1
28 +++ challenge#3: SQL: SELECT tmp2.res > 0 res FROM (SELECT COUNT(*) res FROM (SELECT c.Car_id, COUNT(c_Ownership.ownedCars)
res FROM Car AS c LEFT JOIN Ownership AS c_Ownership ON c.Car_id = c_Ownership.ownedCars GROUP BY c.Car_id) AS tmp1
WHERE tmp1.res = 1) AS tmp2
29 ***** Stage#8 ***
30 +++ challenge#0: SQL: SELECT tmp2.res > 0 res FROM (SELECT COUNT(*) res FROM (SELECT c.Car_id, COUNT(p.Person_id) res FROM
Car AS c LEFT JOIN Ownership AS c_Ownership ON c.Car_id = c_Ownership.ownedCars LEFT JOIN Person AS p ON c_Ownership.
ownedCars = p.Person_id WHERE p.name = ’Peter’ GROUP BY c.Car_id) AS tmp1 WHERE tmp1.res > 0) AS tmp2
Listing 9: Resulting SQL Statements
languages: a systematic literature review, Software [8] G. Hinkel, R. Heinrich, R. Reussner, An extensible
and Systems Modeling 20 (2021) 469–503. approach to implicit incremental model analyses,
[3] G. Hinkel, NMF: A multi-platform Modeling Frame- Software & Systems Modeling 18 (2019) 3151–3187.
work, in: A. Rensink, J. S. Cuadrado (Eds.), Theory
and Practice of Model Transformations: 11th Interna-
tional Conference, ICMT 2018, Held as Part of STAF
2018, Toulouse, France, June 25-29, 2018. Proceed-
ings, Springer International Publishing, Cham, 2018,
pp. 184–194.
[4] G. Hinkel, An approach to maintainable model trans-
formations using an internal DSL, Master’s thesis,
Karlsruhe Institute of Technology, 2013.
[5] G. Hinkel, T. Goldschmidt, E. Burger, R. Reuss-
ner, Using Internal Domain-Specific Languages
to Inherit Tool Support and Modularity for Model
Transformations, Software & Systems Model-
ing (2017) 1–27. URL: http://rdcu.be/oTED. doi:10.
1007/s10270-017-0578-9.
[6] G. Hinkel, E. Burger, Change Propagation and Bidi-
rectionality in Internal Transformation DSLs, Soft-
ware & Systems Modeling (2017). URL: http://rdcu.
be/u9PT. doi:10.1007/s10270-017-0617-6.
[7] H. N. P. Bao, M. Clavel, Ocl2psql: An ocl-to-sql
code-generator for model-driven engineering, in: In-
ternational Conference on Future Data and Security
Engineering, Springer, 2019, pp. 185–203.