diff --git a/CHANGELOG.md b/CHANGELOG.md index 6457e6dbd..9344731c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,10 @@ Note: version releases in the 0.x.y range may introduce breaking changes. - Add spotless plugin, Add codestyle check to workflows ([#368](https://github.com/ehrbase/ehrbase/pull/368)) ### Fixed - Skip archetype slots not used by the template in example generator ([#369](https://github.com/ehrbase/openEHR_SDK/pull/369)) +- enhance sdk aql parser to handle more cases ([#376](https://github.com/ehrbase/openEHR_SDK/pull/376)) - update update everit-json-schema to maven version ([#370](https://github.com/ehrbase/openEHR_SDK/pull/370)) + ## [1.19.0] ### Added diff --git a/aql/pom.xml b/aql/pom.xml index 9e88d9c87..35500d854 100644 --- a/aql/pom.xml +++ b/aql/pom.xml @@ -56,10 +56,7 @@ - - org.ehrbase.openehr.sdk - client - + org.antlr antlr4-runtime @@ -79,5 +76,29 @@ junit-vintage-engine test + + org.apache.commons + commons-collections4 + + + org.apache.commons + commons-lang3 + + + org.ehrbase.openehr.sdk + util + + + com.fasterxml.jackson.core + jackson-annotations + + + com.nedap.healthcare.archie + openehr-rm + + + com.nedap.healthcare.archie + archie-utils + diff --git a/aql/src/main/antlr4/org/ehrbase/aql/parser/Aql.g4 b/aql/src/main/antlr4/org/ehrbase/aql/parser/Aql.g4 index a1c8f02be..461c0d0c4 100644 --- a/aql/src/main/antlr4/org/ehrbase/aql/parser/Aql.g4 +++ b/aql/src/main/antlr4/org/ehrbase/aql/parser/Aql.g4 @@ -18,7 +18,7 @@ grammar Aql; query : queryExpr ; -queryExpr : select from (where)? (orderBy limitExpr? | limitExpr orderBy?)? EOF ; +queryExpr : select from (where)? (orderBy (limit offset?)? | (limit offset?) orderBy?)? EOF ; select : SELECT selectExpr @@ -30,7 +30,10 @@ topExpr // | TOP INTEGER FORWARD ; function - : FUNCTION_IDENTIFIER OPEN_PAR (IDENTIFIER|identifiedPath|operand) (COMMA (IDENTIFIER|identifiedPath|operand))* CLOSE_PAR; + : FUNCTION_IDENTIFIER OPEN_PAR (IDENTIFIER|identifiedPath|operand|) (COMMA (IDENTIFIER|identifiedPath|operand))* CLOSE_PAR; + +castFunction : + CAST_FUNCTION_IDENTIFIER OPEN_PAR (IDENTIFIER|identifiedPath|operand) AS STRING CLOSE_PAR; extension : EXTENSION_IDENTIFIER OPEN_PAR STRING COMMA STRING CLOSE_PAR; @@ -41,8 +44,8 @@ where orderBy : ORDERBY orderBySeq ; -limitExpr - : LIMIT INTEGER (offset)?; +limit + : LIMIT INTEGER; offset : OFFSET INTEGER; @@ -67,9 +70,19 @@ selectExpr stdExpression : function + | castFunction | extension | INTEGER | STRING + | FLOAT + | REAL + | DATE + | PARAMETER + | BOOLEAN + | TRUE + | FALSE + | NULL + | UNKNOWN ; //variableSeq_ @@ -176,6 +189,7 @@ nodePredicateRegEx matchesOperand : valueListItems + | identifiedPath | URIVALUE ; valueListItems @@ -216,6 +230,7 @@ operand : STRING | INTEGER | FLOAT + | REAL | DATE | PARAMETER | BOOLEAN @@ -223,7 +238,10 @@ operand | FALSE | NULL | UNKNOWN - | invokeOperand; + | invokeOperand + | function + | castFunction; + invokeOperand : invokeExpr; @@ -305,6 +323,7 @@ UNKNOWN: U N K N O W N; TRUE: T R U E; FALSE: F A L S E; + //demographic binding PERSON: P E R S O N ; AGENT: A G E N T ; @@ -312,17 +331,20 @@ ORGANISATION: O R G A N I S A T I O N ; GROUP: G R O U P ; FUNCTION_IDENTIFIER : COUNT | AVG | BOOL_AND | BOOL_OR | EVERY | MAX | MIN | SUM | -//statistic +//statistics CORR | COVAR_POP | COVAR_SAMP | REGR_AVGX | REGR_AVGY | REGR_COUNT | REGR_INTERCEPT | REGR_R2 | REGR_SLOPE | REGR_SXX | REGR_SXY | REGR_SYY | STDDEV | STDDEV_POP | STDDEV_SAMP | VARIANCE | VAR_POP | VAR_SAMP | -//string function +//string functions SUBSTR | STRPOS | SPLIT_PART | BTRIM | CONCAT | CONCAT_WS | DECODE | ENCODE | FORMAT | INITCAP | LEFT | LENGTH | LPAD | LTRIM | REGEXP_MATCH | REGEXP_REPLACE | REGEXP_SPLIT_TO_ARRAY | REGEXP_SPLIT_TO_TABLE | REPEAT | REPLACE | REVERSE | RIGHT | RPAD | RTRIM | TRANSLATE | -//encoding function - RAW_COMPOSITION_ENCODE +//encoding functions + RAW_COMPOSITION_ENCODE | +//basic date functions + NOW | AGE | CURRENT_TIME | CURRENT_DATE ; +CAST_FUNCTION_IDENTIFIER: C A S T; EXTENSION_IDENTIFIER: '_' E X T; @@ -342,6 +364,7 @@ DEMOGRAPHIC INTEGER : '-'? DIGIT+; FLOAT : '-'? DIGIT+ '.' DIGIT+; +REAL : '-'? DIGIT+ ('.' DIGIT+)? (E (|'+'|'-') DIGIT+)?; DATE : '\'' DIGIT DIGIT DIGIT DIGIT DIGIT DIGIT DIGIT DIGIT 'T' DIGIT DIGIT DIGIT DIGIT DIGIT DIGIT '.' DIGIT DIGIT DIGIT '+' DIGIT DIGIT DIGIT DIGIT '\''; PARAMETER : '$' LETTER IDCHAR*; @@ -440,6 +463,11 @@ VARIANCE : V A R I A N C E; VAR_POP : V A R '_' P O P; VAR_SAMP : V A R '_' S A M P; RAW_COMPOSITION_ENCODE : '_' '_' R A W '_' C O M P O S I T I O N '_' E N C O D E; +CAST : C A S T; +NOW : N O W; +AGE : A G E; +CURRENT_TIME : C U R R E N T '_' T I M E; +CURRENT_DATE : C U R R E N T '_' D A T E; fragment ESC_SEQ diff --git a/aql/src/main/java/org/ehrbase/aql/binder/AqlBinder.java b/aql/src/main/java/org/ehrbase/aql/binder/AqlBinder.java index b46c7e8a1..2e24160d1 100644 --- a/aql/src/main/java/org/ehrbase/aql/binder/AqlBinder.java +++ b/aql/src/main/java/org/ehrbase/aql/binder/AqlBinder.java @@ -95,6 +95,11 @@ public Pair, List> bind(AqlDto aqlDto) { query.top(TopExpresion.backward(aqlDto.getSelect().getTopCount())); } + // build distinct + if (aqlDto.getSelect().isDistinct()) { + query.distinct(true); + } + // build order by if (!CollectionUtils.isEmpty(aqlDto.getOrderBy())) { query.orderBy(orderByBinder.bind(aqlDto.getOrderBy(), containmentMap)); diff --git a/aql/src/main/java/org/ehrbase/aql/binder/SelectBinder.java b/aql/src/main/java/org/ehrbase/aql/binder/SelectBinder.java index 49007923d..954edea8e 100644 --- a/aql/src/main/java/org/ehrbase/aql/binder/SelectBinder.java +++ b/aql/src/main/java/org/ehrbase/aql/binder/SelectBinder.java @@ -19,11 +19,14 @@ import java.util.Map; import org.apache.commons.lang3.StringUtils; +import org.ehrbase.aql.dto.select.FunctionDto; import org.ehrbase.aql.dto.select.SelectFieldDto; import org.ehrbase.aql.dto.select.SelectStatementDto; +import org.ehrbase.aql.parser.AqlParseException; import org.ehrbase.client.aql.containment.Containment; import org.ehrbase.client.aql.field.NativeSelectAqlField; import org.ehrbase.client.aql.field.SelectAqlField; +import org.ehrbase.client.aql.funtion.Function; import org.ehrbase.util.exception.SdkException; public class SelectBinder { @@ -32,6 +35,8 @@ public SelectAqlField bind(SelectStatementDto dto, Map selectAqlField; if (dto instanceof SelectFieldDto) { selectAqlField = handleSelectFieldDto((SelectFieldDto) dto, containmentMap); + } else if (dto instanceof FunctionDto) { + selectAqlField = handleFunctionDto((FunctionDto) dto, containmentMap); } else { throw new SdkException( String.format("Unexpected class: %s", dto.getClass().getSimpleName())); @@ -39,6 +44,23 @@ public SelectAqlField bind(SelectStatementDto dto, Map handleFunctionDto(FunctionDto dto, Map containmentMap) { + + switch (dto.getAqlFunction()) { + case COUNT: + return (SelectAqlField) Function.count(bind(dto.getParameters().get(0), containmentMap), dto.getName()); + case MAX: + return (SelectAqlField) Function.max(bind(dto.getParameters().get(0), containmentMap), dto.getName()); + case MIN: + return (SelectAqlField) Function.min(bind(dto.getParameters().get(0), containmentMap), dto.getName()); + case AVG: + return (SelectAqlField) Function.avg(bind(dto.getParameters().get(0), containmentMap), dto.getName()); + default: + throw new AqlParseException(String.format( + "Unsupported Funktion %s", dto.getAqlFunction().name())); + } + } + public SelectAqlField handleSelectFieldDto(SelectFieldDto dto, Map containmentMap) { SelectAqlField selectAqlField; selectAqlField = new NativeSelectAqlField<>( diff --git a/aql/src/main/java/org/ehrbase/aql/binder/WhereBinder.java b/aql/src/main/java/org/ehrbase/aql/binder/WhereBinder.java index c61b4364b..42e3f1d66 100644 --- a/aql/src/main/java/org/ehrbase/aql/binder/WhereBinder.java +++ b/aql/src/main/java/org/ehrbase/aql/binder/WhereBinder.java @@ -28,7 +28,9 @@ import org.ehrbase.aql.dto.condition.ConditionDto; import org.ehrbase.aql.dto.condition.ConditionLogicalOperatorDto; import org.ehrbase.aql.dto.condition.ConditionLogicalOperatorSymbol; +import org.ehrbase.aql.dto.condition.ExistsConditionOperatorDto; import org.ehrbase.aql.dto.condition.MatchesOperatorDto; +import org.ehrbase.aql.dto.condition.NotConditionOperatorDto; import org.ehrbase.aql.dto.condition.ParameterValue; import org.ehrbase.aql.dto.condition.SimpleValue; import org.ehrbase.client.aql.condition.Condition; @@ -68,6 +70,12 @@ public Pair> bind(ConditionDto dto, Map="), + GT("greaterThan", ">"), + LT_EQ("lessOrEqual", "<="), + LT("lessThan", "<"); private final String javaName; + private final String symbole; - ConditionComparisonOperatorSymbol(String javaName) { + ConditionComparisonOperatorSymbol(String javaName, String symbole) { this.javaName = javaName; + this.symbole = symbole; } public String getJavaName() { return javaName; } + + public String getSymbole() { + return symbole; + } + + public static ConditionComparisonOperatorSymbol fromSymbol(String symbole) { + return Arrays.stream(values()) + .filter(s -> s.getSymbole().equals(symbole)) + .findAny() + .orElseThrow(); + } } diff --git a/aql/src/main/java/org/ehrbase/aql/dto/condition/ConditionLogicalOperatorDto.java b/aql/src/main/java/org/ehrbase/aql/dto/condition/ConditionLogicalOperatorDto.java index 2a6ed52ba..476fdc62e 100644 --- a/aql/src/main/java/org/ehrbase/aql/dto/condition/ConditionLogicalOperatorDto.java +++ b/aql/src/main/java/org/ehrbase/aql/dto/condition/ConditionLogicalOperatorDto.java @@ -19,23 +19,28 @@ import java.util.List; -public class ConditionLogicalOperatorDto implements ConditionDto { +public class ConditionLogicalOperatorDto + implements ConditionDto, LogicalOperatorDto { private ConditionLogicalOperatorSymbol symbol; private List values; + @Override public ConditionLogicalOperatorSymbol getSymbol() { return this.symbol; } + @Override public List getValues() { return this.values; } + @Override public void setSymbol(ConditionLogicalOperatorSymbol symbol) { this.symbol = symbol; } + @Override public void setValues(List values) { this.values = values; } diff --git a/aql/src/main/java/org/ehrbase/aql/dto/condition/ExistsConditionOperatorDto.java b/aql/src/main/java/org/ehrbase/aql/dto/condition/ExistsConditionOperatorDto.java new file mode 100644 index 000000000..c870c2140 --- /dev/null +++ b/aql/src/main/java/org/ehrbase/aql/dto/condition/ExistsConditionOperatorDto.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2022 vitasystems GmbH and Hannover Medical School. + * + * This file is part of project openEHR_SDK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ehrbase.aql.dto.condition; + +import java.util.Objects; +import org.ehrbase.aql.dto.select.SelectFieldDto; + +/** + * @author Stefan Spiska + */ +public class ExistsConditionOperatorDto implements ConditionDto { + + private SelectFieldDto value; + + public ExistsConditionOperatorDto() {} + + public ExistsConditionOperatorDto(SelectFieldDto value) { + this.value = value; + } + + public SelectFieldDto getValue() { + return value; + } + + public void setValue(SelectFieldDto value) { + this.value = value; + } + + @Override + public String toString() { + return "ExistsConditionOperator{" + "value=" + value + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ExistsConditionOperatorDto that = (ExistsConditionOperatorDto) o; + return Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } +} diff --git a/aql/src/main/java/org/ehrbase/aql/dto/condition/LogicalOperatorDto.java b/aql/src/main/java/org/ehrbase/aql/dto/condition/LogicalOperatorDto.java new file mode 100644 index 000000000..36ba12e8d --- /dev/null +++ b/aql/src/main/java/org/ehrbase/aql/dto/condition/LogicalOperatorDto.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2022 vitasystems GmbH and Hannover Medical School. + * + * This file is part of project openEHR_SDK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ehrbase.aql.dto.condition; + +import java.util.List; + +/** + * @author Stefan Spiska + */ +public interface LogicalOperatorDto { + S getSymbol(); + + List getValues(); + + void setSymbol(S symbol); + + void setValues(List values); +} diff --git a/aql/src/main/java/org/ehrbase/aql/dto/condition/NotConditionOperatorDto.java b/aql/src/main/java/org/ehrbase/aql/dto/condition/NotConditionOperatorDto.java new file mode 100644 index 000000000..2ac355f2e --- /dev/null +++ b/aql/src/main/java/org/ehrbase/aql/dto/condition/NotConditionOperatorDto.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2022 vitasystems GmbH and Hannover Medical School. + * + * This file is part of project openEHR_SDK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ehrbase.aql.dto.condition; + +import java.util.Objects; + +/** + * @author Stefan Spiska + */ +public class NotConditionOperatorDto implements ConditionDto { + + private ConditionDto conditionDto; + + public NotConditionOperatorDto() {} + + public NotConditionOperatorDto(ConditionDto conditionDto) { + this.conditionDto = conditionDto; + } + + public ConditionDto getConditionDto() { + return conditionDto; + } + + public void setConditionDto(ConditionDto conditionDto) { + this.conditionDto = conditionDto; + } + + @Override + public String toString() { + return "NotConditionOperator{" + "conditionDto=" + conditionDto + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + NotConditionOperatorDto that = (NotConditionOperatorDto) o; + return Objects.equals(conditionDto, that.conditionDto); + } + + @Override + public int hashCode() { + return Objects.hash(conditionDto); + } +} diff --git a/aql/src/main/java/org/ehrbase/aql/dto/condition/ParameterValue.java b/aql/src/main/java/org/ehrbase/aql/dto/condition/ParameterValue.java index c9a4f5ac2..80c7646d9 100644 --- a/aql/src/main/java/org/ehrbase/aql/dto/condition/ParameterValue.java +++ b/aql/src/main/java/org/ehrbase/aql/dto/condition/ParameterValue.java @@ -17,11 +17,21 @@ */ package org.ehrbase.aql.dto.condition; -public class ParameterValue implements Value { +import java.io.Serializable; +import org.ehrbase.aql.dto.path.predicate.SimplePredicateDto; + +public class ParameterValue implements Value, SimplePredicateDto, Serializable { private String name; private String type; + public ParameterValue() {} + + public ParameterValue(ParameterValue other) { + this.name = other.name; + this.type = other.type; + } + public String getName() { return this.name; } diff --git a/aql/src/main/java/org/ehrbase/aql/dto/condition/SimpleValue.java b/aql/src/main/java/org/ehrbase/aql/dto/condition/SimpleValue.java index 38fa339bf..b459c18b2 100644 --- a/aql/src/main/java/org/ehrbase/aql/dto/condition/SimpleValue.java +++ b/aql/src/main/java/org/ehrbase/aql/dto/condition/SimpleValue.java @@ -17,9 +17,10 @@ */ package org.ehrbase.aql.dto.condition; +import java.io.Serializable; import java.util.Objects; -public class SimpleValue implements Value { +public class SimpleValue implements Value, Serializable { private Object value; diff --git a/aql/src/main/java/org/ehrbase/aql/dto/containment/ContainmentLogicalOperator.java b/aql/src/main/java/org/ehrbase/aql/dto/containment/ContainmentLogicalOperator.java index e48999d47..992c67acd 100644 --- a/aql/src/main/java/org/ehrbase/aql/dto/containment/ContainmentLogicalOperator.java +++ b/aql/src/main/java/org/ehrbase/aql/dto/containment/ContainmentLogicalOperator.java @@ -18,8 +18,11 @@ package org.ehrbase.aql.dto.containment; import java.util.List; +import org.ehrbase.aql.dto.condition.LogicalOperatorDto; -public class ContainmentLogicalOperator implements ContainmentExpresionDto { +public class ContainmentLogicalOperator + implements ContainmentExpresionDto, + LogicalOperatorDto { ContainmentLogicalOperatorSymbol symbol; List values; diff --git a/web-template/src/main/java/org/ehrbase/webtemplate/parser/AqlPath.java b/aql/src/main/java/org/ehrbase/aql/dto/path/AqlPath.java similarity index 74% rename from web-template/src/main/java/org/ehrbase/webtemplate/parser/AqlPath.java rename to aql/src/main/java/org/ehrbase/aql/dto/path/AqlPath.java index 4e11bd613..21d39c003 100644 --- a/web-template/src/main/java/org/ehrbase/webtemplate/parser/AqlPath.java +++ b/aql/src/main/java/org/ehrbase/aql/dto/path/AqlPath.java @@ -15,30 +15,40 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.ehrbase.webtemplate.parser; +package org.ehrbase.aql.dto.path; -import static org.ehrbase.webtemplate.util.CharSequenceHelper.subSequence; +import static org.ehrbase.aql.dto.path.predicate.PredicateHelper.ARCHETYPE_NODE_ID; +import static org.ehrbase.aql.dto.path.predicate.PredicateHelper.NAME_VALUE; +import static org.ehrbase.aql.dto.path.predicate.PredicateHelper.find; +import static org.ehrbase.aql.util.CharSequenceHelper.subSequence; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.function.UnaryOperator; import org.apache.commons.lang3.StringUtils; -import org.ehrbase.webtemplate.util.CharSequenceHelper; +import org.ehrbase.aql.dto.condition.ConditionComparisonOperatorSymbol; +import org.ehrbase.aql.dto.condition.SimpleValue; +import org.ehrbase.aql.dto.path.predicate.PredicateComparisonOperatorDto; +import org.ehrbase.aql.dto.path.predicate.PredicateDto; +import org.ehrbase.aql.dto.path.predicate.PredicateHelper; +import org.ehrbase.aql.dto.path.predicate.PredicateLogicalAndOperation; +import org.ehrbase.aql.dto.path.predicate.PredicateLogicalOrOperation; +import org.ehrbase.aql.dto.path.predicate.SimplePredicateDto; +import org.ehrbase.aql.util.CharSequenceHelper; +import org.ehrbase.util.exception.SdkException; public final class AqlPath implements Serializable { public static final AqlPath EMPTY_PATH = new AqlPath(true, new AqlNode[0], 0, null); public static final AqlPath ROOT_PATH = new AqlPath(false, new AqlNode[0], 0, null); - private static final AqlNode NO_NODE = new AqlNode("", null, Collections.emptyMap()); + private static final AqlNode NO_NODE = new AqlNode("", null, new PredicateLogicalAndOperation()); public static final String NAME_VALUE_KEY = "name/value"; private final boolean isEmpty; @@ -301,7 +311,7 @@ public static AqlPath parse(String pathExp, String nameValue) { } String attributeName = null; - final CharSequence[] nodeStrings = split(CharSequenceHelper.removeStart(pathExp, "/"), null, "/"); + final CharSequence[] nodeStrings = split(CharSequenceHelper.removeStart(pathExp, "/"), null, false, "/"); List nodes = new ArrayList<>(nodeStrings.length); @@ -312,7 +322,7 @@ public static AqlPath parse(String pathExp, String nameValue) { // remove attribute if (isLastNode) { - CharSequence[] attributeSplit = split(currentNode, 2, "|"); + CharSequence[] attributeSplit = split(currentNode, 2, false, "|"); if (attributeSplit.length == 2) { attributeName = attributeSplit[1].toString(); } @@ -332,7 +342,7 @@ private static Optional parseNode(CharSequence currentNode, boolean isL if (StringUtils.endsWith(currentNode, "]")) { int fist = StringUtils.indexOf(currentNode, '['); nodeName = subSequence(currentNode, 0, fist).toString(); - predicatesExp = subSequence(currentNode, fist, currentNode.length()); + predicatesExp = subSequence(currentNode, fist + 1, currentNode.length() - 1); } else { nodeName = currentNode.toString(); predicatesExp = null; @@ -343,53 +353,39 @@ private static Optional parseNode(CharSequence currentNode, boolean isL } String atCode; - Map otherPredicates; - if (predicatesExp == null) { - atCode = null; - otherPredicates = Collections.emptyMap(); - - } else { - CharSequence node = CharSequenceHelper.removeEnd(CharSequenceHelper.removeStart(predicatesExp, "["), "]"); + PredicateLogicalAndOperation otherPredicates; - CharSequence[] predicates = split(node, null, " and ", ","); - atCode = predicates[0].toString().trim(); + PredicateDto predicateDto = null; + if (predicatesExp != null) { + predicateDto = PredicateHelper.buildPredicate(predicatesExp.toString()); - if (predicates.length == 1) { - if (isLastNode && StringUtils.isNotEmpty(nameValue)) { - otherPredicates = Collections.singletonMap(NAME_VALUE_KEY, nameValue); - } else { - otherPredicates = Collections.emptyMap(); - } + if (predicateDto instanceof PredicateLogicalOrOperation) { + throw new SdkException("Or in predicate not supported"); + } else if (predicateDto instanceof PredicateLogicalAndOperation) { + otherPredicates = (PredicateLogicalAndOperation) predicateDto; } else { - otherPredicates = new LinkedHashMap<>(); - - for (int j = 1; j < predicates.length; j++) { - CharSequence[] pair = split(predicates[j], 2, "="); - String key; - CharSequence value; - if (j == 1 && pair.length == 1) { - key = NAME_VALUE_KEY; - value = pair[0]; - } else if (pair.length == 2) { - key = pair[0].toString(); - value = pair[1]; - } else { - throw new IllegalArgumentException("Illegal predicate format"); - } - otherPredicates.put( - key.trim(), StringUtils.unwrap(value.toString().trim(), "'")); - } - - if (isLastNode && StringUtils.isNotEmpty(nameValue)) { - otherPredicates.put(NAME_VALUE_KEY, nameValue); - } + otherPredicates = new PredicateLogicalAndOperation(); + otherPredicates.getValues().add((SimplePredicateDto) predicateDto); } - } - return Optional.of(new AqlNode(nodeName, atCode, otherPredicates)); + atCode = find(otherPredicates, ARCHETYPE_NODE_ID) + .map(PredicateComparisonOperatorDto::getValue) + .map(SimpleValue.class::cast) + .map(SimpleValue::getValue) + .map(Object::toString) + .orElse(null); + } else { + atCode = null; + otherPredicates = new PredicateLogicalAndOperation(); + } + AqlNode node = new AqlNode(nodeName, atCode, otherPredicates); + if (nameValue != null && isLastNode) { + node = node.withNameValue(nameValue); + } + return Optional.of(node); } - private static CharSequence[] split(CharSequence path, Integer max, String... search) { + public static CharSequence[] split(CharSequence path, Integer max, boolean addSearch, String... search) { List strings = new ArrayList<>(); Arrays.sort(search, CharSequence::compare); @@ -418,6 +414,9 @@ private static CharSequence[] split(CharSequence path, Integer max, String... se escape = false; } else { strings.add(subSequence(path, last, i)); + if (addSearch) { + strings.add(prefix); + } last = prefix.length() + i; if (max != null && strings.size() == max - 1) { strings.add(subSequence(path, last, path.length())); @@ -509,14 +508,14 @@ public enum OtherPredicatesFormat { public static final class AqlNode implements Serializable { private final String name; private final String atCode; - private final Map otherPredicates; + private final PredicateLogicalAndOperation otherPredicate; private transient Integer hashCode; - private AqlNode(String name, String atCode, Map otherPredicates) { + private AqlNode(String name, String atCode, PredicateLogicalAndOperation otherPredicates) { this.name = name; this.atCode = StringUtils.isBlank(atCode) ? null : atCode; - this.otherPredicates = otherPredicates; + this.otherPredicate = otherPredicates; } public String getName() { @@ -528,51 +527,76 @@ public String getAtCode() { } public AqlNode withAtCode(String atCode) { - return new AqlNode(name, atCode, otherPredicates); + + if (atCode != null) { + + return new AqlNode(name, atCode, replace(ARCHETYPE_NODE_ID, atCode)); + } else { + + return new AqlNode(name, null, remove(ARCHETYPE_NODE_ID)); + } + } + + private PredicateLogicalAndOperation replace(String archetypeNodeId, String atCode) { + PredicateLogicalAndOperation newPredicateDto = PredicateHelper.clone(otherPredicate); + + Optional predicateComparisonOperatorDto = + find(newPredicateDto, archetypeNodeId); + + if (predicateComparisonOperatorDto.isPresent()) { + predicateComparisonOperatorDto.get().setValue(new SimpleValue(atCode)); + } else { + PredicateComparisonOperatorDto add = new PredicateComparisonOperatorDto(); + add.setStatement(archetypeNodeId); + add.setSymbol(ConditionComparisonOperatorSymbol.EQ); + add.setValue(new SimpleValue(atCode)); + newPredicateDto.getValues().add(add); + } + return newPredicateDto; } public AqlNode withNameValue(String nameValue) { - if (Objects.equals(nameValue, otherPredicates.get(NAME_VALUE_KEY))) { + if (Objects.equals(nameValue, find(otherPredicate, NAME_VALUE))) { return this; } - - Map map; - if (StringUtils.isEmpty(nameValue)) { - if (otherPredicates.size() == 1) { - map = Map.of(); - } else { - map = new LinkedHashMap<>(otherPredicates); - map.remove(NAME_VALUE_KEY); - } - } else if (otherPredicates.containsKey(NAME_VALUE_KEY)) { - if (otherPredicates.size() == 1) { - map = Map.of(NAME_VALUE_KEY, nameValue); - } else { - map = new LinkedHashMap<>(otherPredicates); - map.put(NAME_VALUE_KEY, nameValue); - } + if (nameValue != null) { + return new AqlNode(name, atCode, replace(NAME_VALUE, nameValue)); } else { - if (otherPredicates.isEmpty()) { - map = Map.of(NAME_VALUE_KEY, nameValue); - } else { - map = new LinkedHashMap<>(otherPredicates); - map.put(NAME_VALUE_KEY, nameValue); - } + + return new AqlNode(name, atCode, remove(NAME_VALUE)); } - return new AqlNode(name, atCode, map); + } + + private PredicateLogicalAndOperation remove(String nameValue) { + PredicateLogicalAndOperation newPredicateDto = PredicateHelper.clone(otherPredicate); + Optional predicateComparisonOperatorDto = find(newPredicateDto, nameValue); + predicateComparisonOperatorDto.ifPresent( + p -> newPredicateDto.getValues().remove(p)); + return newPredicateDto; } public String findOtherPredicate(String name) { - return otherPredicates.get(name); + return find(otherPredicate, name) + .map(PredicateComparisonOperatorDto::getValue) + .filter(SimpleValue.class::isInstance) + .map(SimpleValue.class::cast) + .map(SimpleValue::getValue) + .map(Object::toString) + .orElse(null); } public AqlNode clearOtherPredicates() { - if (otherPredicates.isEmpty()) { - return this; - } else { - return new AqlNode(name, atCode, Map.of()); + final PredicateLogicalAndOperation otherPredicates = new PredicateLogicalAndOperation(); + if (atCode != null) { + PredicateComparisonOperatorDto predicateComparisonOperatorDto = new PredicateComparisonOperatorDto(); + + predicateComparisonOperatorDto.setStatement(ARCHETYPE_NODE_ID); + predicateComparisonOperatorDto.setSymbol(ConditionComparisonOperatorSymbol.EQ); + predicateComparisonOperatorDto.setValue(new SimpleValue(atCode)); + otherPredicates.getValues().add(predicateComparisonOperatorDto); } + return new AqlNode(name, atCode, otherPredicates); } @Override @@ -583,7 +607,7 @@ public boolean equals(Object o) { AqlNode aqlNode = (AqlNode) o; return Objects.equals(name, aqlNode.name) && Objects.equals(atCode, aqlNode.atCode) - && Objects.equals(otherPredicates, aqlNode.otherPredicates); + && Objects.equals(otherPredicate, aqlNode.otherPredicate); } public boolean equals(AqlNode o, boolean withOtherPredicates) { @@ -597,29 +621,16 @@ public boolean equals(AqlNode o, boolean withOtherPredicates) { @Override public int hashCode() { if (hashCode == null) { - hashCode = Objects.hash(name, atCode, otherPredicates); + hashCode = Objects.hash(name, atCode, otherPredicate); } return hashCode; } public void appendFormat(StringBuilder sb, OtherPredicatesFormat otherPredicatesFormat) { sb.append(this.name); - if (this.atCode != null) { - sb.append("[").append(this.atCode); - if (otherPredicatesFormat != OtherPredicatesFormat.NONE) { - this.otherPredicates.forEach((key, value) -> { - // XXX escape value?? ('\) - if (otherPredicatesFormat == OtherPredicatesFormat.SHORTED && key.equals(NAME_VALUE_KEY)) { - sb.append(",'").append(value).append("'"); - } else { - sb.append(" and ") - .append(key) - .append("='") - .append(value) - .append("'"); - } - }); - } + if (!otherPredicate.getValues().isEmpty()) { + sb.append("["); + PredicateHelper.format(sb, otherPredicate, otherPredicatesFormat); sb.append("]"); } } diff --git a/aql/src/main/java/org/ehrbase/aql/dto/path/predicate/PredicateComparisonOperatorDto.java b/aql/src/main/java/org/ehrbase/aql/dto/path/predicate/PredicateComparisonOperatorDto.java new file mode 100644 index 000000000..4ac7b178b --- /dev/null +++ b/aql/src/main/java/org/ehrbase/aql/dto/path/predicate/PredicateComparisonOperatorDto.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2020 vitasystems GmbH and Hannover Medical School. + * + * This file is part of project openEHR_SDK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ehrbase.aql.dto.path.predicate; + +import java.io.Serializable; +import org.ehrbase.aql.dto.condition.ConditionComparisonOperatorSymbol; +import org.ehrbase.aql.dto.condition.ParameterValue; +import org.ehrbase.aql.dto.condition.SimpleValue; +import org.ehrbase.aql.dto.condition.Value; + +public class PredicateComparisonOperatorDto implements SimplePredicateDto, Serializable { + + private String statement; + private ConditionComparisonOperatorSymbol symbol; + private Value value; + + public PredicateComparisonOperatorDto() {} + + public PredicateComparisonOperatorDto(PredicateComparisonOperatorDto other) { + this.statement = other.statement; + this.symbol = other.symbol; + + if (other.value instanceof ParameterValue) { + this.value = new ParameterValue((ParameterValue) other.value); + } else { + this.value = new SimpleValue(((SimpleValue) other.value).getValue()); + } + } + + public String getStatement() { + return this.statement; + } + + public ConditionComparisonOperatorSymbol getSymbol() { + return this.symbol; + } + + public Value getValue() { + return this.value; + } + + public void setStatement(String statement) { + this.statement = statement; + } + + public void setSymbol(ConditionComparisonOperatorSymbol symbol) { + this.symbol = symbol; + } + + public void setValue(Value value) { + this.value = value; + } + + public boolean equals(final Object o) { + if (o == this) return true; + if (!(o instanceof PredicateComparisonOperatorDto)) return false; + final PredicateComparisonOperatorDto other = (PredicateComparisonOperatorDto) o; + if (!other.canEqual((Object) this)) return false; + final Object this$statement = this.getStatement(); + final Object other$statement = other.getStatement(); + if (this$statement == null ? other$statement != null : !this$statement.equals(other$statement)) return false; + final Object this$symbol = this.getSymbol(); + final Object other$symbol = other.getSymbol(); + if (this$symbol == null ? other$symbol != null : !this$symbol.equals(other$symbol)) return false; + final Object this$value = this.getValue(); + final Object other$value = other.getValue(); + if (this$value == null ? other$value != null : !this$value.equals(other$value)) return false; + return true; + } + + protected boolean canEqual(final Object other) { + return other instanceof PredicateComparisonOperatorDto; + } + + public int hashCode() { + final int PRIME = 59; + int result = 1; + final Object $statement = this.getStatement(); + result = result * PRIME + ($statement == null ? 43 : $statement.hashCode()); + final Object $symbol = this.getSymbol(); + result = result * PRIME + ($symbol == null ? 43 : $symbol.hashCode()); + final Object $value = this.getValue(); + result = result * PRIME + ($value == null ? 43 : $value.hashCode()); + return result; + } + + public String toString() { + return "ConditionComparisonOperatorDto(statement=" + + this.getStatement() + + ", symbol=" + + this.getSymbol() + + ", value=" + + this.getValue() + + ")"; + } +} diff --git a/aql/src/main/java/org/ehrbase/aql/dto/path/predicate/PredicateDto.java b/aql/src/main/java/org/ehrbase/aql/dto/path/predicate/PredicateDto.java new file mode 100644 index 000000000..1ec7bdba5 --- /dev/null +++ b/aql/src/main/java/org/ehrbase/aql/dto/path/predicate/PredicateDto.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2022 vitasystems GmbH and Hannover Medical School. + * + * This file is part of project openEHR_SDK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ehrbase.aql.dto.path.predicate; + +/** + * @author Stefan Spiska + */ +public interface PredicateDto {} diff --git a/aql/src/main/java/org/ehrbase/aql/dto/path/predicate/PredicateHelper.java b/aql/src/main/java/org/ehrbase/aql/dto/path/predicate/PredicateHelper.java new file mode 100644 index 000000000..f216bd72e --- /dev/null +++ b/aql/src/main/java/org/ehrbase/aql/dto/path/predicate/PredicateHelper.java @@ -0,0 +1,387 @@ +/* + * Copyright (c) 2022 vitasystems GmbH and Hannover Medical School. + * + * This file is part of project openEHR_SDK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ehrbase.aql.dto.path.predicate; + +import static org.ehrbase.aql.parser.AqlToDtoVisitor.buildLogicalOperator; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.ehrbase.aql.dto.condition.ConditionComparisonOperatorSymbol; +import org.ehrbase.aql.dto.condition.LogicalOperatorDto; +import org.ehrbase.aql.dto.condition.ParameterValue; +import org.ehrbase.aql.dto.condition.SimpleValue; +import org.ehrbase.aql.dto.condition.Value; +import org.ehrbase.aql.dto.path.AqlPath; +import org.ehrbase.util.exception.SdkException; + +/** + * @author Stefan Spiska + */ +public class PredicateHelper { + + public static final String NAME_VALUE = "name/value"; + public static final String ARCHETYPE_NODE_ID = "archetype_node_id"; + + public static final Comparator PREDICATE_DTO_COMPARATOR = new Comparator() { + @Override + public int compare(PredicateDto o1, PredicateDto o2) { + + if (o1 instanceof PredicateComparisonOperatorDto && o2 instanceof PredicateComparisonOperatorDto) { + + int x = toInt((PredicateComparisonOperatorDto) o1); + return Integer.compare(x, toInt((PredicateComparisonOperatorDto) o2)); + } + + return 0; + } + + int toInt(PredicateComparisonOperatorDto dto) { + switch (dto.getStatement()) { + case ARCHETYPE_NODE_ID: + return 1; + case NAME_VALUE: + return 2; + default: + return 3; + } + } + }; + + private PredicateHelper() { + // NOP + } + + public static PredicateDto buildPredicate(String predicate) { + + List boolList = parsePredicate(predicate); + + if (boolList.size() == 1) { + return (PredicateDto) boolList.get(0); + } else { + + LogicalOperatorDto predicateLogicalOperatorSymbolPredicateDtoLogicalOperatorDto = + buildLogicalOperator(boolList, (Function< + PredicateLogicalOperatorSymbol, + LogicalOperatorDto>) + s -> { + if (PredicateLogicalOperatorSymbol.AND.equals(s)) { + return (LogicalOperatorDto) + new PredicateLogicalAndOperation(); + } else { + + return new PredicateLogicalOrOperation(); + } + }); + return (PredicateDto) predicateLogicalOperatorSymbolPredicateDtoLogicalOperatorDto; + } + } + + static List parsePredicate(String predicate) { + + CharSequence[] split = AqlPath.split(predicate, null, true, " and ", " AND ", " or ", " OR ", ","); + + return IntStream.range(0, split.length) + .mapToObj(i -> { + if (i % 2 == 0) { + return handleOperator(split[i], i); + } else { + return handleSymbol(split[i]); + } + }) + .collect(Collectors.toList()); + } + + private static PredicateLogicalOperatorSymbol handleSymbol(CharSequence sequence) { + switch (sequence.toString().trim()) { + case ",": + case "and": + case "AND": + return PredicateLogicalOperatorSymbol.AND; + case "or": + case "OR": + return PredicateLogicalOperatorSymbol.OR; + default: + throw new SdkException(String.format("Unknown symbol %s", sequence)); + } + } + + private static PredicateComparisonOperatorDto handleOperator(CharSequence sequence, int i) { + CharSequence[] split = AqlPath.split( + sequence, + 3, + true, + Arrays.stream(ConditionComparisonOperatorSymbol.values()) + .map(ConditionComparisonOperatorSymbol::getSymbole) + .toArray(String[]::new)); + PredicateComparisonOperatorDto comparisonOperatorDto = new PredicateComparisonOperatorDto(); + + if (split.length == 1) { + if (i == 0) { + + comparisonOperatorDto.setStatement(ARCHETYPE_NODE_ID); + comparisonOperatorDto.setValue( + parseValue(ARCHETYPE_NODE_ID, split[0].toString().trim())); + comparisonOperatorDto.setSymbol(ConditionComparisonOperatorSymbol.EQ); + } else if (i == 2) { + + comparisonOperatorDto.setStatement(NAME_VALUE); + comparisonOperatorDto.setValue( + parseValue(NAME_VALUE, split[0].toString().trim())); + comparisonOperatorDto.setSymbol(ConditionComparisonOperatorSymbol.EQ); + } + } else { + + comparisonOperatorDto.setStatement(split[0].toString().trim()); + + comparisonOperatorDto.setValue(parseValue( + comparisonOperatorDto.getStatement(), split[2].toString().trim())); + comparisonOperatorDto.setSymbol(ConditionComparisonOperatorSymbol.fromSymbol(split[1].toString())); + } + + return comparisonOperatorDto; + } + + private static Value parseValue(String statement, String s) { + + if (s.startsWith("$")) { + ParameterValue parameterValue = new ParameterValue(); + parameterValue.setName(StringUtils.removeStart(s, "$")); + return parameterValue; + } + + if (ARCHETYPE_NODE_ID.equals(statement)) { + SimpleValue simpleValue = new SimpleValue(); + simpleValue.setValue(s); + return simpleValue; + } + + if (s.startsWith("'")) { + SimpleValue simpleValue = new SimpleValue(); + + simpleValue.setValue(StringUtils.unwrap(s, "'")); + return simpleValue; + } + + if (StringUtils.contains(s, '.')) { + SimpleValue simpleValue = new SimpleValue(); + simpleValue.setValue(Double.parseDouble(s)); + return simpleValue; + } else { + SimpleValue simpleValue = new SimpleValue(); + simpleValue.setValue(Long.parseLong(s)); + return simpleValue; + } + } + + private static void format(String statement, StringBuilder sb, Value value) { + if (value instanceof SimpleValue) { + Object o = ((SimpleValue) value).getValue(); + + if (o instanceof String && !ARCHETYPE_NODE_ID.equals(statement)) { + sb.append(StringUtils.wrap(o.toString(), "'")); + } else { + sb.append(o); + } + } else if (value instanceof ParameterValue) { + + sb.append("$").append(((ParameterValue) value).getName()); + } + } + + public static void format( + StringBuilder sb, PredicateDto predicateDto, AqlPath.OtherPredicatesFormat otherPredicatesFormat) { + + if (predicateDto instanceof ParameterValue) { + sb.append("$").append(((ParameterValue) predicateDto).getName()); + } else if (predicateDto instanceof PredicateComparisonOperatorDto) { + formatPredicateComparisonOperatorDto( + sb, (PredicateComparisonOperatorDto) predicateDto, otherPredicatesFormat); + } else if (predicateDto instanceof PredicateLogicalAndOperation) { + formatPredicateLogicalAndOperation(sb, (PredicateLogicalAndOperation) predicateDto, otherPredicatesFormat); + } else if (predicateDto instanceof PredicateLogicalOrOperation) { + formatPredicateLogicalOrOperation(sb, (PredicateLogicalOrOperation) predicateDto, otherPredicatesFormat); + } + } + + private static void formatPredicateLogicalOrOperation( + StringBuilder sb, + PredicateLogicalOrOperation predicateDto, + AqlPath.OtherPredicatesFormat otherPredicatesFormat) { + if (otherPredicatesFormat.equals(AqlPath.OtherPredicatesFormat.SHORTED)) { + otherPredicatesFormat = AqlPath.OtherPredicatesFormat.FULL; + } + List values = predicateDto.getValues(); + for (int i = 0; i < values.size(); i++) { + + if (i > 0 && !isNone(values.get(i), otherPredicatesFormat)) { + sb.append(" ").append("or").append(" "); + } + format(sb, values.get(i), otherPredicatesFormat); + } + } + + private static void formatPredicateLogicalAndOperation( + StringBuilder sb, + PredicateLogicalAndOperation predicateDto, + AqlPath.OtherPredicatesFormat otherPredicatesFormat) { + List values = predicateDto.getValues(); + values.sort(PREDICATE_DTO_COMPARATOR); + for (int i = 0; i < values.size(); i++) { + if (i > 0 && !isNone(values.get(i), otherPredicatesFormat)) { + if (isShorten(values.get(i), otherPredicatesFormat)) { + sb.append(","); + } else { + sb.append(" ").append("and").append(" "); + } + } + if (!isNone(values.get(i), otherPredicatesFormat)) { + format(sb, values.get(i), otherPredicatesFormat); + } + } + } + + private static void formatPredicateComparisonOperatorDto( + StringBuilder sb, + PredicateComparisonOperatorDto predicateDto, + AqlPath.OtherPredicatesFormat otherPredicatesFormat) { + if (isShorten(predicateDto, otherPredicatesFormat)) { + format(predicateDto.getStatement(), sb, predicateDto.getValue()); + } else if (!isNone(predicateDto, otherPredicatesFormat)) { + sb.append(predicateDto.getStatement()) + .append(predicateDto.getSymbol().getSymbole()); + format(predicateDto.getStatement(), sb, predicateDto.getValue()); + } + } + + private static boolean isNone( + SimplePredicateDto predicateDto, AqlPath.OtherPredicatesFormat otherPredicatesFormat) { + + if (!otherPredicatesFormat.equals(AqlPath.OtherPredicatesFormat.NONE)) { + return false; + } + + if (predicateDto instanceof PredicateComparisonOperatorDto) { + return !Objects.equals(ARCHETYPE_NODE_ID, ((PredicateComparisonOperatorDto) predicateDto).getStatement()); + } + + return false; + } + + private static boolean isShorten( + SimplePredicateDto predicateDto, AqlPath.OtherPredicatesFormat otherPredicatesFormat) { + + if (predicateDto instanceof PredicateComparisonOperatorDto + && ((PredicateComparisonOperatorDto) predicateDto) + .getStatement() + .equals(ARCHETYPE_NODE_ID)) { + return true; + } + if (otherPredicatesFormat.equals(AqlPath.OtherPredicatesFormat.FULL)) { + return false; + } + if (predicateDto instanceof PredicateLogicalOrOperation) { + return false; + } + if (predicateDto instanceof PredicateLogicalAndOperation) { + return CollectionUtils.isNotEmpty(((PredicateLogicalAndOperation) predicateDto).getValues()) + && isShorten( + ((PredicateLogicalAndOperation) predicateDto) + .getValues() + .get(((PredicateLogicalAndOperation) predicateDto) + .getValues() + .size()), + otherPredicatesFormat); + } + if (predicateDto instanceof PredicateComparisonOperatorDto) { + + return List.of(NAME_VALUE, ARCHETYPE_NODE_ID) + .contains(((PredicateComparisonOperatorDto) predicateDto).getStatement()) + && ((PredicateComparisonOperatorDto) predicateDto) + .getSymbol() + .equals(ConditionComparisonOperatorSymbol.EQ); + } + return false; + } + + public static Optional find(PredicateDto predicateDto, String statement) { + + if (predicateDto instanceof PredicateLogicalOrOperation) { + + return ((PredicateLogicalOrOperation) predicateDto) + .getValues().stream() + .map(p -> find(p, statement)) + .flatMap(Optional::stream) + .findAny(); + } + + if (predicateDto instanceof PredicateLogicalAndOperation) { + + return ((PredicateLogicalAndOperation) predicateDto) + .getValues().stream() + .map(p -> find(p, statement)) + .flatMap(Optional::stream) + .findAny(); + } + + if (predicateDto instanceof PredicateComparisonOperatorDto) { + return Optional.of((PredicateComparisonOperatorDto) predicateDto) + .filter(p -> p.getStatement().equals(statement)); + } + + return Optional.empty(); + } + + public static

P clone(P predicateDto) { + + if (predicateDto instanceof PredicateLogicalAndOperation) { + + PredicateLogicalAndOperation clone = new PredicateLogicalAndOperation(); + clone.setValues(((PredicateLogicalAndOperation) predicateDto) + .getValues().stream().map(PredicateHelper::clone).collect(Collectors.toList())); + return (P) clone; + } + + if (predicateDto instanceof PredicateComparisonOperatorDto) { + + return (P) new PredicateComparisonOperatorDto((PredicateComparisonOperatorDto) predicateDto); + } + + return predicateDto; + } + + public static SimplePredicateDto add(SimplePredicateDto simplePredicateDto, SimplePredicateDto add) { + if (simplePredicateDto instanceof PredicateLogicalAndOperation) { + ((PredicateLogicalAndOperation) simplePredicateDto).getValues().add(add); + return simplePredicateDto; + } else { + PredicateLogicalAndOperation and = new PredicateLogicalAndOperation(); + and.getValues().add(simplePredicateDto); + and.getValues().add(add); + + return and; + } + } +} diff --git a/aql/src/main/java/org/ehrbase/aql/dto/path/predicate/PredicateLogicalAndOperation.java b/aql/src/main/java/org/ehrbase/aql/dto/path/predicate/PredicateLogicalAndOperation.java new file mode 100644 index 000000000..5fa212a8c --- /dev/null +++ b/aql/src/main/java/org/ehrbase/aql/dto/path/predicate/PredicateLogicalAndOperation.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2022 vitasystems GmbH and Hannover Medical School. + * + * This file is part of project openEHR_SDK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ehrbase.aql.dto.path.predicate; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import org.ehrbase.aql.dto.condition.LogicalOperatorDto; + +/** + * @author Stefan Spiska + */ +public class PredicateLogicalAndOperation + implements LogicalOperatorDto, + SimplePredicateDto, + Serializable { + + private final PredicateLogicalOperatorSymbol symbol = PredicateLogicalOperatorSymbol.AND; + private List values = new ArrayList<>(); + + @Override + public PredicateLogicalOperatorSymbol getSymbol() { + return symbol; + } + + @Override + public List getValues() { + return values; + } + + @Override + public void setSymbol(PredicateLogicalOperatorSymbol symbol) { + throw new UnsupportedOperationException(); + } + + @Override + public void setValues(List values) { + this.values = values; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PredicateLogicalAndOperation that = (PredicateLogicalAndOperation) o; + return symbol == that.symbol && Objects.equals(values, that.values); + } + + @Override + public int hashCode() { + return Objects.hash(symbol, values); + } + + @Override + public String toString() { + return "PredicateLogicalAndOperation{" + "symbol=" + symbol + ", values=" + values + '}'; + } +} diff --git a/aql/src/main/java/org/ehrbase/aql/dto/path/predicate/PredicateLogicalOperatorSymbol.java b/aql/src/main/java/org/ehrbase/aql/dto/path/predicate/PredicateLogicalOperatorSymbol.java new file mode 100644 index 000000000..138926194 --- /dev/null +++ b/aql/src/main/java/org/ehrbase/aql/dto/path/predicate/PredicateLogicalOperatorSymbol.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2020 vitasystems GmbH and Hannover Medical School. + * + * This file is part of project openEHR_SDK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ehrbase.aql.dto.path.predicate; + +import org.ehrbase.aql.dto.LogicalOperatorSymbol; + +public enum PredicateLogicalOperatorSymbol implements LogicalOperatorSymbol { + OR(4), + AND(2); + + private final int precedence; + + PredicateLogicalOperatorSymbol(int precedence) { + this.precedence = precedence; + } + + public int getPrecedence() { + return precedence; + } +} diff --git a/aql/src/main/java/org/ehrbase/aql/dto/path/predicate/PredicateLogicalOrOperation.java b/aql/src/main/java/org/ehrbase/aql/dto/path/predicate/PredicateLogicalOrOperation.java new file mode 100644 index 000000000..5f66ce4e3 --- /dev/null +++ b/aql/src/main/java/org/ehrbase/aql/dto/path/predicate/PredicateLogicalOrOperation.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2022 vitasystems GmbH and Hannover Medical School. + * + * This file is part of project openEHR_SDK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ehrbase.aql.dto.path.predicate; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import org.ehrbase.aql.dto.condition.LogicalOperatorDto; + +/** + * @author Stefan Spiska + */ +public class PredicateLogicalOrOperation + implements PredicateDto, LogicalOperatorDto, Serializable { + + private final PredicateLogicalOperatorSymbol symbol = PredicateLogicalOperatorSymbol.OR; + private List values = new ArrayList<>(); + + @Override + public PredicateLogicalOperatorSymbol getSymbol() { + return symbol; + } + + @Override + public List getValues() { + return values; + } + + @Override + public void setSymbol(PredicateLogicalOperatorSymbol symbol) { + throw new UnsupportedOperationException(); + } + + @Override + public void setValues(List values) { + this.values = values; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PredicateLogicalOrOperation that = (PredicateLogicalOrOperation) o; + return symbol == that.symbol && Objects.equals(values, that.values); + } + + @Override + public int hashCode() { + return Objects.hash(symbol, values); + } + + @Override + public String toString() { + return "PredicateLogicalOrOperation{" + "symbol=" + symbol + ", values=" + values + '}'; + } +} diff --git a/aql/src/main/java/org/ehrbase/aql/dto/path/predicate/SimplePredicateDto.java b/aql/src/main/java/org/ehrbase/aql/dto/path/predicate/SimplePredicateDto.java new file mode 100644 index 000000000..4f6cd2cf9 --- /dev/null +++ b/aql/src/main/java/org/ehrbase/aql/dto/path/predicate/SimplePredicateDto.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2022 vitasystems GmbH and Hannover Medical School. + * + * This file is part of project openEHR_SDK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ehrbase.aql.dto.path.predicate; + +/** + * @author Stefan Spiska + */ +public interface SimplePredicateDto extends PredicateDto {} diff --git a/aql/src/main/java/org/ehrbase/aql/dto/select/AQLFunction.java b/aql/src/main/java/org/ehrbase/aql/dto/select/AQLFunction.java new file mode 100644 index 000000000..ad15b4a86 --- /dev/null +++ b/aql/src/main/java/org/ehrbase/aql/dto/select/AQLFunction.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2022 vitasystems GmbH and Hannover Medical School. + * + * This file is part of project openEHR_SDK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ehrbase.aql.dto.select; + +/** + * @author Stefan Spiska + */ +public enum AQLFunction { + COUNT, + MIN, + MAX, + AVG; +} diff --git a/aql/src/main/java/org/ehrbase/aql/dto/select/FunctionDto.java b/aql/src/main/java/org/ehrbase/aql/dto/select/FunctionDto.java new file mode 100644 index 000000000..250e0ef08 --- /dev/null +++ b/aql/src/main/java/org/ehrbase/aql/dto/select/FunctionDto.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2022 vitasystems GmbH and Hannover Medical School. + * + * This file is part of project openEHR_SDK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ehrbase.aql.dto.select; + +import java.util.List; +import java.util.Objects; + +/** + * @author Stefan Spiska + */ +public class FunctionDto implements SelectStatementDto { + + private AQLFunction aqlFunction; + + private List parameters; + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public AQLFunction getAqlFunction() { + return aqlFunction; + } + + public void setAqlFunction(AQLFunction aqlFunction) { + this.aqlFunction = aqlFunction; + } + + public List getParameters() { + return parameters; + } + + public void setParameters(List parameters) { + this.parameters = parameters; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + FunctionDto that = (FunctionDto) o; + return aqlFunction == that.aqlFunction + && Objects.equals(parameters, that.parameters) + && Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return Objects.hash(aqlFunction, parameters, name); + } + + @Override + public String toString() { + return "FunctionDto{" + "aqlFunction=" + + aqlFunction + ", parameters=" + + parameters + ", name='" + + name + '\'' + '}'; + } +} diff --git a/aql/src/main/java/org/ehrbase/aql/dto/select/SelectDto.java b/aql/src/main/java/org/ehrbase/aql/dto/select/SelectDto.java index 9d4561477..9eb924e7a 100644 --- a/aql/src/main/java/org/ehrbase/aql/dto/select/SelectDto.java +++ b/aql/src/main/java/org/ehrbase/aql/dto/select/SelectDto.java @@ -24,6 +24,9 @@ public class SelectDto { private Integer topCount; private Direction topDirection; + + private boolean isDistinct = false; + private List statement; public Integer getTopCount() { @@ -50,6 +53,14 @@ public void setStatement(List statement) { this.statement = statement; } + public boolean isDistinct() { + return isDistinct; + } + + public void setDistinct(boolean distinct) { + isDistinct = distinct; + } + public boolean equals(final Object o) { if (o == this) return true; if (!(o instanceof SelectDto)) return false; diff --git a/aql/src/main/java/org/ehrbase/aql/dto/select/SelectFieldDto.java b/aql/src/main/java/org/ehrbase/aql/dto/select/SelectFieldDto.java index ccb4aff75..e5eca69a7 100644 --- a/aql/src/main/java/org/ehrbase/aql/dto/select/SelectFieldDto.java +++ b/aql/src/main/java/org/ehrbase/aql/dto/select/SelectFieldDto.java @@ -17,10 +17,12 @@ */ package org.ehrbase.aql.dto.select; +import org.ehrbase.aql.dto.path.AqlPath; + public class SelectFieldDto implements SelectStatementDto { private String name; - private String aqlPath; + private AqlPath aqlPath; private int containmentId; public String getName() { @@ -28,7 +30,7 @@ public String getName() { } public String getAqlPath() { - return this.aqlPath; + return this.aqlPath.format(AqlPath.OtherPredicatesFormat.SHORTED, false); } public int getContainmentId() { @@ -40,7 +42,7 @@ public void setName(String name) { } public void setAqlPath(String aqlPath) { - this.aqlPath = aqlPath; + this.aqlPath = AqlPath.parse(aqlPath); } public void setContainmentId(int containmentId) { diff --git a/aql/src/main/java/org/ehrbase/aql/parser/AqlToDtoVisitor.java b/aql/src/main/java/org/ehrbase/aql/parser/AqlToDtoVisitor.java index 70095807f..283636943 100644 --- a/aql/src/main/java/org/ehrbase/aql/parser/AqlToDtoVisitor.java +++ b/aql/src/main/java/org/ehrbase/aql/parser/AqlToDtoVisitor.java @@ -21,14 +21,20 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.misc.Interval; import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.TerminalNode; import org.apache.commons.collections4.MultiValuedMap; import org.apache.commons.collections4.multimap.ArrayListValuedHashMap; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; import org.ehrbase.aql.dto.AqlDto; import org.ehrbase.aql.dto.EhrDto; import org.ehrbase.aql.dto.LogicalOperatorSymbol; @@ -37,7 +43,10 @@ import org.ehrbase.aql.dto.condition.ConditionDto; import org.ehrbase.aql.dto.condition.ConditionLogicalOperatorDto; import org.ehrbase.aql.dto.condition.ConditionLogicalOperatorSymbol; +import org.ehrbase.aql.dto.condition.ExistsConditionOperatorDto; +import org.ehrbase.aql.dto.condition.LogicalOperatorDto; import org.ehrbase.aql.dto.condition.MatchesOperatorDto; +import org.ehrbase.aql.dto.condition.NotConditionOperatorDto; import org.ehrbase.aql.dto.condition.ParameterValue; import org.ehrbase.aql.dto.condition.SimpleValue; import org.ehrbase.aql.dto.condition.Value; @@ -47,6 +56,13 @@ import org.ehrbase.aql.dto.containment.ContainmentLogicalOperatorSymbol; import org.ehrbase.aql.dto.orderby.OrderByExpressionDto; import org.ehrbase.aql.dto.orderby.OrderByExpressionSymbol; +import org.ehrbase.aql.dto.path.predicate.PredicateComparisonOperatorDto; +import org.ehrbase.aql.dto.path.predicate.PredicateDto; +import org.ehrbase.aql.dto.path.predicate.PredicateHelper; +import org.ehrbase.aql.dto.path.predicate.PredicateLogicalAndOperation; +import org.ehrbase.aql.dto.path.predicate.PredicateLogicalOrOperation; +import org.ehrbase.aql.dto.select.AQLFunction; +import org.ehrbase.aql.dto.select.FunctionDto; import org.ehrbase.aql.dto.select.SelectDto; import org.ehrbase.aql.dto.select.SelectFieldDto; import org.ehrbase.aql.dto.select.SelectStatementDto; @@ -63,7 +79,10 @@ public class AqlToDtoVisitor extends AqlBaseVisitor { public AqlDto visitQuery(AqlParser.QueryContext ctx) { AqlDto aqlDto = new AqlDto(); - aqlDto.setEhr(visitFromEHR(ctx.queryExpr().from().fromEHR())); + Pair visitFromEHR = + visitFromEHR(ctx.queryExpr().from().fromEHR()); + aqlDto.setEhr(visitFromEHR.getLeft()); + if (ctx.queryExpr().from().containsExpression() != null) { aqlDto.setContains(visitContainsExpression(ctx.queryExpr().from().containsExpression())); } @@ -71,36 +90,118 @@ public AqlDto visitQuery(AqlParser.QueryContext ctx) { if (ctx.queryExpr().where() != null) { aqlDto.setWhere(visitIdentifiedExpr(ctx.queryExpr().where().identifiedExpr())); } + if (visitFromEHR.getRight() != null) { + if (aqlDto.getWhere() == null) { + aqlDto.setWhere(visitFromEHR.getRight()); + } else { + ConditionLogicalOperatorDto and = new ConditionLogicalOperatorDto(); + and.setSymbol(ConditionLogicalOperatorSymbol.AND); + and.setValues(new ArrayList<>()); + and.getValues().add(aqlDto.getWhere()); + and.getValues().add(visitFromEHR.getRight()); + aqlDto.setWhere(and); + } + } if (ctx.queryExpr().orderBy() != null) { aqlDto.setOrderBy(visitOrderBySeq(ctx.queryExpr().orderBy().orderBySeq())); } - if (ctx.queryExpr().limitExpr() != null) { - AqlParser.LimitExprContext limitExpr = ctx.queryExpr().limitExpr(); + if (ctx.queryExpr().limit() != null) { + AqlParser.LimitContext limitExpr = ctx.queryExpr().limit(); aqlDto.setLimit(Integer.parseInt(limitExpr.INTEGER().getText())); - if (limitExpr.offset() != null) { - aqlDto.setOffset(Integer.parseInt(limitExpr.offset().INTEGER().getText())); + if (ctx.queryExpr().offset() != null) { + aqlDto.setOffset( + Integer.parseInt(ctx.queryExpr().offset().INTEGER().getText())); } } - selectFieldDtoMultiMap.entries().forEach(e -> e.getValue() - .setContainmentId( - Optional.ofNullable(identifierMap.get(e.getKey())).orElseThrow())); + selectFieldDtoMultiMap.entries().forEach(e -> { + if (identifierMap.containsKey(e.getKey())) { + e.getValue().setContainmentId(identifierMap.get(e.getKey())); + } + }); + + // replace reference by name + selectFieldDtoMultiMap.entries().stream() + .filter(e -> !identifierMap.containsKey(e.getKey())) + .forEach(e -> { + SelectFieldDto selectFieldDto = selectFieldDtoMultiMap.values().stream() + .filter(d -> e.getKey().equals(d.getName())) + .findAny() + .orElseThrow(); + + SelectFieldDto value = e.getValue(); + value.setName(selectFieldDto.getName()); + value.setAqlPath(selectFieldDto.getAqlPath()); + value.setContainmentId(selectFieldDto.getContainmentId()); + }); return aqlDto; } @Override - public EhrDto visitFromEHR(AqlParser.FromEHRContext ctx) { + public Pair visitFromEHR(AqlParser.FromEHRContext ctx) { EhrDto ehrDto = new EhrDto(); ehrDto.setContainmentId(buildContainmentId()); if (ctx.IDENTIFIER() != null) { identifierMap.put(ctx.IDENTIFIER().getText(), ehrDto.getContainmentId()); ehrDto.setIdentifier(ctx.IDENTIFIER().getText()); } + return Pair.of( + ehrDto, + Optional.ofNullable(ctx.standardPredicate()) + .map(AqlParser.StandardPredicateContext::predicateExpr) + .map(p -> buildConditionDtoFromPredicate(p, ehrDto.getContainmentId())) + .orElse(null)); + } + + private ConditionDto buildConditionDtoFromPredicate(AqlParser.PredicateExprContext p, int containmentId) { + PredicateDto predicateDto = PredicateHelper.buildPredicate(getFullText(p)); + return to(predicateDto, containmentId); + } + + public static String getFullText(ParserRuleContext context) { + if (context.start == null + || context.stop == null + || context.start.getStartIndex() < 0 + || context.stop.getStopIndex() < 0) return context.getText(); // Fallback + + return context.start + .getInputStream() + .getText(Interval.of(context.start.getStartIndex(), context.stop.getStopIndex())); + } + + private ConditionDto to(PredicateDto predicateDto, int containmentId) { + if (predicateDto instanceof PredicateComparisonOperatorDto) { + ConditionComparisonOperatorDto conditionComparisonOperatorDto = new ConditionComparisonOperatorDto(); + SelectFieldDto statement = new SelectFieldDto(); + statement.setContainmentId(containmentId); + statement.setAqlPath( + StringUtils.prependIfMissing(((PredicateComparisonOperatorDto) predicateDto).getStatement(), "/")); + conditionComparisonOperatorDto.setStatement(statement); + conditionComparisonOperatorDto.setSymbol(((PredicateComparisonOperatorDto) predicateDto).getSymbol()); + conditionComparisonOperatorDto.setValue(((PredicateComparisonOperatorDto) predicateDto).getValue()); + return conditionComparisonOperatorDto; + } - return ehrDto; + if (predicateDto instanceof PredicateLogicalAndOperation) { + ConditionLogicalOperatorDto and = new ConditionLogicalOperatorDto(); + and.setSymbol(ConditionLogicalOperatorSymbol.AND); + and.setValues(((PredicateLogicalAndOperation) predicateDto) + .getValues().stream().map(p -> to(p, containmentId)).collect(Collectors.toList())); + return and; + } + + if (predicateDto instanceof PredicateLogicalOrOperation) { + ConditionLogicalOperatorDto or = new ConditionLogicalOperatorDto(); + or.setSymbol(ConditionLogicalOperatorSymbol.OR); + or.setValues(((PredicateLogicalOrOperation) predicateDto) + .getValues().stream().map(p -> to(p, containmentId)).collect(Collectors.toList())); + return or; + } + + return null; } @Override @@ -111,6 +212,9 @@ public SelectDto visitSelect(AqlParser.SelectContext ctx) { selectDto.setTopDirection(extractSymbol(ctx.topExpr())); selectDto.setTopCount(Integer.parseInt(ctx.topExpr().INTEGER().getText())); } + if (ctx.selectExpr().DISTINCT() != null) { + selectDto.setDistinct(true); + } return selectDto; } @@ -132,6 +236,14 @@ public List visitSelectExpr(AqlParser.SelectExprContext ctx) selectFieldDto.setName(ctx.IDENTIFIER().getText()); } selectStatementDtos.add(selectFieldDto); + } else if (ctx.stdExpression() != null) { + if (ctx.stdExpression().function() != null) { + FunctionDto functionDto = visitFunction(ctx.stdExpression().function()); + selectStatementDtos.add(functionDto); + if (ctx.IDENTIFIER() != null) { + functionDto.setName(ctx.IDENTIFIER().getText()); + } + } } if (ctx.selectExpr() != null) { @@ -140,6 +252,24 @@ public List visitSelectExpr(AqlParser.SelectExprContext ctx) return selectStatementDtos; } + @Override + public FunctionDto visitFunction(AqlParser.FunctionContext ctx) { + + FunctionDto functionDto = new FunctionDto(); + + AQLFunction aqlFunction = + AQLFunction.valueOf(ctx.FUNCTION_IDENTIFIER().toString().toUpperCase(Locale.ROOT)); + + functionDto.setAqlFunction(aqlFunction); + + if (ctx.identifiedPath() != null) { + functionDto.setParameters( + ctx.identifiedPath().stream().map(this::visitIdentifiedPath).collect(Collectors.toList())); + } + + return functionDto; + } + @Override public SelectFieldDto visitIdentifiedPath(AqlParser.IdentifiedPathContext ctx) { SelectFieldDto selectStatementDto = new SelectFieldDto(); @@ -173,40 +303,18 @@ public ContainmentExpresionDto visitContainsExpression(AqlParser.ContainsExpress } } - private ContainmentLogicalOperator buildContainmentLogicalOperator(List boolList) { + public ContainmentLogicalOperator buildContainmentLogicalOperator(List boolList) { - ContainmentLogicalOperator currentOperator = new ContainmentLogicalOperator(); - ContainmentLogicalOperatorSymbol currentSymbol = (ContainmentLogicalOperatorSymbol) boolList.get(1); - currentOperator.setSymbol(currentSymbol); - currentOperator.setValues(new ArrayList<>()); - currentOperator.getValues().add((ContainmentExpresionDto) boolList.get(0)); - ContainmentLogicalOperator lowestOperator = currentOperator; - for (int i = 2; i < boolList.size(); i = i + 2) { - ContainmentLogicalOperatorSymbol nextSymbol = - i + 1 < boolList.size() ? (ContainmentLogicalOperatorSymbol) boolList.get(i + 1) : null; - if (nextSymbol == null || Objects.equals(currentSymbol, nextSymbol)) { - currentOperator.getValues().add((ContainmentExpresionDto) boolList.get(i)); - currentSymbol = nextSymbol; - } else { - ContainmentLogicalOperator nextOperator = new ContainmentLogicalOperator(); - nextOperator.setSymbol(nextSymbol); - nextOperator.setValues(new ArrayList<>()); - - if (hasHigherPrecedence(currentSymbol, nextSymbol)) { - currentOperator.getValues().add((ContainmentExpresionDto) boolList.get(i)); - nextOperator.getValues().add(currentOperator); - lowestOperator = nextOperator; - } else { - nextOperator.getValues().add((ContainmentExpresionDto) boolList.get(i)); - currentOperator.getValues().add(nextOperator); - lowestOperator = currentOperator; - } + return (ContainmentLogicalOperator) buildLogicalOperator(boolList, (Function< + ContainmentLogicalOperatorSymbol, + LogicalOperatorDto>) + s -> { + ContainmentLogicalOperator conditionLogicalOperatorDto = new ContainmentLogicalOperator(); + conditionLogicalOperatorDto.setSymbol(s); + conditionLogicalOperatorDto.setValues(new ArrayList<>()); - currentOperator = nextOperator; - currentSymbol = nextSymbol; - } - } - return lowestOperator; + return conditionLogicalOperatorDto; + }); } @Override @@ -280,30 +388,40 @@ public ConditionDto visitIdentifiedExpr(AqlParser.IdentifiedExprContext ctx) { private ConditionLogicalOperatorDto buildConditionLogicalOperator(List boolList) { - ConditionLogicalOperatorDto currentOperator = new ConditionLogicalOperatorDto(); - ConditionLogicalOperatorSymbol currentSymbol = (ConditionLogicalOperatorSymbol) boolList.get(1); - currentOperator.setSymbol(currentSymbol); - currentOperator.setValues(new ArrayList<>()); - currentOperator.getValues().add((ConditionDto) boolList.get(0)); - ConditionLogicalOperatorDto lowestOperator = currentOperator; + return (ConditionLogicalOperatorDto) buildLogicalOperator(boolList, (Function< + ConditionLogicalOperatorSymbol, + LogicalOperatorDto>) + s -> { + ConditionLogicalOperatorDto conditionLogicalOperatorDto = new ConditionLogicalOperatorDto(); + conditionLogicalOperatorDto.setSymbol(s); + conditionLogicalOperatorDto.setValues(new ArrayList<>()); + + return conditionLogicalOperatorDto; + }); + } + + public static LogicalOperatorDto buildLogicalOperator( + List boolList, Function> creator) { + + S currentSymbol = (S) boolList.get(1); + LogicalOperatorDto currentOperator = creator.apply(currentSymbol); + currentOperator.getValues().add((T) boolList.get(0)); + LogicalOperatorDto lowestOperator = currentOperator; for (int i = 2; i < boolList.size(); i = i + 2) { - ConditionLogicalOperatorSymbol nextSymbol = - i + 1 < boolList.size() ? (ConditionLogicalOperatorSymbol) boolList.get(i + 1) : null; + S nextSymbol = i + 1 < boolList.size() ? (S) boolList.get(i + 1) : null; if (nextSymbol == null || Objects.equals(currentSymbol, nextSymbol)) { - currentOperator.getValues().add((ConditionDto) boolList.get(i)); + currentOperator.getValues().add((T) boolList.get(i)); currentSymbol = nextSymbol; } else { - ConditionLogicalOperatorDto nextOperator = new ConditionLogicalOperatorDto(); - nextOperator.setSymbol(nextSymbol); - nextOperator.setValues(new ArrayList<>()); + LogicalOperatorDto nextOperator = creator.apply(nextSymbol); if (hasHigherPrecedence(currentSymbol, nextSymbol)) { - currentOperator.getValues().add((ConditionDto) boolList.get(i)); - nextOperator.getValues().add(currentOperator); + currentOperator.getValues().add((T) boolList.get(i)); + nextOperator.getValues().add((T) currentOperator); lowestOperator = nextOperator; } else { - nextOperator.getValues().add((ConditionDto) boolList.get(i)); - currentOperator.getValues().add(nextOperator); + nextOperator.getValues().add((T) boolList.get(i)); + currentOperator.getValues().add((T) nextOperator); lowestOperator = currentOperator; } @@ -319,7 +437,7 @@ private ConditionLogicalOperatorSymbol extractSymbolTerminal(TerminalNode child) return null; } - switch (child.getSymbol().getText()) { + switch (child.getSymbol().getText().toLowerCase(Locale.ROOT)) { case "or": return ConditionLogicalOperatorSymbol.OR; case "and": @@ -373,8 +491,17 @@ public ConditionDto visitIdentifiedEquality(AqlParser.IdentifiedEqualityContext } conditionDto = matchesOperatorDto; + } else if (ctx.EXISTS() != null) { + conditionDto = new ExistsConditionOperatorDto(visitIdentifiedPath(ctx.identifiedPath())); + } + + if (ctx.NOT() != null + // "NOT" not belonging to is, in or between. + && (ctx.IS() == null && ctx.IN() == null && ctx.BETWEEN() == null)) { + return new NotConditionOperatorDto(conditionDto); + } else { + return conditionDto; } - return conditionDto; } @Override @@ -461,7 +588,7 @@ private ConditionComparisonOperatorSymbol extractSymbol(AqlParser.IdentifiedEqua } } - private boolean hasHigherPrecedence( + private static boolean hasHigherPrecedence( LogicalOperatorSymbol operatorSymbol, LogicalOperatorSymbol nextOperatorSymbol) { if (nextOperatorSymbol == null) { return true; diff --git a/web-template/src/main/java/org/ehrbase/webtemplate/util/CharSequenceHelper.java b/aql/src/main/java/org/ehrbase/aql/util/CharSequenceHelper.java similarity index 98% rename from web-template/src/main/java/org/ehrbase/webtemplate/util/CharSequenceHelper.java rename to aql/src/main/java/org/ehrbase/aql/util/CharSequenceHelper.java index a109f3421..fcccd83a0 100644 --- a/web-template/src/main/java/org/ehrbase/webtemplate/util/CharSequenceHelper.java +++ b/aql/src/main/java/org/ehrbase/aql/util/CharSequenceHelper.java @@ -15,7 +15,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.ehrbase.webtemplate.util; +package org.ehrbase.aql.util; import java.nio.CharBuffer; import org.apache.commons.lang3.StringUtils; diff --git a/client/src/main/java/org/ehrbase/client/aql/condition/And.java b/aql/src/main/java/org/ehrbase/client/aql/condition/And.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/condition/And.java rename to aql/src/main/java/org/ehrbase/client/aql/condition/And.java diff --git a/client/src/main/java/org/ehrbase/client/aql/condition/BinaryLogicalOperator.java b/aql/src/main/java/org/ehrbase/client/aql/condition/BinaryLogicalOperator.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/condition/BinaryLogicalOperator.java rename to aql/src/main/java/org/ehrbase/client/aql/condition/BinaryLogicalOperator.java diff --git a/client/src/main/java/org/ehrbase/client/aql/condition/ComparisonOperator.java b/aql/src/main/java/org/ehrbase/client/aql/condition/ComparisonOperator.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/condition/ComparisonOperator.java rename to aql/src/main/java/org/ehrbase/client/aql/condition/ComparisonOperator.java diff --git a/client/src/main/java/org/ehrbase/client/aql/condition/Condition.java b/aql/src/main/java/org/ehrbase/client/aql/condition/Condition.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/condition/Condition.java rename to aql/src/main/java/org/ehrbase/client/aql/condition/Condition.java diff --git a/client/src/main/java/org/ehrbase/client/aql/condition/Equal.java b/aql/src/main/java/org/ehrbase/client/aql/condition/Equal.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/condition/Equal.java rename to aql/src/main/java/org/ehrbase/client/aql/condition/Equal.java diff --git a/client/src/main/java/org/ehrbase/client/aql/condition/Exists.java b/aql/src/main/java/org/ehrbase/client/aql/condition/Exists.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/condition/Exists.java rename to aql/src/main/java/org/ehrbase/client/aql/condition/Exists.java diff --git a/client/src/main/java/org/ehrbase/client/aql/condition/GreaterOrEqual.java b/aql/src/main/java/org/ehrbase/client/aql/condition/GreaterOrEqual.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/condition/GreaterOrEqual.java rename to aql/src/main/java/org/ehrbase/client/aql/condition/GreaterOrEqual.java diff --git a/client/src/main/java/org/ehrbase/client/aql/condition/GreaterThan.java b/aql/src/main/java/org/ehrbase/client/aql/condition/GreaterThan.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/condition/GreaterThan.java rename to aql/src/main/java/org/ehrbase/client/aql/condition/GreaterThan.java diff --git a/client/src/main/java/org/ehrbase/client/aql/condition/LessOrEqual.java b/aql/src/main/java/org/ehrbase/client/aql/condition/LessOrEqual.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/condition/LessOrEqual.java rename to aql/src/main/java/org/ehrbase/client/aql/condition/LessOrEqual.java diff --git a/client/src/main/java/org/ehrbase/client/aql/condition/LessThan.java b/aql/src/main/java/org/ehrbase/client/aql/condition/LessThan.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/condition/LessThan.java rename to aql/src/main/java/org/ehrbase/client/aql/condition/LessThan.java diff --git a/client/src/main/java/org/ehrbase/client/aql/condition/Matches.java b/aql/src/main/java/org/ehrbase/client/aql/condition/Matches.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/condition/Matches.java rename to aql/src/main/java/org/ehrbase/client/aql/condition/Matches.java diff --git a/client/src/main/java/org/ehrbase/client/aql/condition/Not.java b/aql/src/main/java/org/ehrbase/client/aql/condition/Not.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/condition/Not.java rename to aql/src/main/java/org/ehrbase/client/aql/condition/Not.java diff --git a/client/src/main/java/org/ehrbase/client/aql/condition/NotEqual.java b/aql/src/main/java/org/ehrbase/client/aql/condition/NotEqual.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/condition/NotEqual.java rename to aql/src/main/java/org/ehrbase/client/aql/condition/NotEqual.java diff --git a/client/src/main/java/org/ehrbase/client/aql/condition/Or.java b/aql/src/main/java/org/ehrbase/client/aql/condition/Or.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/condition/Or.java rename to aql/src/main/java/org/ehrbase/client/aql/condition/Or.java diff --git a/client/src/main/java/org/ehrbase/client/aql/containment/And.java b/aql/src/main/java/org/ehrbase/client/aql/containment/And.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/containment/And.java rename to aql/src/main/java/org/ehrbase/client/aql/containment/And.java diff --git a/client/src/main/java/org/ehrbase/client/aql/containment/BinaryLogicalOperator.java b/aql/src/main/java/org/ehrbase/client/aql/containment/BinaryLogicalOperator.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/containment/BinaryLogicalOperator.java rename to aql/src/main/java/org/ehrbase/client/aql/containment/BinaryLogicalOperator.java diff --git a/client/src/main/java/org/ehrbase/client/aql/containment/Containment.java b/aql/src/main/java/org/ehrbase/client/aql/containment/Containment.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/containment/Containment.java rename to aql/src/main/java/org/ehrbase/client/aql/containment/Containment.java diff --git a/client/src/main/java/org/ehrbase/client/aql/containment/ContainmentExpression.java b/aql/src/main/java/org/ehrbase/client/aql/containment/ContainmentExpression.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/containment/ContainmentExpression.java rename to aql/src/main/java/org/ehrbase/client/aql/containment/ContainmentExpression.java diff --git a/client/src/main/java/org/ehrbase/client/aql/containment/ContainmentPath.java b/aql/src/main/java/org/ehrbase/client/aql/containment/ContainmentPath.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/containment/ContainmentPath.java rename to aql/src/main/java/org/ehrbase/client/aql/containment/ContainmentPath.java diff --git a/client/src/main/java/org/ehrbase/client/aql/containment/Or.java b/aql/src/main/java/org/ehrbase/client/aql/containment/Or.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/containment/Or.java rename to aql/src/main/java/org/ehrbase/client/aql/containment/Or.java diff --git a/client/src/main/java/org/ehrbase/client/aql/field/AqlField.java b/aql/src/main/java/org/ehrbase/client/aql/field/AqlField.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/field/AqlField.java rename to aql/src/main/java/org/ehrbase/client/aql/field/AqlField.java diff --git a/client/src/main/java/org/ehrbase/client/aql/field/AqlFieldImp.java b/aql/src/main/java/org/ehrbase/client/aql/field/AqlFieldImp.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/field/AqlFieldImp.java rename to aql/src/main/java/org/ehrbase/client/aql/field/AqlFieldImp.java diff --git a/client/src/main/java/org/ehrbase/client/aql/field/EhrFields.java b/aql/src/main/java/org/ehrbase/client/aql/field/EhrFields.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/field/EhrFields.java rename to aql/src/main/java/org/ehrbase/client/aql/field/EhrFields.java diff --git a/client/src/main/java/org/ehrbase/client/aql/field/ListAqlFieldImp.java b/aql/src/main/java/org/ehrbase/client/aql/field/ListAqlFieldImp.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/field/ListAqlFieldImp.java rename to aql/src/main/java/org/ehrbase/client/aql/field/ListAqlFieldImp.java diff --git a/client/src/main/java/org/ehrbase/client/aql/field/ListSelectAqlField.java b/aql/src/main/java/org/ehrbase/client/aql/field/ListSelectAqlField.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/field/ListSelectAqlField.java rename to aql/src/main/java/org/ehrbase/client/aql/field/ListSelectAqlField.java diff --git a/client/src/main/java/org/ehrbase/client/aql/field/NativeSelectAqlField.java b/aql/src/main/java/org/ehrbase/client/aql/field/NativeSelectAqlField.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/field/NativeSelectAqlField.java rename to aql/src/main/java/org/ehrbase/client/aql/field/NativeSelectAqlField.java diff --git a/client/src/main/java/org/ehrbase/client/aql/field/SelectAqlField.java b/aql/src/main/java/org/ehrbase/client/aql/field/SelectAqlField.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/field/SelectAqlField.java rename to aql/src/main/java/org/ehrbase/client/aql/field/SelectAqlField.java diff --git a/aql/src/main/java/org/ehrbase/client/aql/funtion/AbstractFunction.java b/aql/src/main/java/org/ehrbase/client/aql/funtion/AbstractFunction.java new file mode 100644 index 000000000..696dae06f --- /dev/null +++ b/aql/src/main/java/org/ehrbase/client/aql/funtion/AbstractFunction.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2022 vitasystems GmbH and Hannover Medical School. + * + * This file is part of project openEHR_SDK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ehrbase.client.aql.funtion; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import org.ehrbase.aql.dto.select.AQLFunction; +import org.ehrbase.client.aql.containment.Containment; +import org.ehrbase.client.aql.field.SelectAqlField; + +/** + * @author Stefan Spiska + */ +public abstract class AbstractFunction implements Function, SelectAqlField { + + private final List> parameters = new ArrayList<>(); + + private final AQLFunction function; + + private final String name; + + protected AbstractFunction(List> parameters, AQLFunction function, String name) { + this.parameters.addAll(parameters); + this.function = function; + this.name = name; + } + + @Override + public String getName() { + return name; + } + + @Override + public List> getParameters() { + return parameters; + } + + @Override + public String buildAQL(Containment ehrContainment) { + StringBuilder sb = new StringBuilder(); + + sb.append(function.toString()).append("("); + + if (parameters != null) { + + sb.append(parameters.stream().map(f -> f.buildAQL(ehrContainment)).collect(Collectors.joining(","))); + } + + sb.append(")"); + return sb.toString(); + } + + protected static AbstractFunction create( + List> parameters, AQLFunction function, String name, Class aClass) { + + return new AbstractFunction(parameters, function, name) { + @Override + public Containment getContainment() { + return null; + } + + @Override + public String getPath() { + return function.name(); + } + + @Override + public Class getEntityClass() { + return null; + } + + @Override + public Class getValueClass() { + return aClass; + } + + @Override + public boolean isMultiValued() { + return false; + } + }; + } +} diff --git a/aql/src/main/java/org/ehrbase/client/aql/funtion/Function.java b/aql/src/main/java/org/ehrbase/client/aql/funtion/Function.java new file mode 100644 index 000000000..f0cacedb7 --- /dev/null +++ b/aql/src/main/java/org/ehrbase/client/aql/funtion/Function.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2022 vitasystems GmbH and Hannover Medical School. + * + * This file is part of project openEHR_SDK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ehrbase.client.aql.funtion; + +import java.util.Collections; +import java.util.List; +import org.ehrbase.aql.dto.select.AQLFunction; +import org.ehrbase.client.aql.field.SelectAqlField; + +/** + * @author Stefan Spiska + */ +public interface Function { + + List> getParameters(); + + static AbstractFunction count(SelectAqlField field, String as) { + + return AbstractFunction.create(Collections.singletonList(field), AQLFunction.COUNT, as, Integer.class); + } + + static AbstractFunction count(SelectAqlField field) { + + return count(field, null); + } + + static AbstractFunction max(SelectAqlField field, String as) { + + return AbstractFunction.create(Collections.singletonList(field), AQLFunction.MAX, as, Integer.class); + } + + static AbstractFunction max(SelectAqlField field) { + + return max(field, null); + } + + static AbstractFunction min(SelectAqlField field, String as) { + + return AbstractFunction.create(Collections.singletonList(field), AQLFunction.MAX, as, Integer.class); + } + + static AbstractFunction min(SelectAqlField field) { + + return min(field, null); + } + + static AbstractFunction avg(SelectAqlField field, String as) { + + return AbstractFunction.create(Collections.singletonList(field), AQLFunction.AVG, as, Integer.class); + } + + static AbstractFunction avg(SelectAqlField field) { + + return avg(field, null); + } +} diff --git a/client/src/main/java/org/ehrbase/client/aql/orderby/AbstractOrderBy.java b/aql/src/main/java/org/ehrbase/client/aql/orderby/AbstractOrderBy.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/orderby/AbstractOrderBy.java rename to aql/src/main/java/org/ehrbase/client/aql/orderby/AbstractOrderBy.java diff --git a/client/src/main/java/org/ehrbase/client/aql/orderby/AndThen.java b/aql/src/main/java/org/ehrbase/client/aql/orderby/AndThen.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/orderby/AndThen.java rename to aql/src/main/java/org/ehrbase/client/aql/orderby/AndThen.java diff --git a/client/src/main/java/org/ehrbase/client/aql/orderby/Ascending.java b/aql/src/main/java/org/ehrbase/client/aql/orderby/Ascending.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/orderby/Ascending.java rename to aql/src/main/java/org/ehrbase/client/aql/orderby/Ascending.java diff --git a/client/src/main/java/org/ehrbase/client/aql/orderby/Descending.java b/aql/src/main/java/org/ehrbase/client/aql/orderby/Descending.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/orderby/Descending.java rename to aql/src/main/java/org/ehrbase/client/aql/orderby/Descending.java diff --git a/client/src/main/java/org/ehrbase/client/aql/orderby/OrderByExpression.java b/aql/src/main/java/org/ehrbase/client/aql/orderby/OrderByExpression.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/orderby/OrderByExpression.java rename to aql/src/main/java/org/ehrbase/client/aql/orderby/OrderByExpression.java diff --git a/client/src/main/java/org/ehrbase/client/aql/parameter/AqlValue.java b/aql/src/main/java/org/ehrbase/client/aql/parameter/AqlValue.java similarity index 83% rename from client/src/main/java/org/ehrbase/client/aql/parameter/AqlValue.java rename to aql/src/main/java/org/ehrbase/client/aql/parameter/AqlValue.java index 7b4c0075e..5c82ebab4 100644 --- a/client/src/main/java/org/ehrbase/client/aql/parameter/AqlValue.java +++ b/aql/src/main/java/org/ehrbase/client/aql/parameter/AqlValue.java @@ -18,12 +18,12 @@ package org.ehrbase.client.aql.parameter; import com.fasterxml.jackson.core.JsonProcessingException; +import com.nedap.archie.json.JacksonUtil; import java.time.temporal.TemporalAccessor; import java.util.Objects; import java.util.UUID; import org.apache.commons.lang3.StringUtils; -import org.ehrbase.client.exception.ClientException; -import org.ehrbase.serialisation.jsonencoding.ArchieObjectMapperProvider; +import org.ehrbase.util.exception.SdkException; public class AqlValue { @@ -47,13 +47,13 @@ public String buildAql() { } else if (TemporalAccessor.class.isAssignableFrom(value.getClass())) { String valueAsString; try { - valueAsString = ArchieObjectMapperProvider.getObjectMapper().writeValueAsString(value); + valueAsString = JacksonUtil.getObjectMapper().writeValueAsString(value); } catch (JsonProcessingException e) { - throw new ClientException(e.getMessage(), e); + throw new SdkException(e.getMessage(), e); } return StringUtils.wrap(valueAsString.replace("\"", ""), "'"); } else { - throw new ClientException(String.format("%s is not an valid AQL Value", value.getClass())); + throw new SdkException(String.format("%s is not an valid AQL Value", value.getClass())); } } } diff --git a/client/src/main/java/org/ehrbase/client/aql/parameter/Parameter.java b/aql/src/main/java/org/ehrbase/client/aql/parameter/Parameter.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/parameter/Parameter.java rename to aql/src/main/java/org/ehrbase/client/aql/parameter/Parameter.java diff --git a/client/src/main/java/org/ehrbase/client/aql/parameter/ParameterValue.java b/aql/src/main/java/org/ehrbase/client/aql/parameter/ParameterValue.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/parameter/ParameterValue.java rename to aql/src/main/java/org/ehrbase/client/aql/parameter/ParameterValue.java diff --git a/client/src/main/java/org/ehrbase/client/aql/query/EntityQuery.java b/aql/src/main/java/org/ehrbase/client/aql/query/EntityQuery.java similarity index 89% rename from client/src/main/java/org/ehrbase/client/aql/query/EntityQuery.java rename to aql/src/main/java/org/ehrbase/client/aql/query/EntityQuery.java index 59f396195..231a0c675 100644 --- a/client/src/main/java/org/ehrbase/client/aql/query/EntityQuery.java +++ b/aql/src/main/java/org/ehrbase/client/aql/query/EntityQuery.java @@ -30,6 +30,7 @@ import org.ehrbase.client.aql.field.AqlField; import org.ehrbase.client.aql.field.AqlFieldImp; import org.ehrbase.client.aql.field.SelectAqlField; +import org.ehrbase.client.aql.funtion.Function; import org.ehrbase.client.aql.orderby.OrderByExpression; import org.ehrbase.client.aql.parameter.Parameter; import org.ehrbase.client.aql.record.Record; @@ -49,6 +50,8 @@ public class EntityQuery implements Query { private Integer limit; private Integer offset; + private boolean isDistinct = false; + protected EntityQuery(ContainmentExpression containmentExpression, SelectAqlField... fields) { this(containmentExpression, new HashMap<>(), fields); } @@ -76,6 +79,19 @@ protected EntityQuery( } private SelectAqlField replace(SelectAqlField selectAqlField) { + + if (selectAqlField instanceof Function) { + + List> parameters = ((Function) selectAqlField).getParameters(); + List> replaceList = + parameters.stream().map(this::replace).collect(Collectors.toList()); + + parameters.clear(); + parameters.addAll(replaceList); + + return (SelectAqlField) selectAqlField; + } + if (selectAqlField.getContainment().getTypeName().equals("EHR")) { return new AqlFieldImp( selectAqlField.getEntityClass(), @@ -92,6 +108,11 @@ private SelectAqlField replace(SelectAqlField selectAqlField) { public String buildAql() { StringBuilder sb = new StringBuilder(); sb.append("Select "); + + if (isDistinct) { + sb.append("DISTINCT").append(" "); + } + if (topExpresion != null) { sb.append(topExpresion.buildAql()).append(" "); } @@ -107,10 +128,6 @@ public String buildAql() { if (where != null) { sb.append(" where ").append(where.buildAql(ehrContainment)); } - if (orderByExpression != null) { - sb.append(" order by ").append(orderByExpression.buildAql(ehrContainment)); - } - if (limit != null) { sb.append(" LIMIT ").append(limit); } @@ -118,6 +135,11 @@ public String buildAql() { if (offset != null) { sb.append(" OFFSET ").append(offset); } + + if (orderByExpression != null) { + sb.append(" order by ").append(orderByExpression.buildAql(ehrContainment)); + } + return sb.toString(); } @@ -164,6 +186,11 @@ public EntityQuery top(TopExpresion topExpresion) { return this; } + public EntityQuery distinct(boolean isDistinct) { + this.isDistinct = isDistinct; + return this; + } + public EntityQuery limit(Integer limit) { this.limit = limit; return this; diff --git a/client/src/main/java/org/ehrbase/client/aql/query/NativeQuery.java b/aql/src/main/java/org/ehrbase/client/aql/query/NativeQuery.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/query/NativeQuery.java rename to aql/src/main/java/org/ehrbase/client/aql/query/NativeQuery.java diff --git a/client/src/main/java/org/ehrbase/client/aql/query/Query.java b/aql/src/main/java/org/ehrbase/client/aql/query/Query.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/query/Query.java rename to aql/src/main/java/org/ehrbase/client/aql/query/Query.java diff --git a/client/src/main/java/org/ehrbase/client/aql/record/AbstractRecordImp.java b/aql/src/main/java/org/ehrbase/client/aql/record/AbstractRecordImp.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/record/AbstractRecordImp.java rename to aql/src/main/java/org/ehrbase/client/aql/record/AbstractRecordImp.java diff --git a/client/src/main/java/org/ehrbase/client/aql/record/Record.java b/aql/src/main/java/org/ehrbase/client/aql/record/Record.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/record/Record.java rename to aql/src/main/java/org/ehrbase/client/aql/record/Record.java diff --git a/client/src/main/java/org/ehrbase/client/aql/record/Record1.java b/aql/src/main/java/org/ehrbase/client/aql/record/Record1.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/record/Record1.java rename to aql/src/main/java/org/ehrbase/client/aql/record/Record1.java diff --git a/client/src/main/java/org/ehrbase/client/aql/record/Record10.java b/aql/src/main/java/org/ehrbase/client/aql/record/Record10.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/record/Record10.java rename to aql/src/main/java/org/ehrbase/client/aql/record/Record10.java diff --git a/client/src/main/java/org/ehrbase/client/aql/record/Record11.java b/aql/src/main/java/org/ehrbase/client/aql/record/Record11.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/record/Record11.java rename to aql/src/main/java/org/ehrbase/client/aql/record/Record11.java diff --git a/client/src/main/java/org/ehrbase/client/aql/record/Record12.java b/aql/src/main/java/org/ehrbase/client/aql/record/Record12.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/record/Record12.java rename to aql/src/main/java/org/ehrbase/client/aql/record/Record12.java diff --git a/client/src/main/java/org/ehrbase/client/aql/record/Record13.java b/aql/src/main/java/org/ehrbase/client/aql/record/Record13.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/record/Record13.java rename to aql/src/main/java/org/ehrbase/client/aql/record/Record13.java diff --git a/client/src/main/java/org/ehrbase/client/aql/record/Record14.java b/aql/src/main/java/org/ehrbase/client/aql/record/Record14.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/record/Record14.java rename to aql/src/main/java/org/ehrbase/client/aql/record/Record14.java diff --git a/client/src/main/java/org/ehrbase/client/aql/record/Record15.java b/aql/src/main/java/org/ehrbase/client/aql/record/Record15.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/record/Record15.java rename to aql/src/main/java/org/ehrbase/client/aql/record/Record15.java diff --git a/client/src/main/java/org/ehrbase/client/aql/record/Record16.java b/aql/src/main/java/org/ehrbase/client/aql/record/Record16.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/record/Record16.java rename to aql/src/main/java/org/ehrbase/client/aql/record/Record16.java diff --git a/client/src/main/java/org/ehrbase/client/aql/record/Record17.java b/aql/src/main/java/org/ehrbase/client/aql/record/Record17.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/record/Record17.java rename to aql/src/main/java/org/ehrbase/client/aql/record/Record17.java diff --git a/client/src/main/java/org/ehrbase/client/aql/record/Record18.java b/aql/src/main/java/org/ehrbase/client/aql/record/Record18.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/record/Record18.java rename to aql/src/main/java/org/ehrbase/client/aql/record/Record18.java diff --git a/client/src/main/java/org/ehrbase/client/aql/record/Record19.java b/aql/src/main/java/org/ehrbase/client/aql/record/Record19.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/record/Record19.java rename to aql/src/main/java/org/ehrbase/client/aql/record/Record19.java diff --git a/client/src/main/java/org/ehrbase/client/aql/record/Record2.java b/aql/src/main/java/org/ehrbase/client/aql/record/Record2.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/record/Record2.java rename to aql/src/main/java/org/ehrbase/client/aql/record/Record2.java diff --git a/client/src/main/java/org/ehrbase/client/aql/record/Record20.java b/aql/src/main/java/org/ehrbase/client/aql/record/Record20.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/record/Record20.java rename to aql/src/main/java/org/ehrbase/client/aql/record/Record20.java diff --git a/client/src/main/java/org/ehrbase/client/aql/record/Record21.java b/aql/src/main/java/org/ehrbase/client/aql/record/Record21.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/record/Record21.java rename to aql/src/main/java/org/ehrbase/client/aql/record/Record21.java diff --git a/client/src/main/java/org/ehrbase/client/aql/record/Record3.java b/aql/src/main/java/org/ehrbase/client/aql/record/Record3.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/record/Record3.java rename to aql/src/main/java/org/ehrbase/client/aql/record/Record3.java diff --git a/client/src/main/java/org/ehrbase/client/aql/record/Record4.java b/aql/src/main/java/org/ehrbase/client/aql/record/Record4.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/record/Record4.java rename to aql/src/main/java/org/ehrbase/client/aql/record/Record4.java diff --git a/client/src/main/java/org/ehrbase/client/aql/record/Record5.java b/aql/src/main/java/org/ehrbase/client/aql/record/Record5.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/record/Record5.java rename to aql/src/main/java/org/ehrbase/client/aql/record/Record5.java diff --git a/client/src/main/java/org/ehrbase/client/aql/record/Record6.java b/aql/src/main/java/org/ehrbase/client/aql/record/Record6.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/record/Record6.java rename to aql/src/main/java/org/ehrbase/client/aql/record/Record6.java diff --git a/client/src/main/java/org/ehrbase/client/aql/record/Record7.java b/aql/src/main/java/org/ehrbase/client/aql/record/Record7.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/record/Record7.java rename to aql/src/main/java/org/ehrbase/client/aql/record/Record7.java diff --git a/client/src/main/java/org/ehrbase/client/aql/record/Record8.java b/aql/src/main/java/org/ehrbase/client/aql/record/Record8.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/record/Record8.java rename to aql/src/main/java/org/ehrbase/client/aql/record/Record8.java diff --git a/client/src/main/java/org/ehrbase/client/aql/record/Record9.java b/aql/src/main/java/org/ehrbase/client/aql/record/Record9.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/record/Record9.java rename to aql/src/main/java/org/ehrbase/client/aql/record/Record9.java diff --git a/client/src/main/java/org/ehrbase/client/aql/record/RecordImp.java b/aql/src/main/java/org/ehrbase/client/aql/record/RecordImp.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/record/RecordImp.java rename to aql/src/main/java/org/ehrbase/client/aql/record/RecordImp.java diff --git a/client/src/main/java/org/ehrbase/client/aql/top/Direction.java b/aql/src/main/java/org/ehrbase/client/aql/top/Direction.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/top/Direction.java rename to aql/src/main/java/org/ehrbase/client/aql/top/Direction.java diff --git a/client/src/main/java/org/ehrbase/client/aql/top/Top.java b/aql/src/main/java/org/ehrbase/client/aql/top/Top.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/top/Top.java rename to aql/src/main/java/org/ehrbase/client/aql/top/Top.java diff --git a/client/src/main/java/org/ehrbase/client/aql/top/TopExpresion.java b/aql/src/main/java/org/ehrbase/client/aql/top/TopExpresion.java similarity index 100% rename from client/src/main/java/org/ehrbase/client/aql/top/TopExpresion.java rename to aql/src/main/java/org/ehrbase/client/aql/top/TopExpresion.java diff --git a/web-template/src/test/java/org/ehrbase/webtemplate/parser/AqlPathTest.java b/aql/src/test/java/org/ehrbase/aql/dto/path/AqlPathTest.java similarity index 97% rename from web-template/src/test/java/org/ehrbase/webtemplate/parser/AqlPathTest.java rename to aql/src/test/java/org/ehrbase/aql/dto/path/AqlPathTest.java index e9ed68d01..5ddf53bbb 100644 --- a/web-template/src/test/java/org/ehrbase/webtemplate/parser/AqlPathTest.java +++ b/aql/src/test/java/org/ehrbase/aql/dto/path/AqlPathTest.java @@ -15,15 +15,28 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.ehrbase.webtemplate.parser; +package org.ehrbase.aql.dto.path; import static org.assertj.core.api.Assertions.assertThat; +import java.util.Arrays; +import java.util.stream.Collectors; import org.junit.Assert; import org.junit.Test; public class AqlPathTest { + @Test + public void testSplit() { + + String cut = "at001, name/value = 'dfd' or name/value= 'fdf'"; + + CharSequence[] ands = AqlPath.split(cut, null, true, "and", "or", ","); + + assertThat(Arrays.stream(ands).map(CharSequence::toString).collect(Collectors.toList())) + .containsExactly("at001", ",", " name/value = 'dfd' ", "or", " name/value= 'fdf'"); + } + @Test public void testParse() { String path = "/other_context[at0001]/items[at0006]"; @@ -407,7 +420,7 @@ public void testParseSpace() { AqlPath path; path = AqlPath.parse( - "/content[openEHR-EHR-OBSERVATION.laboratory_test_result.v1, 'Einsenderstandort']/protocol[at0004]/items[at0094]/items[openEHR-EHR-CLUSTER.location.v1]"); + "/content[openEHR-EHR-OBSERVATION.laboratory_test_result.v1,'Einsenderstandort']/protocol[at0004]/items[at0094]/items[openEHR-EHR-CLUSTER.location.v1]"); assertThat(path.format(AqlPath.OtherPredicatesFormat.FULL, true)) .isEqualTo( "/content[openEHR-EHR-OBSERVATION.laboratory_test_result.v1 and name/value='Einsenderstandort']/protocol[at0004]/items[at0094]/items[openEHR-EHR-CLUSTER.location.v1]"); diff --git a/aql/src/test/java/org/ehrbase/aql/dto/path/predicate/PredicateHelperTest.java b/aql/src/test/java/org/ehrbase/aql/dto/path/predicate/PredicateHelperTest.java new file mode 100644 index 000000000..141a5134b --- /dev/null +++ b/aql/src/test/java/org/ehrbase/aql/dto/path/predicate/PredicateHelperTest.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2022 vitasystems GmbH and Hannover Medical School. + * + * This file is part of project openEHR_SDK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ehrbase.aql.dto.path.predicate; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import org.ehrbase.aql.dto.path.AqlPath; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +/** + * @author Stefan Spiska + */ +class PredicateHelperTest { + + private enum TestCase { + // T1_FULL("archetype_node_id=at001 and name/value='name1' or archetype_node_id=at001 and name/value='name2'", + // AqlPath.OtherPredicatesFormat.FULL,"archetype_node_id=at001 and name/value='name1' or archetype_node_id=at001 + // and name/value='name2'"), + // T1_SHORT("archetype_node_id=at001 and name/value='name1' or archetype_node_id=at001 and name/value='name2'", + // AqlPath.OtherPredicatesFormat.SHORTED,"archetype_node_id=at001 and name/value='name1' or + // archetype_node_id=at001 and name/value='name2'"), + // T1_NODE("archetype_node_id=at001 and name/value='name1' or archetype_node_id=at001 and name/value='name2'", + // AqlPath.OtherPredicatesFormat.NONE,"at001, 'name1' or at001, 'name2'"), + T2_FULL( + "at001,'name1' and path/value='abc'", + AqlPath.OtherPredicatesFormat.FULL, + "at001 and name/value='name1' and path/value='abc'"), + T2_SHORT( + "at001,'name1' and path/value='abc'", + AqlPath.OtherPredicatesFormat.SHORTED, + "at001,'name1' and path/value='abc'"), + T2_NONE("at001,'name1' and path/value='abc'", AqlPath.OtherPredicatesFormat.NONE, "at001"), + T3_FULL( + "name/value='name1' and archetype_node_id=at001", + AqlPath.OtherPredicatesFormat.FULL, + "at001 and name/value='name1'"), + T3_SHORT( + "name/value='name1' and archetype_node_id=at001", + AqlPath.OtherPredicatesFormat.SHORTED, + "at001,'name1'"), + T3_NONE("name/value='name1' and archetype_node_id=at001", AqlPath.OtherPredicatesFormat.NONE, "at001"); + + final String input; + final AqlPath.OtherPredicatesFormat format; + final String expected; + + TestCase(String input, AqlPath.OtherPredicatesFormat format, String expected) { + this.input = input; + this.format = format; + this.expected = expected; + } + } + + @ParameterizedTest + @EnumSource(TestCase.class) + void roundTrip(TestCase testCase) { + + PredicateDto predicateDto = PredicateHelper.buildPredicate(testCase.input); + + StringBuilder sb = new StringBuilder(); + PredicateHelper.format(sb, predicateDto, testCase.format); + + assertThat(sb).hasToString(testCase.expected); + } +} diff --git a/aql/src/test/java/org/ehrbase/aql/parser/AqlToDtoParserTest.java b/aql/src/test/java/org/ehrbase/aql/parser/AqlToDtoParserTest.java index 1317180cd..2bc86c549 100644 --- a/aql/src/test/java/org/ehrbase/aql/parser/AqlToDtoParserTest.java +++ b/aql/src/test/java/org/ehrbase/aql/parser/AqlToDtoParserTest.java @@ -37,12 +37,12 @@ import org.ehrbase.aql.dto.containment.ContainmentExpresionDto; import org.ehrbase.aql.dto.containment.ContainmentLogicalOperator; import org.ehrbase.aql.dto.select.SelectFieldDto; -import org.junit.Test; +import org.junit.jupiter.api.Test; -public class AqlToDtoParserTest { +class AqlToDtoParserTest { @Test - public void parse() { + void parse() { String aql = "Select c/context/other_context[at0001]/items[at0002]/value/value as Bericht_ID__value, d/ehr_id/value as ehr_id from EHR d contains COMPOSITION c[openEHR-EHR-COMPOSITION.report.v1]"; @@ -50,7 +50,7 @@ public void parse() { } @Test - public void parseDoubleAlias() { + void parseDoubleAlias() { String aql = "Select e/ehr_id/value ,c0 as F1 from EHR e contains COMPOSITION c0[openEHR-EHR-COMPOSITION.report.v1]"; @@ -60,7 +60,7 @@ public void parseDoubleAlias() { } @Test - public void parseDoubleAlias2() { + void parseDoubleAlias2() { String aql = "Select c0 as F1, e/ehr_id/value from EHR e contains COMPOSITION c0[openEHR-EHR-COMPOSITION.report.v1]"; @@ -70,14 +70,14 @@ public void parseDoubleAlias2() { } @Test - public void parseObservation() { + void parseObservation() { String aql = "SELECT o FROM EHR e CONTAINS OBSERVATION o"; testAql(aql, "Select o as F1 from EHR e contains OBSERVATION o"); } @Test - public void parseObservation2() { + void parseObservation2() { String aql = "Select e/ehr_id/value as F1, o/data[at0001]/events[at0002]/data[at0003]/items[at0022]/items[at0005]/value/value as F2, o/data[at0001]/events[at0002]/data[at0003]/items[at0022]/items[at0004]/value/value as F3 from EHR e contains (COMPOSITION c0 and SECTION s4[openEHR-EHR-SECTION.adhoc.v1] contains OBSERVATION o[openEHR-EHR-OBSERVATION.symptom_sign_screening.v0]) where (e/ehr_id/value matches {'47dc21a2-7076-4a57-89dc-bd83729ed52f'} and c0/archetype_details/template_id/value matches {'Corona_Anamnese'})"; @@ -85,7 +85,7 @@ public void parseObservation2() { } @Test - public void parseMultiWhere() { + void parseMultiWhere() { String aql = "Select c0 as openEHR_EHR_COMPOSITION_self_monitoring_v0, c1 as openEHR_EHR_COMPOSITION_report_v1 from EHR e contains (COMPOSITION c0[openEHR-EHR-COMPOSITION.self_monitoring.v0] and COMPOSITION c1[openEHR-EHR-COMPOSITION.report.v1]) where (e/ehr_id/value matches {'b3a40b41-36e1-4802-8748-062d4000aaae'} and c0/archetype_details/template_id/value matches {'Corona_Anamnese'} and c1/archetype_details/template_id/value matches {'Corona_Anamnese'})"; @@ -93,7 +93,7 @@ public void parseMultiWhere() { } @Test - public void parseMultiMixed() { + void parseMultiMixed() { String aql = "Select c0 as F1, e/ehr_id/value as F2 from EHR e contains COMPOSITION c0[openEHR-EHR-COMPOSITION.report.v1] where (e/ehr_id/value = $ehrid or (e/ehr_id/value = $ehrid2 and e/ehr_id/value = $ehrid3))"; @@ -101,7 +101,7 @@ public void parseMultiMixed() { } @Test - public void parseMatches() { + void parseMatches() { String aql = "Select c/context/other_context[at0001]/items[at0002]/value/value as Bericht_ID__value, d/ehr_id/value as ehr_id from EHR d contains COMPOSITION c[openEHR-EHR-COMPOSITION.report.v1] where d/ehr_id/value matches {'f4da8646-8e36-4d9d-869c-af9dce5935c7','61861e76-1606-48c9-adcf-49ebbb2c6bbd'}"; @@ -109,7 +109,7 @@ public void parseMatches() { } @Test - public void addMatches() { + void addMatches() { String aql = "Select o0/data[at0001]/events[at0002]/data[at0003]/items[at0004]/value/magnitude as Systolic__magnitude, e/ehr_id/value as ehr_id from EHR e contains OBSERVATION o0[openEHR-EHR-OBSERVATION.sample_blood_pressure.v1] where (o0/data[at0001]/events[at0002]/data[at0003]/items[at0004]/value/magnitude >= $magnitude and o0/data[at0001]/events[at0002]/data[at0003]/items[at0004]/value/magnitude < 1.1)"; @@ -150,14 +150,14 @@ public void addMatches() { } @Test - public void parseWithoutContains() { + void parseWithoutContains() { String aql = "SELECT e/ehr_id/value FROM EHR e"; testAql(aql, "Select e/ehr_id/value as F1 from EHR e"); } @Test - public void parseLimitOffset() { + void parseLimitOffset() { String aql = "Select c/context/other_context[at0001]/items[at0002]/value/value as Bericht_ID__value, d/ehr_id/value as ehr_id from EHR d contains COMPOSITION c[openEHR-EHR-COMPOSITION.report.v1] LIMIT 5 OFFSET 1"; @@ -165,7 +165,7 @@ public void parseLimitOffset() { } @Test - public void parseError() { + void parseError() { String aql = "Select c/context/other_context[at0001]/items[at0002]/value/value as Bericht_ID__value, d/ehr_id/value as ehr_id EHR d contains COMPOSITION c[openEHR-EHR-COMPOSITION.report.v1]"; @@ -180,7 +180,7 @@ public void parseError() { } @Test - public void parseWhere() { + void parseWhere() { String aql = "Select o0/data[at0001]/events[at0002]/data[at0003]/items[at0004]/value/magnitude as Systolic__magnitude, e/ehr_id/value as ehr_id from EHR e contains OBSERVATION o0[openEHR-EHR-OBSERVATION.sample_blood_pressure.v1] where (o0/data[at0001]/events[at0002]/data[at0003]/items[at0004]/value/magnitude >= $magnitude and o0/data[at0001]/events[at0002]/data[at0003]/items[at0004]/value/magnitude < 1.1)"; @@ -188,7 +188,7 @@ public void parseWhere() { } @Test - public void parseTop() { + void parseTop() { String aql = "Select TOP 10 FORWARD o0/data[at0001]/events[at0002]/data[at0003]/items[at0004]/value/magnitude as Systolic__magnitude, e/ehr_id/value as ehr_id from EHR e contains OBSERVATION o0[openEHR-EHR-OBSERVATION.sample_blood_pressure.v1] where (o0/data[at0001]/events[at0002]/data[at0003]/items[at0004]/value/magnitude >= $magnitude and o0/data[at0001]/events[at0002]/data[at0003]/items[at0004]/value/magnitude < 1.1)"; @@ -201,7 +201,7 @@ public void parseTop() { } @Test - public void parseOrderBy() { + void parseOrderBy() { String aqlTwoOrderBy = "Select e/ehr_id/value as ehr_id from EHR e contains OBSERVATION o0[openEHR-EHR-OBSERVATION.sample_blood_pressure.v1]" @@ -232,7 +232,7 @@ public void parseOrderBy() { + " order by o0/data[at0001]/events[at0002]/data[at0003]/items[at0004]/value/magnitude DESCENDING, e/ehr_id/value ASCENDING"); } - public void testAql(String aql, String expected) { + void testAql(String aql, String expected) { AqlToDtoParser cut = new AqlToDtoParser(); AqlDto actual = cut.parse(aql); @@ -244,7 +244,7 @@ public void testAql(String aql, String expected) { } @Test - public void parseContains() { + void parseContains() { String aql = "Select c0/context/other_context[at0001]/items[at0002]/value/value as Bericht_ID__value from EHR e contains COMPOSITION c0[openEHR-EHR-COMPOSITION.report.v1] contains OBSERVATION o0[openEHR-EHR-OBSERVATION.sample_blood_pressure.v1]"; @@ -252,7 +252,7 @@ public void parseContains() { } @Test - public void parseContainsLogical() { + void parseContainsLogical() { String aql = "Select c0/context/other_context[at0001]/items[at0002]/value/value as Bezeichnung_des_Symptoms_oder_Anzeichens___value, o3/data[at0001]/events[at0002]/data[at0042]/items[at0055]/value/value as Kommentar__value from EHR e contains COMPOSITION c0[openEHR-EHR-COMPOSITION.report.v1] contains (OBSERVATION o1[openEHR-EHR-OBSERVATION.story.v1] and OBSERVATION o2[openEHR-EHR-OBSERVATION.symptom_sign_screening.v0] or OBSERVATION o3[openEHR-EHR-OBSERVATION.exposure_assessment.v0])"; @@ -262,7 +262,7 @@ public void parseContainsLogical() { } @Test - public void parseContainsLogical2() { + void parseContainsLogical2() { String aql = "Select c0/context/other_context[at0001]/items[at0002]/value/value as Bezeichnung_des_Symptoms_oder_Anzeichens___value, o3/data[at0001]/events[at0002]/data[at0042]/items[at0055]/value/value as Kommentar__value from EHR e contains COMPOSITION c0[openEHR-EHR-COMPOSITION.report.v1] contains (OBSERVATION o1[openEHR-EHR-OBSERVATION.story.v1] or OBSERVATION o2[openEHR-EHR-OBSERVATION.symptom_sign_screening.v0] and OBSERVATION o3[openEHR-EHR-OBSERVATION.exposure_assessment.v0])"; @@ -272,7 +272,7 @@ public void parseContainsLogical2() { } @Test - public void parseContainsLogical3() { + void parseContainsLogical3() { String aql = "Select c0/context/other_context[at0001]/items[at0002]/value/value as Bezeichnung_des_Symptoms_oder_Anzeichens___value, o3/data[at0001]/events[at0002]/data[at0042]/items[at0055]/value/value as Kommentar__value from EHR e contains COMPOSITION c0[openEHR-EHR-COMPOSITION.report.v1] contains ((OBSERVATION o1[openEHR-EHR-OBSERVATION.story.v1] or OBSERVATION o2[openEHR-EHR-OBSERVATION.symptom_sign_screening.v0]) and OBSERVATION o3[openEHR-EHR-OBSERVATION.exposure_assessment.v0])"; @@ -281,7 +281,7 @@ public void parseContainsLogical3() { "openEHR-EHR-COMPOSITION.report.v1 --> ((openEHR-EHR-OBSERVATION.story.v1 OR openEHR-EHR-OBSERVATION.symptom_sign_screening.v0) AND openEHR-EHR-OBSERVATION.exposure_assessment.v0)"); } - public void testContains(String aql, String s) { + void testContains(String aql, String s) { AqlToDtoParser cut = new AqlToDtoParser(); AqlDto actual = cut.parse(aql); @@ -292,7 +292,7 @@ public void testContains(String aql, String s) { } @Test - public void parseContainsLogical4() { + void parseContainsLogical4() { String aql = "Select c0/context/other_context[at0001]/items[at0002]/value/value as Bezeichnung_des_Symptoms_oder_Anzeichens___value, o3/data[at0001]/events[at0002]/data[at0042]/items[at0055]/value/value as Kommentar__value from EHR e contains COMPOSITION c0[openEHR-EHR-COMPOSITION.report.v1] contains (((OBSERVATION o1[openEHR-EHR-OBSERVATION.story.v1] contains CLUSTER) or OBSERVATION o2[openEHR-EHR-OBSERVATION.symptom_sign_screening.v0]) and OBSERVATION o3[openEHR-EHR-OBSERVATION.exposure_assessment.v0])"; @@ -301,7 +301,7 @@ public void parseContainsLogical4() { "openEHR-EHR-COMPOSITION.report.v1 --> ((openEHR-EHR-OBSERVATION.story.v1 --> CLUSTER OR openEHR-EHR-OBSERVATION.symptom_sign_screening.v0) AND openEHR-EHR-OBSERVATION.exposure_assessment.v0)"); } - private String render(ContainmentExpresionDto containmentExpresion) { + String render(ContainmentExpresionDto containmentExpresion) { StringBuilder sb = new StringBuilder(); if (containmentExpresion instanceof ContainmentDto) { sb.append(((ContainmentDto) containmentExpresion).getArchetypeId()); @@ -326,7 +326,7 @@ private String render(ContainmentExpresionDto containmentExpresion) { } @Test - public void parseAqlLimitOffset() { + void parseAqlLimitOffset() { var parser = new AqlToDtoParser(); var query1 = "select e/ehr_id/value " @@ -377,7 +377,7 @@ public void parseAqlLimitOffset() { } @Test - public void parseWhereClauseWithBoolean() { + void parseWhereClauseWithBoolean() { String aql; aql = @@ -418,20 +418,21 @@ public void parseWhereClauseWithBoolean() { } @Test - public void orderByAndLimitOrder() { + void orderByAndLimitOrder() { var aql1 = "Select " + "c/name/value as Name, c/context/start_time as date_time, c/composer/name as Composer " + "from EHR e contains COMPOSITION c " + "order by c/context/start_time ASCENDING " + "LIMIT 10 OFFSET 10"; - testAql(aql1, aql1); var aql2 = "Select " + "c/name/value as Name, c/context/start_time as date_time, c/composer/name as Composer " + "from EHR e contains COMPOSITION c " + "LIMIT 10 OFFSET 10 " + "order by c/context/start_time ASCENDING"; - testAql(aql2, aql1); + testAql(aql1, aql2); + + testAql(aql2, aql2); var aql3 = "Select " + "c/name/value as Name, c/context/start_time as date_time, c/composer/name as Composer " diff --git a/aql/src/test/java/org/ehrbase/aql/roundtriptest/AqlExpressionTestCase.java b/aql/src/test/java/org/ehrbase/aql/roundtriptest/AqlExpressionTestCase.java new file mode 100644 index 000000000..f6063cb49 --- /dev/null +++ b/aql/src/test/java/org/ehrbase/aql/roundtriptest/AqlExpressionTestCase.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2022 vitasystems GmbH and Hannover Medical School. + * + * This file is part of project openEHR_SDK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ehrbase.aql.roundtriptest; + +/** + * @author Stefan Spiska + * @see AqlExpressionTest + * in ehrbase + */ +public enum AqlExpressionTestCase implements AqlTestCase { + DUMP( + "SELECT o/data[at0002]/events[at0003] AS systolic " + + "FROM EHR [ehr_id/value='1234'] " + + "CONTAINS COMPOSITION c [openEHR-EHR-COMPOSITION.encounter.v1] " + + "CONTAINS OBSERVATION o [openEHR-EHR-OBSERVATION.blood_pressure.v1]" + + "WHERE o/data[at0001]/events[at0006]/data[at0003]/items[at0004]/value/value > 140", + "Select o/data[at0002]/events[at0003] as systolic " + + "from EHR e " + + "contains COMPOSITION c[openEHR-EHR-COMPOSITION.encounter.v1] " + + "contains OBSERVATION o[openEHR-EHR-OBSERVATION.blood_pressure.v1] " + + "where (o/data[at0001]/events[at0006]/data[at0003]/items[at0004]/value/value > 140 " + + "and e/ehr_id/value = '1234')", + "testDump"), + WHERE_EXPRESSION_WITH_PARENTHESIS( + "select e/ehr_id, a/data[at0002]/events[at0003]/data[at0001]/items[at0004]/value/magnitude, a_a/data[at0002]/events[at0003]/time/value " + + "from EHR e " + + "contains COMPOSITION a " + + "contains OBSERVATION a_a[openEHR-EHR-OBSERVATION.body_temperature.v1] " + + "where a_a/data[at0002]/events[at0003]/data[at0001]/items[at0004]/value/magnitude>38 " + + "AND a_a/data[at0002]/events[at0003]/data[at0001]/items[at0004]/value/units = '°C' " + + "AND e/ehr_id/value MATCHES {" + + "'849bf097-bd16-44fc-a394-10676284a012'," + + "'34b2e263-00eb-40b8-88f1-823c87096457'}" + + "OR (a_a/data[at0002]/events[at0003]/data[at0001]/items[at0004]/value/units = '°C' AND a_a/data[at0002]/events[at0003]/data[at0001]/items[at0004]/value/units = '°C')", + "Select e/ehr_id as F1, a/data[at0002]/events[at0003]/data[at0001]/items[at0004]/value/magnitude as F2, a_a/data[at0002]/events[at0003]/time/value as F3 " + + "from EHR e " + + "contains COMPOSITION a " + + "contains OBSERVATION a_a[openEHR-EHR-OBSERVATION.body_temperature.v1] " + + "where ((" + + "a_a/data[at0002]/events[at0003]/data[at0001]/items[at0004]/value/magnitude > 38 " + + "and a_a/data[at0002]/events[at0003]/data[at0001]/items[at0004]/value/units = '°C' " + + "and e/ehr_id/value matches {'849bf097-bd16-44fc-a394-10676284a012','34b2e263-00eb-40b8-88f1-823c87096457'}) " + + "or (a_a/data[at0002]/events[at0003]/data[at0001]/items[at0004]/value/units = '°C' and a_a/data[at0002]/events[at0003]/data[at0001]/items[at0004]/value/units = '°C'))", + "testWhereExpressionWithParenthesis"); + + private final String testAql; + private final String expectedAql; + + AqlExpressionTestCase(String testAql, String expectedAql, String description) { + this.testAql = testAql; + this.expectedAql = expectedAql; + } + + @Override + public String getTestAql() { + return testAql; + } + + @Override + public String getExpectedAql() { + return expectedAql; + } +} diff --git a/aql/src/test/java/org/ehrbase/aql/roundtriptest/AqlExpressionWithParameterTestCase.java b/aql/src/test/java/org/ehrbase/aql/roundtriptest/AqlExpressionWithParameterTestCase.java new file mode 100644 index 000000000..bc47e3dcb --- /dev/null +++ b/aql/src/test/java/org/ehrbase/aql/roundtriptest/AqlExpressionWithParameterTestCase.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2022 vitasystems GmbH and Hannover Medical School. + * + * This file is part of project openEHR_SDK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ehrbase.aql.roundtriptest; + +/** + * @author Stefan Spiska + * @see AqlExpressionWithParametersTest in ehrbase + */ +public enum AqlExpressionWithParameterTestCase implements AqlTestCase { + DUMP( + "select o_bp/data[at0001]/events[at0006]/data[at0003]/items[at0005, $nameValue1]/value/magnitude as diastolic, o_bp/data[at0001]/events[at0006]/data[at0003]/items[at0004, $nameValue2]/value/magnitude as systolic " + + "from EHR e[ehr_id/value=$ehrId] " + + "contains COMPOSITION a " + + "contains OBSERVATION o_bp[openEHR-EHR-OBSERVATION.blood_pressure.v1] " + + "where o_bp/data[at0001]/events[at0006]/data[at0003]/items[at0005]/value/magnitude < $max_value " + + "and o_bp/data[at0001]/events[at0006]/data[at0003]/items[at0004]/value/magnitude > $another_value", + "Select o_bp/data[at0001]/events[at0006]/data[at0003]/items[at0005,$nameValue1]/value/magnitude as diastolic, o_bp/data[at0001]/events[at0006]/data[at0003]/items[at0004,$nameValue2]/value/magnitude as systolic " + + "from EHR e " + + "contains COMPOSITION a " + + "contains OBSERVATION o_bp[openEHR-EHR-OBSERVATION.blood_pressure.v1] " + + "where (o_bp/data[at0001]/events[at0006]/data[at0003]/items[at0005]/value/magnitude < $max_value " + + "and o_bp/data[at0001]/events[at0006]/data[at0003]/items[at0004]/value/magnitude > $another_value " + + "and e/ehr_id/value = $ehrId)", + "testDump"), + DUMP2( + "select o_bp/data[at0001]/events[at0006]/data[at0003]/items[at0005, $nameValue1]/value/magnitude as diastolic, o_bp/data[at0001]/events[at0006]/data[at0003]/items[at0004, $nameValue2]/value/magnitude as systolic " + + "from EHR e[ehr_id/value=$ehrId and system_id/value != '123'] " + + "contains COMPOSITION a " + + "contains OBSERVATION o_bp[openEHR-EHR-OBSERVATION.blood_pressure.v1] " + + "where o_bp/data[at0001]/events[at0006]/data[at0003]/items[at0005]/value/magnitude < $max_value " + + "and o_bp/data[at0001]/events[at0006]/data[at0003]/items[at0004]/value/magnitude > $another_value", + "Select o_bp/data[at0001]/events[at0006]/data[at0003]/items[at0005,$nameValue1]/value/magnitude as diastolic, o_bp/data[at0001]/events[at0006]/data[at0003]/items[at0004,$nameValue2]/value/magnitude as systolic " + + "from EHR e " + + "contains COMPOSITION a " + + "contains OBSERVATION o_bp[openEHR-EHR-OBSERVATION.blood_pressure.v1] " + + "where (o_bp/data[at0001]/events[at0006]/data[at0003]/items[at0005]/value/magnitude < $max_value " + + "and o_bp/data[at0001]/events[at0006]/data[at0003]/items[at0004]/value/magnitude > $another_value " + + "and (e/ehr_id/value = $ehrId and e/system_id/value != '123'))", + "testDump"); + + private final String testAql; + private final String expectedAql; + + AqlExpressionWithParameterTestCase(String testAql, String expectedAql, String description) { + this.testAql = testAql; + this.expectedAql = expectedAql; + } + + @Override + public String getTestAql() { + return testAql; + } + + @Override + public String getExpectedAql() { + return expectedAql; + } +} diff --git a/aql/src/test/java/org/ehrbase/aql/roundtriptest/AqlRoundTripTest.java b/aql/src/test/java/org/ehrbase/aql/roundtriptest/AqlRoundTripTest.java new file mode 100644 index 000000000..1a6885f06 --- /dev/null +++ b/aql/src/test/java/org/ehrbase/aql/roundtriptest/AqlRoundTripTest.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2022 vitasystems GmbH and Hannover Medical School. + * + * This file is part of project openEHR_SDK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ehrbase.aql.roundtriptest; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.ehrbase.aql.binder.AqlBinder; +import org.ehrbase.aql.dto.AqlDto; +import org.ehrbase.aql.parser.AqlToDtoParser; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +/** + * @author Stefan Spiska + */ +class AqlRoundTripTest { + + @ParameterizedTest + @EnumSource(AqlExpressionTestCase.class) + void testAqlExpressionTest(AqlExpressionTestCase testData) { + + assertRoundTrip(testData); + } + + @ParameterizedTest + @EnumSource(AqlExpressionWithParameterTestCase.class) + void testAqlExpressionWithParameterTest(AqlExpressionWithParameterTestCase testData) { + + assertRoundTrip(testData); + } + + @ParameterizedTest + @EnumSource(UCTestData.class) + void testUC(UCTestData testData) { + + assertRoundTrip(testData); + } + + @ParameterizedTest + @EnumSource(PerformanceTestCase.class) + void testPerformanceQuery(PerformanceTestCase testData) { + + assertRoundTrip(testData); + } + + void assertRoundTrip(AqlTestCase testData) { + AqlToDtoParser cut = new AqlToDtoParser(); + AqlDto actual = cut.parse(testData.getTestAql()); + + assertThat(actual).isNotNull(); + + String actualAql = new AqlBinder().bind(actual).getLeft().buildAql(); + + assertThat(actualAql).isEqualTo(testData.getExpectedAql()); + } +} diff --git a/aql/src/test/java/org/ehrbase/aql/roundtriptest/AqlTestCase.java b/aql/src/test/java/org/ehrbase/aql/roundtriptest/AqlTestCase.java new file mode 100644 index 000000000..d0f74544a --- /dev/null +++ b/aql/src/test/java/org/ehrbase/aql/roundtriptest/AqlTestCase.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2022 vitasystems GmbH and Hannover Medical School. + * + * This file is part of project openEHR_SDK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ehrbase.aql.roundtriptest; + +/** + * @author Stefan Spiska + */ +public interface AqlTestCase { + + String getTestAql(); + + String getExpectedAql(); +} diff --git a/aql/src/test/java/org/ehrbase/aql/roundtriptest/PerformanceTestCase.java b/aql/src/test/java/org/ehrbase/aql/roundtriptest/PerformanceTestCase.java new file mode 100644 index 000000000..488f09056 --- /dev/null +++ b/aql/src/test/java/org/ehrbase/aql/roundtriptest/PerformanceTestCase.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2022 vitasystems GmbH and Hannover Medical School. + * + * This file is part of project openEHR_SDK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ehrbase.aql.roundtriptest; + +/** + * @author Stefan Spiska + */ +public enum PerformanceTestCase implements AqlTestCase { + QUERY_1( + "SELECT c/uid/value as composition_id, c/context/start_time/value as start_time, o/data[at0002]/events[at0003]/data[at0001]/items[at0004]/value/magnitude as body_temperature" + + " FROM EHR e" + + " CONTAINS COMPOSITION c" + + " CONTAINS OBSERVATION o [openEHR-EHR-OBSERVATION.body_temperature.v2]" + + " WHERE e/ehr_id/value = '96940df3-c979-4d96-9a51-34b6c5222777'" + + " AND o/data[at0002]/events[at0003]/data[at0001]/items[at0004]/value/magnitude > 37.5" + + " LIMIT 10 OFFSET 10" + + " ORDER BY c/context/start_time/value DESC", + "Select c/uid/value as composition_id, c/context/start_time/value as start_time, o/data[at0002]/events[at0003]/data[at0001]/items[at0004]/value/magnitude as body_temperature " + + "from EHR e " + + "contains COMPOSITION c " + + "contains OBSERVATION o[openEHR-EHR-OBSERVATION.body_temperature.v2] " + + "where (e/ehr_id/value = '96940df3-c979-4d96-9a51-34b6c5222777' " + + "and o/data[at0002]/events[at0003]/data[at0001]/items[at0004]/value/magnitude > 37.5) " + + "LIMIT 10 OFFSET 10 " + + "order by c/context/start_time/value DESCENDING", + "Get second page of measurements with page size 10 which indicated high temperature and their time"), + QUERY_2( + "SELECT c as composition" + + " FROM EHR e" + + " CONTAINS COMPOSITION c[openEHR-EHR-COMPOSITION.report.v1]" + + " WHERE e/ehr_id/value = '96940df3-c979-4d96-9a51-34b6c5222777' " + + " AND c/archetype_details/template_id/value = 'Corona_Anamnese'" + + " LIMIT 1" + + " ORDER BY c/context/start_time/value DESC", + "Select c as composition " + "from EHR e " + + "contains COMPOSITION c[openEHR-EHR-COMPOSITION.report.v1] " + + "where (e/ehr_id/value = '96940df3-c979-4d96-9a51-34b6c5222777' " + + "and c/archetype_details/template_id/value = 'Corona_Anamnese') " + + "LIMIT 1 " + + "order by c/context/start_time/value DESCENDING", + "Get latest 'Corona_Anamnese' document for a specific patient"), + QUERY_3( + "SELECT DISTINCT e/ehr_id/value " + + "FROM EHR e " + + "CONTAINS COMPOSITION c " + + "CONTAINS EVALUATION ev[openEHR-EHR-EVALUATION.problem_diagnosis.v1] " + + "WHERE c/context/health_care_facility/name/value='hcf0' " + + " AND ev/data[at0001]/items[at0002]/value/value='Problem/Diagnosis name 10'", + "Select DISTINCT e/ehr_id/value as F1 " + "from EHR e " + + "contains COMPOSITION c " + + "contains EVALUATION ev[openEHR-EHR-EVALUATION.problem_diagnosis.v1] " + + "where (c/context/health_care_facility/name/value = 'hcf0' " + + "and ev/data[at0001]/items[at0002]/value/value = 'Problem/Diagnosis name 10')", + "Get latest 'Corona_Anamnese' document for a specific patient"); + + private final String testAql; + private final String expectedAql; + + PerformanceTestCase(String testAql, String expectedAql, String description) { + this.testAql = testAql; + this.expectedAql = expectedAql; + } + + @Override + public String getTestAql() { + return testAql; + } + + @Override + public String getExpectedAql() { + return expectedAql; + } +} diff --git a/aql/src/test/java/org/ehrbase/aql/roundtriptest/UCTestData.java b/aql/src/test/java/org/ehrbase/aql/roundtriptest/UCTestData.java new file mode 100644 index 000000000..1052448d9 --- /dev/null +++ b/aql/src/test/java/org/ehrbase/aql/roundtriptest/UCTestData.java @@ -0,0 +1,355 @@ +/* + * Copyright (c) 2022 vitasystems GmbH and Hannover Medical School. + * + * This file is part of project openEHR_SDK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ehrbase.aql.roundtriptest; + +/** + * @author Stefan Spiska + * @see UC* + * in ehrbase + */ +public enum UCTestData implements AqlTestCase { + UC1("select e/ehr_id/value from EHR e", "Select e/ehr_id/value as F1 from EHR e", "UC1"), + UC2( + "select e/ehr_id/value from EHR e " + "where e/ehr_id/value = '30580007'", + "Select e/ehr_id/value as F1 from EHR e " + "where e/ehr_id/value = '30580007'", + "UC1"), + UC3( + "select c/composer/name from EHR e " + "contains COMPOSITION c[openEHR-EHR-COMPOSITION.health_summary.v1]", + "Select c/composer/name as F1 from EHR e " + + "contains COMPOSITION c[openEHR-EHR-COMPOSITION.health_summary.v1]", + "UC3"), + UC4( + "select c/composer/name from EHR e " + + "contains COMPOSITION c[openEHR-EHR-COMPOSITION.health_summary.v1] " + + "order by c/context/start_time/value DESC", + "Select c/composer/name as F1 from EHR e " + + "contains COMPOSITION c[openEHR-EHR-COMPOSITION.health_summary.v1] " + + "order by c/context/start_time/value DESCENDING", + "UC4"), + UC5( + "select a/description[at0001]/items[at0002]/value/value from EHR e " + + "contains COMPOSITION c[openEHR-EHR-COMPOSITION.health_summary.v1] contains ACTION a[openEHR-EHR-ACTION.immunisation_procedure.v1]", + "Select a/description[at0001]/items[at0002]/value/value as F1 from EHR e " + + "contains COMPOSITION c[openEHR-EHR-COMPOSITION.health_summary.v1] contains ACTION a[openEHR-EHR-ACTION.immunisation_procedure.v1]", + "UC5"), + UC6( + "select a/description[at0001]/items[at0002]/value/value as description from EHR e " + + "contains COMPOSITION c[openEHR-EHR-COMPOSITION.health_summary.v1] " + + "contains ACTION a[openEHR-EHR-ACTION.immunisation_procedure.v1]" + + "order by description ASC", + "Select a/description[at0001]/items[at0002]/value/value as description from EHR e " + + "contains COMPOSITION c[openEHR-EHR-COMPOSITION.health_summary.v1] " + + "contains ACTION a[openEHR-EHR-ACTION.immunisation_procedure.v1]" + + " order by a/description[at0001]/items[at0002]/value/value ASCENDING", + "UC6"), + UC7( + "select a/description[at0001]/items[at0002]/value/value from EHR e " + + "contains COMPOSITION c[openEHR-EHR-COMPOSITION.health_summary.v1] " + + "contains ACTION a[openEHR-EHR-ACTION.immunisation_procedure.v1]" + + "where a/description[at0001]/items[at0002]/value/value = 'Hepatitis A'", + "Select a/description[at0001]/items[at0002]/value/value as F1 from EHR e " + + "contains COMPOSITION c[openEHR-EHR-COMPOSITION.health_summary.v1] " + + "contains ACTION a[openEHR-EHR-ACTION.immunisation_procedure.v1]" + + " where a/description[at0001]/items[at0002]/value/value = 'Hepatitis A'", + "UC7"), + UC8( + "select c from EHR e contains COMPOSITION c[openEHR-EHR-COMPOSITION.health_summary.v1]", + "Select c as F1 from EHR e contains COMPOSITION c[openEHR-EHR-COMPOSITION.health_summary.v1]", + "UC8"), + UC9( + "select a from EHR e " + + "contains COMPOSITION c[openEHR-EHR-COMPOSITION.health_summary.v1] " + + "contains ACTION a[openEHR-EHR-ACTION.immunisation_procedure.v1]", + "Select a as F1 from EHR e " + + "contains COMPOSITION c[openEHR-EHR-COMPOSITION.health_summary.v1] " + + "contains ACTION a[openEHR-EHR-ACTION.immunisation_procedure.v1]", + "UC9"), + UC10( + "select e/ehr_id/value from EHR e LIMIT 10 OFFSET 5", + "Select e/ehr_id/value as F1 from EHR e LIMIT 10 OFFSET 5", + "UC10"), + UC11("select TOP 5 e/ehr_id/value from EHR e", "Select TOP 5 FORWARD e/ehr_id/value as F1 from EHR e", "UC11"), + UC12( + "select e/ehr_id/value from EHR e " + + "contains COMPOSITION c[openEHR-EHR-COMPOSITION.health_summary.v1] " + + "contains ACTION a[openEHR-EHR-ACTION.immunisation_procedure.v1]" + + "where a/description[at0001]/items[at0002]/value/value matches {'Hepatitis A','Hepatitis B'} ", + "Select e/ehr_id/value as F1 from EHR e " + + "contains COMPOSITION c[openEHR-EHR-COMPOSITION.health_summary.v1] " + + "contains ACTION a[openEHR-EHR-ACTION.immunisation_procedure.v1]" + + " where a/description[at0001]/items[at0002]/value/value matches {'Hepatitis A','Hepatitis B'}", + "UC12"), + UC13( + "select" + + " count (d/description[at0001]/items[at0004]/value/magnitude) as count_magnitude" + + " from EHR e" + + " contains COMPOSITION" + + " contains ACTION d[openEHR-EHR-ACTION.immunisation_procedure.v1]", + "Select" + + " COUNT(d/description[at0001]/items[at0004]/value/magnitude) as count_magnitude" + + " from EHR e" + + " contains COMPOSITION c0" + + " contains ACTION d[openEHR-EHR-ACTION.immunisation_procedure.v1]", + "UC13"), + UC14( + "select c/composer/name from EHR e " + "contains COMPOSITION c[openEHR-EHR-COMPOSITION.unknown.v1]", + "Select c/composer/name as F1 from EHR e " + "contains COMPOSITION c[openEHR-EHR-COMPOSITION.unknown.v1]", + "UC14"), + + UC15( + "select e/ehr_status/other_details from EHR e[ehr_id/value='2a3b673f-d1b1-44c5-9e38-dcadf67ff2fc']", + "Select e/ehr_status/other_details as F1 from EHR e where e/ehr_id/value = '2a3b673f-d1b1-44c5-9e38-dcadf67ff2fc'", + "UC15"), + + UC16( + "select a/description[at0001]/items[openEHR-EHR-CLUSTER.test_all_types.v1]/items[at0001]/items[at0002]/items[at0003]/value/value " + + "from EHR e " + + "contains COMPOSITION c[openEHR-EHR-COMPOSITION.health_summary.v1] " + + "contains ACTION a[openEHR-EHR-ACTION.immunisation_procedure.v1]" + + "WHERE a/description[at0001]/items[at0001]/items[at0002]/items[at0003]/value/value = true " + + "OR " + + "(" + + "a/description[at0001]/items[at0001]/items[at0002]/items[at0003]/value/value = true " + + "AND" + + " a/description[at0001]/items[at0001]/items[at0002]/items[at0003]/value/value = true" + + ")", + "Select a/description[at0001]/items[openEHR-EHR-CLUSTER.test_all_types.v1]/items[at0001]/items[at0002]/items[at0003]/value/value as F1 " + + "from EHR e " + + "contains COMPOSITION c[openEHR-EHR-COMPOSITION.health_summary.v1] " + + "contains ACTION a[openEHR-EHR-ACTION.immunisation_procedure.v1]" + + " where (a/description[at0001]/items[at0001]/items[at0002]/items[at0003]/value/value = true " + + "or " + + "(" + + "a/description[at0001]/items[at0001]/items[at0002]/items[at0003]/value/value = true " + + "and" + + " a/description[at0001]/items[at0001]/items[at0002]/items[at0003]/value/value = true" + + "))", + "UC16"), + UC17( + "select a from EHR e [ehr_id/value = '4a7c01cf-bb1c-4d3d-8385-4ae0674befb1']" + + "contains COMPOSITION c[openEHR-EHR-COMPOSITION.health_summary.v1] " + + "contains ACTION a[openEHR-EHR-ACTION.immunisation_procedure.v1]", + "Select a as F1 from EHR e contains COMPOSITION c[openEHR-EHR-COMPOSITION.health_summary.v1] contains ACTION a[openEHR-EHR-ACTION.immunisation_procedure.v1] where e/ehr_id/value = '4a7c01cf-bb1c-4d3d-8385-4ae0674befb1'", + "UC17"), + UC18( + "select a from EHR e [ehr_id/value = '4a7c01cf-bb1c-4d3d-8385-4ae0674befb1']" + + "contains COMPOSITION c[openEHR-EHR-COMPOSITION.health_summary.v1] " + + "contains ACTION a[openEHR-EHR-ACTION.immunisation_procedure.v1] " + + "where c/template_id='openEHR-EHR-COMPOSITION.health_summary.v1'", + "Select a as F1 from EHR e contains COMPOSITION c[openEHR-EHR-COMPOSITION.health_summary.v1] contains ACTION a[openEHR-EHR-ACTION.immunisation_procedure.v1] where (c/template_id = 'openEHR-EHR-COMPOSITION.health_summary.v1' and e/ehr_id/value = '4a7c01cf-bb1c-4d3d-8385-4ae0674befb1')", + "UC18"), + UC19( + "select c/category from EHR e [ehr_id/value = '4a7c01cf-bb1c-4d3d-8385-4ae0674befb1']" + + "contains COMPOSITION c[openEHR-EHR-COMPOSITION.health_summary.v1]", + "Select c/category as F1 from EHR e contains COMPOSITION c[openEHR-EHR-COMPOSITION.health_summary.v1] where e/ehr_id/value = '4a7c01cf-bb1c-4d3d-8385-4ae0674befb1'", + "UC19"), + UC20( + "select c/category/defining_code from EHR e [ehr_id/value = '4a7c01cf-bb1c-4d3d-8385-4ae0674befb1']" + + "contains COMPOSITION c[openEHR-EHR-COMPOSITION.health_summary.v1]", + "Select c/category/defining_code as F1 from EHR e contains COMPOSITION c[openEHR-EHR-COMPOSITION.health_summary.v1] where e/ehr_id/value = '4a7c01cf-bb1c-4d3d-8385-4ae0674befb1'", + "UC20"), + UC21( + "select c/category/defining_code/terminology_id/value from EHR e [ehr_id/value = '4a7c01cf-bb1c-4d3d-8385-4ae0674befb1']" + + "contains COMPOSITION c[openEHR-EHR-COMPOSITION.health_summary.v1]", + "Select c/category/defining_code/terminology_id/value as F1 from EHR e contains COMPOSITION c[openEHR-EHR-COMPOSITION.health_summary.v1] where e/ehr_id/value = '4a7c01cf-bb1c-4d3d-8385-4ae0674befb1'", + "UC21"), + UC22( + "select count(a/description[at0001]/items[openEHR-EHR-CLUSTER.test_all_types.v1]/items[at0001]/items[at0002]/items[at0003]/value/value," + + "a/description[at0001]/items[openEHR-EHR-CLUSTER.test_all_types.v1]/items[at0001]/items[at0002]/items[at0004]/value/value)" + + "from EHR e " + + "contains COMPOSITION c[openEHR-EHR-COMPOSITION.health_summary.v1] " + + "contains ACTION a[openEHR-EHR-ACTION.immunisation_procedure.v1]", + "Select COUNT(a/description[at0001]/items[openEHR-EHR-CLUSTER.test_all_types.v1]/items[at0001]/items[at0002]/items[at0003]/value/value) as F1 from EHR e contains COMPOSITION c[openEHR-EHR-COMPOSITION.health_summary.v1] contains ACTION a[openEHR-EHR-ACTION.immunisation_procedure.v1]", + "UC22"), + UC23("SELECT count(e/time_created) FROM EHR e", "Select COUNT(e/time_created) as F1 from EHR e", "UC23"), + + UC24( + "select c" + + " from EHR e" + + " contains COMPOSITION c[openEHR-EHR-COMPOSITION.health_summary.v1]" + + " WHERE NOT EXISTS c/content[openEHR-EHR-ADMIN_ENTRY.hospitalization.v0]", + "Select c as F1 from EHR e contains COMPOSITION c[openEHR-EHR-COMPOSITION.health_summary.v1] where NOT EXISTS c/content[openEHR-EHR-ADMIN_ENTRY.hospitalization.v0]", + "UC24"), + UC25( + "select c/feeder_audit/originating_system_audit/system_id from EHR e " + + "contains COMPOSITION c[openEHR-EHR-COMPOSITION.health_summary.v1]", + "Select c/feeder_audit/originating_system_audit/system_id as F1 from EHR e contains COMPOSITION c[openEHR-EHR-COMPOSITION.health_summary.v1]", + "UC25"), + /* unsupported CONTAINS syntax + UC26( + "select c from EHR e contains COMPOSITION c AND NOT CONTAINS ADMIN_ENTRY u[openEHR-EHR-ADMIN_ENTRY.hospitalization.v0]", + "Select c as F1 from EHR e contains COMPOSITION c AND NOT CONTAINS ADMIN_ENTRY u[openEHR-EHR-ADMIN_ENTRY.hospitalization.v0]", + "UC26"), + */ + /* + IN not official aql feature + UC27( + "Select e/folders/name/value" + " from EHR e" + + " where e/ehr_id/value = 'c2561bab-4d2b-4ffd-a893-4382e9048f8c'" + + " and 'case1' IN(e/folders/name/value)", + "Select e/folders/name/value" + " from EHR e" + + " where e/ehr_id/value = 'c2561bab-4d2b-4ffd-a893-4382e9048f8c'" + + " and 'case1' IN(e/folders/name/value)", + "UC27"), + + */ + /* + SOME not official aql feature + UC28( + "Select e/folders/name/value\n" + " from EHR e\n" + + " where e/ehr_id/value = 'c2561bab-4d2b-4ffd-a893-4382e9048f8c'\n" + + " and 'case1' = SOME(e/folders/name/value)", + "Select e/folders/name/value\n" + " from EHR e\n" + + " where e/ehr_id/value = 'c2561bab-4d2b-4ffd-a893-4382e9048f8c'\n" + + " and 'case1' = SOME(e/folders/name/value)", + "UC28"), + */ + /* any not official aql feature + UC29( + "Select e/folders/name/value\n" + " from EHR e\n" + + " where e/ehr_id/value = 'c2561bab-4d2b-4ffd-a893-4382e9048f8c'\n" + + " and 'case1' = ANY(e/folders/name/value)", + "Select e/folders/name/value\n" + " from EHR e\n" + + " where e/ehr_id/value = 'c2561bab-4d2b-4ffd-a893-4382e9048f8c'\n" + + " and 'case1' = ANY(e/folders/name/value)", + "UC29"), + */ + /* ALL not official aql feature + UC30( + "Select e/folders/name/value\n" + " from EHR e\n" + + " where e/ehr_id/value = 'c2561bab-4d2b-4ffd-a893-4382e9048f8c'\n" + + " and 'case1' = ALL(e/folders/name/value)", + "Select e/folders/name/value\n" + " from EHR e\n" + + " where e/ehr_id/value = 'c2561bab-4d2b-4ffd-a893-4382e9048f8c'\n" + + " and 'case1' = ALL(e/folders/name/value)", + "UC30") + */ + /* ALL not official aql feature + UC31( + "Select e/folders/name/value\n" + " from EHR e\n" + + " where 'case1' = ALL(e/folders/name/value)" + + " and e/ehr_id/value = 'c2561bab-4d2b-4ffd-a893-4382e9048f8c'", + "Select e/folders/name/value\n" + " from EHR e\n" + + " where 'case1' = ALL(e/folders/name/value)" + + " and e/ehr_id/value = 'c2561bab-4d2b-4ffd-a893-4382e9048f8c'", + "UC31"), + */ + /* + UC32( + "Select e/folders/name/value\n" + " from EHR e\n" + + " where 'case1' IN (regexp_split_to_array('case1,case2', ','))" + + " and e/ehr_id/value = 'c2561bab-4d2b-4ffd-a893-4382e9048f8c'", + "Select e/folders/name/value\n" + " from EHR e\n" + + " where 'case1' IN (regexp_split_to_array('case1,case2', ','))" + + " and e/ehr_id/value = 'c2561bab-4d2b-4ffd-a893-4382e9048f8c'", + "UC32"), + + */ + /* + UC33( + "Select e/folders/name/value\n" + " from EHR e\n" + + " where 'case1' IN ('case1','case2')" + + " and e/ehr_id/value = 'c2561bab-4d2b-4ffd-a893-4382e9048f8c'", + "Select e/folders/name/value\n" + " from EHR e\n" + + " where 'case1' IN ('case1','case2')" + + " and e/ehr_id/value = 'c2561bab-4d2b-4ffd-a893-4382e9048f8c'", + "UC33"), + */ + UC34("SELECT min(e/time_created) FROM EHR e", "Select MAX(e/time_created) as F1 from EHR e", "UC34"), + UC35("SELECT e/directory FROM EHR e", "Select e/directory as F1 from EHR e", "UC35"), + UC36("SELECT e FROM EHR e", "Select e as F1 from EHR e", "UC36"), + UC37( + "select" + " avg (d/description[at0001]/items[at0004]/value/magnitude) as avg_magnitude" + + " from EHR e" + + " contains COMPOSITION" + + " contains ACTION d[openEHR-EHR-ACTION.immunisation_procedure.v1]", + "Select AVG(d/description[at0001]/items[at0004]/value/magnitude) as avg_magnitude from EHR e contains COMPOSITION c0 contains ACTION d[openEHR-EHR-ACTION.immunisation_procedure.v1]", + "UC37"), + UC38( + "select" + " min (d/description[at0001]/items[at0004]/value/magnitude) as min_magnitude" + + " from EHR e" + + " contains COMPOSITION" + + " contains ACTION d[openEHR-EHR-ACTION.immunisation_procedure.v1]", + "Select MAX(d/description[at0001]/items[at0004]/value/magnitude) as min_magnitude from EHR e contains COMPOSITION c0 contains ACTION d[openEHR-EHR-ACTION.immunisation_procedure.v1]", + "UC38"), + UC39( + "select" + " max (d/description[at0001]/items[at0004]/value/magnitude) as max_magnitude" + + " from EHR e" + + " contains COMPOSITION" + + " contains ACTION d[openEHR-EHR-ACTION.immunisation_procedure.v1]", + "Select MAX(d/description[at0001]/items[at0004]/value/magnitude) as max_magnitude from EHR e contains COMPOSITION c0 contains ACTION d[openEHR-EHR-ACTION.immunisation_procedure.v1]", + "UC39"), + /* + UC40("select" + " CAST (d/description[at0001]/items[at0004]/value/magnitude AS 'FLOAT') as max_magnitude" + + " from EHR e" + + " contains COMPOSITION" + + " contains ACTION d[openEHR-EHR-ACTION.immunisation_procedure.v1]", "", "UC40"), + + */ + /* + UC41("SELECT TRUE as constant FROM EHR e", "", "UC41"), + + */ + /* + UC42("SELECT\n" + " \t c[name/value = 'Diagnose']/uid/value as Diagnose,\n" + + " \t c[composer/external_ref/id/value = 'Dr Mabuse']/uid/value as MabuseComposition,\n" + + " \t c[context/start_time/value > '2020-01-01']/uid/value as NewerComposition\n" + + "\tFROM\n" + + " \t EHR e\n" + + " \t contains COMPOSITION c[openEHR-EHR-COMPOSITION.report-result.v1]", + "Select c['Diagnose']/uid/value as Diagnose, c/[composer/external_ref/id/value='Dr Mabuse']/uid/value as MabuseComposition," + + " c[context/start_time/value>'2020-01-01']/uid/value as NewerComposition " + + "from EHR e " + + "contains COMPOSITION c[openEHR-EHR-COMPOSITION.report-result.v1]", "UC42"), + UC43("select c[name/value = 'Laborbefund-1']/uid/value as uid1,\n" + + "\t\t\t c[name/value = 'Laborbefund-2']/uid/value as uid2\n" + + "\t\t\t\tfrom EHR e \n" + + "\t\t\t\tcontains COMPOSITION c[openEHR-EHR-COMPOSITION.report-result.v1]", "", "UC43"), + + */ + UC46( + "select DISTINCT " + + " a/description[at0001]/items[at0002]/value/value as description," + + " a/time as timing" + + " from EHR e " + + "contains COMPOSITION c[openEHR-EHR-COMPOSITION.health_summary.v1] " + + "contains ACTION a[openEHR-EHR-ACTION.immunisation_procedure.v1]" + + "order by description ASC", + "Select DISTINCT a/description[at0001]/items[at0002]/value/value as description, a/time as timing from EHR e contains COMPOSITION c[openEHR-EHR-COMPOSITION.health_summary.v1] contains ACTION a[openEHR-EHR-ACTION.immunisation_procedure.v1] order by a/description[at0001]/items[at0002]/value/value ASCENDING", + "UC46"), + ; + + private final String testAql; + private final String expectedAql; + + UCTestData(String testAql, String expectedAql, String description) { + this.testAql = testAql; + this.expectedAql = expectedAql; + } + + @Override + public String getTestAql() { + return testAql; + } + + @Override + public String getExpectedAql() { + return expectedAql; + } +} diff --git a/web-template/src/test/java/org/ehrbase/webtemplate/util/CharSequenceHelperTest.java b/aql/src/test/java/org/ehrbase/aql/util/CharSequenceHelperTest.java similarity index 98% rename from web-template/src/test/java/org/ehrbase/webtemplate/util/CharSequenceHelperTest.java rename to aql/src/test/java/org/ehrbase/aql/util/CharSequenceHelperTest.java index c8050774b..b08812b67 100644 --- a/web-template/src/test/java/org/ehrbase/webtemplate/util/CharSequenceHelperTest.java +++ b/aql/src/test/java/org/ehrbase/aql/util/CharSequenceHelperTest.java @@ -15,7 +15,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.ehrbase.webtemplate.util; +package org.ehrbase.aql.util; import static org.junit.Assert.*; diff --git a/client/src/main/java/org/ehrbase/client/flattener/DtoFromCompositionWalker.java b/client/src/main/java/org/ehrbase/client/flattener/DtoFromCompositionWalker.java index d3dcd832b..3906b2170 100644 --- a/client/src/main/java/org/ehrbase/client/flattener/DtoFromCompositionWalker.java +++ b/client/src/main/java/org/ehrbase/client/flattener/DtoFromCompositionWalker.java @@ -32,6 +32,7 @@ import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.text.CaseUtils; +import org.ehrbase.aql.dto.path.AqlPath; import org.ehrbase.client.annotations.Entity; import org.ehrbase.client.annotations.OptionFor; import org.ehrbase.client.annotations.Path; @@ -43,7 +44,6 @@ import org.ehrbase.util.exception.SdkException; import org.ehrbase.util.reflection.ReflectionHelper; import org.ehrbase.webtemplate.model.WebTemplateNode; -import org.ehrbase.webtemplate.parser.AqlPath; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/client/src/main/java/org/ehrbase/client/flattener/DtoToCompositionWalker.java b/client/src/main/java/org/ehrbase/client/flattener/DtoToCompositionWalker.java index faf48fd47..19493e105 100644 --- a/client/src/main/java/org/ehrbase/client/flattener/DtoToCompositionWalker.java +++ b/client/src/main/java/org/ehrbase/client/flattener/DtoToCompositionWalker.java @@ -36,6 +36,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.commons.lang3.tuple.ImmutablePair; +import org.ehrbase.aql.dto.path.AqlPath; import org.ehrbase.client.annotations.Entity; import org.ehrbase.client.annotations.OptionFor; import org.ehrbase.client.annotations.Path; @@ -44,7 +45,6 @@ import org.ehrbase.serialisation.walker.Context; import org.ehrbase.serialisation.walker.ToCompositionWalker; import org.ehrbase.webtemplate.model.WebTemplateNode; -import org.ehrbase.webtemplate.parser.AqlPath; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/client/src/main/java/org/ehrbase/client/flattener/DtoWithMatchingFields.java b/client/src/main/java/org/ehrbase/client/flattener/DtoWithMatchingFields.java index 277dd34ad..7ede44a68 100644 --- a/client/src/main/java/org/ehrbase/client/flattener/DtoWithMatchingFields.java +++ b/client/src/main/java/org/ehrbase/client/flattener/DtoWithMatchingFields.java @@ -19,7 +19,7 @@ import java.lang.reflect.Field; import java.util.Map; -import org.ehrbase.webtemplate.parser.AqlPath; +import org.ehrbase.aql.dto.path.AqlPath; class DtoWithMatchingFields { diff --git a/client/src/main/java/org/ehrbase/client/flattener/PathMatcher.java b/client/src/main/java/org/ehrbase/client/flattener/PathMatcher.java index 323ef98d8..8ad281091 100644 --- a/client/src/main/java/org/ehrbase/client/flattener/PathMatcher.java +++ b/client/src/main/java/org/ehrbase/client/flattener/PathMatcher.java @@ -19,9 +19,9 @@ import java.util.Map; import java.util.Objects; +import org.ehrbase.aql.dto.path.AqlPath; import org.ehrbase.serialisation.walker.Context; import org.ehrbase.webtemplate.model.WebTemplateNode; -import org.ehrbase.webtemplate.parser.AqlPath; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/generator/src/main/java/org/ehrbase/client/classgenerator/DefaultNamingStrategy.java b/generator/src/main/java/org/ehrbase/client/classgenerator/DefaultNamingStrategy.java index d192379f2..321a3bfb0 100644 --- a/generator/src/main/java/org/ehrbase/client/classgenerator/DefaultNamingStrategy.java +++ b/generator/src/main/java/org/ehrbase/client/classgenerator/DefaultNamingStrategy.java @@ -24,10 +24,10 @@ import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.text.CaseUtils; +import org.ehrbase.aql.dto.path.AqlPath; import org.ehrbase.serialisation.util.SnakeCase; import org.ehrbase.webtemplate.model.WebTemplateAnnotation; import org.ehrbase.webtemplate.model.WebTemplateNode; -import org.ehrbase.webtemplate.parser.AqlPath; public class DefaultNamingStrategy implements NamingStrategy { diff --git a/generator/src/main/java/org/ehrbase/client/classgenerator/FieldGenerator.java b/generator/src/main/java/org/ehrbase/client/classgenerator/FieldGenerator.java index 4214cb1e3..0699b069c 100644 --- a/generator/src/main/java/org/ehrbase/client/classgenerator/FieldGenerator.java +++ b/generator/src/main/java/org/ehrbase/client/classgenerator/FieldGenerator.java @@ -21,6 +21,7 @@ import java.util.List; import javax.lang.model.element.Modifier; import org.apache.commons.lang3.StringUtils; +import org.ehrbase.aql.dto.path.AqlPath; import org.ehrbase.client.annotations.Archetype; import org.ehrbase.client.annotations.Path; import org.ehrbase.client.aql.containment.Containment; @@ -29,7 +30,6 @@ import org.ehrbase.client.aql.field.ListSelectAqlField; import org.ehrbase.client.aql.field.SelectAqlField; import org.ehrbase.serialisation.util.SnakeCase; -import org.ehrbase.webtemplate.parser.AqlPath; public class FieldGenerator { diff --git a/serialisation/src/main/java/org/ehrbase/serialisation/dbencoding/wrappers/json/writer/translator_db2raw/LinkedTreeMapAdapter.java b/serialisation/src/main/java/org/ehrbase/serialisation/dbencoding/wrappers/json/writer/translator_db2raw/LinkedTreeMapAdapter.java index 8fa749793..449795536 100644 --- a/serialisation/src/main/java/org/ehrbase/serialisation/dbencoding/wrappers/json/writer/translator_db2raw/LinkedTreeMapAdapter.java +++ b/serialisation/src/main/java/org/ehrbase/serialisation/dbencoding/wrappers/json/writer/translator_db2raw/LinkedTreeMapAdapter.java @@ -28,10 +28,10 @@ import com.nedap.archie.rminfo.ArchieRMInfoLookup; import java.io.IOException; import java.util.*; +import org.ehrbase.aql.dto.path.AqlPath; import org.ehrbase.serialisation.dbencoding.CompositionSerializer; import org.ehrbase.serialisation.dbencoding.wrappers.json.I_DvTypeAdapter; import org.ehrbase.serialisation.util.SnakeCase; -import org.ehrbase.webtemplate.parser.AqlPath; /** * GSON adapter for LinkedTreeMap diff --git a/serialisation/src/main/java/org/ehrbase/serialisation/walker/ItemExtractor.java b/serialisation/src/main/java/org/ehrbase/serialisation/walker/ItemExtractor.java index 2b7b1f7dd..30cf396c1 100644 --- a/serialisation/src/main/java/org/ehrbase/serialisation/walker/ItemExtractor.java +++ b/serialisation/src/main/java/org/ehrbase/serialisation/walker/ItemExtractor.java @@ -25,10 +25,10 @@ import java.util.List; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; +import org.ehrbase.aql.dto.path.AqlPath; import org.ehrbase.util.exception.SdkException; import org.ehrbase.util.rmconstants.RmConstants; import org.ehrbase.webtemplate.model.WebTemplateNode; -import org.ehrbase.webtemplate.parser.AqlPath; public class ItemExtractor { private RMObject currentRM; diff --git a/web-template/pom.xml b/web-template/pom.xml index a5163c857..76a9f082f 100644 --- a/web-template/pom.xml +++ b/web-template/pom.xml @@ -39,6 +39,10 @@ org.ehrbase.openehr.sdk opt-1.4 + + org.ehrbase.openehr.sdk + aql + org.ehrbase.openehr.sdk util diff --git a/web-template/src/main/java/org/ehrbase/webtemplate/model/AqlPathSerializer.java b/web-template/src/main/java/org/ehrbase/webtemplate/model/AqlPathSerializer.java index 14e759c82..f5ddb8132 100644 --- a/web-template/src/main/java/org/ehrbase/webtemplate/model/AqlPathSerializer.java +++ b/web-template/src/main/java/org/ehrbase/webtemplate/model/AqlPathSerializer.java @@ -21,7 +21,7 @@ import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.ser.std.StdSerializer; import java.io.IOException; -import org.ehrbase.webtemplate.parser.AqlPath; +import org.ehrbase.aql.dto.path.AqlPath; public class AqlPathSerializer extends StdSerializer { diff --git a/web-template/src/main/java/org/ehrbase/webtemplate/model/WebTemplate.java b/web-template/src/main/java/org/ehrbase/webtemplate/model/WebTemplate.java index d1d54630c..c9d684422 100644 --- a/web-template/src/main/java/org/ehrbase/webtemplate/model/WebTemplate.java +++ b/web-template/src/main/java/org/ehrbase/webtemplate/model/WebTemplate.java @@ -21,7 +21,7 @@ import java.io.Serializable; import java.util.*; import java.util.stream.Collectors; -import org.ehrbase.webtemplate.parser.AqlPath; +import org.ehrbase.aql.dto.path.AqlPath; import org.ehrbase.webtemplate.parser.NodeId; @JsonInclude(JsonInclude.Include.NON_NULL) diff --git a/web-template/src/main/java/org/ehrbase/webtemplate/model/WebTemplateNode.java b/web-template/src/main/java/org/ehrbase/webtemplate/model/WebTemplateNode.java index fc9788a49..fc9255a9a 100644 --- a/web-template/src/main/java/org/ehrbase/webtemplate/model/WebTemplateNode.java +++ b/web-template/src/main/java/org/ehrbase/webtemplate/model/WebTemplateNode.java @@ -33,7 +33,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import org.apache.commons.lang3.StringUtils; -import org.ehrbase.webtemplate.parser.AqlPath; +import org.ehrbase.aql.dto.path.AqlPath; @JsonInclude(JsonInclude.Include.NON_EMPTY) public class WebTemplateNode implements Serializable { diff --git a/web-template/src/main/java/org/ehrbase/webtemplate/parser/OPTParser.java b/web-template/src/main/java/org/ehrbase/webtemplate/parser/OPTParser.java index e9d847d55..7deda9967 100644 --- a/web-template/src/main/java/org/ehrbase/webtemplate/parser/OPTParser.java +++ b/web-template/src/main/java/org/ehrbase/webtemplate/parser/OPTParser.java @@ -51,6 +51,7 @@ import org.apache.xmlbeans.XmlCursor; import org.apache.xmlbeans.XmlObject; import org.apache.xmlbeans.XmlTokenSource; +import org.ehrbase.aql.dto.path.AqlPath; import org.ehrbase.terminology.client.terminology.TermDefinition; import org.ehrbase.terminology.client.terminology.TerminologyProvider; import org.ehrbase.terminology.client.terminology.ValueSet; diff --git a/web-template/src/main/java/org/ehrbase/webtemplate/path/flat/FlatPathParser.java b/web-template/src/main/java/org/ehrbase/webtemplate/path/flat/FlatPathParser.java index 32859adf9..3100aa632 100644 --- a/web-template/src/main/java/org/ehrbase/webtemplate/path/flat/FlatPathParser.java +++ b/web-template/src/main/java/org/ehrbase/webtemplate/path/flat/FlatPathParser.java @@ -17,8 +17,8 @@ */ package org.ehrbase.webtemplate.path.flat; -import static org.ehrbase.webtemplate.util.CharSequenceHelper.removeStart; -import static org.ehrbase.webtemplate.util.CharSequenceHelper.splitFirst; +import static org.ehrbase.aql.util.CharSequenceHelper.removeStart; +import static org.ehrbase.aql.util.CharSequenceHelper.splitFirst; import org.apache.commons.lang3.StringUtils;