2024 09 gg vs sql (#1738)
* Fix bug processing value set includes / excludes that are just value sets (no system value) * fix value set rendering creating wrong references * Update SQL-On-FHIR implementation for latest cases, and clone test cases to general test care repository * Fix expression for con-3 properly (fix validation problem on some condition resources) * FHIRPath: Allow _ in constant names (per FHIRPath spec) * Fix FHIRPath bug using wrong type on simple elements when checking FHIRPath types * release notes * fix sql-on-fhir tests --------- Co-authored-by: Grahame Grieve <grahameg@gmail.ccom>
This commit is contained in:
parent
0c457a14e4
commit
ffe0ab6414
|
@ -1,7 +1,18 @@
|
|||
## Validator Changes
|
||||
|
||||
* no changes
|
||||
* Fix expression for con-3 properly (fix validation problem on some condition resources)
|
||||
* Fix FHIRPath bug using wrong type on simple elements when checking FHIRPath types
|
||||
* FHIRPath: Allow _ in constant names (per FHIRPath spec)
|
||||
* Fix value set rendering creating wrong references
|
||||
* Fix bug processing value set includes / excludes that are just value sets (no system value)
|
||||
* Alter processing of unknown code systems per discussion at ,https://chat.fhir.org/#narrow/stream/179252-IG-creation/topic/Don't.20error.20when.20you.20can't.20find.20code.20system and implement unknown-codesystems-cause-errors
|
||||
* Improve message for when elements are out of order in profile differentials
|
||||
|
||||
|
||||
## Other code changes
|
||||
|
||||
* no changes
|
||||
* fix problem where profile rendering had spurious 'slices for' nodes everywhere
|
||||
* Update SQL-On-FHIR implementation for latest cases, and clone test cases to general test care repository
|
||||
* Fix problem generating value set spreadsheets
|
||||
* fix concurrent modification error processing language translations
|
||||
* Check for null fetcher processing ConceptMaps (#1728)
|
||||
|
|
|
@ -934,10 +934,14 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
|
|||
throw new Error(formatMessage(I18nConstants.NO_VALUE_SET_IN_URL));
|
||||
}
|
||||
for (ConceptSetComponent inc : vs.getCompose().getInclude()) {
|
||||
codeSystemsUsed.add(inc.getSystem());
|
||||
if (inc.hasSystem()) {
|
||||
codeSystemsUsed.add(inc.getSystem());
|
||||
}
|
||||
}
|
||||
for (ConceptSetComponent inc : vs.getCompose().getExclude()) {
|
||||
codeSystemsUsed.add(inc.getSystem());
|
||||
if (inc.hasSystem()) {
|
||||
codeSystemsUsed.add(inc.getSystem());
|
||||
}
|
||||
}
|
||||
|
||||
CacheToken cacheToken = txCache.generateExpandToken(vs, hierarchical);
|
||||
|
|
|
@ -187,7 +187,7 @@ public class FHIRLexer {
|
|||
cursor++;
|
||||
} else
|
||||
while (cursor < source.length() && ((source.charAt(cursor) >= 'A' && source.charAt(cursor) <= 'Z') || (source.charAt(cursor) >= 'a' && source.charAt(cursor) <= 'z') ||
|
||||
(source.charAt(cursor) >= '0' && source.charAt(cursor) <= '9') || source.charAt(cursor) == ':' || source.charAt(cursor) == '-'))
|
||||
(source.charAt(cursor) >= '0' && source.charAt(cursor) <= '9') || source.charAt(cursor) == ':' || source.charAt(cursor) == '-' || source.charAt(cursor) == '_'))
|
||||
cursor++;
|
||||
current = source.substring(currentStart, cursor);
|
||||
} else if (ch == '/') {
|
||||
|
|
|
@ -773,6 +773,25 @@ public class FHIRPathEngine {
|
|||
return execute(new ExecutionContext(null, base != null && base.isResource() ? base : null, base != null && base.isResource() ? base : null, base, base), list, ExpressionNode, true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* evaluate a path and return the matching elements
|
||||
*
|
||||
* @param base - the object against which the path is being evaluated
|
||||
* @param ExpressionNode - the parsed ExpressionNode statement to use
|
||||
* @return
|
||||
* @throws FHIRException
|
||||
* @
|
||||
*/
|
||||
public List<Base> evaluate(Object appContext, Base base, ExpressionNode ExpressionNode) throws FHIRException {
|
||||
List<Base> list = new ArrayList<Base>();
|
||||
if (base != null) {
|
||||
list.add(base);
|
||||
}
|
||||
log = new StringBuilder();
|
||||
return execute(new ExecutionContext(appContext, base != null && base.isResource() ? base : null, base != null && base.isResource() ? base : null, base, base), list, ExpressionNode, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* evaluate a path and return the matching elements
|
||||
*
|
||||
|
@ -3741,6 +3760,8 @@ public class FHIRPathEngine {
|
|||
}
|
||||
if ((focus.hasType("date") || focus.hasType("datetime") || focus.hasType("instant"))) {
|
||||
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal, TypeDetails.FP_DateTime);
|
||||
} else if ((focus.hasType("time"))) {
|
||||
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Time, TypeDetails.FP_Time);
|
||||
} else if (focus.hasType("decimal") || focus.hasType("integer")) {
|
||||
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal);
|
||||
} else {
|
||||
|
@ -6351,7 +6372,7 @@ public class FHIRPathEngine {
|
|||
}
|
||||
result.addTypes(worker.getResourceNames());
|
||||
} else {
|
||||
pt = new ProfiledType(t.getCode());
|
||||
pt = new ProfiledType(t.getWorkingCode());
|
||||
}
|
||||
if (pt != null) {
|
||||
if (t.hasProfile()) {
|
||||
|
|
|
@ -754,16 +754,7 @@ public class ValueSetRenderer extends TerminologyRenderer {
|
|||
if (ref == null) {
|
||||
ref = (String) cs.getWebPath();
|
||||
}
|
||||
if (ref == null && cs.hasUserData("webroot")) {
|
||||
ref = (String) cs.getUserData("webroot");
|
||||
}
|
||||
if (ref == null) {
|
||||
return "?ngen-14?.html";
|
||||
}
|
||||
if (!ref.contains(".html")) {
|
||||
ref = ref + ".html";
|
||||
}
|
||||
return ref.replace("\\", "/");
|
||||
return ref == null ? null : ref.replace("\\", "/");
|
||||
}
|
||||
|
||||
private void scanForDesignations(ValueSetExpansionContainsComponent c, List<String> langs, Map<String, String> designations) {
|
||||
|
@ -922,14 +913,18 @@ public class ValueSetRenderer extends TerminologyRenderer {
|
|||
td.addText(code);
|
||||
} else {
|
||||
String href = context.fixReference(getCsRef(e));
|
||||
if (href.contains("#"))
|
||||
href = href + "-"+Utilities.nmtokenize(code);
|
||||
else
|
||||
href = href + "#"+e.getId()+"-"+Utilities.nmtokenize(code);
|
||||
if (isAbstract)
|
||||
td.ah(context.prefixLocalHref(href)).setAttribute("title", context.formatPhrase(RenderingContext.VS_ABSTRACT_CODE_HINT)).i().addText(code);
|
||||
else
|
||||
td.ah(context.prefixLocalHref(href)).addText(code);
|
||||
if (href == null) {
|
||||
td.code().tx(code);
|
||||
} else {
|
||||
if (href.contains("#"))
|
||||
href = href + "-"+Utilities.nmtokenize(code);
|
||||
else
|
||||
href = href + "#"+e.getId()+"-"+Utilities.nmtokenize(code);
|
||||
if (isAbstract)
|
||||
td.ah(context.prefixLocalHref(href)).setAttribute("title", context.formatPhrase(RenderingContext.VS_ABSTRACT_CODE_HINT)).i().addText(code);
|
||||
else
|
||||
td.ah(context.prefixLocalHref(href)).addText(code);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1247,11 +1242,15 @@ public class ValueSetRenderer extends TerminologyRenderer {
|
|||
wli.tx(f.getProperty()+" "+describe(f.getOp())+" ");
|
||||
if (e != null && codeExistsInValueSet(e, f.getValue())) {
|
||||
String href = getContext().fixReference(getCsRef(e));
|
||||
if (href.contains("#"))
|
||||
href = href + "-"+Utilities.nmtokenize(f.getValue());
|
||||
else
|
||||
href = href + "#"+e.getId()+"-"+Utilities.nmtokenize(f.getValue());
|
||||
wli.ah(context.prefixLocalHref(href)).addText(f.getValue());
|
||||
if (href == null) {
|
||||
wli.code().tx(f.getValue());
|
||||
} else {
|
||||
if (href.contains("#"))
|
||||
href = href + "-"+Utilities.nmtokenize(f.getValue());
|
||||
else
|
||||
href = href + "#"+e.getId()+"-"+Utilities.nmtokenize(f.getValue());
|
||||
wli.ah(context.prefixLocalHref(href)).addText(f.getValue());
|
||||
}
|
||||
} else if (inc.hasSystem()) {
|
||||
wli.addText(f.getValue());
|
||||
ValidationResult vr = getContext().getWorker().validateCode(getContext().getTerminologyServiceOptions(), inc.getSystem(), inc.getVersion(), f.getValue(), null);
|
||||
|
|
|
@ -554,6 +554,9 @@ public class RenderingContext extends RenderingI18nContext {
|
|||
}
|
||||
|
||||
public String fixReference(String ref) {
|
||||
if (ref == null) {
|
||||
return null;
|
||||
}
|
||||
if (!Utilities.isAbsoluteUrl(ref)) {
|
||||
return (localPrefix == null ? "" : localPrefix)+ref;
|
||||
}
|
||||
|
|
|
@ -606,33 +606,35 @@ public class ValueSetExpander extends ValueSetProcessBase {
|
|||
excludeCodes(wc, importValueSetForExclude(wc, imp.getValue(), exp, expParams, false, vs).getExpansion());
|
||||
}
|
||||
|
||||
CodeSystem cs = context.fetchSupplementedCodeSystem(exc.getSystem());
|
||||
if ((cs == null || cs.getContent() != CodeSystemContentMode.COMPLETE) && context.supportsSystem(exc.getSystem(), opContext.getOptions().getFhirVersion())) {
|
||||
ValueSetExpansionOutcome vse = context.expandVS(exc, false, false);
|
||||
ValueSet valueset = vse.getValueset();
|
||||
if (valueset == null)
|
||||
throw failTSE("Error Expanding ValueSet: "+vse.getError());
|
||||
excludeCodes(wc, valueset.getExpansion());
|
||||
return;
|
||||
}
|
||||
|
||||
for (ConceptReferenceComponent c : exc.getConcept()) {
|
||||
excludeCode(wc, exc.getSystem(), c.getCode());
|
||||
}
|
||||
|
||||
if (exc.getFilter().size() > 0) {
|
||||
if (cs.getContent() == CodeSystemContentMode.FRAGMENT) {
|
||||
addFragmentWarning(exp, cs);
|
||||
if (exc.hasSystem()) {
|
||||
CodeSystem cs = context.fetchSupplementedCodeSystem(exc.getSystem());
|
||||
if ((cs == null || cs.getContent() != CodeSystemContentMode.COMPLETE) && context.supportsSystem(exc.getSystem(), opContext.getOptions().getFhirVersion())) {
|
||||
ValueSetExpansionOutcome vse = context.expandVS(exc, false, false);
|
||||
ValueSet valueset = vse.getValueset();
|
||||
if (valueset == null)
|
||||
throw failTSE("Error Expanding ValueSet: "+vse.getError());
|
||||
excludeCodes(wc, valueset.getExpansion());
|
||||
return;
|
||||
}
|
||||
List<WorkingContext> filters = new ArrayList<>();
|
||||
for (int i = 1; i < exc.getFilter().size(); i++) {
|
||||
WorkingContext wc1 = new WorkingContext();
|
||||
filters.add(wc1);
|
||||
processFilter(exc, exp, expParams, null, cs, false, exc.getFilter().get(i), wc1, null, true);
|
||||
|
||||
for (ConceptReferenceComponent c : exc.getConcept()) {
|
||||
excludeCode(wc, exc.getSystem(), c.getCode());
|
||||
}
|
||||
|
||||
if (exc.getFilter().size() > 0) {
|
||||
if (cs.getContent() == CodeSystemContentMode.FRAGMENT) {
|
||||
addFragmentWarning(exp, cs);
|
||||
}
|
||||
List<WorkingContext> filters = new ArrayList<>();
|
||||
for (int i = 1; i < exc.getFilter().size(); i++) {
|
||||
WorkingContext wc1 = new WorkingContext();
|
||||
filters.add(wc1);
|
||||
processFilter(exc, exp, expParams, null, cs, false, exc.getFilter().get(i), wc1, null, true);
|
||||
}
|
||||
ConceptSetFilterComponent fc = exc.getFilter().get(0);
|
||||
WorkingContext wc1 = dwc;
|
||||
processFilter(exc, exp, expParams, null, cs, false, fc, wc1, filters, true);
|
||||
}
|
||||
ConceptSetFilterComponent fc = exc.getFilter().get(0);
|
||||
WorkingContext wc1 = dwc;
|
||||
processFilter(exc, exp, expParams, null, cs, false, fc, wc1, filters, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -728,7 +730,6 @@ public class ValueSetExpander extends ValueSetProcessBase {
|
|||
expParams = makeDefaultExpansion();
|
||||
altCodeParams.seeParameters(expParams);
|
||||
altCodeParams.seeValueSet(source);
|
||||
|
||||
source.checkNoModifiers("ValueSet", "expanding");
|
||||
focus = source.copy();
|
||||
focus.setIdBase(null);
|
||||
|
|
|
@ -103,7 +103,7 @@ public class Runner implements IEvaluationContext {
|
|||
for (JsonObject w : vd.getJsonObjects("where")) {
|
||||
String expr = w.asString("path");
|
||||
ExpressionNode node = fpe.parse(expr);
|
||||
boolean pass = fpe.evaluateToBoolean(null, b, b, b, node);
|
||||
boolean pass = fpe.evaluateToBoolean(vd, b, b, b, node);
|
||||
if (!pass) {
|
||||
ok = false;
|
||||
break;
|
||||
|
@ -114,7 +114,7 @@ public class Runner implements IEvaluationContext {
|
|||
rows.add(new ArrayList<Cell>());
|
||||
|
||||
for (JsonObject select : vd.getJsonObjects("select")) {
|
||||
executeSelect(select, b, rows);
|
||||
executeSelect(vd, select, b, rows);
|
||||
}
|
||||
for (List<Cell> row : rows) {
|
||||
storage.addRow(store, row);
|
||||
|
@ -124,14 +124,14 @@ public class Runner implements IEvaluationContext {
|
|||
storage.finish(store);
|
||||
}
|
||||
|
||||
private void executeSelect(JsonObject select, Base b, List<List<Cell>> rows) {
|
||||
private void executeSelect(JsonObject vd, JsonObject select, Base b, List<List<Cell>> rows) {
|
||||
List<Base> focus = new ArrayList<>();
|
||||
|
||||
if (select.has("forEach")) {
|
||||
focus.addAll(executeForEach(select, b));
|
||||
focus.addAll(executeForEach(vd, select, b));
|
||||
} else if (select.has("forEachOrNull")) {
|
||||
|
||||
focus.addAll(executeForEachOrNull(select, b));
|
||||
focus.addAll(executeForEachOrNull(vd, select, b));
|
||||
if (focus.isEmpty()) {
|
||||
List<Column> columns = (List<Column>) select.getUserData("columns");
|
||||
for (List<Cell> row : rows) {
|
||||
|
@ -159,20 +159,20 @@ public class Runner implements IEvaluationContext {
|
|||
List<List<Cell>> rowsToAdd = cloneRows(tempRows);
|
||||
|
||||
for (JsonObject column : select.getJsonObjects("column")) {
|
||||
executeColumn(column, f, rowsToAdd);
|
||||
executeColumn(vd, column, f, rowsToAdd);
|
||||
}
|
||||
|
||||
for (JsonObject sub : select.getJsonObjects("select")) {
|
||||
executeSelect(sub, f, rowsToAdd);
|
||||
executeSelect(vd, sub, f, rowsToAdd);
|
||||
}
|
||||
|
||||
executeUnionAll(select.getJsonObjects("unionAll"), f, rowsToAdd);
|
||||
executeUnionAll(vd, select.getJsonObjects("unionAll"), f, rowsToAdd);
|
||||
|
||||
rows.addAll(rowsToAdd);
|
||||
}
|
||||
}
|
||||
|
||||
private void executeUnionAll(List<JsonObject> unionList, Base b, List<List<Cell>> rows) {
|
||||
private void executeUnionAll(JsonObject vd, List<JsonObject> unionList, Base b, List<List<Cell>> rows) {
|
||||
if (unionList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
@ -183,7 +183,7 @@ public class Runner implements IEvaluationContext {
|
|||
for (JsonObject union : unionList) {
|
||||
List<List<Cell>> tempRows = new ArrayList<>();
|
||||
tempRows.addAll(sourceRows);
|
||||
executeSelect(union, b, tempRows);
|
||||
executeSelect(vd, union, b, tempRows);
|
||||
rows.addAll(tempRows);
|
||||
}
|
||||
}
|
||||
|
@ -204,25 +204,25 @@ public class Runner implements IEvaluationContext {
|
|||
return list;
|
||||
}
|
||||
|
||||
private List<Base> executeForEach(JsonObject focus, Base b) {
|
||||
private List<Base> executeForEach(JsonObject vd, JsonObject focus, Base b) {
|
||||
ExpressionNode n = (ExpressionNode) focus.getUserData("forEach");
|
||||
List<Base> result = new ArrayList<>();
|
||||
result.addAll(fpe.evaluate(b, n));
|
||||
result.addAll(fpe.evaluate(vd, b, n));
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<Base> executeForEachOrNull(JsonObject focus, Base b) {
|
||||
private List<Base> executeForEachOrNull(JsonObject vd, JsonObject focus, Base b) {
|
||||
ExpressionNode n = (ExpressionNode) focus.getUserData("forEachOrNull");
|
||||
List<Base> result = new ArrayList<>();
|
||||
result.addAll(fpe.evaluate(b, n));
|
||||
result.addAll(fpe.evaluate(vd, b, n));
|
||||
return result;
|
||||
}
|
||||
|
||||
private void executeColumn(JsonObject column, Base b, List<List<Cell>> rows) {
|
||||
private void executeColumn(JsonObject vd, JsonObject column, Base b, List<List<Cell>> rows) {
|
||||
ExpressionNode n = (ExpressionNode) column.getUserData("path");
|
||||
List<Base> bl2 = new ArrayList<>();
|
||||
if (b != null) {
|
||||
bl2.addAll(fpe.evaluate(b, n));
|
||||
bl2.addAll(fpe.evaluate(vd, b, n));
|
||||
}
|
||||
Column col = (Column) column.getUserData("column");
|
||||
if (col == null) {
|
||||
|
@ -344,14 +344,43 @@ public class Runner implements IEvaluationContext {
|
|||
|
||||
@Override
|
||||
public List<Base> resolveConstant(FHIRPathEngine engine, Object appContext, String name, boolean beforeContext, boolean explicitConstant) throws PathEngineException {
|
||||
throw new Error("Not implemented yet: resolveConstant");
|
||||
List<Base> list = new ArrayList<Base>();
|
||||
if (explicitConstant) {
|
||||
JsonObject vd = (JsonObject) appContext;
|
||||
JsonObject constant = findConstant(vd, name);
|
||||
if (constant != null) {
|
||||
Base b = (Base) constant.getUserData("value");
|
||||
if (b != null) {
|
||||
list.add(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeDetails resolveConstantType(FHIRPathEngine engine, Object appContext, String name, boolean explicitConstant) throws PathEngineException {
|
||||
throw new Error("Not implemented yet: resolveConstantType");
|
||||
if (explicitConstant) {
|
||||
JsonObject vd = (JsonObject) appContext;
|
||||
JsonObject constant = findConstant(vd, name.substring(1));
|
||||
if (constant != null) {
|
||||
Base b = (Base) constant.getUserData("value");
|
||||
if (b != null) {
|
||||
return new TypeDetails(CollectionStatus.SINGLETON, b.fhirType());
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private JsonObject findConstant(JsonObject vd, String name) {
|
||||
for (JsonObject o : vd.getJsonObjects("constant")) {
|
||||
if (name.equals(o.asString("name"))) {
|
||||
return o;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@Override
|
||||
public boolean log(String argument, List<Base> focus) {
|
||||
throw new Error("Not implemented yet: log");
|
||||
|
|
|
@ -2,6 +2,7 @@ package org.hl7.fhir.r5.utils.sql;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.r5.model.Base;
|
||||
import org.hl7.fhir.utilities.json.model.JsonArray;
|
||||
import org.hl7.fhir.utilities.json.model.JsonBoolean;
|
||||
|
@ -33,16 +34,16 @@ public class StorageJson implements Storage {
|
|||
JsonObject row = new JsonObject();
|
||||
rows.add(row);
|
||||
for (Cell cell : cells) {
|
||||
if (cell.getValues().size() == 0) {
|
||||
row.add(cell.getColumn().getName(), new JsonNull());
|
||||
} else if (cell.getValues().size() == 1) {
|
||||
row.add(cell.getColumn().getName(), makeJsonNode(cell.getValues().get(0)));
|
||||
} else {
|
||||
if (cell.getColumn().isColl() || cell.getValues().size() > 1) {
|
||||
JsonArray arr = new JsonArray();
|
||||
row.add(cell.getColumn().getName(), arr);
|
||||
row.add(cell.getColumn().getName(), arr);
|
||||
for (Value value : cell.getValues()) {
|
||||
arr.add(makeJsonNode(value));
|
||||
}
|
||||
}
|
||||
} else if (cell.getValues().size() == 0) {
|
||||
row.add(cell.getColumn().getName(), new JsonNull());
|
||||
} else {
|
||||
row.add(cell.getColumn().getName(), makeJsonNode(cell.getValues().get(0)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -87,7 +88,7 @@ public class StorageJson implements Storage {
|
|||
|
||||
@Override
|
||||
public String getKeyForSourceResource(Base res) {
|
||||
return res.getIdBase();
|
||||
return res.fhirType()+"/"+res.getIdBase();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -11,6 +11,27 @@ import org.hl7.fhir.r5.context.IWorkerContext;
|
|||
import org.hl7.fhir.r5.fhirpath.ExpressionNode;
|
||||
import org.hl7.fhir.r5.fhirpath.FHIRPathEngine;
|
||||
import org.hl7.fhir.r5.fhirpath.TypeDetails;
|
||||
import org.hl7.fhir.r5.formats.JsonParser;
|
||||
import org.hl7.fhir.r5.model.Base64BinaryType;
|
||||
import org.hl7.fhir.r5.model.BooleanType;
|
||||
import org.hl7.fhir.r5.model.CanonicalType;
|
||||
import org.hl7.fhir.r5.model.CodeType;
|
||||
import org.hl7.fhir.r5.model.DateTimeType;
|
||||
import org.hl7.fhir.r5.model.DateType;
|
||||
import org.hl7.fhir.r5.model.DecimalType;
|
||||
import org.hl7.fhir.r5.model.IdType;
|
||||
import org.hl7.fhir.r5.model.InstantType;
|
||||
import org.hl7.fhir.r5.model.Integer64Type;
|
||||
import org.hl7.fhir.r5.model.IntegerType;
|
||||
import org.hl7.fhir.r5.model.OidType;
|
||||
import org.hl7.fhir.r5.model.PositiveIntType;
|
||||
import org.hl7.fhir.r5.model.PrimitiveType;
|
||||
import org.hl7.fhir.r5.model.StringType;
|
||||
import org.hl7.fhir.r5.model.TimeType;
|
||||
import org.hl7.fhir.r5.model.UnsignedIntType;
|
||||
import org.hl7.fhir.r5.model.UriType;
|
||||
import org.hl7.fhir.r5.model.UrlType;
|
||||
import org.hl7.fhir.r5.model.UuidType;
|
||||
import org.hl7.fhir.r5.fhirpath.ExpressionNode.CollectionStatus;
|
||||
import org.hl7.fhir.r5.fhirpath.FHIRPathEngine.IssueMessage;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
|
@ -99,7 +120,7 @@ public class Validator {
|
|||
i = 0;
|
||||
if (checkAllObjects(path, viewDefinition, "where")) {
|
||||
for (JsonObject where : viewDefinition.getJsonObjects("where")) {
|
||||
checkWhere(path+".where["+i+"]", where);
|
||||
checkWhere(viewDefinition, path+".where["+i+"]", where);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
@ -108,7 +129,7 @@ public class Validator {
|
|||
i = 0;
|
||||
if (checkAllObjects(path, viewDefinition, "select")) {
|
||||
for (JsonObject select : viewDefinition.getJsonObjects("select")) {
|
||||
columns.addAll(checkSelect(path+".select["+i+"]", select, t));
|
||||
columns.addAll(checkSelect(viewDefinition, path+".select["+i+"]", select, t));
|
||||
i++;
|
||||
}
|
||||
if (i == 0) {
|
||||
|
@ -119,15 +140,15 @@ public class Validator {
|
|||
}
|
||||
}
|
||||
|
||||
private List<Column> checkSelect(String path, JsonObject select, TypeDetails t) {
|
||||
private List<Column> checkSelect(JsonObject vd, String path, JsonObject select, TypeDetails t) {
|
||||
List<Column> columns = new ArrayList<>();
|
||||
select.setUserData("columns", columns);
|
||||
checkProperties(select, path, "column", "select", "forEach", "forEachOrNull", "unionAll");
|
||||
|
||||
if (select.has("forEach")) {
|
||||
t = checkForEach(path, select, select.get("forEach"), t);
|
||||
t = checkForEach(vd, path, select, select.get("forEach"), t);
|
||||
} else if (select.has("forEachOrNull")) {
|
||||
t = checkForEachOrNull(path, select, select.get("forEachOrNull"), t);
|
||||
t = checkForEachOrNull(vd, path, select, select.get("forEachOrNull"), t);
|
||||
}
|
||||
|
||||
if (t != null) {
|
||||
|
@ -142,7 +163,7 @@ public class Validator {
|
|||
if (!(e instanceof JsonObject)) {
|
||||
error(path+".column["+i+"]", a, "column["+i+"] is a "+e.type().toName()+" not an object", IssueType.INVALID);
|
||||
} else {
|
||||
columns.add(checkColumn(path+".column["+i+"]", (JsonObject) e, t));
|
||||
columns.add(checkColumn(vd, path+".column["+i+"]", (JsonObject) e, t));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -158,14 +179,14 @@ public class Validator {
|
|||
if (!(e instanceof JsonObject)) {
|
||||
error(path+".select["+i+"]", e, "select["+i+"] is not an object", IssueType.INVALID);
|
||||
} else {
|
||||
columns.addAll(checkSelect(path+".select["+i+"]", (JsonObject) e, t));
|
||||
columns.addAll(checkSelect(vd, path+".select["+i+"]", (JsonObject) e, t));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (select.has("unionAll")) {
|
||||
columns.addAll(checkUnion(path, select, select.get("unionAll"), t));
|
||||
columns.addAll(checkUnion(vd, path, select, select.get("unionAll"), t));
|
||||
}
|
||||
if (columns.isEmpty()) {
|
||||
error(path, select, "The select has no columns or selects", IssueType.REQUIRED);
|
||||
|
@ -191,7 +212,7 @@ public class Validator {
|
|||
}
|
||||
}
|
||||
|
||||
private List<Column> checkUnion(String path, JsonObject focus, JsonElement expression, TypeDetails t) {
|
||||
private List<Column> checkUnion(JsonObject vd, String path, JsonObject focus, JsonElement expression, TypeDetails t) {
|
||||
JsonElement a = focus.get("unionAll");
|
||||
if (!(a instanceof JsonArray)) {
|
||||
error(path+".unionAll", a, "union is not an array", IssueType.INVALID);
|
||||
|
@ -203,7 +224,7 @@ public class Validator {
|
|||
if (!(e instanceof JsonObject)) {
|
||||
error(path+".unionAll["+i+"]", e, "unionAll["+i+"] is not an object", IssueType.INVALID);
|
||||
} else {
|
||||
unionColumns.add(checkSelect(path+".unionAll["+i+"]", (JsonObject) e, t));
|
||||
unionColumns.add(checkSelect(vd, path+".unionAll["+i+"]", (JsonObject) e, t));
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
@ -242,7 +263,7 @@ public class Validator {
|
|||
}
|
||||
}
|
||||
|
||||
private Column checkColumn(String path, JsonObject column, TypeDetails t) {
|
||||
private Column checkColumn(JsonObject vd, String path, JsonObject column, TypeDetails t) {
|
||||
checkProperties(column, path, "path", "name", "description", "collection", "type", "tag");
|
||||
|
||||
if (!column.has("path")) {
|
||||
|
@ -260,7 +281,7 @@ public class Validator {
|
|||
try {
|
||||
node = fpe.parse(expr);
|
||||
column.setUserData("path", node);
|
||||
td = fpe.checkOnTypes(null, resourceName, t, node, warnings);
|
||||
td = fpe.checkOnTypes(vd, resourceName, t, node, warnings);
|
||||
} catch (Exception e) {
|
||||
error(path, expression, e.getMessage(), IssueType.INVALID);
|
||||
}
|
||||
|
@ -296,25 +317,31 @@ public class Validator {
|
|||
// ok, name is sorted!
|
||||
if (columnName != null) {
|
||||
column.setUserData("name", columnName);
|
||||
boolean isColl = (td.getCollectionStatus() != CollectionStatus.SINGLETON);
|
||||
boolean isColl = false;
|
||||
if (column.has("collection")) {
|
||||
JsonElement collectionJ = column.get("collection");
|
||||
if (!(collectionJ instanceof JsonBoolean)) {
|
||||
error(path+".collection", collectionJ, "collection is not a boolean", IssueType.INVALID);
|
||||
} else {
|
||||
boolean collection = collectionJ.asJsonBoolean().asBoolean();
|
||||
if (!collection && isColl) {
|
||||
isColl = false;
|
||||
warning(path, column, "collection is false, but the path statement(s) might return multiple values for the column '"+columnName+"' some inputs");
|
||||
if (collection) {
|
||||
isColl = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isColl) {
|
||||
if (td.getCollectionStatus() == CollectionStatus.SINGLETON) {
|
||||
hint(path, column, "collection is true, but the path statement(s) can only return single values for the column '"+columnName+"'");
|
||||
}
|
||||
} else {
|
||||
if (arrays == null) {
|
||||
warning(path, expression, "The column '"+columnName+"' appears to be a collection based on it's path. Collections are not supported in all execution contexts");
|
||||
} else if (!arrays) {
|
||||
warning(path, expression, "The column '"+columnName+"' appears to be a collection based on it's path, but this is not allowed in the current execution context");
|
||||
}
|
||||
if (td.getCollectionStatus() != CollectionStatus.SINGLETON) {
|
||||
warning(path, column, "collection is not true, but the path statement(s) might return multiple values for the column '"+columnName+"' for some inputs");
|
||||
}
|
||||
}
|
||||
Set<String> types = new HashSet<>();
|
||||
if (node.isNullSet()) {
|
||||
|
@ -330,7 +357,7 @@ public class Validator {
|
|||
if (typeJ instanceof JsonString) {
|
||||
String type = typeJ.asString();
|
||||
if (!td.hasType(type)) {
|
||||
error(path+".type", typeJ, "The path expression does not return a value of the type '"+type, IssueType.VALUE);
|
||||
error(path+".type", typeJ, "The path expression does not return a value of the type '"+type+"' - found "+td.describe(), IssueType.VALUE);
|
||||
} else {
|
||||
types.clear();
|
||||
types.add(simpleType(type));
|
||||
|
@ -377,6 +404,8 @@ public class Validator {
|
|||
case "integer": return ColumnKind.Integer;
|
||||
case "decimal": return ColumnKind.Decimal;
|
||||
case "string": return ColumnKind.String;
|
||||
case "id": return ColumnKind.String;
|
||||
case "code": return ColumnKind.String;
|
||||
case "base64Binary": return ColumnKind.Binary;
|
||||
case "time": return ColumnKind.Time;
|
||||
default: return ColumnKind.Complex;
|
||||
|
@ -384,7 +413,7 @@ public class Validator {
|
|||
}
|
||||
|
||||
private boolean isSimpleType(String type) {
|
||||
return Utilities.existsInList(type, "dateTime", "boolean", "integer", "decimal", "string", "base64Binary");
|
||||
return Utilities.existsInList(type, "dateTime", "boolean", "integer", "decimal", "string", "base64Binary", "id", "code", "date", "time");
|
||||
}
|
||||
|
||||
private String simpleType(String type) {
|
||||
|
@ -413,7 +442,7 @@ public class Validator {
|
|||
return type;
|
||||
}
|
||||
|
||||
private TypeDetails checkForEach(String path, JsonObject focus, JsonElement expression, TypeDetails t) {
|
||||
private TypeDetails checkForEach(JsonObject vd, String path, JsonObject focus, JsonElement expression, TypeDetails t) {
|
||||
if (!(expression instanceof JsonString)) {
|
||||
error(path+".forEach", expression, "forEach is not a string", IssueType.INVALID);
|
||||
return null;
|
||||
|
@ -425,7 +454,7 @@ public class Validator {
|
|||
try {
|
||||
ExpressionNode n = fpe.parse(expr);
|
||||
focus.setUserData("forEach", n);
|
||||
td = fpe.checkOnTypes(null, resourceName, t, n, warnings);
|
||||
td = fpe.checkOnTypes(vd, resourceName, t, n, warnings);
|
||||
} catch (Exception e) {
|
||||
error(path, expression, e.getMessage(), IssueType.INVALID);
|
||||
}
|
||||
|
@ -438,7 +467,7 @@ public class Validator {
|
|||
}
|
||||
}
|
||||
|
||||
private TypeDetails checkForEachOrNull(String path, JsonObject focus, JsonElement expression, TypeDetails t) {
|
||||
private TypeDetails checkForEachOrNull(JsonObject vd, String path, JsonObject focus, JsonElement expression, TypeDetails t) {
|
||||
if (!(expression instanceof JsonString)) {
|
||||
error(path+".forEachOrNull", expression, "forEachOrNull is not a string", IssueType.INVALID);
|
||||
return null;
|
||||
|
@ -450,7 +479,7 @@ public class Validator {
|
|||
try {
|
||||
ExpressionNode n = fpe.parse(expr);
|
||||
focus.setUserData("forEachOrNull", n);
|
||||
td = fpe.checkOnTypes(null, resourceName, t, n, warnings);
|
||||
td = fpe.checkOnTypes(vd, resourceName, t, n, warnings);
|
||||
} catch (Exception e) {
|
||||
error(path, expression, e.getMessage(), IssueType.INVALID);
|
||||
}
|
||||
|
@ -477,69 +506,79 @@ public class Validator {
|
|||
}
|
||||
}
|
||||
if (constant.has("valueBase64Binary")) {
|
||||
checkIsString(path, constant, "valueBase64Binary");
|
||||
checkIsString(path, constant, "valueBase64Binary", new Base64BinaryType());
|
||||
} else if (constant.has("valueBoolean")) {
|
||||
checkIsBoolean(path, constant, "valueBoolean");
|
||||
checkIsBoolean(path, constant, "valueBoolean", new BooleanType());
|
||||
} else if (constant.has("valueCanonical")) {
|
||||
checkIsString(path, constant, "valueCanonical");
|
||||
checkIsString(path, constant, "valueCanonical", new CanonicalType());
|
||||
} else if (constant.has("valueCode")) {
|
||||
checkIsString(path, constant, "valueCode");
|
||||
checkIsString(path, constant, "valueCode", new CodeType());
|
||||
} else if (constant.has("valueDate")) {
|
||||
checkIsString(path, constant, "valueDate");
|
||||
checkIsString(path, constant, "valueDate", new DateType());
|
||||
} else if (constant.has("valueDateTime")) {
|
||||
checkIsString(path, constant, "valueDateTime");
|
||||
checkIsString(path, constant, "valueDateTime", new DateTimeType());
|
||||
} else if (constant.has("valueDecimal")) {
|
||||
checkIsNumber(path, constant, "valueDecimal");
|
||||
checkIsNumber(path, constant, "valueDecimal", new DecimalType());
|
||||
} else if (constant.has("valueId")) {
|
||||
checkIsString(path, constant, "valueId");
|
||||
checkIsString(path, constant, "valueId", new IdType());
|
||||
} else if (constant.has("valueInstant")) {
|
||||
checkIsString(path, constant, "valueInstant");
|
||||
checkIsString(path, constant, "valueInstant", new InstantType());
|
||||
} else if (constant.has("valueInteger")) {
|
||||
checkIsNumber(path, constant, "valueInteger");
|
||||
checkIsNumber(path, constant, "valueInteger", new IntegerType());
|
||||
} else if (constant.has("valueInteger64")) {
|
||||
checkIsNumber(path, constant, "valueInteger64");
|
||||
checkIsNumber(path, constant, "valueInteger64", new Integer64Type());
|
||||
} else if (constant.has("valueOid")) {
|
||||
checkIsString(path, constant, "valueOid");
|
||||
checkIsString(path, constant, "valueOid", new OidType());
|
||||
} else if (constant.has("valueString")) {
|
||||
checkIsString(path, constant, "valueString");
|
||||
checkIsString(path, constant, "valueString", new StringType());
|
||||
} else if (constant.has("valuePositiveInt")) {
|
||||
checkIsNumber(path, constant, "valuePositiveInt");
|
||||
checkIsNumber(path, constant, "valuePositiveInt", new PositiveIntType());
|
||||
} else if (constant.has("valueTime")) {
|
||||
checkIsString(path, constant, "valueTime");
|
||||
checkIsString(path, constant, "valueTime", new TimeType());
|
||||
} else if (constant.has("valueUnsignedInt")) {
|
||||
checkIsNumber(path, constant, "valueUnsignedInt");
|
||||
checkIsNumber(path, constant, "valueUnsignedInt", new UnsignedIntType());
|
||||
} else if (constant.has("valueUri")) {
|
||||
checkIsString(path, constant, "valueUri");
|
||||
checkIsString(path, constant, "valueUri", new UriType());
|
||||
} else if (constant.has("valueUrl")) {
|
||||
checkIsString(path, constant, "valueUrl");
|
||||
checkIsString(path, constant, "valueUrl", new UrlType());
|
||||
} else if (constant.has("valueUuid")) {
|
||||
checkIsString(path, constant, "valueUuid");
|
||||
checkIsString(path, constant, "valueUuid", new UuidType());
|
||||
} else {
|
||||
error(path, constant, "No value found", IssueType.REQUIRED);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkIsString(String path, JsonObject constant, String name) {
|
||||
private void checkIsString(String path, JsonObject constant, String name, PrimitiveType<?> value) {
|
||||
JsonElement j = constant.get(name);
|
||||
if (!(j instanceof JsonString)) {
|
||||
error(path+"."+name, j, name+" must be a string", IssueType.INVALID);
|
||||
} else {
|
||||
value.setValueAsString(j.asString());
|
||||
constant.setUserData("value", value);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkIsBoolean(String path, JsonObject constant, String name) {
|
||||
private void checkIsBoolean(String path, JsonObject constant, String name, PrimitiveType<?> value) {
|
||||
JsonElement j = constant.get(name);
|
||||
if (!(j instanceof JsonBoolean)) {
|
||||
error(path+"."+name, j, name+" must be a boolean", IssueType.INVALID);
|
||||
} else {
|
||||
value.setValueAsString(j.asString());
|
||||
constant.setUserData("value", value);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkIsNumber(String path, JsonObject constant, String name) {
|
||||
private void checkIsNumber(String path, JsonObject constant, String name, PrimitiveType<?> value) {
|
||||
JsonElement j = constant.get(name);
|
||||
if (!(j instanceof JsonNumber)) {
|
||||
error(path+"."+name, j, name+" must be a number", IssueType.INVALID);
|
||||
} else {
|
||||
value.setValueAsString(j.asString());
|
||||
constant.setUserData("value", value);
|
||||
}
|
||||
}
|
||||
private void checkWhere(String path, JsonObject where) {
|
||||
|
||||
private void checkWhere(JsonObject vd, String path, JsonObject where) {
|
||||
checkProperties(where, path, "path", "description");
|
||||
|
||||
String expr = where.asString("path");
|
||||
|
@ -553,7 +592,7 @@ public class Validator {
|
|||
try {
|
||||
ExpressionNode n = fpe.parse(expr);
|
||||
where.setUserData("path", n);
|
||||
td = fpe.checkOnTypes(null, resourceName, types, n, warnings);
|
||||
td = fpe.checkOnTypes(vd, resourceName, types, n, warnings);
|
||||
} catch (Exception e) {
|
||||
error(path, where.get("path"), e.getMessage(), IssueType.INVALID);
|
||||
}
|
||||
|
|
|
@ -83,23 +83,21 @@ public class SQLOnFhirTests {
|
|||
this.resources = resources;
|
||||
this.testCase = testCase;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static Stream<Arguments> data() throws ParserConfigurationException, SAXException, IOException {
|
||||
List<Arguments> objects = new ArrayList<>();
|
||||
File dir = ManagedFileAccess.file("/Users/grahamegrieve/work/sql-on-fhir-v2/tests/content");
|
||||
for (File f : dir.listFiles()) {
|
||||
if (f.getName().endsWith(".json")) {
|
||||
JsonObject json = JsonParser.parseObject(f);
|
||||
String name1 = f.getName().replace(".json", "");
|
||||
List<JsonObject> resources = json.getJsonObjects("resources");
|
||||
int i = 0;
|
||||
for (JsonObject test : json.getJsonObjects("tests")) {
|
||||
String name2 = test.asString("title");
|
||||
objects.add(Arguments.of(name1+":"+name2, new TestDetails(name1+":"+name2, "$.tests["+i+"]", resources, test)));
|
||||
i++;
|
||||
}
|
||||
JsonArray testFiles = (JsonArray) JsonParser.parse(TestingUtilities.loadTestResourceStream("sql-on-fhir", "manifest.json"));
|
||||
|
||||
for (String s : testFiles.asStrings()) {
|
||||
JsonObject json = JsonParser.parseObject(TestingUtilities.loadTestResourceStream("sql-on-fhir", s));
|
||||
String name1 = s.replace(".json", "");
|
||||
List<JsonObject> resources = json.getJsonObjects("resources");
|
||||
int i = 0;
|
||||
for (JsonObject test : json.getJsonObjects("tests")) {
|
||||
String name2 = test.asString("title");
|
||||
objects.add(Arguments.of(name1+":"+name2, new TestDetails(name1+":"+name2, "$.tests["+i+"]", resources, test)));
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return objects.stream();
|
||||
|
@ -110,7 +108,6 @@ public class SQLOnFhirTests {
|
|||
@SuppressWarnings("deprecation")
|
||||
@ParameterizedTest(name = "{index}: file {0}")
|
||||
@MethodSource("data")
|
||||
@Disabled
|
||||
public void test(String name, TestDetails test) throws FileNotFoundException, IOException, FHIRException, org.hl7.fhir.exceptions.FHIRException, UcumException {
|
||||
this.details = test;
|
||||
Runner runner = new Runner();
|
||||
|
@ -137,8 +134,8 @@ public class SQLOnFhirTests {
|
|||
rows.add("rows", results);
|
||||
JsonObject exp = new JsonObject();
|
||||
exp.add("rows", test.testCase.getJsonArray("expect"));
|
||||
sortResults(exp);
|
||||
sortResults(rows);
|
||||
// sortResults(exp);
|
||||
// sortResults(rows);
|
||||
String expS = JsonParser.compose(exp, true);
|
||||
String rowS = JsonParser.compose(rows, true);
|
||||
String c = CompareUtilities.checkJsonSrcIsSame(name, expS, rowS, null);
|
||||
|
|
|
@ -1819,9 +1819,14 @@ public class Utilities {
|
|||
|
||||
private static Object applyDatePrecision(String v, int precision) {
|
||||
switch (precision) {
|
||||
case 4: return v.substring(0, 4);
|
||||
case 6: return v.substring(0, 7);
|
||||
case 8: return v.substring(0, 10);
|
||||
case 4:
|
||||
return v.substring(0, 4);
|
||||
case 6:
|
||||
case 7:
|
||||
return v.substring(0, 7);
|
||||
case 8:
|
||||
case 10:
|
||||
return v.substring(0, 10);
|
||||
case 14: return v.substring(0, 17);
|
||||
case 17: return v;
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ public class FHIRPathExpressionFixer {
|
|||
}
|
||||
// con-3 in R4
|
||||
if (expr.equals("clinicalStatus.exists() or verificationStatus.coding.where(system='http://terminology.hl7.org/CodeSystem/condition-ver-status' and code = 'entered-in-error').exists() or category.select($this='problem-list-item').empty()")) {
|
||||
return "clinicalStatus.exists() or verificationStatus.coding.where(system='http://terminology.hl7.org/CodeSystem/condition-ver-status' and code = 'entered-in-error').exists() or category.coding.exists(system='http://terminology.hl7.org/CodeSystem/condition-category' and code ='problem-list-item').empty()";
|
||||
return "(verificationStatus.coding.where(system='http://terminology.hl7.org/CodeSystem/condition-ver-status' and code = 'entered-in-error').exists() and category.coding.exists(system='http://terminology.hl7.org/CodeSystem/condition-category' and code ='problem-list-item').empty()) implies (clinicalStatus.exists())";
|
||||
}
|
||||
|
||||
// R5 ballot
|
||||
|
|
Loading…
Reference in New Issue