diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/ExpressionNode.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/ExpressionNode.java
index 0742ef2e9..d4e3b697e 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/ExpressionNode.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/ExpressionNode.java
@@ -729,5 +729,9 @@ public class ExpressionNode {
}
return names;
}
+
+ public boolean isNullSet() {
+ return kind == Kind.Constant && constant == null;
+ }
}
\ No newline at end of file
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/sql/Cell.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/sql/Cell.java
index 0e8e37f9a..8e2e07c5e 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/sql/Cell.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/sql/Cell.java
@@ -13,6 +13,12 @@ public class Cell {
this.column = column;
}
+ public Cell(Column column, Value value) {
+ super();
+ this.column = column;
+ this.values.add(value);
+ }
+
public Column getColumn() {
return column;
}
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/sql/Column.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/sql/Column.java
index abddd4664..745e5c99d 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/sql/Column.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/sql/Column.java
@@ -7,6 +7,7 @@ public class Column {
private String type;
private ColumnKind kind;
private boolean isColl;
+ private boolean duplicateReported;
protected Column() {
super();
@@ -57,6 +58,42 @@ public class Column {
public void setColl(boolean isColl) {
this.isColl = isColl;
}
+
+ public String diff(Column other) {
+ if (!name.equals(other.name)) {
+ return "Names differ: '"+name+"' vs '"+other.name+"'";
+ }
+ if (kind != ColumnKind.Null && other.kind != ColumnKind.Null) {
+ if (length != other.length) {
+ return "Lengths differ: '"+length+"' vs '"+other.length+"'";
+ }
+ if (kind != other.kind) {
+ return "Kinds differ: '"+kind+"' vs '"+other.kind+"'";
+ }
+ if (isColl != other.isColl) {
+ return "Collection status differs: '"+isColl+"' vs '"+other.isColl+"'";
+ }
+ } else if (kind == ColumnKind.Null) {
+ kind = other.kind;
+ length = other.length;
+ isColl = other.isColl;
+ }
+ return null;
+ }
+
+ public boolean isDuplicateReported() {
+ return duplicateReported;
+ }
+
+ public void setDuplicateReported(boolean duplicateReported) {
+ this.duplicateReported = duplicateReported;
+ }
+
+ @Override
+ public String toString() {
+ return "Column [name=" + name + ", length=" + length + ", type=" + type + ", kind=" + kind + ", isColl=" + isColl
+ + "]";
+ }
}
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/sql/ColumnKind.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/sql/ColumnKind.java
index a1733f1d6..536d2addb 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/sql/ColumnKind.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/sql/ColumnKind.java
@@ -1,5 +1,5 @@
package org.hl7.fhir.r5.utils.sql;
public enum ColumnKind {
- String, DateTime, Integer, Decimal, Binary, Time, Boolean, Complex
+ String, DateTime, Integer, Decimal, Binary, Time, Boolean, Complex, Null
}
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/sql/Provider.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/sql/Provider.java
index d1aeeaabb..9e464a330 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/sql/Provider.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/sql/Provider.java
@@ -7,5 +7,5 @@ import org.hl7.fhir.r5.model.Base;
public interface Provider {
List fetch(String resourceType);
- Base resolveReference(String ref, String resourceType);
+ Base resolveReference(Base rootResource, String ref, String specifiedResourceType);
}
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/sql/Runner.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/sql/Runner.java
index 5087a497d..ea4ac1a10 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/sql/Runner.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/sql/Runner.java
@@ -24,6 +24,7 @@ import org.hl7.fhir.r5.utils.FHIRPathEngine;
import org.hl7.fhir.r5.utils.FHIRPathEngine.IEvaluationContext;
import org.hl7.fhir.r5.utils.FHIRPathUtilityClasses.FunctionDetails;
import org.hl7.fhir.utilities.json.model.JsonObject;
+import org.hl7.fhir.utilities.validation.ValidationMessage;
public class Runner implements IEvaluationContext {
@@ -33,9 +34,8 @@ public class Runner implements IEvaluationContext {
private List prohibitedNames = new ArrayList();
private FHIRPathEngine fpe;
- private List columns = new ArrayList<>();
-
private String resourceName;
+ private List issues;
public IWorkerContext getContext() {
@@ -86,15 +86,15 @@ public class Runner implements IEvaluationContext {
}
Validator validator = new Validator(context, fpe, prohibitedNames, storage.supportsArrays(), storage.supportsComplexTypes(), storage.needsName());
validator.checkViewDefinition(path, viewDefinition);
+ issues = validator.getIssues();
validator.dump();
validator.check();
resourceName = validator.getResourceName();
- columns = validator.getColumns();
evaluate(viewDefinition);
}
private void evaluate(JsonObject vd) {
- Store store = storage.createStore(vd.asString("name"), columns);
+ Store store = storage.createStore(vd.asString("name"), (List) vd.getUserData("columns"));
List data = provider.fetch(resourceName);
@@ -130,7 +130,20 @@ public class Runner implements IEvaluationContext {
if (select.has("forEach")) {
focus.addAll(executeForEach(select, b));
} else if (select.has("forEachOrNull")) {
+
focus.addAll(executeForEachOrNull(select, b));
+ if (focus.isEmpty()) {
+ List columns = (List) select.getUserData("columns");
+ for (List row : rows) {
+ for (Column c : columns) {
+ Cell cell = cell(row, c.getName());
+ if (cell == null) {
+ row.add(new Cell(c, null));
+ }
+ }
+ }
+ return;
+ }
} else {
focus.add(b);
}
@@ -149,19 +162,30 @@ public class Runner implements IEvaluationContext {
executeColumn(column, f, rowsToAdd);
}
- for (JsonObject sub : select.getJsonObjects("unionAll")) {
- executeSelect(sub, f, rowsToAdd);
- }
-
for (JsonObject sub : select.getJsonObjects("select")) {
executeSelect(sub, f, rowsToAdd);
}
+
+ executeUnionAll(select.getJsonObjects("unionAll"), f, rowsToAdd);
+
rows.addAll(rowsToAdd);
}
}
- private List executeUnion(JsonObject focus, Base b, List> rows) {
- throw new FHIRException("union is not supported");
+ private void executeUnionAll(List unionList, Base b, List> rows) {
+ if (unionList.isEmpty()) {
+ return;
+ }
+ List> sourceRows = new ArrayList<>();
+ sourceRows.addAll(rows);
+ rows.clear();
+
+ for (JsonObject union : unionList) {
+ List> tempRows = new ArrayList<>();
+ tempRows.addAll(sourceRows);
+ executeSelect(union, b, tempRows);
+ rows.addAll(tempRows);
+ }
}
private List> cloneRows(List> rows) {
@@ -191,9 +215,6 @@ public class Runner implements IEvaluationContext {
ExpressionNode n = (ExpressionNode) focus.getUserData("forEachOrNull");
List result = new ArrayList<>();
result.addAll(fpe.evaluate(b, n));
- if (result.size() == 0) {
- result.add(null);
- }
return result;
}
@@ -203,23 +224,27 @@ public class Runner implements IEvaluationContext {
if (b != null) {
bl2.addAll(fpe.evaluate(b, n));
}
- String name = column.getUserString("name");
- for (List row : rows) {
- Cell c = cell(row, name);
- if (c == null) {
- c = new Cell(column(name));
- row.add(c);
- }
- if (!bl2.isEmpty()) {
- if (bl2.size() + c.getValues().size() > 1) {
- // this is a problem if collection != true or if the storage can't deal with it
- // though this should've been picked up before now - but there are circumstances where it wouldn't be
- if (!c.getColumn().isColl()) {
- throw new FHIRException("The column "+c.getColumn().getName()+" is not allowed multiple values, but at least one row has multiple values");
+ Column col = (Column) column.getUserData("column");
+ if (col == null) {
+ System.out.println("Error");
+ } else {
+ for (List row : rows) {
+ Cell c = cell(row, col.getName());
+ if (c == null) {
+ c = new Cell(col);
+ row.add(c);
+ }
+ if (!bl2.isEmpty()) {
+ if (bl2.size() + c.getValues().size() > 1) {
+ // this is a problem if collection != true or if the storage can't deal with it
+ // though this should've been picked up before now - but there are circumstances where it wouldn't be
+ if (!c.getColumn().isColl()) {
+ throw new FHIRException("The column "+c.getColumn().getName()+" is not allowed multiple values, but at least one row has multiple values");
+ }
+ }
+ for (Base b2 : bl2) {
+ c.getValues().add(genValue(c.getColumn(), b2));
}
- }
- for (Base b2 : bl2) {
- c.getValues().add(genValue(c.getColumn(), b2));
}
}
}
@@ -299,7 +324,7 @@ public class Runner implements IEvaluationContext {
}
}
- private Column column(String columnName) {
+ private Column column(String columnName, List columns) {
for (Column t : columns) {
if (t.getName().equalsIgnoreCase(columnName)) {
return t;
@@ -353,7 +378,7 @@ public class Runner implements IEvaluationContext {
public List executeFunction(FHIRPathEngine engine, Object appContext, List focus, String functionName, List> parameters) {
switch (functionName) {
case "getResourceKey" : return executeResourceKey(focus);
- case "getReferenceKey" : return executeReferenceKey(focus, parameters);
+ case "getReferenceKey" : return executeReferenceKey(null, focus, parameters);
default: throw new Error("Not known: "+functionName);
}
}
@@ -375,7 +400,7 @@ public class Runner implements IEvaluationContext {
return base;
}
- private List executeReferenceKey(List focus, List> parameters) {
+ private List executeReferenceKey(Base rootResource, List focus, List> parameters) {
String rt = null;
if (parameters.size() > 0) {
rt = parameters.get(0).get(0).primitiveValue();
@@ -395,7 +420,7 @@ public class Runner implements IEvaluationContext {
throw new FHIRException("Unable to generate a reference key based on a "+res.fhirType());
}
if (ref != null) {
- Base target = provider.resolveReference(ref, rt);
+ Base target = provider.resolveReference(rootResource, ref, rt);
if (target != null) {
if (!res.hasUserData("Storage.key")) {
String key = storage.getKeyForTargetResource(target);
@@ -437,6 +462,9 @@ public class Runner implements IEvaluationContext {
public boolean paramIsType(String name, int index) {
return "getReferenceKey".equals(name);
}
+ public List getIssues() {
+ return issues;
+ }
}
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/sql/StorageJson.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/sql/StorageJson.java
index 9aca2cb22..d0e3aaeed 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/sql/StorageJson.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/sql/StorageJson.java
@@ -48,7 +48,9 @@ public class StorageJson implements Storage {
}
private JsonElement makeJsonNode(Value value) {
- if (value.getValueInt() != null) {
+ if (value == null) {
+ return new JsonNull();
+ } else if (value.getValueInt() != null) {
return new JsonNumber(value.getValueInt().intValue());
}
if (value.getValueBoolean() != null) {
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/sql/StorageSqlite3.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/sql/StorageSqlite3.java
index 0332e17fd..1a32d9c46 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/sql/StorageSqlite3.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/sql/StorageSqlite3.java
@@ -2,6 +2,7 @@ package org.hl7.fhir.r5.utils.sql;
import java.sql.Connection;
import java.sql.PreparedStatement;
+import java.sql.SQLType;
import java.util.List;
import org.hl7.fhir.exceptions.FHIRException;
@@ -27,7 +28,7 @@ public class StorageSqlite3 implements Storage {
private Connection conn;
private int nextKey = 0;
- protected StorageSqlite3(Connection conn) {
+ public StorageSqlite3(Connection conn) {
super();
this.conn = conn;
}
@@ -38,7 +39,7 @@ public class StorageSqlite3 implements Storage {
CommaSeparatedStringBuilder fields = new CommaSeparatedStringBuilder(", ");
CommaSeparatedStringBuilder values = new CommaSeparatedStringBuilder(", ");
StringBuilder b = new StringBuilder();
- b.append("Create Table "+name+" { ");
+ b.append("Create Table "+name+" ( ");
b.append("ViewRowKey integer NOT NULL");
for (Column column : columns) {
b.append(", "+column.getName()+" "+sqliteType(column.getKind())+" NULL"); // index columns are always nullable
@@ -48,7 +49,7 @@ public class StorageSqlite3 implements Storage {
b.append(", PRIMARY KEY (ViewRowKey))\r\n");
conn.createStatement().execute(b.toString());
- String isql = "Insert into "+name+" ("+fields.toString()+") values ("+values.toString()+")";
+ String isql = "Insert into "+name+" (ViewRowKey, "+fields.toString()+") values (?, "+values.toString()+")";
PreparedStatement psql = conn.prepareStatement(isql);
return new SQLiteStore(name, psql);
} catch (Exception e) {
@@ -79,6 +80,8 @@ public class StorageSqlite3 implements Storage {
for (int i = 0; i < cells.size(); i++) {
Cell c = cells.get(i);
switch (c.getColumn().getKind()) {
+ case Null:
+ p.setNull(i+2, java.sql.Types.NVARCHAR);
case Binary:
p.setBytes(i+2, c.getValues().size() == 0 ? null : c.getValues().get(0).getValueBinary());
break;
@@ -103,6 +106,7 @@ public class StorageSqlite3 implements Storage {
case Complex: throw new FHIRException("SQLite runner does not handle complexes");
}
}
+ p.execute();
} catch (Exception e) {
throw new FHIRException(e);
}
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/sql/Validator.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/sql/Validator.java
index 7aa5cc8f9..c2dbd1b54 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/sql/Validator.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/sql/Validator.java
@@ -18,6 +18,7 @@ import org.hl7.fhir.utilities.json.model.JsonBoolean;
import org.hl7.fhir.utilities.json.model.JsonElement;
import org.hl7.fhir.utilities.json.model.JsonNumber;
import org.hl7.fhir.utilities.json.model.JsonObject;
+import org.hl7.fhir.utilities.json.model.JsonProperty;
import org.hl7.fhir.utilities.json.model.JsonString;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
@@ -35,7 +36,6 @@ public class Validator {
private Boolean needsName;
private String resourceName;
- private List columns = new ArrayList();
private String name;
public Validator(IWorkerContext context, FHIRPathEngine fpe, List prohibitedNames, Boolean arrays, Boolean complexTypes, Boolean needsName) {
@@ -52,11 +52,10 @@ public class Validator {
return resourceName;
}
- public List getColumns() {
- return columns;
- }
- public void checkViewDefinition(String path, JsonObject viewDefinition) {
+ public void checkViewDefinition(String path, JsonObject viewDefinition) {
+ checkProperties(viewDefinition, path, "url", "identifier", "name", "version", "title", "status", "experimental", "date", "publisher", "contact", "description", "useContext", "copyright", "resource", "constant", "select", "where");
+
JsonElement nameJ = viewDefinition.get("name");
if (nameJ == null) {
if (needsName == null) {
@@ -76,9 +75,12 @@ public class Validator {
}
}
+ List columns = new ArrayList<>();
+ viewDefinition.setUserData("columns", columns);
+
JsonElement resourceNameJ = viewDefinition.get("resource");
if (resourceNameJ == null) {
- error(path, viewDefinition, "No resource provided", IssueType.REQUIRED);
+ error(path, viewDefinition, "No resource specified", IssueType.REQUIRED);
} else if (!(resourceNameJ instanceof JsonString)) {
error(path, viewDefinition, "resource must be a string", IssueType.INVALID);
} else {
@@ -102,29 +104,24 @@ public class Validator {
}
TypeDetails t = new TypeDetails(CollectionStatus.SINGLETON, resourceName);
- if (viewDefinition.has("forEach")) {
- checkForEach(path, viewDefinition, viewDefinition.get("forEach"), t);
- } else if (viewDefinition.has("forEachOrNull")) {
- checkForEachOrNull(path, viewDefinition, viewDefinition.get("forEachOrNull"), t);
- } else if (viewDefinition.has("unionAll")) {
- checkUnion(path, viewDefinition, viewDefinition.get("unionAll"), t);
- } else {
- i = 0;
- if (checkAllObjects(path, viewDefinition, "select")) {
- for (JsonObject select : viewDefinition.getJsonObjects("select")) {
- checkSelect(path+".select["+i+"]", select, t);
- i++;
- }
- if (i == 0) {
- error(path, viewDefinition, "No select statements found", IssueType.REQUIRED);
- }
+ i = 0;
+ if (checkAllObjects(path, viewDefinition, "select")) {
+ for (JsonObject select : viewDefinition.getJsonObjects("select")) {
+ columns.addAll(checkSelect(path+".select["+i+"]", select, t));
+ i++;
+ }
+ if (i == 0) {
+ error(path, viewDefinition, "No select statements found", IssueType.REQUIRED);
}
}
}
}
}
- private void checkSelect(String path, JsonObject select, TypeDetails t) {
+ private List checkSelect(String path, JsonObject select, TypeDetails t) {
+ List 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);
@@ -133,24 +130,18 @@ public class Validator {
}
if (t != null) {
- boolean content = false;
-
- if (select.has("unionAll")) {
- content = checkUnion(path, select, select.get("unionAll"), t);
- }
if (select.has("column")) {
JsonElement a = select.get("column");
if (!(a instanceof JsonArray)) {
error(path+".column", a, "column is not an array", IssueType.INVALID);
} else {
- content = true;
int i = 0;
for (JsonElement e : ((JsonArray) a)) {
if (!(e instanceof JsonObject)) {
error(path+".column["+i+"]", a, "column["+i+"] is a "+e.type().toName()+" not an object", IssueType.INVALID);
} else {
- checkColumn(path+".column["+i+"]", (JsonObject) e, t);
+ columns.add(checkColumn(path+".column["+i+"]", (JsonObject) e, t));
}
}
}
@@ -161,47 +152,98 @@ public class Validator {
if (!(a instanceof JsonArray)) {
error(path+".select", a, "select is not an array", IssueType.INVALID);
} else {
- content = true;
int i = 0;
for (JsonElement e : ((JsonArray) a)) {
if (!(e instanceof JsonObject)) {
error(path+".select["+i+"]", e, "select["+i+"] is not an object", IssueType.INVALID);
} else {
- checkSelect(path+".select["+i+"]", (JsonObject) e, t);
+ columns.addAll(checkSelect(path+".select["+i+"]", (JsonObject) e, t));
}
}
}
}
- if (!content) {
+ if (select.has("unionAll")) {
+ columns.addAll(checkUnion(path, select, select.get("unionAll"), t));
+ }
+ if (columns.isEmpty()) {
error(path, select, "The select has no columns or selects", IssueType.REQUIRED);
+ } else {
+ checkColumnNamesUnique(select, path, columns);
}
}
+ return columns;
}
- private boolean checkUnion(String path, JsonObject focus, JsonElement expression, TypeDetails t) {
+ private void checkColumnNamesUnique(JsonObject select, String path, List columns) {
+ Set names = new HashSet<>();
+ for (Column col : columns) {
+ if (col != null) {
+ if (!names.contains(col.getName())) {
+ names.add(col.getName());
+ } else if (!col.isDuplicateReported()) {
+ col.setDuplicateReported(true);
+ error(path, select, "Duplicate Column Name '"+col.getName()+"'", IssueType.BUSINESSRULE);
+ }
+ }
+ }
+ }
+
+ private List checkUnion(String path, JsonObject focus, JsonElement expression, TypeDetails t) {
JsonElement a = focus.get("unionAll");
if (!(a instanceof JsonArray)) {
- error(path+".union", a, "union is not an array", IssueType.INVALID);
- return false;
- } else {
+ error(path+".unionAll", a, "union is not an array", IssueType.INVALID);
+ return null;
+ } else {
+ List> unionColumns = new ArrayList<>();
int i = 0;
for (JsonElement e : ((JsonArray) a)) {
if (!(e instanceof JsonObject)) {
- error(path+".union["+i+"]", e, "union["+i+"] is not an object", IssueType.INVALID);
+ error(path+".unionAll["+i+"]", e, "unionAll["+i+"] is not an object", IssueType.INVALID);
} else {
- checkSelect(path+".union["+i+"]", (JsonObject) e, t);
+ unionColumns.add(checkSelect(path+".unionAll["+i+"]", (JsonObject) e, t));
}
+ i++;
}
if (i < 2) {
- warning(path+".union", a, "union should have more than one item");
+ warning(path+".unionAll", a, "unionAll should have more than one item");
+ }
+ if (unionColumns.size() > 1) {
+ List columns = unionColumns.get(0);
+ for (int ic = 1; ic < unionColumns.size(); ic++) {
+ String diff = columnDiffs(columns, unionColumns.get(ic));
+ if (diff != null) {
+ error(path+".unionAll["+i+"]", ((JsonArray) a).get(ic), "unionAll["+i+"] column definitions do not match: "+diff, IssueType.INVALID);
+ }
+ }
+ a.setUserData("colunms", columns);
+ return columns;
}
- return true;
}
+ return null;
}
- private void checkColumn(String path, JsonObject column, TypeDetails t) {
+ private String columnDiffs(List list1, List list2) {
+ if (list1.size() == list2.size()) {
+ for (int i = 0; i < list1.size(); i++) {
+ if (list1.get(i) == null || list2.get(i) == null) {
+ return null; // just suppress any addition errors
+ }
+ String diff = list1.get(i).diff(list2.get(i));
+ if (diff != null) {
+ return diff+" at #"+i;
+ }
+ }
+ return null;
+ } else {
+ return "Column counts differ: "+list1.size()+" vs "+list2.size();
+ }
+ }
+
+ private Column checkColumn(String path, JsonObject column, TypeDetails t) {
+ checkProperties(column, path, "path", "name", "description", "collection", "type", "tag");
+
if (!column.has("path")) {
error(path, column, "no path found", IssueType.INVALID);
} else {
@@ -253,7 +295,7 @@ public class Validator {
// ok, name is sorted!
if (columnName != null) {
column.setUserData("name", columnName);
- boolean isColl = (td.getCollectionStatus() != CollectionStatus.SINGLETON) || column(columnName) != null;
+ boolean isColl = (td.getCollectionStatus() != CollectionStatus.SINGLETON);
if (column.has("collection")) {
JsonElement collectionJ = column.get("collection");
if (!(collectionJ instanceof JsonBoolean)) {
@@ -273,24 +315,28 @@ public class Validator {
warning(path, expression, "column appears to be a collection, but this is not allowed in this context");
}
}
- // ok collection is sorted
Set types = new HashSet<>();
- for (String type : td.getTypes()) {
- types.add(simpleType(type));
- }
+ if (node.isNullSet()) {
+ types.add("null");
+ } else {
+ // ok collection is sorted
+ for (String type : td.getTypes()) {
+ types.add(simpleType(type));
+ }
- JsonElement typeJ = column.get("type");
- if (typeJ != null) {
- 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);
+ JsonElement typeJ = column.get("type");
+ if (typeJ != null) {
+ 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);
+ } else {
+ types.clear();
+ types.add(simpleType(type));
+ }
} else {
- types.clear();
- types.add(simpleType(type));
+ error(path+".type", typeJ, "type must be a string", IssueType.INVALID);
}
- } else {
- error(path+".type", typeJ, "type must be a string", IssueType.INVALID);
}
}
if (types.size() != 1) {
@@ -298,7 +344,7 @@ public class Validator {
} else {
String type = types.iterator().next();
boolean ok = false;
- if (!isSimpleType(type)) {
+ if (!isSimpleType(type) && !"null".equals(type)) {
if (complexTypes) {
warning(path, expression, "Column is a complex type. This is not supported in some Runners");
} else if (!complexTypes) {
@@ -310,28 +356,21 @@ public class Validator {
ok = true;
}
if (ok) {
- Column col = column(columnName);
- if (col != null) {
- if (!col.getType().equals(type)) {
- error(path, expression, "Duplicate definition for "+columnName+" has different types ("+col.getType()+" vs "+type+")", IssueType.BUSINESSRULE);
- }
- if (col.isColl() != isColl) {
- error(path, expression, "Duplicate definition for "+columnName+" has different status for collection ("+col.isColl()+" vs "+isColl+")", IssueType.BUSINESSRULE);
- }
- } else {
- columns.add(new Column(columnName, isColl, type, kindForType(type)));
-
- }
+ Column col = new Column(columnName, isColl, type, kindForType(type));
+ column.setUserData("column", col);
+ return col;
}
}
}
}
}
}
+ return null;
}
private ColumnKind kindForType(String type) {
switch (type) {
+ case "null": return ColumnKind.Null;
case "dateTime": return ColumnKind.DateTime;
case "boolean": return ColumnKind.Boolean;
case "integer": return ColumnKind.Integer;
@@ -343,15 +382,6 @@ public class Validator {
}
}
- private Column column(String columnName) {
- for (Column t : columns) {
- if (t.getName().equalsIgnoreCase(columnName)) {
- return t;
- }
- }
- return null;
- }
-
private boolean isSimpleType(String type) {
return Utilities.existsInList(type, "dateTime", "boolean", "integer", "decimal", "string", "base64Binary");
}
@@ -433,6 +463,7 @@ public class Validator {
}
private void checkConstant(String path, JsonObject constant) {
+ checkProperties(constant, path, "name", "valueBase64Binary", "valueBoolean", "valueCanonical", "valueCode", "valueDate", "valueDateTime", "valueDecimal", "valueId", "valueInstant", "valueInteger", "valueInteger64", "valueOid", "valueString", "valuePositiveInt", "valueTime", "valueUnsignedInt", "valueUri", "valueUrl", "valueUuid");
JsonElement nameJ = constant.get("name");
if (nameJ == null) {
error(path, constant, "No name provided", IssueType.REQUIRED);
@@ -508,6 +539,8 @@ public class Validator {
}
}
private void checkWhere(String path, JsonObject where) {
+ checkProperties(where, path, "path", "description");
+
String expr = where.asString("path");
if (expr == null) {
error(path, where, "No path provided", IssueType.REQUIRED);
@@ -534,6 +567,19 @@ public class Validator {
}
}
+ private void checkProperties(JsonObject obj, String path, String... names) {
+ for (JsonProperty p : obj.getProperties()) {
+ boolean nameOk = "extension".equals(p.getName());
+ for (String name : names) {
+ nameOk = nameOk || name.equals(p.getName());
+ }
+ if (!nameOk) {
+ error(path+"."+p.getName(), p.getValue(), "Unknown JSON "+p.getValue().type().name(), IssueType.UNKNOWN);
+ }
+ }
+
+ }
+
private boolean isValidName(String name) {
boolean first = true;
for (char c : name.toCharArray()) {
diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/sql/SQLOnFhirTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/sql/SQLOnFhirTests.java
index 74daafaec..e0bf364e9 100644
--- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/sql/SQLOnFhirTests.java
+++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/sql/SQLOnFhirTests.java
@@ -54,7 +54,7 @@ public class SQLOnFhirTests {
}
@Override
- public Base resolveReference(String ref, String rt) {
+ public Base resolveReference(Base rootResource, String ref, String rt) {
if (ref == null) {
return null;
}
@@ -109,7 +109,7 @@ public class SQLOnFhirTests {
@SuppressWarnings("deprecation")
@ParameterizedTest(name = "{index}: file {0}")
@MethodSource("data")
- @Disabled
+ //@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();
@@ -126,6 +126,7 @@ public class SQLOnFhirTests {
runner.execute(test.path+".view", test.testCase.getJsonObject("view"));
results = store.getRows();
} catch (Exception e) {
+ e.printStackTrace();
Assertions.assertTrue(test.testCase.has("expectError"), e.getMessage());
}
if (results != null) {
| | |