Skip to content

Commit

Permalink
🤠 Add support for more types of correlated queries (#50)
Browse files Browse the repository at this point in the history
* 🤠 Add support for more types of correlated queries.

* Fix for failing legacy tests

Co-authored-by: Luke Rasmussen <luke.rasmussen@gmail.com>
  • Loading branch information
psbrandt and lrasmus authored Mar 2, 2021
1 parent fb50750 commit 560cee3
Show file tree
Hide file tree
Showing 11 changed files with 979 additions and 347 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<maven.compiler.target>1.8</maven.compiler.target>
<junit.jupiter.version>5.4.2</junit.jupiter.version>
<mockito.version>2.28.2</mockito.version>
<cqframework.version>1.4.6</cqframework.version>
<cqframework.version>1.5.1</cqframework.version>
<jaxb.version>2.3.0</jaxb.version>
<bintray.subject>phema</bintray.subject>
<bintray.repo>maven</bintray.repo>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,64 +24,62 @@
* Confusingly, a Criteria has field called CorelatedCriteria, which is of type CriteriaGroup.
*/
public class CorelatedCriteriaTranslator {
private CorelatedCriteriaTranslator() {
super();
}

private static CorelatedCriteria generateCorelatedCriteriaForRetrieve(Retrieve retrieve, PhemaElmToOmopTranslatorContext context) throws Exception {
Occurrence occurrence = CirceUtil.defaultOccurrence();
private CorelatedCriteriaTranslator() {
super();
}

CorelatedCriteria corelatedCriteria = CirceUtil.defaultCorelatedCriteria();
corelatedCriteria.occurrence = occurrence;
private static CorelatedCriteria generateCorelatedCriteriaForRetrieve(Retrieve retrieve, PhemaElmToOmopTranslatorContext context) throws Exception {
Occurrence occurrence = CirceUtil.defaultOccurrence();

corelatedCriteria.criteria = CriteriaTranslator.generateCriteriaForExpression(retrieve, context);
CorelatedCriteria corelatedCriteria = CirceUtil.defaultCorelatedCriteria();
corelatedCriteria.occurrence = occurrence;

return corelatedCriteria;
}
corelatedCriteria.criteria = CriteriaTranslator.generateCriteriaForExpression(retrieve, context);

private static CorelatedCriteria generateCorelatedCriteriaForQuery(Query query, PhemaElmToOmopTranslatorContext context) throws Exception {
List<RelationshipClause> relationships = query.getRelationship();
return corelatedCriteria;
}

if (relationships == null || relationships.isEmpty()) {
// We are dealing with an uncorrelated query
private static CorelatedCriteria generateCorelatedCriteriaForQuery(Query query, PhemaElmToOmopTranslatorContext context) throws Exception {
List<RelationshipClause> relationships = query.getRelationship();

// We don't currently support where clause filtering
if (query.getWhere() != null) {
throw new PhemaNotImplementedException("Query filtering which Where clause is currently unsupported");
}
if (query.getWhere() == null && (relationships == null || relationships.isEmpty())) {
// We are dealing with an uncorrelated query

// Right now we basic just support a simple aliased query like [Condition: "valueset"] C, which is exactly
// the same a simple retrieve, so we evaluate it as such. If there are no relationships, there should only
// be one AliasedSource
return generateCorelatedCriteriaForExpression(query.getSource().get(0).getExpression(), context);
} else if (relationships.size() == 1) {
// We are dealing with a correlated query
return CorrelatedQueryTranslator.generateCorelatedCriteriaForCorrelatedQuery(query, context);
} else {
throw new PhemaNotImplementedException("The translator is currently only able to handle a single relationship for a data element");
}
// Right now we basic just support a simple aliased query like [Condition: "valueset"] C, which is exactly
// the same a simple retrieve, so we evaluate it as such. If there are no relationships, there should only
// be one AliasedSource
return generateCorelatedCriteriaForExpression(query.getSource().get(0).getExpression(), context);
} else if (relationships.size() == 1) {
// We are dealing with a correlated query using "such that"
return CorrelatedQueryTranslator.generateCorelatedCriteriaForCorrelatedQuery(query, context);
} else if (query.getWhere() != null) {
// We are dealing with a correlated query using "where"
return CorrelatedQueryTranslator.generateCorelatedCriteriaForCorrelatedQueryWithWhere(query, context);
} else {
throw new PhemaNotImplementedException("The translator is currently only able to handle a single relationship for a data element");
}
}

/**
* Given an Expression from CQL/ELM, convert it into an OHDSI CorelatedCriteria.
*
* @param expression The ELM expression
* @param context The ELM translation context
* @return The CorelatedCriteria for the expression
* @throws Exception
*/
public static CorelatedCriteria generateCorelatedCriteriaForExpression(Expression expression, PhemaElmToOmopTranslatorContext context) throws Exception {
if (expression instanceof Retrieve) {
return generateCorelatedCriteriaForRetrieve((Retrieve) expression, context);
} else if (expression instanceof Exists) {
return generateCorelatedCriteriaForExpression(((Exists) expression).getOperand(), context);
} else if (expression instanceof Query) {
return generateCorelatedCriteriaForQuery((Query) expression, context);
} else if (ComparisonExpressionTranslator.isNumericComparison(expression)) {
return ComparisonExpressionTranslator.generateCorelatedCriteriaForComparison(expression, context);
} else {
// TODO - Need to handle more than simple query types
throw new PhemaNotImplementedException(String.format("Unable to generate CorelatedCriteria for type: %s", expression.getClass().getName()));
}
/**
* Given an Expression from CQL/ELM, convert it into an OHDSI CorelatedCriteria.
*
* @param expression The ELM expression
* @param context The ELM translation context
* @return The CorelatedCriteria for the expression
* @throws Exception
*/
public static CorelatedCriteria generateCorelatedCriteriaForExpression(Expression expression, PhemaElmToOmopTranslatorContext context) throws Exception {
if (expression instanceof Retrieve) {
return generateCorelatedCriteriaForRetrieve((Retrieve) expression, context);
} else if (expression instanceof Exists) {
return generateCorelatedCriteriaForExpression(((Exists) expression).getOperand(), context);
} else if (expression instanceof Query) {
return generateCorelatedCriteriaForQuery((Query) expression, context);
} else if (ComparisonExpressionTranslator.isNumericComparison(expression)) {
return ComparisonExpressionTranslator.generateCorelatedCriteriaForComparison(expression, context);
} else {
// TODO - Need to handle more than simple query types
throw new PhemaNotImplementedException(String.format("Unable to generate CorelatedCriteria for type: %s", expression.getClass().getName()));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,130 +4,167 @@
import edu.phema.elm_to_omop.vocabulary.phema.PhemaConceptSet;
import org.hl7.elm.r1.*;

import java.util.HashMap;
import java.util.List;
import java.util.Optional;

public class PhemaElmToOmopTranslatorContext {
private Library library;
private List<PhemaConceptSet> conceptSets;
private Library library;
private List<PhemaConceptSet> conceptSets;

public PhemaElmToOmopTranslatorContext(Library library, List<PhemaConceptSet> conceptSets) {
this.library = library;
this.conceptSets = conceptSets;
}
private HashMap<String, Expression> aliases;

public Library getLibrary() {
return library;
}
public PhemaElmToOmopTranslatorContext(Library library, List<PhemaConceptSet> conceptSets) {
this.library = library;
this.conceptSets = conceptSets;
this.aliases = new HashMap();
}

public List<PhemaConceptSet> getConceptSets() {
return conceptSets;
}
public Library getLibrary() {
return library;
}

public ValueSetDef getValueset(String valuesetReference) throws PhemaTranslationException {
Optional<ValueSetDef> valueset = library
.getValueSets()
.getDef()
.stream()
.filter(vd -> vd.getName().equals(valuesetReference))
.findFirst();

if (valueset.isPresent()) {
return valueset.get();
} else {
throw new PhemaTranslationException(String.format("Value set %s not found", valuesetReference));
}
}
public List<PhemaConceptSet> getConceptSets() {
return conceptSets;
}

public CodeDef getCode(String codeReference) throws PhemaTranslationException {
Optional<CodeDef> codeDef = library
.getCodes()
.getDef()
.stream()
.filter(cd -> cd.getName().equals(codeReference))
.findFirst();

if (codeDef.isPresent()) {
return codeDef.get();
} else {
throw new PhemaTranslationException(String.format("Code %s not found", codeReference));
}
public String addAlias(Query query) throws PhemaTranslationException {
if (query.getSource().size() != 1) {
throw new PhemaTranslationException("Multi-source queries not supported");
}

/**
* Return the appropriate vocabulary reference depending on whether we are dealing with a value set or a code
*
* @param vocabularyReference The reference used in CQL
* @return The OID we use internally to identify the vocabulary reference
* @throws PhemaTranslationException
*/
private String getInternalVocabularyReference(String vocabularyReference) throws PhemaTranslationException {
try {
ValueSetDef valueSetDef = getValueset(vocabularyReference);

return valueSetDef.getId();
} catch (PhemaTranslationException e) { /* Noop */ }

try {
CodeDef codeDef = getCode(vocabularyReference);

return codeDef.getName();
} catch (PhemaTranslationException e) {
throw new PhemaTranslationException(String.format("No valueset or code found for vocabulary reference %s", vocabularyReference));
}
String alias = query.getSource().get(0).getAlias();

if (aliases.containsKey(alias)) {
// The parser should catch the problem for us
throw new PhemaTranslationException("Ambiguous query source alias");
}

public int getCodesetId(String valuesetReference) throws PhemaTranslationException {
ValueSetDef valueset = getValueset(valuesetReference);
aliases.put(alias, query.getSource().get(0).getExpression());

Optional<PhemaConceptSet> result = conceptSets
.stream()
.filter(c -> c.getOid().equals(valueset.getId()))
.findFirst();
return alias;
}

if (result.isPresent()) {
return result.get().id;
} else {
throw new PhemaTranslationException(String.format("Value set %s not found", valuesetReference));
}
}
public Expression resolveAlias(String alias) {
return aliases.get(alias);
}

public int getCodeSetIdForVocabularyReference(String vocabularyReference) throws PhemaTranslationException {
String internalVocabularyReference = getInternalVocabularyReference(vocabularyReference);
public void removeAlias(String alias) {
aliases.remove(alias);
}

Optional<PhemaConceptSet> result = conceptSets
.stream()
.filter(c -> c.getOid().equals(internalVocabularyReference))
.findFirst();
public ValueSetDef getValueset(String valuesetReference) throws PhemaTranslationException {
Optional<ValueSetDef> valueset = library
.getValueSets()
.getDef()
.stream()
.filter(vd -> vd.getName().equals(valuesetReference))
.findFirst();

if (result.isPresent()) {
return result.get().id;
} else {
throw new PhemaTranslationException(String.format("Codeset not found for code %s", vocabularyReference));
}
if (valueset.isPresent()) {
return valueset.get();
} else {
throw new PhemaTranslationException(String.format("Value set %s not found", valuesetReference));
}
}

public CodeDef getCode(String codeReference) throws PhemaTranslationException {
Optional<CodeDef> codeDef = library
.getCodes()
.getDef()
.stream()
.filter(cd -> cd.getName().equals(codeReference))
.findFirst();

if (codeDef.isPresent()) {
return codeDef.get();
} else {
throw new PhemaTranslationException(String.format("Code %s not found", codeReference));
}
}

/**
* Return the appropriate vocabulary reference depending on whether we are dealing with a value set or a code
*
* @param vocabularyReference The reference used in CQL
* @return The OID we use internally to identify the vocabulary reference
* @throws PhemaTranslationException
*/
private String getInternalVocabularyReference(String vocabularyReference) throws PhemaTranslationException {
try {
ValueSetDef valueSetDef = getValueset(vocabularyReference);

return valueSetDef.getId();
} catch (PhemaTranslationException e) { /* Noop */ }

try {
CodeDef codeDef = getCode(vocabularyReference);

return codeDef.getName();
} catch (PhemaTranslationException e) {
throw new PhemaTranslationException(String.format("No valueset or code found for vocabulary reference %s", vocabularyReference));
}
}

public int getCodesetId(String valuesetReference) throws PhemaTranslationException {
ValueSetDef valueset = getValueset(valuesetReference);

public String getVocabularyReferenceForRetrieve(Retrieve retrieve) throws PhemaTranslationException {
Expression codeExpression = retrieve.getCodes();

if (codeExpression instanceof ValueSetRef) {
// The retrieve references a value set
return ((ValueSetRef) retrieve.getCodes()).getName();
} else if (codeExpression instanceof ToList) {
// The retrieve references a code
ToList toList = (ToList) codeExpression;
ToConcept toConcept = (ToConcept) toList.getOperand();
CodeRef codeRef = (CodeRef) toConcept.getOperand();

return codeRef.getName();
} else {
throw new PhemaTranslationException(String.format("Unable to retrieve codesetId for code expression of type %s", codeExpression.getClass().getSimpleName()));
}
Optional<PhemaConceptSet> result = conceptSets
.stream()
.filter(c -> c.getOid().equals(valueset.getId()))
.findFirst();

if (result.isPresent()) {
return result.get().id;
} else {
throw new PhemaTranslationException(String.format("Value set %s not found", valuesetReference));
}
}

public int getCodeSetIdForVocabularyReference(String vocabularyReference) throws PhemaTranslationException {
String internalVocabularyReference = getInternalVocabularyReference(vocabularyReference);

public int getCodesetIdForRetrieve(Retrieve retrieve) throws PhemaTranslationException {
String vocabularyReference = getVocabularyReferenceForRetrieve(retrieve);
Optional<PhemaConceptSet> result = conceptSets
.stream()
.filter(c -> c.getOid().equals(internalVocabularyReference))
.findFirst();

return getCodeSetIdForVocabularyReference(vocabularyReference);
if (result.isPresent()) {
return result.get().id;
} else {
throw new PhemaTranslationException(String.format("Codeset not found for code %s", vocabularyReference));
}
}

public String getVocabularyReferenceForRetrieve(Retrieve retrieve) throws PhemaTranslationException {
Expression codeExpression = retrieve.getCodes();

if (codeExpression instanceof ValueSetRef) {
// The retrieve references a value set
return ((ValueSetRef) retrieve.getCodes()).getName();
} else if (codeExpression instanceof ToList) {
// The retrieve references a code
ToList toList = (ToList) codeExpression;
Expression toListOperand = toList.getOperand();
CodeRef codeRef = null;
if (toListOperand instanceof ToConcept) {
ToConcept toConcept = (ToConcept) toList.getOperand();
codeRef = (CodeRef) toConcept.getOperand();
} else if (toListOperand instanceof CodeRef) {
codeRef = (CodeRef)toListOperand;
} else {
throw new PhemaTranslationException("Unsupported list operand");
}

return codeRef.getName();
} else {
throw new PhemaTranslationException(String.format("Unable to retrieve codesetId for code expression of type %s", codeExpression.getClass().getSimpleName()));
}
}

public int getCodesetIdForRetrieve(Retrieve retrieve) throws PhemaTranslationException {
String vocabularyReference = getVocabularyReferenceForRetrieve(retrieve);

return getCodeSetIdForVocabularyReference(vocabularyReference);
}
}
Loading

0 comments on commit 560cee3

Please sign in to comment.