Commit d72d039d authored by Georg Fette's avatar Georg Fette
Browse files

CQL adjustments:

added further testcases
added partial support for valuesets
added filtering within retrieves
parent dcc137af
......@@ -26,7 +26,7 @@
<dependency>
<groupId>UniWueUtils</groupId>
<artifactId>UniWueUtils</artifactId>
<version>0.0.2</version>
<version>0.0.3</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
......
......@@ -25,7 +25,7 @@ public class CQLOperatorMapper {
aMap.addOp("In", OperatorType.IN);
aMap.addOp("contains", OperatorType.CONTAINS);
aMap.addOp("In", OperatorType.IN_VALUE_SET);
aMap.addOp("in", OperatorType.IN_VALUE_SET);
aMap.addOp("Now()", OperatorType.NOW);
aMap.addOp("Today()", OperatorType.TODAY);
......
......@@ -52,6 +52,7 @@ import org.cqframework.cql.gen.cqlParser.QueryExpressionContext;
import org.cqframework.cql.gen.cqlParser.QueryInclusionClauseContext;
import org.cqframework.cql.gen.cqlParser.QuerySourceContext;
import org.cqframework.cql.gen.cqlParser.RetrieveContext;
import org.cqframework.cql.gen.cqlParser.RetrieveExpressionContext;
import org.cqframework.cql.gen.cqlParser.ReturnClauseContext;
import org.cqframework.cql.gen.cqlParser.SingleSourceClauseContext;
import org.cqframework.cql.gen.cqlParser.SortByItemContext;
......@@ -62,6 +63,7 @@ import org.cqframework.cql.gen.cqlParser.TemporalRelationshipContext;
import org.cqframework.cql.gen.cqlParser.TermContext;
import org.cqframework.cql.gen.cqlParser.TermExpressionContext;
import org.cqframework.cql.gen.cqlParser.TermExpressionTermContext;
import org.cqframework.cql.gen.cqlParser.TerminologyContext;
import org.cqframework.cql.gen.cqlParser.TimingExpressionContext;
import org.cqframework.cql.gen.cqlParser.TupleElementSelectorContext;
import org.cqframework.cql.gen.cqlParser.TupleSelectorContext;
......@@ -76,6 +78,7 @@ import de.uniwue.misc.util.RegexUtil;
import de.uniwue.query.GraphPath;
import de.uniwue.query.GraphQuery;
import de.uniwue.query.OperatorType;
import de.uniwue.query.Primitive;
import de.uniwue.query.PrimitiveDateTime;
import de.uniwue.query.QueryConcept;
import de.uniwue.query.QueryOperator;
......@@ -90,9 +93,7 @@ public class CQL_2_Graph_Mapper {
private GraphQuery query;
// because the alias is already defined in the parent structure it has to be cached to be provided
// when it is needed
public String nextAlias;
private boolean parsingReturnRoot;
public CQL_2_Graph_Mapper() {
initialize();
......@@ -110,6 +111,7 @@ public class CQL_2_Graph_Mapper {
query = new GraphQuery();
aliases.clear();
currentOp = null;
parsingReturnRoot = false;
}
public void dispose() {
......@@ -120,11 +122,12 @@ public class CQL_2_Graph_Mapper {
return prettyTree;
}
private void addOp(OperatorType aType) {
private QueryOperator addOp(OperatorType aType) {
currentOp = new QueryOperator(currentOp, aType);
return currentOp;
}
private cqlParser.QueryExpressionContext parseWithAntlr(String aQueryString) {
private ExpressionContext parseWithAntlr(String aQueryString) {
ANTLRInputStream stream = new ANTLRInputStream(aQueryString);
cqlLexer lexer = new cqlLexer(stream);
CommonTokenStream tokens = new CommonTokenStream(lexer);
......@@ -132,14 +135,13 @@ public class CQL_2_Graph_Mapper {
parser.setBuildParseTree(true);
ExpressionContext expression = parser.expression();
String prettyPrint = prettyPrint(expression);
cqlParser.QueryExpressionContext queryExpContext = (cqlParser.QueryExpressionContext) expression;
return queryExpContext;
return expression;
}
public GraphQuery parse(String aQueryString) throws IOException {
clear();
QueryExpressionContext qe = parseWithAntlr(aQueryString);
parseQueryExpression(qe);
ExpressionContext e = parseWithAntlr(aQueryString);
parseExpression(e);
addLeafsToRootClause();
addDefaultReturnIfNecessary();
return query;
......@@ -204,6 +206,7 @@ public class CQL_2_Graph_Mapper {
private void parseReturnClauseContext(ReturnClauseContext returnClause) {
String prettyPrint = prettyPrint(returnClause);
parsingReturnRoot = true;
TermExpressionContext te = (TermExpressionContext) returnClause.expression();
TermExpressionTermContext tet = (TermExpressionTermContext) te.expressionTerm();
TermContext term = tet.term();
......@@ -235,8 +238,11 @@ public class CQL_2_Graph_Mapper {
currentOp = query.rootClause;
String text = anElem.identifier().getText();
ExpressionContext expression = anElem.expression();
parseExpression(expression);
if (currentPath != null) {
QueryOperator lastCreatedOp = parseExpression(expression);
if (lastCreatedOp != null) {
lastCreatedOp.includeInResult = true;
lastCreatedOp.outputName = text;
} else if (currentPath != null) {
QueryConcept lastElement = currentPath.getLastElement();
lastElement.includeInResult = true;
lastElement.outputName = text;
......@@ -318,69 +324,98 @@ public class CQL_2_Graph_Mapper {
}
private void parseRetrieve(RetrieveContext retrieve) {
QueryOperator lastOp = currentOp;
String prettyPrint = prettyPrint(retrieve);
NamedTypeSpecifierContext namedTypeSpecifier = retrieve.namedTypeSpecifier();
String text = namedTypeSpecifier.identifier().getText();
QueryConcept root = new QueryConcept(text);
query.rootConcepts.add(root);
setCurrentPath(new GraphPath(root));
TerminologyContext terminology = retrieve.terminology();
currentOp = query.rootClause;
if (terminology != null) {
QualifiedIdentifierContext qualifiedIdentifier = terminology.qualifiedIdentifier();
if (qualifiedIdentifier != null) {
String text2 = qualifiedIdentifier.identifier().getText();
text2 = text2.replaceAll("\"", "");
QueryConcept codeConc = new QueryConcept(root, "code");
addOp(OperatorType.IN_VALUE_SET);
currentOp.addParameter(codeConc);
Primitive addParameter = currentOp.addParameter(text2);
}
ExpressionContext expression = terminology.expression();
if (expression != null) {
parseExpression(expression);
}
}
currentOp = lastOp;
}
private void parseExpression(ExpressionContext anExp) {
private QueryOperator parseExpression(ExpressionContext anExp) {
String prettyPrint = prettyPrint(anExp);
boolean cacheParsingReturnRoot = parsingReturnRoot;
parsingReturnRoot = false;
QueryOperator createdOp;
if (anExp instanceof InequalityExpressionContext) {
InequalityExpressionContext ineq = (InequalityExpressionContext) anExp;
List<ExpressionContext> expression = ineq.expression();
String op = ineq.children.get(1).getText();
if (op.equals("<")) {
parseOpWithParams(expression, OperatorType.LESS);
createdOp = parseOpWithParams(expression, OperatorType.LESS);
} else if (op.equals("<=")) {
parseOpWithParams(expression, OperatorType.LESS_OR_EQUAL);
createdOp = parseOpWithParams(expression, OperatorType.LESS_OR_EQUAL);
} else if (op.equals(">")) {
parseOpWithParams(expression, OperatorType.MORE);
createdOp = parseOpWithParams(expression, OperatorType.MORE);
} else if (op.equals(">=")) {
parseOpWithParams(expression, OperatorType.MORE_OR_EQUAL);
createdOp = parseOpWithParams(expression, OperatorType.MORE_OR_EQUAL);
} else {
throw new RuntimeException("unexpected");
}
} else if (anExp instanceof EqualityExpressionContext) {
EqualityExpressionContext eqe = (EqualityExpressionContext) anExp;
List<ExpressionContext> expression = eqe.expression();
parseOpWithParams(expression, OperatorType.EQUALS);
createdOp = parseOpWithParams(expression, OperatorType.EQUALS);
} else if (anExp instanceof ExistenceExpressionContext) {
ExistenceExpressionContext ee = (ExistenceExpressionContext) anExp;
parseExistenceExpression(ee);
createdOp = parseExistenceExpression(ee);
} else if (anExp instanceof TypeExpressionContext) {
TypeExpressionContext te = (TypeExpressionContext) anExp;
parseTypeExpression(te);
createdOp = parseTypeExpression(te);
} else if (anExp instanceof AndExpressionContext) {
AndExpressionContext ae = (AndExpressionContext) anExp;
parseAndExpressionContext(ae);
createdOp = parseAndExpressionContext(ae);
} else if (anExp instanceof TimingExpressionContext) {
TimingExpressionContext te = (TimingExpressionContext) anExp;
parseTimingExpressionContext(te);
createdOp = parseTimingExpressionContext(te);
} else if (anExp instanceof QueryExpressionContext) {
QueryExpressionContext qe = (QueryExpressionContext) anExp;
parseQueryExpression(qe);
createdOp = null;
} else if (anExp instanceof RetrieveExpressionContext) {
RetrieveExpressionContext re = (RetrieveExpressionContext) anExp;
RetrieveContext retrieve = re.retrieve();
parseRetrieve(retrieve);
createdOp = null;
} else if (anExp instanceof TermExpressionContext) {
TermExpressionContext te = (TermExpressionContext) anExp;
ExpressionTermContext et = te.expressionTerm();
if (et instanceof InvocationExpressionTermContext) {
InvocationExpressionTermContext iet = (InvocationExpressionTermContext) et;
parseInvocationExpressionTermContext(iet);
createdOp = parseInvocationExpressionTermContext(iet);
} else {
parseExpressionTerm(et);
createdOp = parseExpressionTerm(et);
}
} else {
throw new RuntimeException("unexpected");
}
return createdOp;
}
private void parseTypeExpression(TypeExpressionContext te) {
private QueryOperator parseTypeExpression(TypeExpressionContext te) {
// this is very sloppy
String prettyPrint = prettyPrint(te);
QueryOperator lastOp = currentOp;
addOp(OperatorType.IS);
QueryOperator thisOp = addOp(OperatorType.IS);
ExpressionContext e = te.expression();
parseExpression(e);
addCurrentPathToCurrentOp();
......@@ -389,18 +424,20 @@ public class CQL_2_Graph_Mapper {
String text = namedTypeSpecifier.identifier().getText();
currentOp.addParameter(text);
currentOp = lastOp;
return thisOp;
}
private void parseExistenceExpression(ExistenceExpressionContext ee) {
private QueryOperator parseExistenceExpression(ExistenceExpressionContext ee) {
String prettyPrint = prettyPrint(ee);
QueryOperator lastOp = currentOp;
addOp(OperatorType.EXISTS);
QueryOperator thisOp = addOp(OperatorType.EXISTS);
TermExpressionContext te = (TermExpressionContext) ee.expression();
TermExpressionTermContext tet = (TermExpressionTermContext) te.expressionTerm();
ParenthesizedTermContext pt = (ParenthesizedTermContext) tet.term();
QueryExpressionContext qe = (QueryExpressionContext) pt.expression();
parseQueryExpression(qe);
currentOp = lastOp;
return thisOp;
}
private void addCurrentPathToCurrentOp() {
......@@ -410,9 +447,10 @@ public class CQL_2_Graph_Mapper {
}
}
private void parseTimingExpressionContext(TimingExpressionContext te) {
private QueryOperator parseTimingExpressionContext(TimingExpressionContext te) {
String prettyPrint = prettyPrint(te);
QueryOperator lastOp = currentOp;
QueryOperator thisOp;
List<ExpressionContext> expression = te.expression();
if (te.children.get(1) instanceof IntervalOperatorPhraseContext) {
IntervalOperatorPhraseContext iop = (IntervalOperatorPhraseContext) te.children.get(1);
......@@ -423,13 +461,13 @@ public class CQL_2_Graph_Mapper {
String text2 = temporalRelationship.getText();
QueryOperator tempOp, intervalOp;
if (text2.equals("after")) {
addOp(OperatorType.AFTER);
thisOp = addOp(OperatorType.AFTER);
tempOp = currentOp;
} else {
throw new RuntimeException("unexpected");
}
if (text.equals("ends")) {
addOp(OperatorType.END);
thisOp = addOp(OperatorType.END);
intervalOp = currentOp;
} else {
throw new RuntimeException("unexpected");
......@@ -440,56 +478,80 @@ public class CQL_2_Graph_Mapper {
currentOp = tempOp;
parseExpression(expression.get(1));
addCurrentPathToCurrentOp();
} else {
throw new RuntimeException("unexpected");
}
} else {
throw new RuntimeException("unexpected");
}
currentOp = lastOp;
return thisOp;
}
private void parseAndExpressionContext(AndExpressionContext ae) {
private QueryOperator parseAndExpressionContext(AndExpressionContext ae) {
String prettyPrint = prettyPrint(ae);
QueryOperator lastOp = currentOp;
QueryOperator thisOp;
if ((currentOp.type != OperatorType.QUERY) && (currentOp.type != OperatorType.AND)) {
addOp(OperatorType.AND);
thisOp = addOp(OperatorType.AND);
} else {
thisOp = currentOp;
}
List<ExpressionContext> expression = ae.expression();
for (ExpressionContext ec : expression) {
parseExpression(ec);
}
currentOp = lastOp;
return thisOp;
}
private void parseExpressionTerm(ExpressionTermContext te) {
private QueryOperator parseExpressionTerm(ExpressionTermContext te) {
String prettyPrint = prettyPrint(te);
QueryOperator lastOp = currentOp;
QueryOperator thisOp;
boolean isDotInvocation = false;
if ((te.children.size() == 3) && te.children.get(1).getText().equals(".")) {
isDotInvocation = true;
}
if (te instanceof PowerExpressionTermContext) {
addOp(OperatorType.POWER);
}
if (te instanceof AdditionExpressionTermContext) {
thisOp = addOp(OperatorType.POWER);
} else if (te instanceof AdditionExpressionTermContext) {
if (te.children.get(1).getText().equals("+")) {
addOp(OperatorType.ADD);
thisOp = addOp(OperatorType.ADD);
} else if (te.children.get(1).getText().equals("-")) {
addOp(OperatorType.SUBTRACT);
thisOp = addOp(OperatorType.SUBTRACT);
} else {
throw new RuntimeException("unexpected");
}
}
if (te instanceof MultiplicationExpressionTermContext) {
} else if (te instanceof MultiplicationExpressionTermContext) {
if (te.children.get(1).getText().equals("*")) {
addOp(OperatorType.MULTIPLY);
thisOp = addOp(OperatorType.MULTIPLY);
} else if (te.children.get(1).getText().equals("/")) {
addOp(OperatorType.DIVIDE);
thisOp = addOp(OperatorType.DIVIDE);
} else if (te.children.get(1).getText().equals("div")) {
addOp(OperatorType.TRUNCATED_DIVIDE);
thisOp = addOp(OperatorType.TRUNCATED_DIVIDE);
} else if (te.children.get(1).getText().equals("mod")) {
addOp(OperatorType.MODULO);
thisOp = addOp(OperatorType.MODULO);
} else {
throw new RuntimeException("unexpected");
}
} else if ((te instanceof TermExpressionTermContext)
|| (te instanceof InvocationExpressionTermContext)) {
// these only have children, which are handled below
thisOp = null;
} else {
throw new RuntimeException("unexpected");
}
QueryOperator lastCreatedOp = parseExpressionTermChildren(te, isDotInvocation);
if (thisOp == null) {
thisOp = lastCreatedOp;
}
currentOp = lastOp;
return thisOp;
}
private QueryOperator parseExpressionTermChildren(ExpressionTermContext te, boolean isDotInvocation) {
QueryOperator lastCreatedOp = null;
for (ParseTree aChild : te.children) {
String prettyPrint2 = prettyPrint(aChild);
if (aChild instanceof TermExpressionTermContext) {
......@@ -514,24 +576,24 @@ public class CQL_2_Graph_Mapper {
} else if (aChild instanceof InvocationTermContext) {
InvocationTermContext it = (InvocationTermContext) aChild;
InvocationContext invocation = it.invocation();
parseInvocationContext(invocation);
lastCreatedOp = parseInvocationContext(invocation);
} else if (aChild instanceof MemberInvocationContext) {
MemberInvocationContext mi = (MemberInvocationContext) aChild;
parseMemberInvocationContext(mi);
} else if (aChild instanceof InvocationExpressionTermContext) {
InvocationExpressionTermContext iet = (InvocationExpressionTermContext) aChild;
parseInvocationExpressionTermContext(iet);
lastCreatedOp = parseInvocationExpressionTermContext(iet);
if (!isDotInvocation) {
addCurrentPathToCurrentOp();
}
} else if (aChild instanceof ExpressionTermContext) {
ExpressionTermContext et = (ExpressionTermContext) aChild;
parseExpressionTerm(et);
lastCreatedOp = parseExpressionTerm(et);
} else {
throw new RuntimeException("unexpected");
}
}
currentOp = lastOp;
return lastCreatedOp;
}
private boolean containsFunctionCall(InvocationExpressionTermContext iet) {
......@@ -553,17 +615,19 @@ public class CQL_2_Graph_Mapper {
return false;
}
private void parseInvocationExpressionTermContext(InvocationExpressionTermContext iet) {
private QueryOperator parseInvocationExpressionTermContext(InvocationExpressionTermContext iet) {
String prettyPrint = prettyPrint(iet);
QueryOperator thisOp;
if (containsFunctionCall(iet)) {
QueryOperator lastOp = currentOp;
addOp(OperatorType.OPERATOR_RESULT_PROPERTY);
thisOp = addOp(OperatorType.OPERATOR_RESULT_PROPERTY);
parseExpressionTerm(iet);
addCurrentPathToCurrentOp();
currentOp = lastOp;
} else {
parseExpressionTerm(iet);
thisOp = parseExpressionTerm(iet);
}
return thisOp;
}
private QueryOperator parseOpWithParams(List<ExpressionContext> parameters, OperatorType aType) {
......@@ -579,11 +643,12 @@ public class CQL_2_Graph_Mapper {
return newOp;
}
private void parseInvocationContext(InvocationContext invocation) {
private QueryOperator parseInvocationContext(InvocationContext invocation) {
String prettyPrint = prettyPrint(invocation);
if (invocation instanceof MemberInvocationContext) {
MemberInvocationContext mi = (MemberInvocationContext) invocation;
parseMemberInvocationContext(mi);
parseMemberInvocationContext(mi);
return null;
} else if (invocation instanceof FunctionInvocationContext) {
FunctionInvocationContext fi = (FunctionInvocationContext) invocation;
FunctionContext function = fi.function();
......@@ -624,6 +689,7 @@ public class CQL_2_Graph_Mapper {
if (opType == OperatorType.COUNT) {
// here should something happen that determines the scope of the aggregation op
}
return newOp;
} else {
throw new RuntimeException("unexpected");
}
......
......@@ -72,7 +72,7 @@ public class Graph_2_CQL_Mapper extends Graph_2_System_Mapper {
aliases.put(getFirstRoot(), alias);
String conceptsString = writeRootConcepts();
builder.append(conceptsString);
String where = writeQuery(query.rootClause);
String where = writeWhere(query.rootClause);
if (!where.isEmpty()) {
builder.append(" where " + where);
}
......@@ -105,7 +105,7 @@ public class Graph_2_CQL_Mapper extends Graph_2_System_Mapper {
result += ", ";
}
first = false;
result += aCon.outputName + ":" + getPath(aCon);
result += aCon.outputName + ": " + getPath(aCon);
}
}
for (QueryOperator anOp : query.rootClause.getOperatorsRecursive()) {
......@@ -114,8 +114,9 @@ public class Graph_2_CQL_Mapper extends Graph_2_System_Mapper {
result += ", ";
}
first = false;
result += anOp.outputName + ":";
throw new RuntimeException("not yet implemented");
result += anOp.outputName + ": ";
String opString = writeOp(anOp);
result += opString;
}
}
result += " }";
......@@ -123,11 +124,14 @@ public class Graph_2_CQL_Mapper extends Graph_2_System_Mapper {
return result;
}
private String writeQuery(QueryOperator queryOp) {
private String writeWhere(QueryOperator queryOp) {
StringBuilder builder = new StringBuilder();
List<QueryOperator> ops = queryOp.getOperators();
boolean first = true;
for (QueryOperator anOp : ops) {
if (anOp.includeInResult) {
continue;
}
if (anOp.type == OperatorType.SORT) {
String sortString = writeSort(anOp);
builder.append(sortString);
......@@ -205,6 +209,12 @@ public class Graph_2_CQL_Mapper extends Graph_2_System_Mapper {
return result;
}
private String getNextAlias(QueryConcept aConc) {
String alias = aliasNames[aliasIndex++];
aliases.put(aConc, alias);
return alias;
}
private String writeRootConcepts() {
StringBuilder builder = new StringBuilder();
Set<QueryConcept> conceptsToBeAliased = getConceptsToBeAliased();
......@@ -213,8 +223,7 @@ public class Graph_2_CQL_Mapper extends Graph_2_System_Mapper {
alreadyMentionedConcepts.add(getFirstRoot());
for (QueryConcept aConc : conceptsToBeAliased) {
builder.append(" with [" + aConc.code + "] ");
String alias = aliasNames[aliasIndex++];
aliases.put(aConc, alias);
String alias = getNextAlias(aConc);
builder.append(alias + " ");
String suchThat = "";
List<QueryOperator> hullOfSuchThatOperators = getHullOfSuchThatOperators(aConc,
......@@ -331,7 +340,7 @@ public class Graph_2_CQL_Mapper extends Graph_2_System_Mapper {
}
String opString = opMap.graph_2_system_operatorMap.get(anOp.type);
if (anOp.type == OperatorType.QUERY) {
String queryString = writeQuery(anOp);
String queryString = writeWhere(anOp);
builder.append(queryString);
} else if (anOp.isBooleanOp()) {
if (anOp.type == OperatorType.NOT) {
......@@ -350,6 +359,13 @@ public class Graph_2_CQL_Mapper extends Graph_2_System_Mapper {
} else if (anOp.type == OperatorType.INTERVAL) {
String intString = writeInterval(anOp);
builder.append(intString);
} else if (anOp.type == OperatorType.IN_VALUE_SET) {
String string1 = writeExpr(anOp.getParameters().get(0));
builder.append(string1);
nextPrimitiveIsValueSet = true;
builder.append(" " + opString + " ");
String string2 = writeExpr(anOp.getParameters().get(1));
builder.append(string2);
} else if (anOp.type == OperatorType.LIST) {
String intString = writeList(anOp);
builder.append(intString);
......@@ -407,18 +423,20 @@ public class Graph_2_CQL_Mapper extends Graph_2_System_Mapper {
String exp = writeExpr(aConc);
builder.append(exp);
if (anOp.getParameters().size() > 1) {
QueryOperator sort = anOp.getParameters().get(1).asOperator();
String alias = getNextAlias(aConc);
builder.append(" " + alias + " ");
QueryOperator sort = anOp.getParameters().get(1).asOperator();
QueryConcept currentConc = sort.getParameters().get(0).asConcept();
String sortPath = currentConc.code;
while (currentConc.parentConcept != aConc) {
currentConc = currentConc.parentConcept;
sortPath = currentConc.code + "." + sortPath;
}
builder.append(" sort by " + sortPath);
builder.append("sort by " + sortPath);
}
builder.append(")");
}
public static String getSystem() {
return Graph_2_CQL_Mapper.system;
}
......
......@@ -74,6 +74,7 @@ public enum OperatorType {
SORT_ITEM,
// constructors
VALUESET,
QUANTITY,
LIST,
INTERVAL
......
......@@ -20,6 +20,7 @@ import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionSnapshotComp
import ca.uhn.fhir.context.FhirContext;
import de.uniwue.misc.util.ResourceUtil;
import de.uniwue.misc.util.StringUtilsUniWue;
import de.uniwue.query.Expression;